diff --git a/assets/TenancyServiceProvider.stub.php b/assets/TenancyServiceProvider.stub.php index 59a74f52..f845df88 100644 --- a/assets/TenancyServiceProvider.stub.php +++ b/assets/TenancyServiceProvider.stub.php @@ -20,6 +20,8 @@ use Illuminate\Support\Facades\Route as RouteFacade; use Stancl\Tenancy\Middleware\InitializeTenancyByPath; use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData; +// todo update all the docblock sections here, including the Livewire references + class TenancyServiceProvider extends ServiceProvider { // By default, no namespace is used to support the callable array syntax. @@ -66,6 +68,7 @@ class TenancyServiceProvider extends ServiceProvider return $event->tenant; })->shouldBeQueued(false), // `false` by default, but you probably want to make this `true` for production. ], + Events\TenantMaintenanceModeEnabled::class => [], Events\TenantMaintenanceModeDisabled::class => [], @@ -137,17 +140,21 @@ class TenancyServiceProvider extends ServiceProvider protected function overrideUrlInTenantContext(): void { - /** - * Example of CLI tenant URL root override: - * - * RootUrlBootstrapper::$rootUrlOverride = function (Tenant $tenant) { - * $baseUrl = env('APP_URL'); - * $scheme = str($baseUrl)->before('://'); - * $hostname = str($baseUrl)->after($scheme . '://'); - * - * return $scheme . '://' . $tenant->getTenantKey() . '.' . $hostname; - * }; - */ + // Import your tenant model! + // \Stancl\Tenancy\Bootstrappers\RootUrlBootstrapper::$rootUrlOverride = function (Tenant $tenant, string $originalRootUrl) { + // $tenantDomain = $tenant instanceof \Stancl\Tenancy\Contracts\SingleDomainTenant + // ? $tenant->domain + // : $tenant->domains->first()->domain; + // + // $scheme = str($originalRootUrl)->before('://'); + // + // // If you're using subdomain identification: + // // $originalDomain = str($originalRootUrl)->after($scheme . '://'); + // // return $scheme . '://' . $tenantDomain . '.' . $originalDomain . '/'; + // + // // If you're using domain identification: + // return $scheme . '://' . $tenantDomain . '/'; + // }; } public function register() @@ -239,7 +246,7 @@ class TenancyServiceProvider extends ServiceProvider { $this->app->booted(function () { if (file_exists(base_path('routes/tenant.php'))) { - RouteFacade::namespace(static::$controllerNamespace) + RouteFacade::namespace(static::$controllerNamespace) ->middleware('tenant') ->group(base_path('routes/tenant.php')); } diff --git a/assets/config.php b/assets/config.php index 8d6c4570..68cab1f9 100644 --- a/assets/config.php +++ b/assets/config.php @@ -205,19 +205,29 @@ return [ ], /** - * Cache tenancy config. Used by the custom CacheManager and the PrefixCacheTenancyBootstrapper. + * Cache tenancy config. Used by the CacheTenancyBootstrapper, the CacheTagsBootstrapper, and the custom CacheManager. * * This works for all Cache facade calls, cache() helper * calls and direct calls to injected cache stores. * - * Each key in cache will have a tag applied on it. This tag is used to - * scope the cache both when writing to it and when reading from it. + * CacheTenancyBootstrapper: + * A prefix is applied *GLOBALLY*, using the `cache.prefix` config. This separates + * one tenant's cache from another's. The list of stores is used for refreshing + * them so that they re-load the prefix from the `cache.prefix` configuration. + * + * CacheTagsBootstrapper: + * Each key in cache will have a tag applied on it. This tag is used to + * scope the cache both when writing to it and when reading from it. * * You can clear cache selectively by specifying the tag. */ 'cache' => [ + 'prefix_base' => 'tenant', // This prefix_base, followed by the tenant_id, will form a cache prefix that will be used for every cache key. + 'stores' => [ + env('CACHE_STORE'), + ], + 'tag_base' => 'tenant', // This tag_base, followed by the tenant_id, will form a tag that will be applied on each cache call. - 'prefix_base' => 'tenant_', // This prefix_base, followed by the tenant_id, will form a cache prefix that will be used for every cache key. ], /** @@ -297,6 +307,7 @@ return [ 'prefix_base' => 'tenant', // Each key in Redis will be prepended by this prefix_base, followed by the tenant id. 'prefixed_connections' => [ // Redis connections whose keys are prefixed, to separate one tenant's keys from another. 'default', + // 'cache', // Enable this if you want to scope cache using RedisTenancyBootstrapper ], ], diff --git a/composer.json b/composer.json index 18bc62bf..b3e32a9c 100644 --- a/composer.json +++ b/composer.json @@ -68,8 +68,8 @@ "phpstan": "vendor/bin/phpstan", "phpstan-pro": "vendor/bin/phpstan --pro", "cs": "php-cs-fixer fix --config=.php-cs-fixer.php", - "test": "./test --no-coverage --color=always", - "test-full": "./test --color=always" + "test": "./test --no-coverage", + "test-full": "./test" }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/phpstan.neon b/phpstan.neon index e9851c60..7442b209 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -40,10 +40,6 @@ parameters: message: '#Illuminate\\Routing\\UrlGenerator#' paths: - src/Bootstrappers/FilesystemTenancyBootstrapper.php - - - message: '#Unable to resolve the template type (TMapWithKeysKey|TMapWithKeysValue) in call to method#' - paths: - - src/Concerns/DealsWithTenantSymlinks.php - '#Method Stancl\\Tenancy\\Tenancy::cachedResolvers\(\) should return array#' - '#Access to an undefined property Stancl\\Tenancy\\Middleware\\IdentificationMiddleware\:\:\$tenancy#' - '#Access to an undefined property Stancl\\Tenancy\\Middleware\\IdentificationMiddleware\:\:\$resolver#' diff --git a/src/Bootstrappers/CacheTenancyBootstrapper.php b/src/Bootstrappers/CacheTenancyBootstrapper.php index a1336bc1..a6084a80 100644 --- a/src/Bootstrappers/CacheTenancyBootstrapper.php +++ b/src/Bootstrappers/CacheTenancyBootstrapper.php @@ -17,10 +17,10 @@ use Stancl\Tenancy\Contracts\Tenant; */ class CacheTenancyBootstrapper implements TenancyBootstrapper { - protected string|null $originalPrefix = null; - public static array $tenantCacheStores = []; // E.g. ['redis'] public static Closure|null $prefixGenerator = null; + protected string|null $originalPrefix = null; + public function __construct( protected ConfigRepository $config, protected CacheManager $cacheManager, @@ -33,7 +33,7 @@ class CacheTenancyBootstrapper implements TenancyBootstrapper $prefix = $this->generatePrefix($tenant); - foreach (static::$tenantCacheStores as $store) { + foreach ($this->config->get('tenancy.cache.stores') as $store) { $this->setCachePrefix($store, $prefix); // Now that the store uses the passed prefix @@ -44,7 +44,7 @@ class CacheTenancyBootstrapper implements TenancyBootstrapper public function revert(): void { - foreach (static::$tenantCacheStores as $store) { + foreach ($this->config->get('tenancy.cache.stores') as $store) { $this->setCachePrefix($store, $this->originalPrefix); } } diff --git a/src/Bootstrappers/FilesystemTenancyBootstrapper.php b/src/Bootstrappers/FilesystemTenancyBootstrapper.php index 67914740..07f8f859 100644 --- a/src/Bootstrappers/FilesystemTenancyBootstrapper.php +++ b/src/Bootstrappers/FilesystemTenancyBootstrapper.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace Stancl\Tenancy\Bootstrappers; -use Illuminate\Contracts\Foundation\Application; +use Illuminate\Foundation\Application; use Illuminate\Routing\UrlGenerator; use Illuminate\Support\Facades\Storage; use Stancl\Tenancy\Contracts\TenancyBootstrapper; @@ -12,20 +12,15 @@ use Stancl\Tenancy\Contracts\Tenant; class FilesystemTenancyBootstrapper implements TenancyBootstrapper { - /** @var Application */ - protected $app; + public array $originalDisks = []; + public string|null $originalAssetUrl; + public string $originalStoragePath; - /** @var array */ - public $originalPaths = []; - - public function __construct(Application $app) - { - $this->app = $app; - $this->originalPaths = [ - 'disks' => [], - 'storage' => $this->app->storagePath(), - 'asset_url' => $this->app['config']['app.asset_url'], - ]; + public function __construct( + protected Application $app, + ) { + $this->originalAssetUrl = $this->app['config']['app.asset_url']; + $this->originalStoragePath = $app->storagePath(); $this->app['url']->macro('setAssetRoot', function ($root) { /** @var UrlGenerator $this */ @@ -37,83 +32,128 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper public function bootstrap(Tenant $tenant): void { - $suffix = $this->app['config']['tenancy.filesystem.suffix_base'] . $tenant->getTenantKey(); + $suffix = $this->suffix($tenant); - // storage_path() - if ($this->app['config']['tenancy.filesystem.suffix_storage_path'] ?? true) { - $this->app->useStoragePath($this->originalPaths['storage'] . "/{$suffix}"); - } - - // asset() - if ($this->app['config']['tenancy.filesystem.asset_helper_tenancy']) { - if ($this->originalPaths['asset_url']) { - $this->app['config']['app.asset_url'] = $this->originalPaths['asset_url'] . "/$suffix"; - $this->app['url']->setAssetRoot($this->app['config']['app.asset_url']); - } else { - $this->app['url']->setAssetRoot($this->app['url']->route('stancl.tenancy.asset', ['path' => ''])); - } - } - - // Storage facade - Storage::forgetDisk($this->app['config']['tenancy.filesystem.disks']); + $this->storagePath($suffix); + $this->assetHelper($suffix); + $this->forgetDisks(); foreach ($this->app['config']['tenancy.filesystem.disks'] as $disk) { - // todo@v4 \League\Flysystem\PathPrefixer is making this a lot more painful in flysystem v2 - $diskConfig = $this->app['config']["filesystems.disks.{$disk}"]; - $originalRoot = $diskConfig['root'] ?? null; + $this->diskRoot($disk, $tenant); - $this->originalPaths['disks']['path'][$disk] = $originalRoot; - - $finalPrefix = str_replace( - ['%storage_path%', '%tenant%'], - [storage_path(), $tenant->getTenantKey()], - $this->app['config']["tenancy.filesystem.root_override.{$disk}"] ?? '', + $this->diskUrl( + $disk, + str($this->app['config']["tenancy.filesystem.url_override.{$disk}"]) + ->replace('%tenant%', (string) $tenant->getTenantKey()) + ->toString(), ); - - if (! $finalPrefix) { - $finalPrefix = $originalRoot - ? rtrim($originalRoot, '/') . '/' . $suffix - : $suffix; - } - - $this->app['config']["filesystems.disks.{$disk}.root"] = $finalPrefix; - - // Storage Url - if ($diskConfig['driver'] === 'local') { - $this->originalPaths['disks']['url'][$disk] = $diskConfig['url'] ?? null; - - if ($url = str_replace( - '%tenant%', - (string) $tenant->getTenantKey(), - $this->app['config']["tenancy.filesystem.url_override.{$disk}"] ?? '' - )) { - $this->app['config']["filesystems.disks.{$disk}.url"] = url($url); - } - } } } public function revert(): void { - // storage_path() - $this->app->useStoragePath($this->originalPaths['storage']); + $this->storagePath(false); + $this->assetHelper(false); + $this->forgetDisks(); - // asset() - $this->app['config']['app.asset_url'] = $this->originalPaths['asset_url']; - $this->app['url']->setAssetRoot($this->app['config']['app.asset_url']); + foreach ($this->app['config']['tenancy.filesystem.disks'] as $disk) { + $this->diskRoot($disk, false); + $this->diskUrl($disk, false); + } + } - // Storage facade + protected function suffix(Tenant $tenant): string + { + return $this->app['config']['tenancy.filesystem.suffix_base'] . $tenant->getTenantKey(); + } + + protected function storagePath(string|false $suffix): void + { + if ($this->app['config']['tenancy.filesystem.suffix_storage_path'] === false) { + return; + } + + if ($suffix === false) { + $this->app->useStoragePath($this->originalStoragePath); + } else { + $this->app->useStoragePath($this->originalStoragePath . "/{$suffix}"); + } + } + + protected function assetHelper(string|false $suffix): void + { + if (! $this->app['config']['tenancy.filesystem.asset_helper_tenancy']) { + return; + } + + if ($suffix === false) { + $this->app['config']['app.asset_url'] = $this->originalAssetUrl; + $this->app['url']->setAssetRoot($this->originalAssetUrl); + + return; + } + + if ($this->originalAssetUrl) { + $this->app['config']['app.asset_url'] = $this->originalAssetUrl . "/$suffix"; + $this->app['url']->setAssetRoot($this->app['config']['app.asset_url']); + } else { + $this->app['url']->setAssetRoot($this->app['url']->route('stancl.tenancy.asset', ['path' => ''])); + } + } + + protected function forgetDisks(): void + { Storage::forgetDisk($this->app['config']['tenancy.filesystem.disks']); - foreach ($this->app['config']['tenancy.filesystem.disks'] as $diskName) { - $this->app['config']["filesystems.disks.$diskName.root"] = $this->originalPaths['disks']['path'][$diskName]; - $diskConfig = $this->app['config']['filesystems.disks.' . $diskName]; + } - // Storage Url - $url = data_get($this->originalPaths, "disks.url.$diskName"); + protected function diskRoot(string $disk, Tenant|false $tenant): void + { + if ($tenant === false) { + $this->app['config']["filesystems.disks.$disk.root"] = $this->originalDisks[$disk]['root']; - if ($diskConfig['driver'] === 'local' && ! is_null($url)) { - $this->app['config']["filesystems.disks.$diskName.url"] = $url; - } + return; + } + + $suffix = $this->suffix($tenant); + + $diskConfig = $this->app['config']["filesystems.disks.{$disk}"]; + $originalRoot = $diskConfig['root'] ?? null; + + $this->originalDisks[$disk]['root'] = $originalRoot; + + if ($override = $this->app['config']["tenancy.filesystem.root_override.{$disk}"]) { + // This is executed if the disk is in tenancy.filesystem.disks AND has a root_override + // This behavior is used for local disks. + $newRoot = str($override) + ->replace('%storage_path%', storage_path()) + ->replace('%original_storage_path%', $this->originalStoragePath) + ->replace('%tenant%', (string) $tenant->getTenantKey()) + ->toString(); + } else { + // This is executed if the disk is in tenancy.filesystem.disks but does NOT have a root_override + // This behavior is used for disks like S3. + $newRoot = $originalRoot + ? rtrim($originalRoot, '/') . '/' . $suffix + : $suffix; + } + + $this->app['config']["filesystems.disks.{$disk}.root"] = $newRoot; + } + + protected function diskUrl(string $disk, string|false $override): void + { + $diskConfig = $this->app['config']["filesystems.disks.{$disk}"]; + + if ($diskConfig['driver'] !== 'local' || $this->app['config']["tenancy.filesystem.url_override.{$disk}"] === null) { + return; + } + + if ($override === false) { + $url = data_get($this->originalDisks, "$disk.url"); + $this->app['config']["filesystems.disks.$disk.url"] = $url; + } else { + $this->originalDisks[$disk]['url'] ??= $diskConfig['url'] ?? null; + $this->app['config']["filesystems.disks.{$disk}.url"] = url($override); } } } diff --git a/src/Bootstrappers/RootUrlBootstrapper.php b/src/Bootstrappers/RootUrlBootstrapper.php index c623667d..e475fc54 100644 --- a/src/Bootstrappers/RootUrlBootstrapper.php +++ b/src/Bootstrappers/RootUrlBootstrapper.php @@ -6,7 +6,7 @@ namespace Stancl\Tenancy\Bootstrappers; use Closure; use Illuminate\Config\Repository; -use Illuminate\Contracts\Routing\UrlGenerator; +use Illuminate\Routing\UrlGenerator; use Stancl\Tenancy\Contracts\TenancyBootstrapper; use Stancl\Tenancy\Contracts\Tenant; @@ -26,7 +26,7 @@ class RootUrlBootstrapper implements TenancyBootstrapper $this->originalRootUrl = $this->urlGenerator->to('/'); if (static::$rootUrlOverride) { - $newRootUrl = (static::$rootUrlOverride)($tenant); + $newRootUrl = (static::$rootUrlOverride)($tenant, $this->originalRootUrl); $this->urlGenerator->forceRootUrl($newRootUrl); $this->config->set('app.url', $newRootUrl); diff --git a/src/Commands/Migrate.php b/src/Commands/Migrate.php index 47b95bd2..8fbd9c69 100644 --- a/src/Commands/Migrate.php +++ b/src/Commands/Migrate.php @@ -30,7 +30,7 @@ class Migrate extends MigrateCommand { parent::__construct($migrator, $dispatcher); - $this->addOption('skip-failing'); + $this->addOption('skip-failing', description: 'Continue execution if migration fails for a tenant'); $this->specifyParameters(); } @@ -49,19 +49,21 @@ class Migrate extends MigrateCommand foreach ($this->getTenants() as $tenant) { try { - $tenant->run(function ($tenant) { - $this->line("Tenant: {$tenant->getTenantKey()}"); + $this->components->info("Migrating tenant {$tenant->getTenantKey()}"); + $tenant->run(function ($tenant) { event(new MigratingDatabase($tenant)); // Migrate parent::handle(); event(new DatabaseMigrated($tenant)); }); - } catch (TenantDatabaseDoesNotExistException|QueryException $th) { + } catch (TenantDatabaseDoesNotExistException|QueryException $e) { if (! $this->option('skip-failing')) { - throw $th; + throw $e; } + + $this->components->warn("Migration failed for tenant {$tenant->getTenantKey()}: {$e->getMessage()}"); } } diff --git a/src/Concerns/DealsWithTenantSymlinks.php b/src/Concerns/DealsWithTenantSymlinks.php index bc908b22..37984dc8 100644 --- a/src/Concerns/DealsWithTenantSymlinks.php +++ b/src/Concerns/DealsWithTenantSymlinks.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace Stancl\Tenancy\Concerns; -use Illuminate\Support\Collection; +use Exception; use Stancl\Tenancy\Contracts\Tenant; trait DealsWithTenantSymlinks @@ -13,31 +13,44 @@ trait DealsWithTenantSymlinks * Get all possible tenant symlinks, existing or not (array of ['public path' => 'storage path']). * * Tenants can have a symlink for each disk registered in the tenancy.filesystem.url_override config. - * * This is used for creating all possible tenant symlinks and removing all existing tenant symlinks. + * The same storage path can be symlinked to multiple public paths, which is why the public path + * is the Collection key. * - * @return Collection + * @return array */ - protected function possibleTenantSymlinks(Tenant $tenant): Collection + protected function possibleTenantSymlinks(Tenant $tenant): array { - $diskUrls = config('tenancy.filesystem.url_override'); - $disks = config('tenancy.filesystem.root_override'); - $suffixBase = config('tenancy.filesystem.suffix_base'); + $disks = config('filesystems.disks'); + $urlOverrides = config('tenancy.filesystem.url_override'); + $rootOverrides = config('tenancy.filesystem.root_override'); + $tenantKey = $tenant->getTenantKey(); + $tenantStoragePath = tenancy()->run($tenant, fn () => storage_path()); - /** @var Collection> $symlinks */ - $symlinks = collect([]); + /** @var array $symlinks */ + $symlinks = []; + + foreach ($urlOverrides as $disk => $publicPath) { + if (! isset($disks[$disk])) { + continue; + } + + if (! isset($rootOverrides[$disk])) { + continue; + } + + if ($disks[$disk]['driver'] !== 'local') { + throw new Exception("Disk $disk is not a local disk. Only local disks can be symlinked."); + } - foreach ($diskUrls as $disk => $publicPath) { - $storagePath = str_replace('%storage_path%', $suffixBase . $tenantKey, $disks[$disk]); $publicPath = str_replace('%tenant%', (string) $tenantKey, $publicPath); + $storagePath = str_replace('%storage_path%', $tenantStoragePath, $rootOverrides[$disk]); - tenancy()->central(function () use ($symlinks, $publicPath, $storagePath) { - $symlinks->push([public_path($publicPath) => storage_path($storagePath)]); - }); + $symlinks[public_path($publicPath)] = $storagePath; } - return $symlinks->mapWithKeys(fn ($item) => $item); // [[a => b], [c => d]] -> [a => b, c => d] + return $symlinks; } /** Determine if the provided path is an existing symlink. */ diff --git a/src/Concerns/HasTenantOptions.php b/src/Concerns/HasTenantOptions.php index b558da64..9b3db143 100644 --- a/src/Concerns/HasTenantOptions.php +++ b/src/Concerns/HasTenantOptions.php @@ -16,8 +16,8 @@ trait HasTenantOptions protected function getOptions() { return array_merge([ - ['tenants', null, InputOption::VALUE_IS_ARRAY|InputOption::VALUE_OPTIONAL, '', null], - ['with-pending', null, InputOption::VALUE_NONE, 'include pending tenants in query'], + ['tenants', null, InputOption::VALUE_IS_ARRAY|InputOption::VALUE_OPTIONAL, 'The tenants to run this command for. Leave empty for all tenants', null], + ['with-pending', null, InputOption::VALUE_NONE, 'Include pending tenants in query'], ], parent::getOptions()); } diff --git a/src/Middleware/InitializeTenancyByDomainOrSubdomain.php b/src/Middleware/InitializeTenancyByDomainOrSubdomain.php index 32159aef..704826d0 100644 --- a/src/Middleware/InitializeTenancyByDomainOrSubdomain.php +++ b/src/Middleware/InitializeTenancyByDomainOrSubdomain.php @@ -27,7 +27,7 @@ class InitializeTenancyByDomainOrSubdomain extends InitializeTenancyBySubdomain if ($this->isSubdomain($domain)) { $domain = $this->makeSubdomain($domain); - if (is_object($domain) && $domain instanceof Exception) { + if ($domain instanceof Exception) { $onFail = static::$onFail ?? function ($e) { throw $e; }; @@ -36,7 +36,7 @@ class InitializeTenancyByDomainOrSubdomain extends InitializeTenancyBySubdomain } // If a Response instance was returned, we return it immediately. - if (is_object($domain) && $domain instanceof Response) { + if ($domain instanceof Response) { return $domain; } } diff --git a/src/Middleware/InitializeTenancyBySubdomain.php b/src/Middleware/InitializeTenancyBySubdomain.php index 8da1b1bc..a08cf116 100644 --- a/src/Middleware/InitializeTenancyBySubdomain.php +++ b/src/Middleware/InitializeTenancyBySubdomain.php @@ -37,7 +37,7 @@ class InitializeTenancyBySubdomain extends InitializeTenancyByDomain $subdomain = $this->makeSubdomain($this->getDomain($request)); - if (is_object($subdomain) && $subdomain instanceof Exception) { + if ($subdomain instanceof Exception) { $onFail = static::$onFail ?? function ($e) { throw $e; }; @@ -46,7 +46,7 @@ class InitializeTenancyBySubdomain extends InitializeTenancyByDomain } // If a Response instance was returned, we return it immediately. - if (is_object($subdomain) && $subdomain instanceof Response) { + if ($subdomain instanceof Response) { return $subdomain; } diff --git a/src/Tenancy.php b/src/Tenancy.php index d1661130..0a4ef9a3 100644 --- a/src/Tenancy.php +++ b/src/Tenancy.php @@ -31,7 +31,7 @@ class Tenancy /** Initialize tenancy for the passed tenant. */ public function initialize(Tenant|int|string $tenant): void { - if (! is_object($tenant)) { + if (! $tenant instanceof Tenant) { $tenantId = $tenant; $tenant = $this->find($tenantId); diff --git a/src/TenancyServiceProvider.php b/src/TenancyServiceProvider.php index cfbeb170..5ab80d08 100644 --- a/src/TenancyServiceProvider.php +++ b/src/TenancyServiceProvider.php @@ -130,7 +130,7 @@ class TenancyServiceProvider extends ServiceProvider $this->app->singleton('globalUrl', function ($app) { if ($app->bound(FilesystemTenancyBootstrapper::class)) { $instance = clone $app['url']; - $instance->setAssetRoot($app[FilesystemTenancyBootstrapper::class]->originalPaths['asset_url']); + $instance->setAssetRoot($app[FilesystemTenancyBootstrapper::class]->originalAssetUrl); } else { $instance = $app['url']; } diff --git a/test b/test index c5b0e99b..ecdbcd9a 100755 --- a/test +++ b/test @@ -1,4 +1,4 @@ #!/bin/bash # --columns doesn't seem to work at the moment, so we're setting it using an environment variable -docker-compose exec -e COLUMNS=$(tput cols) -T test vendor/bin/pest "$@" +docker-compose exec -e COLUMNS=$(tput cols) -T test vendor/bin/pest --colors=always "$@" diff --git a/tests/BootstrapperTest.php b/tests/BootstrapperTest.php index 89ef438b..ed6f002f 100644 --- a/tests/BootstrapperTest.php +++ b/tests/BootstrapperTest.php @@ -50,11 +50,15 @@ use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper; use Stancl\Tenancy\Bootstrappers\BroadcastChannelPrefixBootstrapper; use Stancl\Tenancy\Bootstrappers\Integrations\FortifyRouteBootstrapper; +// todo refactor this file -- too much stuff happening here + beforeEach(function () { $this->mockConsoleOutput = false; - config(['cache.default' => $cacheDriver = 'redis']); - CacheTenancyBootstrapper::$tenantCacheStores = [$cacheDriver]; + config([ + 'cache.default' => 'redis', + 'tenancy.cache.stores' => ['redis'], + ]); // Reset static properties of classes used in this test file to their default values BroadcastingConfigBootstrapper::$credentialsMap = []; TenancyBroadcastManager::$tenantBroadcasters = ['pusher', 'ably']; @@ -74,7 +78,6 @@ beforeEach(function () { afterEach(function () { // Reset static properties of classes used in this test file to their default values RootUrlBootstrapper::$rootUrlOverride = null; - CacheTenancyBootstrapper::$tenantCacheStores = []; TenancyBroadcastManager::$tenantBroadcasters = ['pusher', 'ably']; BroadcastingConfigBootstrapper::$credentialsMap = []; TenancyUrlGenerator::$prefixRouteNames = false; diff --git a/tests/CacheManagerTest.php b/tests/CacheTagsBootstrapperTest.php similarity index 100% rename from tests/CacheManagerTest.php rename to tests/CacheTagsBootstrapperTest.php diff --git a/tests/PrefixCacheBootstrapperTest.php b/tests/CacheTenancyBootstrapperTest.php similarity index 97% rename from tests/PrefixCacheBootstrapperTest.php rename to tests/CacheTenancyBootstrapperTest.php index dd9bb798..6f1e1bcc 100644 --- a/tests/PrefixCacheBootstrapperTest.php +++ b/tests/CacheTenancyBootstrapperTest.php @@ -19,9 +19,9 @@ beforeEach(function () { ], 'cache.default' => $cacheDriver = 'redis', 'cache.stores.' . $secondCacheDriver = 'redis2' => config('cache.stores.redis'), + 'tenancy.cache.stores' => [$cacheDriver, $secondCacheDriver], ]); - CacheTenancyBootstrapper::$tenantCacheStores = [$cacheDriver, $secondCacheDriver]; CacheTenancyBootstrapper::$prefixGenerator = null; Event::listen(TenancyInitialized::class, BootstrapTenancy::class); @@ -29,7 +29,6 @@ beforeEach(function () { }); afterEach(function () { - CacheTenancyBootstrapper::$tenantCacheStores = []; CacheTenancyBootstrapper::$prefixGenerator = null; }); @@ -180,7 +179,7 @@ test('cache is prefixed correctly when using a repository injected in a singleto test('specific central cache store can be used inside a service', function () { // Make sure 'redis' (the default store) is the only prefixed store - CacheTenancyBootstrapper::$tenantCacheStores = ['redis']; + config(['tenancy.cache.stores' => ['redis']]); // Name of the non-default, central cache store that we'll use using cache()->store($cacheStore) $cacheStore = 'redis2'; @@ -217,7 +216,7 @@ test('specific central cache store can be used inside a service', function () { test('only the stores specified in tenantCacheStores get prefixed', function () { // Make sure the currently used store ('redis') is the only store in $tenantCacheStores - CacheTenancyBootstrapper::$tenantCacheStores = [$prefixedStore = 'redis']; + config(['tenancy.cache.stores' => [$prefixedStore = 'redis']]); $centralValue = 'central-value'; $assertStoreIsNotPrefixed = function (string $unprefixedStore) use ($prefixedStore, $centralValue) { diff --git a/tests/CommandsTest.php b/tests/CommandsTest.php index 09f13207..8934248b 100644 --- a/tests/CommandsTest.php +++ b/tests/CommandsTest.php @@ -24,6 +24,7 @@ use Stancl\Tenancy\Events\TenancyInitialized; use Stancl\Tenancy\Listeners\BootstrapTenancy; use Stancl\Tenancy\Listeners\RevertToCentralContext; use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper; +use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper; use Stancl\Tenancy\Database\Exceptions\TenantDatabaseDoesNotExistException; beforeEach(function () { @@ -298,6 +299,8 @@ test('run command with array of tenants works', function () { }); test('link command works', function() { + config(['tenancy.bootstrappers' => [FilesystemTenancyBootstrapper::class]]); + $tenantId1 = Tenant::create()->getTenantKey(); $tenantId2 = Tenant::create()->getTenantKey(); pest()->artisan('tenants:link') @@ -318,6 +321,8 @@ test('link command works', function() { }); test('link command works with a specified tenant', function() { + config(['tenancy.bootstrappers' => [FilesystemTenancyBootstrapper::class]]); + $tenantKey = Tenant::create()->getTenantKey(); pest()->artisan('tenants:link', [ diff --git a/tests/Etc/Tenant.php b/tests/Etc/Tenant.php index d0eaa0ca..731a179b 100644 --- a/tests/Etc/Tenant.php +++ b/tests/Etc/Tenant.php @@ -8,7 +8,6 @@ use Stancl\Tenancy\Database\Contracts\TenantWithDatabase; use Stancl\Tenancy\Database\Concerns\HasDatabase; use Stancl\Tenancy\Database\Concerns\HasDomains; use Stancl\Tenancy\Database\Concerns\HasPending; -use Stancl\Tenancy\Database\Concerns\MaintenanceMode; use Stancl\Tenancy\Database\Models; /** @@ -18,7 +17,7 @@ class Tenant extends Models\Tenant implements TenantWithDatabase { public static array $extraCustomColumns = []; - use HasDatabase, HasDomains, HasPending, MaintenanceMode; + use HasDatabase, HasDomains, HasPending; public static function getCustomColumns(): array { diff --git a/tests/GlobalCacheTest.php b/tests/GlobalCacheTest.php index eefe6a1b..15156827 100644 --- a/tests/GlobalCacheTest.php +++ b/tests/GlobalCacheTest.php @@ -2,7 +2,6 @@ declare(strict_types=1); -use Stancl\Tenancy\Overrides\CacheManager; use Stancl\Tenancy\Tests\Etc\Tenant; use Illuminate\Support\Facades\Event; use Stancl\Tenancy\Events\TenancyEnded; @@ -14,17 +13,15 @@ use Stancl\Tenancy\Bootstrappers\CacheTagsBootstrapper; use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper; beforeEach(function () { - config(['cache.default' => $cacheDriver = 'redis']); - CacheTenancyBootstrapper::$tenantCacheStores = [$cacheDriver]; + config([ + 'cache.default' => 'redis', + 'tenancy.cache.stores' => ['redis'], + ]); Event::listen(TenancyInitialized::class, BootstrapTenancy::class); Event::listen(TenancyEnded::class, RevertToCentralContext::class); }); -afterEach(function () { - CacheTenancyBootstrapper::$tenantCacheStores = []; -}); - test('global cache manager stores data in global cache', function (string $bootstrapper) { config(['tenancy.bootstrappers' => [$bootstrapper]]); diff --git a/tests/MaintenanceModeTest.php b/tests/MaintenanceModeTest.php index 86366e2f..59024c96 100644 --- a/tests/MaintenanceModeTest.php +++ b/tests/MaintenanceModeTest.php @@ -10,6 +10,10 @@ use Stancl\Tenancy\Middleware\CheckTenantForMaintenanceMode; use Stancl\Tenancy\Middleware\InitializeTenancyByDomain; use Stancl\Tenancy\Tests\Etc\Tenant; +beforeEach(function () { + config(['tenancy.models.tenant' => MaintenanceTenant::class]); +}); + test('tenants can be in maintenance mode', function () { Route::get('/foo', function () { return 'bar'; diff --git a/tests/TenantAssetTest.php b/tests/TenantAssetTest.php index 72612aa0..f7191831 100644 --- a/tests/TenantAssetTest.php +++ b/tests/TenantAssetTest.php @@ -16,6 +16,8 @@ use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData; use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper; use Stancl\Tenancy\Bootstrappers\UrlGeneratorBootstrapper; use Stancl\Tenancy\Controllers\TenantAssetController; +use Stancl\Tenancy\Events\TenancyEnded; +use Stancl\Tenancy\Listeners\RevertToCentralContext; use Stancl\Tenancy\Overrides\TenancyUrlGenerator; beforeEach(function () { @@ -32,6 +34,7 @@ beforeEach(function () { $cloneAction->handle(Route::getRoutes()->getByName('stancl.tenancy.asset')); Event::listen(TenancyInitialized::class, BootstrapTenancy::class); + Event::listen(TenancyEnded::class, RevertToCentralContext::class); }); test('asset can be accessed using the url returned by the tenant asset helper', function () { @@ -68,6 +71,9 @@ test('asset helper returns a link to tenant asset controller when asset url is n tenancy()->initialize($tenant); expect(asset('foo'))->toBe(route('stancl.tenancy.asset', ['path' => 'foo'])); + + tenancy()->end(); + expect(asset('foo'))->toBe('http://localhost/foo'); }); test('asset helper returns a link to an external url when asset url is not null', function () { @@ -78,6 +84,9 @@ test('asset helper returns a link to an external url when asset url is not null' tenancy()->initialize($tenant); expect(asset('foo'))->toBe("https://an-s3-bucket/tenant{$tenant->id}/foo"); + + tenancy()->end(); + expect(asset('foo'))->toBe('https://an-s3-bucket/foo'); }); test('asset helper works correctly with path identification', function (bool $kernelIdentification) { @@ -243,14 +252,3 @@ test('test asset controller returns a 404 when accessing a file outside the stor 'X-Tenant' => $tenant->id, ]); }); - -function getEnvironmentSetUp($app) -{ - $app->booted(function () { - if (file_exists(base_path('routes/tenant.php'))) { - Route::middleware(['web']) - ->namespace(pest()->app['config']['tenancy.tenant_route_namespace'] ?? 'App\Http\Controllers') - ->group(base_path('routes/tenant.php')); - } - }); -}