Skip to content

Commit

Permalink
feat: Add cache and session overrides (#50)
Browse files Browse the repository at this point in the history
* refactor: Allow for the disabling of server resetting on tenancy vacancy
* refactor: Refactored cookie helper to backup original session config
* chore: Refactor to use service overrides abstraction
* feat: Add cache override
* chore: Override default database session handler to add where clauses
* chore: Add tenanted database session driver
* chore: Add session and store overrides to config
* build: Make sure test suite runs workbench commands
* build(workbench): Make sure workbench publishes the correct assets
* refactor: Register service overrides during the boot phase
* fix: Disable session override for session resolver
* fix: Fix issues with BelongsToManyTenantObserver and new larastan
* refactor: Move and rename service override tests and skips tests to be refactored
* chore: Ignore  same site and secure overrides for session and cookie overrides
* fix: Have the cache override use 'override' to provide cache store to be overridden
* chore: Make the session identity resolver error out if the session is overridden
* refactor: Remove the 'reset services' tenancy option
* test: Fix skipped tests that involve service overrides
* test: Ignore session and cache from code coverage for now until they can be tested properly
  • Loading branch information
ollieread authored Oct 21, 2024
1 parent 3ea2180 commit 0157640
Show file tree
Hide file tree
Showing 23 changed files with 1,005 additions and 180 deletions.
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
"phpstan"
],
"test" : [
"@clear",
"@prepare",
"@build",
"@php vendor/bin/phpunit"
]
},
Expand Down
50 changes: 35 additions & 15 deletions resources/config/sprout.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,48 @@

/*
|--------------------------------------------------------------------------
| Service Overrides
| The event listeners used to bootstrap a tenancy
|--------------------------------------------------------------------------
|
| This value sets which core Laravel services Sprout should override.
|
| Setting a service to false will disable its tenant-specific
| configuration/settings, and leave them using the default.
| This value contains all the listeners that should be run for the
| \Sprout\Events\CurrentTenantChanged event to bootstrap a tenancy.
|
*/

'services' => [
// This will enable the 'sprout' driver for the filesystem disks,
// allowing for the creation of tenant scoped disks.
'storage' => true,
'bootstrappers' => [
// Calls the setup method on the current identity resolver
\Sprout\Listeners\PerformIdentityResolverSetup::class,
// Performs any clean-up from the previous tenancy
\Sprout\Listeners\CleanupServiceOverrides::class,
// Sets up service overrides for the current tenancy
\Sprout\Listeners\SetupServiceOverrides::class,
// Set the current tenant within the Laravel context
\Sprout\Listeners\SetCurrentTenantContext::class,
],

// This will enable the overwriting of the default settings for cookies.
// Each identity resolver may have effect different settings.
'cookies' => true,
/*
|--------------------------------------------------------------------------
| Service Overrides
|--------------------------------------------------------------------------
|
| This is an array of service override classes.
| These classes will be instantiated and automatically run when relevant.
|
*/

// This will enable the overwriting of the default settings for sessions.
// Each identity resolver may have effect different settings.
'sessions' => true,
'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,
// 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,
// This will override the cookie settings so that all created cookies
// are specific to the tenant.
\Sprout\Overrides\CookieOverride::class,
// This will override the session by introducing a 'sprout' driver
// that wraps any other session store.
\Sprout\Overrides\SessionOverride::class,
],

];
29 changes: 29 additions & 0 deletions src/Contracts/BootableServiceOverride.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Sprout\Contracts;

use Illuminate\Contracts\Foundation\Application;
use Sprout\Sprout;

/**
* Bootable Service Override
*
* This contract marks a {@see \Sprout\Contracts\ServiceOverride} as being
* bootable, meaning that it can perform actions during the boot stage of the
* framework.
*/
interface BootableServiceOverride extends ServiceOverride
{
/**
* Boot a service override
*
* This method should perform any initial steps required for the service
* override that take place during the booting of the framework.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \Sprout\Sprout $sprout
*
* @return void
*/
public function boot(Application $app, Sprout $sprout): void;
}
44 changes: 44 additions & 0 deletions src/Contracts/ServiceOverride.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Sprout\Contracts;

