Skip to content

Commit

Permalink
refactor: Modify service override registration so they're mapped to a…
Browse files Browse the repository at this point in the history
… "service" (#85)

* refactor: Modify service override registration so they're mapped to a "service"

* fix: Correct tests to use new registration method
  • Loading branch information
ollieread authored Jan 9, 2025
1 parent 23b4d58 commit cf99a0c
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 13 deletions.
14 changes: 8 additions & 6 deletions resources/config/sprout.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

use Sprout\Support\Services;

return [

/*
Expand Down Expand Up @@ -53,22 +55,22 @@
'services' => [
// This will override the storage by introducing a 'sprout' driver
// that wraps any other storage drive in a tenant resource subdirectory.
\Sprout\Overrides\StorageOverride::class,
Services::STORAGE => \Sprout\Overrides\StorageOverride::class,
// This will hydrate tenants when running jobs, based on the current
// context.
\Sprout\Overrides\JobOverride::class,
Services::JOB => \Sprout\Overrides\JobOverride::class,
// This will override the cache by introducing a 'sprout' driver
// that adds a prefix to cache stores for the current tenant.
\Sprout\Overrides\CacheOverride::class,
Services::CACHE => \Sprout\Overrides\CacheOverride::class,
// This is a simple override that removes all currently resolved
// guards to prevent user auth leaking.
\Sprout\Overrides\AuthOverride::class,
Services::AUTH => \Sprout\Overrides\AuthOverride::class,
// This will override the cookie settings so that all created cookies
// are specific to the tenant.
\Sprout\Overrides\CookieOverride::class,
Services::COOKIE => \Sprout\Overrides\CookieOverride::class,
// This will override the session by introducing a 'sprout' driver
// that wraps any other session store.
\Sprout\Overrides\SessionOverride::class,
Services::SESSION => \Sprout\Overrides\SessionOverride::class,
],

];
46 changes: 43 additions & 3 deletions src/Concerns/HandlesServiceOverrides.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
use Sprout\Events\ServiceOverrideProcessed;
use Sprout\Events\ServiceOverrideProcessing;
use Sprout\Events\ServiceOverrideRegistered;
use function Sprout\sprout;

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

Expand Down Expand Up @@ -54,20 +55,29 @@ trait HandlesServiceOverrides
/**
* Register a service override
*
* @param string $service
* @param class-string<\Sprout\Contracts\ServiceOverride> $class
*
* @return static
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function registerOverride(string $class): static
public function registerOverride(string $service, string $class): static
{
if (! is_subclass_of($class, ServiceOverride::class)) {
throw new InvalidArgumentException('Provided service override [' . $class . '] does not implement ' . ServiceOverride::class);
}

if ($this->isServiceBeingOverridden($service)) {
$originalClass = $this->registeredOverrides[$service];

if ($this->hasBootedOverride($originalClass) || $this->hasOverrideBeenSetup($originalClass)) {
throw new InvalidArgumentException('The service [' . $service . '] already has an override registered [' . $this->registeredOverrides[$service] . '] which has already been processed');
}
}

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

ServiceOverrideRegistered::dispatch($class);

Expand Down Expand Up @@ -257,6 +267,24 @@ public function hasSetupOverride(Tenancy $tenancy, string $class): bool
return $this->setupOverrides[$tenancy->getName()][$class] ?? false;
}

/**
* Check if a service override has been set up for any tenancy
*
* @param class-string<\Sprout\Contracts\ServiceOverride> $class
*
* @return bool
*/
public function hasOverrideBeenSetup(string $class): bool
{
foreach ($this->setupOverrides as $overrides) {
if (isset($overrides[$class])) {
return true;
}
}

return false;
}

/**
* Set-up all available service overrides
*
Expand Down Expand Up @@ -383,6 +411,18 @@ public function hasRegisteredOverride(string $class): bool
return in_array($class, $this->registeredOverrides, true);
}

/**
* Check if a particular service is being overridden
*
* @param string $service
*
* @return bool
*/
public function isServiceBeingOverridden(string $service): bool
{
return isset($this->registeredOverrides[$service]);
}

/**
* Get all service overrides for a tenancy
*
Expand Down
11 changes: 8 additions & 3 deletions src/SproutServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Routing\Events\RouteMatched;
use Illuminate\Routing\Router;
use Illuminate\Support\ServiceProvider;
use RuntimeException;
use Sprout\Events\CurrentTenantChanged;
use Sprout\Http\Middleware\TenantRoutes;
use Sprout\Http\RouterMethods;
Expand Down Expand Up @@ -106,11 +107,15 @@ private function publishConfig(): void

private function registerServiceOverrides(): void
{
/** @var array<class-string<\Sprout\Contracts\ServiceOverride>> $overrides */
/** @var array<string, class-string<\Sprout\Contracts\ServiceOverride>> $overrides */
$overrides = config('sprout.services', []);

foreach ($overrides as $overrideClass) {
$this->sprout->registerOverride($overrideClass);
foreach ($overrides as $service => $overrideClass) {
if (! is_string($service)) {
throw new RuntimeException('Service overrides must be registered against a "service"');
}

$this->sprout->registerOverride($service, $overrideClass);
}
}

Expand Down
19 changes: 19 additions & 0 deletions src/Support/Services.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);

namespace Sprout\Support;

final class Services
{
public const AUTH = 'auth';

public const CACHE = 'cache';

public const COOKIE = 'cookie';

public const JOB = 'job';

public const SESSION = 'session';

public const STORAGE = 'storage';
}
59 changes: 59 additions & 0 deletions tests/Unit/Overrides/AuthOverrideTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);

