1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-12 08:24:04 +00:00

Filesystem logic refactor, improved defaults for cache tenancy (#42)

* refactor FilesystemTenancyBootstrapper

* clean up tests and improve coverage

* minor maintenance mode changes

* Improve tenants:migrate --skip-failing logic

* make tenants:migrate output consistently formatted

* minor RootUrlBootstrapper + misc changes

* cache bootstrapper-related improvements

* Fix code style (php-cs-fixer)

* misc refactor

* Fix code style (php-cs-fixer)

* add %original_storage_path% to fs bootstrapper, improve default config for cache

* rename method

* inject concrete implementations where needed instead of abstracts

* Fix code style (php-cs-fixer)

* refactor DealsWithTenantSymlinks

* remove obsolete phpstan ignore

---------

Co-authored-by: PHP CS Fixer <phpcsfixer@example.com>
This commit is contained in:
Samuel Štancl 2024-04-02 04:26:10 +02:00 committed by GitHub
parent 4b6fa22aa7
commit a41ad69023
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 234 additions and 160 deletions

View file

@ -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'));
}

View file

@ -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
],
],

View file

@ -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,

View file

@ -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#'

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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);

View file

@ -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()}");
}
}

View file

@ -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<string, string>
* @return array<string, string>
*/
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<array<string, string>> $symlinks */
$symlinks = collect([]);
/** @var array<string, string> $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. */

View file

@ -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());
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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'];
}

2
test
View file

@ -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 "$@"

View file

@ -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;

View file

@ -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) {

View file

@ -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', [

View file

@ -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
{

View file

@ -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]]);

View file

@ -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';

View file

@ -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'));
}
});
}