/**
* Service Override
*
* This contract marks a class as being responsible for handling the overriding
* of a core Laravel service, such as cookies, sessions, or the database.
*/
interface ServiceOverride
{
/**
* Set up the service override
*
* This method should perform any necessary setup actions for the service
* override.
* It is called when a new tenant is marked as the current tenant.
*
* @param \Sprout\Contracts\Tenancy<*> $tenancy
* @param \Sprout\Contracts\Tenant $tenant
*
* @return void
*/
public function setup(Tenancy $tenancy, Tenant $tenant): void;

/**
* Clean up the service override
*
* This method should perform any necessary setup actions for the service
* override.
* It is called when the current tenant is unset, either to be replaced
* by another tenant, or none.
*
* It will be called before {@see self::setup()}, but only if the previous
* tenant was not null.
*
* @param \Sprout\Contracts\Tenancy<*> $tenancy
* @param \Sprout\Contracts\Tenant $tenant
*
* @return void
*/
public function cleanup(Tenancy $tenancy, Tenant $tenant): void;
}
29 changes: 15 additions & 14 deletions src/Database/Eloquent/Observers/BelongsToManyTenantsObserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ class BelongsToManyTenantsObserver
/**
* Check if a model already has a tenant set
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param \Illuminate\Database\Eloquent\Relations\BelongsToMany<TenantModel> $relation
* @param \Sprout\Contracts\Tenancy<TenantModel> $tenancy
* @param \Illuminate\Database\Eloquent\Model $model
* @param \Illuminate\Database\Eloquent\Relations\BelongsToMany<ChildModel, TenantModel> $relation
* @param \Sprout\Contracts\Tenancy<TenantModel> $tenancy
*
* @return bool
*/
Expand All @@ -44,9 +44,9 @@ private function doesModelAlreadyHaveATenant(Model $model, BelongsToMany $relati
/**
* Check if a model belongs to a different tenant
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param \Illuminate\Database\Eloquent\Model&\Sprout\Contracts\Tenant $tenant
* @param \Illuminate\Database\Eloquent\Relations\BelongsToMany<TenantModel> $relation
* @param \Illuminate\Database\Eloquent\Model $model
* @param \Illuminate\Database\Eloquent\Model&\Sprout\Contracts\Tenant $tenant
* @param \Illuminate\Database\Eloquent\Relations\BelongsToMany<ChildModel, TenantModel> $relation
*
* @return bool
*/
Expand All @@ -67,7 +67,8 @@ private function isTenantMismatched(Model $model, Tenant&Model $tenant, BelongsT
*
* @param \Illuminate\Database\Eloquent\Model&\Sprout\Database\Eloquent\Concerns\BelongsToManyTenants $model
* @param \Sprout\Contracts\Tenancy<TenantModel> $tenancy
* @param \Illuminate\Database\Eloquent\Relations\BelongsToMany<TenantModel> $relation
* @param \Illuminate\Database\Eloquent\Relations\BelongsToMany<ChildModel, TenantModel> $relation
* @param bool $succeedOnMatch
*
* @return bool
*
Expand Down Expand Up @@ -137,7 +138,7 @@ private function passesInitialChecks(Model $model, Tenancy $tenancy, BelongsToMa
public function created(Model $model): void
{
/**
* @var \Illuminate\Database\Eloquent\Relations\BelongsToMany<TenantModel> $relation
* @var \Illuminate\Database\Eloquent\Relations\BelongsToMany<ChildModel, TenantModel> $relation
* @phpstan-ignore-next-line
*/
$relation = $model->getTenantRelation();
Expand Down Expand Up @@ -180,7 +181,7 @@ public function created(Model $model): void
public function retrieved(Model $model): void
{
/**
* @var \Illuminate\Database\Eloquent\Relations\BelongsToMany<TenantModel> $relation
* @var \Illuminate\Database\Eloquent\Relations\BelongsToMany<ChildModel, TenantModel> $relation
* @phpstan-ignore-next-line
*/
$relation = $model->getTenantRelation();
Expand Down Expand Up @@ -208,12 +209,12 @@ public function retrieved(Model $model): void
}

/**
* @param \Illuminate\Database\Eloquent\Model $model
* @param \Illuminate\Database\Eloquent\Relations\BelongsToMany<TenantModel> $relation
* @param \Sprout\Contracts\Tenant $tenant
* @param \Illuminate\Database\Eloquent\Model $model
* @param \Illuminate\Database\Eloquent\Relations\BelongsToMany<ChildModel, TenantModel> $relation
* @param \Sprout\Contracts\Tenant $tenant
*
* @phpstan-param ChildModel $model
* @phpstan-param TenantModel $tenant
* @phpstan-param ChildModel $model
* @phpstan-param TenantModel $tenant
*
* @return void
*/
Expand Down
17 changes: 12 additions & 5 deletions src/Http/Resolvers/PathIdentityResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
use Sprout\Contracts\Tenant;
use Sprout\Exceptions\TenantMissing;
use Sprout\Http\Middleware\TenantRoutes;
use Sprout\Overrides\CookieOverride;
use Sprout\Overrides\SessionOverride;
use Sprout\Support\BaseIdentityResolver;
use Sprout\Support\CookieHelper;
use function Sprout\sprout;

final class PathIdentityResolver extends BaseIdentityResolver implements IdentityResolverUsesParameters
{
use FindsIdentityInRouteParameter;
use FindsIdentityInRouteParameter {
setup as parameterSetup;
}

private int $segment = 1;

Expand Down Expand Up @@ -147,10 +149,15 @@ public function getTenantRoutePrefix(Tenancy $tenancy): string
*/
public function setup(Tenancy $tenancy, ?Tenant $tenant): void
{
// Call the parent implementation in case there's something there
parent::setup($tenancy, $tenant);

if ($tenant !== null && sprout()->config('services.cookies', false) === true) {
CookieHelper::setDefaults(path: $this->getTenantRoutePrefix($tenancy));
// Call the trait setup so that parameter has a default value
$this->parameterSetup($tenancy, $tenant);

if ($tenant !== null) {
CookieOverride::setPath($this->getTenantRoutePrefix($tenancy));
SessionOverride::setPath($this->getTenantRoutePrefix($tenancy));
}
}
}
7 changes: 7 additions & 0 deletions src/Http/Resolvers/SessionIdentityResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
use Illuminate\Http\Request;
use Illuminate\Routing\Router;
use Illuminate\Routing\RouteRegistrar;
use RuntimeException;
use Sprout\Contracts\Tenancy;
use Sprout\Http\Middleware\TenantRoutes;
use Sprout\Overrides\SessionOverride;
use Sprout\Support\BaseIdentityResolver;
use Sprout\Support\ResolutionHook;
use function Sprout\sprout;

final class SessionIdentityResolver extends BaseIdentityResolver
{
Expand Down Expand Up @@ -60,6 +63,10 @@ public function getRequestSessionName(Tenancy $tenancy): string
*/
public function resolveFromRequest(Request $request, Tenancy $tenancy): ?string
{
if (sprout()->hasOverride(SessionOverride::class)) {
throw new RuntimeException('Cannot use the session resolver for tenancy [' . $tenancy->getName() . '] and the session override');
}

/**
* This is unfortunately here because of the ludicrous return type
*
Expand Down
17 changes: 12 additions & 5 deletions src/Http/Resolvers/SubdomainIdentityResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
use Sprout\Contracts\Tenant;
use Sprout\Exceptions\TenantMissing;
use Sprout\Http\Middleware\TenantRoutes;
use Sprout\Overrides\CookieOverride;
use Sprout\Overrides\SessionOverride;
use Sprout\Support\BaseIdentityResolver;
use Sprout\Support\CookieHelper;
use function Sprout\sprout;

final class SubdomainIdentityResolver extends BaseIdentityResolver implements IdentityResolverUsesParameters
{
use FindsIdentityInRouteParameter;
use FindsIdentityInRouteParameter {
setup as parameterSetup;
}

private string $domain;

Expand Down Expand Up @@ -139,10 +141,15 @@ public function getTenantRouteDomain(Tenancy $tenancy): string
*/
public function setup(Tenancy $tenancy, ?Tenant $tenant): void
{
// Call the parent implementation in case there's something there
parent::setup($tenancy, $tenant);

if ($tenant !== null && sprout()->config('services.cookies', false) === true) {
CookieHelper::setDefaults(domain: $this->getTenantRouteDomain($tenancy));
// Call the trait setup so that parameter has a default value
$this->parameterSetup($tenancy, $tenant);

if ($tenant !== null) {
CookieOverride::setDomain($this->getTenantRouteDomain($tenancy));
SessionOverride::setDomain($this->getTenantRouteDomain($tenancy));
}
}
}
51 changes: 0 additions & 51 deletions src/Listeners/CleanupLaravelServices.php

This file was deleted.

Loading

0 comments on commit 0157640

Please sign in to comment.