namespace Sprout\Tests\Unit\Overrides;

use Illuminate\Config\Repository;
use PHPUnit\Framework\Attributes\Test;
use Sprout\Contracts\BootableServiceOverride;
use Sprout\Contracts\DeferrableServiceOverride;
use Sprout\Overrides\Auth\TenantAwarePasswordBrokerManager;
use Sprout\Overrides\AuthOverride;
use Sprout\Support\Services;
use Sprout\Tests\Unit\UnitTestCase;
use function Sprout\sprout;

class AuthOverrideTest extends UnitTestCase
{
protected function defineEnvironment($app): void
{
tap($app['config'], static function (Repository $config) {
$config->set('sprout.services', []);
});
}

#[Test]
public function isBuiltCorrectly(): void
{
$this->assertTrue(is_subclass_of(AuthOverride::class, BootableServiceOverride::class));
$this->assertFalse(is_subclass_of(AuthOverride::class, DeferrableServiceOverride::class));
}

#[Test]
public function isRegisteredWithSproutCorrectly(): void
{
$sprout = sprout();

$sprout->registerOverride(Services::AUTH, AuthOverride::class);

$this->assertTrue($sprout->hasRegisteredOverride(AuthOverride::class));
$this->assertTrue($sprout->isBootableOverride(AuthOverride::class));
$this->assertFalse($sprout->isDeferrableOverride(AuthOverride::class));
}

#[Test]
public function isBootedCorrectly(): void
{
$sprout = sprout();

$sprout->registerOverride(Services::AUTH, AuthOverride::class);

$this->assertFalse(app()->isDeferredService('auth.password'));
$this->assertTrue(app()->bound('auth.password'));
$this->assertFalse(app()->resolved('auth.password'));
$this->assertFalse(app()->resolved('auth.password.broker'));
$this->assertInstanceOf(TenantAwarePasswordBrokerManager::class, app()->make('auth.password'));
$this->assertTrue(app()->resolved('auth.password'));
$this->assertFalse(app()->resolved('auth.password.broker'));
}
}
3 changes: 2 additions & 1 deletion tests/_Original/Http/Resolvers/SessionResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Sprout\Overrides\JobOverride;
use Sprout\Overrides\SessionOverride;
use Sprout\Overrides\StorageOverride;
use Sprout\Support\Services;
use Workbench\App\Models\TenantModel;
use function Sprout\sprout;

Expand Down Expand Up @@ -108,7 +109,7 @@ public function throwsExceptionWithoutHeader(): void
#[Test]
public function throwsExceptionIfSessionOverrideIsEnabled(): void
{
sprout()->registerOverride(SessionOverride::class);
sprout()->registerOverride(Services::SESSION, SessionOverride::class);
$tenant = TenantModel::factory()->createOne();

$result = $this->withSession(['multitenancy' => ['tenants' => $tenant->getTenantIdentifier()]])->get(route('session.route'));
Expand Down

0 comments on commit cf99a0c

Please sign in to comment.