Skip to content

Commit

Permalink
feat: Introduce deferrable service overrides
Browse files Browse the repository at this point in the history
  • Loading branch information
ollieread committed Nov 17, 2024
1 parent 2abfc25 commit 21b950e
Show file tree
Hide file tree
Showing 13 changed files with 487 additions and 70 deletions.
380 changes: 380 additions & 0 deletions src/Concerns/HandlesServiceOverrides.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,380 @@
<?php
declare(strict_types=1);

namespace Sprout\Concerns;

use InvalidArgumentException;
use Sprout\Contracts\BootableServiceOverride;
use Sprout\Contracts\DeferrableServiceOverride;
use Sprout\Contracts\ServiceOverride;
use Sprout\Contracts\Tenancy;
use Sprout\Contracts\Tenant;
use Sprout\Overrides\StorageOverride;

trait HandlesServiceOverrides
{
/**
* @var array<class-string<\Sprout\Contracts\ServiceOverride>>
*/
private array $registeredOverrides = [];

/**
* @var array<class-string<\Sprout\Contracts\ServiceOverride>, \Sprout\Contracts\ServiceOverride>
*/
private array $overrides = [];

/**
* @var array<class-string<\Sprout\Contracts\ServiceOverride>, string|class-string>
*/
private array $deferredOverrides = [];

/**
* @var array<class-string<\Sprout\Contracts\ServiceOverride>, BootableServiceOverride>
*/
private array $bootableOverrides = [];

/**
* @var array<class-string<\Sprout\Contracts\ServiceOverride>, bool>
*/
private array $bootedOverrides = [];

/**
* @var array<string, array<class-string<\Sprout\Contracts\ServiceOverride>>
*/
private array $setupOverrides = [];

/**
* @var bool
*/
private bool $hasBooted = false;

/**
* Register a service override
*
* @param class-string<\Sprout\Contracts\ServiceOverride> $overrideClass
*
* @return static
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function registerOverride(string $overrideClass): static
{
if (! is_subclass_of($overrideClass, ServiceOverride::class)) {
throw new InvalidArgumentException('Provided service override [' . $overrideClass . '] does not implement ' . ServiceOverride::class);

Check warning on line 63 in src/Concerns/HandlesServiceOverrides.php

View check run for this annotation

Codecov / codecov/patch

src/Concerns/HandlesServiceOverrides.php#L63

Added line #L63 was not covered by tests
}

// Flag the service override as being registered
$this->registeredOverrides[] = $overrideClass;

if (is_subclass_of($overrideClass, DeferrableServiceOverride::class)) {
$this->registerDeferrableOverride($overrideClass);
} else {
$this->processOverride($overrideClass);
}

return $this;
}

/**
* Process the registration of a service override
*
* This method is an abstraction of the service override registration
* processing, which exists entirely to make deferrable overrides easier.
*
* @param class-string<\Sprout\Contracts\ServiceOverride> $overrideClass
*
* @return static
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function processOverride(string $overrideClass): static
{
// Create a new instance of the override
$override = $this->app->make($overrideClass);

// Register the instance
$this->overrides[$overrideClass] = $override;

// The override is bootable
if ($override instanceof BootableServiceOverride) {
// So register it as one
$this->bootableOverrides[$overrideClass] = $override;
$this->bootedOverrides[$overrideClass] = false;

// If the boot phase has already happened, we'll boot it now
if ($this->haveOverridesBooted()) {
$this->bootOverride($overrideClass);
}
}

return $this;
}

/**
* Register a deferrable service override
*
* @param class-string<\Sprout\Contracts\DeferrableServiceOverride> $overrideClass
*
* @return static
*/
protected function registerDeferrableOverride(string $overrideClass): static
{
// Register the deferred override and its service
$this->deferredOverrides[$overrideClass] = $overrideClass::service();

$this->app->afterResolving($overrideClass::service(), function () use ($overrideClass) {
$this->processOverride($overrideClass);

// Get the current tenancy
$tenancy = $this->getCurrentTenancy();

// If there's a current tenancy WITH a tenant, we can set up the
// override
if ($tenancy !== null && $tenancy->check()) {
$this->setupOverride($overrideClass, $tenancy, $tenancy->tenant());
}
});

return $this;
}

/**
* Check if a service override is bootable
*
* @param class-string<\Sprout\Contracts\ServiceOverride> $overrideClass
*
* @return bool
*/
public function isBootableOverride(string $overrideClass): bool

Check warning on line 148 in src/Concerns/HandlesServiceOverrides.php

View check run for this annotation

Codecov / codecov/patch

src/Concerns/HandlesServiceOverrides.php#L148

Added line #L148 was not covered by tests
{
return isset($this->bootableOverrides[$overrideClass]);

Check warning on line 150 in src/Concerns/HandlesServiceOverrides.php

View check run for this annotation

Codecov / codecov/patch

src/Concerns/HandlesServiceOverrides.php#L150

Added line #L150 was not covered by tests
}

/**
* Check if a service override has been booted
*
* This method returns true if the service override has been booted, or
* false if either it hasn't, or it isn't bootable.
*
* @param class-string<\Sprout\Contracts\ServiceOverride> $overrideClass
*
* @return bool
*/
public function hasBootedOverride(string $overrideClass): bool
{
return $this->bootedOverrides[$overrideClass] ?? false;
}

/**
* Check if the boot phase has already happened
*
* @return bool
*/
public function haveOverridesBooted(): bool
{
return $this->hasBooted;
}

/**
* Boot all bootable overrides
*
* @return void
*/
public function bootOverrides(): void
{
// If the boot phase for the override has already happened, skip it
if ($this->haveOverridesBooted()) {
return;

Check warning on line 187 in src/Concerns/HandlesServiceOverrides.php

View check run for this annotation

Codecov / codecov/patch

src/Concerns/HandlesServiceOverrides.php#L187

Added line #L187 was not covered by tests
}

foreach ($this->bootableOverrides as $overrideClass => $override) {
// It's possible this is being called a second time, so we don't
// want to do it again
if (! $this->hasBootedOverride($overrideClass)) {
// Boot the override
$this->bootOverride($overrideClass);
}
}

// Mark the override boot phase as having completed
$this->hasBooted = true;
}

/**
* Boot a service override
*
* @template TenantClass of \Sprout\Contracts\Tenant
*
* @param class-string<\Sprout\Contracts\BootableServiceOverride> $overrideClass
*
* @phpstan-param TenantClass $tenant
*
* @return void
*/
protected function bootOverride(string $overrideClass): void
{
$this->overrides[$overrideClass]->boot($this->app, $this);
$this->bootedOverrides[$overrideClass] = true;
}

/**
* Check if a service override has been set up
*
* @param \Sprout\Contracts\Tenancy<*> $tenancy
* @param class-string<\Sprout\Contracts\ServiceOverride> $overrideClass
*
* @return bool
*/
public function hasSetupOverride(Tenancy $tenancy, string $overrideClass): bool
{
return $this->setupOverrides[$tenancy->getName()][$overrideClass] ?? false;
}

/**
* Set-up all available service overrides
*
* @template TenantClass of \Sprout\Contracts\Tenant
*
* @param \Sprout\Contracts\Tenancy<TenantClass> $tenancy
* @param \Sprout\Contracts\Tenant $tenant
*
* @phpstan-param TenantClass $tenant
*
* @return void
*/
public function setupOverrides(Tenancy $tenancy, Tenant $tenant): void
{
foreach ($this->overrides as $overrideClass => $override) {
if (! $this->hasSetupOverride($tenancy, $overrideClass)) {
$this->setupOverride($overrideClass, $tenancy, $tenant);
}
}
}

/**
* Set up a service override
*
* @template TenantClass of \Sprout\Contracts\Tenant
*
* @param class-string<\Sprout\Contracts\ServiceOverride> $overrideClass
* @param \Sprout\Contracts\Tenancy<TenantClass> $tenancy
* @param \Sprout\Contracts\Tenant $tenant
*
* @phpstan-param TenantClass $tenant
*
* @return void
*/
protected function setupOverride(string $overrideClass, Tenancy $tenancy, Tenant $tenant): void
{
$this->overrides[$overrideClass]->setup($tenancy, $tenant);
$this->setupOverrides[$tenancy->getName()][$overrideClass] = true;
}

/**
* Clean-up all service overrides
*
* @template TenantClass of \Sprout\Contracts\Tenant
*
* @param \Sprout\Contracts\Tenancy<TenantClass> $tenancy
* @param \Sprout\Contracts\Tenant $tenant
*
* @phpstan-param TenantClass $tenant
*
* @return void
*/
public function cleanupOverrides(Tenancy $tenancy, Tenant $tenant): void
{
$overrides = $this->setupOverrides[$tenancy->getName()] ?? [];

foreach ($overrides as $overrideClass => $status) {
if ($status === true) {
$this->cleanupOverride($overrideClass, $tenancy, $tenant);
}
}
}

/**
* Clean-up a service override
*
* @template TenantClass of \Sprout\Contracts\Tenant
*
* @param class-string<\Sprout\Contracts\ServiceOverride> $overrideClass
* @param \Sprout\Contracts\Tenancy<TenantClass> $tenancy
* @param \Sprout\Contracts\Tenant $tenant
*
* @phpstan-param TenantClass $tenant
*
* @return void
*/
protected function cleanupOverride(string $overrideClass, Tenancy $tenancy, Tenant $tenant): void
{
$this->overrides[$overrideClass]->cleanup($tenancy, $tenant);
unset($this->setupOverrides[$tenancy->getName()][$overrideClass]);
}

/**
* Get all service overrides
*
* @return array<class-string<\Sprout\Contracts\ServiceOverride>, \Sprout\Contracts\ServiceOverride>
*/
public function getOverrides(): array

Check warning on line 320 in src/Concerns/HandlesServiceOverrides.php

View check run for this annotation

Codecov / codecov/patch

src/Concerns/HandlesServiceOverrides.php#L320

Added line #L320 was not covered by tests
{
return $this->overrides;

Check warning on line 322 in src/Concerns/HandlesServiceOverrides.php

View check run for this annotation

Codecov / codecov/patch

src/Concerns/HandlesServiceOverrides.php#L322

Added line #L322 was not covered by tests
}

/**
* Get all registered service overrides
*
* @return array<class-string<\Sprout\Contracts\ServiceOverride>>
*/
public function getRegisteredOverrides(): array

Check warning on line 330 in src/Concerns/HandlesServiceOverrides.php

View check run for this annotation

Codecov / codecov/patch

src/Concerns/HandlesServiceOverrides.php#L330

Added line #L330 was not covered by tests
{
return $this->registeredOverrides;

Check warning on line 332 in src/Concerns/HandlesServiceOverrides.php

View check run for this annotation

Codecov / codecov/patch

src/Concerns/HandlesServiceOverrides.php#L332

Added line #L332 was not covered by tests
}

/**
* Check if a service override is present
*
* @param string $class
*
* @return bool
*/
public function hasOverride(string $class): bool
{
return isset($this->overrides[$class]);
}

/**
* Check if a service override has been registered
*
* @param string $class
*
* @return bool
*/
public function hasRegisteredOverride(string $class): bool

Check warning on line 354 in src/Concerns/HandlesServiceOverrides.php

View check run for this annotation

Codecov / codecov/patch

src/Concerns/HandlesServiceOverrides.php#L354

Added line #L354 was not covered by tests
{
return in_array($class, $this->registeredOverrides, true);

Check warning on line 356 in src/Concerns/HandlesServiceOverrides.php

View check run for this annotation

Codecov / codecov/patch

src/Concerns/HandlesServiceOverrides.php#L356

Added line #L356 was not covered by tests
}

/**
* Get all service overrides for a tenancy
*
* @param \Sprout\Contracts\Tenancy|null $tenancy
*
* @return \Sprout\Contracts\ServiceOverride[]|void
*/
public function getCurrentOverrides(?Tenancy $tenancy = null)

Check warning on line 366 in src/Concerns/HandlesServiceOverrides.php

View check run for this annotation

Codecov / codecov/patch

src/Concerns/HandlesServiceOverrides.php#L366

Added line #L366 was not covered by tests
{
$tenancy ??= $this->getCurrentTenancy();

Check warning on line 368 in src/Concerns/HandlesServiceOverrides.php

View check run for this annotation

Codecov / codecov/patch

src/Concerns/HandlesServiceOverrides.php#L368

Added line #L368 was not covered by tests

if ($tenancy !== null) {
return array_filter(
$this->overrides,
function (string $overrideClass) use ($tenancy) {
return $this->hasSetupOverride($tenancy, $overrideClass);
},
ARRAY_FILTER_USE_KEY
);

Check warning on line 377 in src/Concerns/HandlesServiceOverrides.php

View check run for this annotation

Codecov / codecov/patch

src/Concerns/HandlesServiceOverrides.php#L370-L377

Added lines #L370 - L377 were not covered by tests
}
}
}
Loading

0 comments on commit 21b950e

Please sign in to comment.