mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 08:44:02 +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:
parent
4b6fa22aa7
commit
a41ad69023
23 changed files with 234 additions and 160 deletions
|
|
@ -20,6 +20,8 @@ use Illuminate\Support\Facades\Route as RouteFacade;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
||||||
|
|
||||||
|
// todo update all the docblock sections here, including the Livewire references
|
||||||
|
|
||||||
class TenancyServiceProvider extends ServiceProvider
|
class TenancyServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
// By default, no namespace is used to support the callable array syntax.
|
// By default, no namespace is used to support the callable array syntax.
|
||||||
|
|
@ -66,6 +68,7 @@ class TenancyServiceProvider extends ServiceProvider
|
||||||
return $event->tenant;
|
return $event->tenant;
|
||||||
})->shouldBeQueued(false), // `false` by default, but you probably want to make this `true` for production.
|
})->shouldBeQueued(false), // `false` by default, but you probably want to make this `true` for production.
|
||||||
],
|
],
|
||||||
|
|
||||||
Events\TenantMaintenanceModeEnabled::class => [],
|
Events\TenantMaintenanceModeEnabled::class => [],
|
||||||
Events\TenantMaintenanceModeDisabled::class => [],
|
Events\TenantMaintenanceModeDisabled::class => [],
|
||||||
|
|
||||||
|
|
@ -137,17 +140,21 @@ class TenancyServiceProvider extends ServiceProvider
|
||||||
|
|
||||||
protected function overrideUrlInTenantContext(): void
|
protected function overrideUrlInTenantContext(): void
|
||||||
{
|
{
|
||||||
/**
|
// Import your tenant model!
|
||||||
* Example of CLI tenant URL root override:
|
// \Stancl\Tenancy\Bootstrappers\RootUrlBootstrapper::$rootUrlOverride = function (Tenant $tenant, string $originalRootUrl) {
|
||||||
*
|
// $tenantDomain = $tenant instanceof \Stancl\Tenancy\Contracts\SingleDomainTenant
|
||||||
* RootUrlBootstrapper::$rootUrlOverride = function (Tenant $tenant) {
|
// ? $tenant->domain
|
||||||
* $baseUrl = env('APP_URL');
|
// : $tenant->domains->first()->domain;
|
||||||
* $scheme = str($baseUrl)->before('://');
|
//
|
||||||
* $hostname = str($baseUrl)->after($scheme . '://');
|
// $scheme = str($originalRootUrl)->before('://');
|
||||||
*
|
//
|
||||||
* return $scheme . '://' . $tenant->getTenantKey() . '.' . $hostname;
|
// // 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()
|
public function register()
|
||||||
|
|
@ -239,7 +246,7 @@ class TenancyServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
$this->app->booted(function () {
|
$this->app->booted(function () {
|
||||||
if (file_exists(base_path('routes/tenant.php'))) {
|
if (file_exists(base_path('routes/tenant.php'))) {
|
||||||
RouteFacade::namespace(static::$controllerNamespace)
|
RouteFacade::namespace(static::$controllerNamespace)
|
||||||
->middleware('tenant')
|
->middleware('tenant')
|
||||||
->group(base_path('routes/tenant.php'));
|
->group(base_path('routes/tenant.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
|
* This works for all Cache facade calls, cache() helper
|
||||||
* calls and direct calls to injected cache stores.
|
* calls and direct calls to injected cache stores.
|
||||||
*
|
*
|
||||||
* Each key in cache will have a tag applied on it. This tag is used to
|
* CacheTenancyBootstrapper:
|
||||||
* scope the cache both when writing to it and when reading from it.
|
* 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.
|
* You can clear cache selectively by specifying the tag.
|
||||||
*/
|
*/
|
||||||
'cache' => [
|
'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.
|
'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.
|
'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.
|
'prefixed_connections' => [ // Redis connections whose keys are prefixed, to separate one tenant's keys from another.
|
||||||
'default',
|
'default',
|
||||||
|
// 'cache', // Enable this if you want to scope cache using RedisTenancyBootstrapper
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,8 +68,8 @@
|
||||||
"phpstan": "vendor/bin/phpstan",
|
"phpstan": "vendor/bin/phpstan",
|
||||||
"phpstan-pro": "vendor/bin/phpstan --pro",
|
"phpstan-pro": "vendor/bin/phpstan --pro",
|
||||||
"cs": "php-cs-fixer fix --config=.php-cs-fixer.php",
|
"cs": "php-cs-fixer fix --config=.php-cs-fixer.php",
|
||||||
"test": "./test --no-coverage --color=always",
|
"test": "./test --no-coverage",
|
||||||
"test-full": "./test --color=always"
|
"test-full": "./test"
|
||||||
},
|
},
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,6 @@ parameters:
|
||||||
message: '#Illuminate\\Routing\\UrlGenerator#'
|
message: '#Illuminate\\Routing\\UrlGenerator#'
|
||||||
paths:
|
paths:
|
||||||
- src/Bootstrappers/FilesystemTenancyBootstrapper.php
|
- 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#'
|
- '#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\:\:\$tenancy#'
|
||||||
- '#Access to an undefined property Stancl\\Tenancy\\Middleware\\IdentificationMiddleware\:\:\$resolver#'
|
- '#Access to an undefined property Stancl\\Tenancy\\Middleware\\IdentificationMiddleware\:\:\$resolver#'
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,10 @@ use Stancl\Tenancy\Contracts\Tenant;
|
||||||
*/
|
*/
|
||||||
class CacheTenancyBootstrapper implements TenancyBootstrapper
|
class CacheTenancyBootstrapper implements TenancyBootstrapper
|
||||||
{
|
{
|
||||||
protected string|null $originalPrefix = null;
|
|
||||||
public static array $tenantCacheStores = []; // E.g. ['redis']
|
|
||||||
public static Closure|null $prefixGenerator = null;
|
public static Closure|null $prefixGenerator = null;
|
||||||
|
|
||||||
|
protected string|null $originalPrefix = null;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected ConfigRepository $config,
|
protected ConfigRepository $config,
|
||||||
protected CacheManager $cacheManager,
|
protected CacheManager $cacheManager,
|
||||||
|
|
@ -33,7 +33,7 @@ class CacheTenancyBootstrapper implements TenancyBootstrapper
|
||||||
|
|
||||||
$prefix = $this->generatePrefix($tenant);
|
$prefix = $this->generatePrefix($tenant);
|
||||||
|
|
||||||
foreach (static::$tenantCacheStores as $store) {
|
foreach ($this->config->get('tenancy.cache.stores') as $store) {
|
||||||
$this->setCachePrefix($store, $prefix);
|
$this->setCachePrefix($store, $prefix);
|
||||||
|
|
||||||
// Now that the store uses the passed prefix
|
// Now that the store uses the passed prefix
|
||||||
|
|
@ -44,7 +44,7 @@ class CacheTenancyBootstrapper implements TenancyBootstrapper
|
||||||
|
|
||||||
public function revert(): void
|
public function revert(): void
|
||||||
{
|
{
|
||||||
foreach (static::$tenantCacheStores as $store) {
|
foreach ($this->config->get('tenancy.cache.stores') as $store) {
|
||||||
$this->setCachePrefix($store, $this->originalPrefix);
|
$this->setCachePrefix($store, $this->originalPrefix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Bootstrappers;
|
namespace Stancl\Tenancy\Bootstrappers;
|
||||||
|
|
||||||
use Illuminate\Contracts\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
use Illuminate\Routing\UrlGenerator;
|
use Illuminate\Routing\UrlGenerator;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||||
|
|
@ -12,20 +12,15 @@ use Stancl\Tenancy\Contracts\Tenant;
|
||||||
|
|
||||||
class FilesystemTenancyBootstrapper implements TenancyBootstrapper
|
class FilesystemTenancyBootstrapper implements TenancyBootstrapper
|
||||||
{
|
{
|
||||||
/** @var Application */
|
public array $originalDisks = [];
|
||||||
protected $app;
|
public string|null $originalAssetUrl;
|
||||||
|
public string $originalStoragePath;
|
||||||
|
|
||||||
/** @var array */
|
public function __construct(
|
||||||
public $originalPaths = [];
|
protected Application $app,
|
||||||
|
) {
|
||||||
public function __construct(Application $app)
|
$this->originalAssetUrl = $this->app['config']['app.asset_url'];
|
||||||
{
|
$this->originalStoragePath = $app->storagePath();
|
||||||
$this->app = $app;
|
|
||||||
$this->originalPaths = [
|
|
||||||
'disks' => [],
|
|
||||||
'storage' => $this->app->storagePath(),
|
|
||||||
'asset_url' => $this->app['config']['app.asset_url'],
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->app['url']->macro('setAssetRoot', function ($root) {
|
$this->app['url']->macro('setAssetRoot', function ($root) {
|
||||||
/** @var UrlGenerator $this */
|
/** @var UrlGenerator $this */
|
||||||
|
|
@ -37,83 +32,128 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
|
||||||
|
|
||||||
public function bootstrap(Tenant $tenant): void
|
public function bootstrap(Tenant $tenant): void
|
||||||
{
|
{
|
||||||
$suffix = $this->app['config']['tenancy.filesystem.suffix_base'] . $tenant->getTenantKey();
|
$suffix = $this->suffix($tenant);
|
||||||
|
|
||||||
// storage_path()
|
$this->storagePath($suffix);
|
||||||
if ($this->app['config']['tenancy.filesystem.suffix_storage_path'] ?? true) {
|
$this->assetHelper($suffix);
|
||||||
$this->app->useStoragePath($this->originalPaths['storage'] . "/{$suffix}");
|
$this->forgetDisks();
|
||||||
}
|
|
||||||
|
|
||||||
// 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']);
|
|
||||||
|
|
||||||
foreach ($this->app['config']['tenancy.filesystem.disks'] as $disk) {
|
foreach ($this->app['config']['tenancy.filesystem.disks'] as $disk) {
|
||||||
// todo@v4 \League\Flysystem\PathPrefixer is making this a lot more painful in flysystem v2
|
$this->diskRoot($disk, $tenant);
|
||||||
$diskConfig = $this->app['config']["filesystems.disks.{$disk}"];
|
|
||||||
$originalRoot = $diskConfig['root'] ?? null;
|
|
||||||
|
|
||||||
$this->originalPaths['disks']['path'][$disk] = $originalRoot;
|
$this->diskUrl(
|
||||||
|
$disk,
|
||||||
$finalPrefix = str_replace(
|
str($this->app['config']["tenancy.filesystem.url_override.{$disk}"])
|
||||||
['%storage_path%', '%tenant%'],
|
->replace('%tenant%', (string) $tenant->getTenantKey())
|
||||||
[storage_path(), $tenant->getTenantKey()],
|
->toString(),
|
||||||
$this->app['config']["tenancy.filesystem.root_override.{$disk}"] ?? '',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
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
|
public function revert(): void
|
||||||
{
|
{
|
||||||
// storage_path()
|
$this->storagePath(false);
|
||||||
$this->app->useStoragePath($this->originalPaths['storage']);
|
$this->assetHelper(false);
|
||||||
|
$this->forgetDisks();
|
||||||
|
|
||||||
// asset()
|
foreach ($this->app['config']['tenancy.filesystem.disks'] as $disk) {
|
||||||
$this->app['config']['app.asset_url'] = $this->originalPaths['asset_url'];
|
$this->diskRoot($disk, false);
|
||||||
$this->app['url']->setAssetRoot($this->app['config']['app.asset_url']);
|
$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']);
|
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
|
protected function diskRoot(string $disk, Tenant|false $tenant): void
|
||||||
$url = data_get($this->originalPaths, "disks.url.$diskName");
|
{
|
||||||
|
if ($tenant === false) {
|
||||||
|
$this->app['config']["filesystems.disks.$disk.root"] = $this->originalDisks[$disk]['root'];
|
||||||
|
|
||||||
if ($diskConfig['driver'] === 'local' && ! is_null($url)) {
|
return;
|
||||||
$this->app['config']["filesystems.disks.$diskName.url"] = $url;
|
}
|
||||||
}
|
|
||||||
|
$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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ namespace Stancl\Tenancy\Bootstrappers;
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Config\Repository;
|
use Illuminate\Config\Repository;
|
||||||
use Illuminate\Contracts\Routing\UrlGenerator;
|
use Illuminate\Routing\UrlGenerator;
|
||||||
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Contracts\Tenant;
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ class RootUrlBootstrapper implements TenancyBootstrapper
|
||||||
$this->originalRootUrl = $this->urlGenerator->to('/');
|
$this->originalRootUrl = $this->urlGenerator->to('/');
|
||||||
|
|
||||||
if (static::$rootUrlOverride) {
|
if (static::$rootUrlOverride) {
|
||||||
$newRootUrl = (static::$rootUrlOverride)($tenant);
|
$newRootUrl = (static::$rootUrlOverride)($tenant, $this->originalRootUrl);
|
||||||
|
|
||||||
$this->urlGenerator->forceRootUrl($newRootUrl);
|
$this->urlGenerator->forceRootUrl($newRootUrl);
|
||||||
$this->config->set('app.url', $newRootUrl);
|
$this->config->set('app.url', $newRootUrl);
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class Migrate extends MigrateCommand
|
||||||
{
|
{
|
||||||
parent::__construct($migrator, $dispatcher);
|
parent::__construct($migrator, $dispatcher);
|
||||||
|
|
||||||
$this->addOption('skip-failing');
|
$this->addOption('skip-failing', description: 'Continue execution if migration fails for a tenant');
|
||||||
|
|
||||||
$this->specifyParameters();
|
$this->specifyParameters();
|
||||||
}
|
}
|
||||||
|
|
@ -49,19 +49,21 @@ class Migrate extends MigrateCommand
|
||||||
|
|
||||||
foreach ($this->getTenants() as $tenant) {
|
foreach ($this->getTenants() as $tenant) {
|
||||||
try {
|
try {
|
||||||
$tenant->run(function ($tenant) {
|
$this->components->info("Migrating tenant {$tenant->getTenantKey()}");
|
||||||
$this->line("Tenant: {$tenant->getTenantKey()}");
|
|
||||||
|
|
||||||
|
$tenant->run(function ($tenant) {
|
||||||
event(new MigratingDatabase($tenant));
|
event(new MigratingDatabase($tenant));
|
||||||
// Migrate
|
// Migrate
|
||||||
parent::handle();
|
parent::handle();
|
||||||
|
|
||||||
event(new DatabaseMigrated($tenant));
|
event(new DatabaseMigrated($tenant));
|
||||||
});
|
});
|
||||||
} catch (TenantDatabaseDoesNotExistException|QueryException $th) {
|
} catch (TenantDatabaseDoesNotExistException|QueryException $e) {
|
||||||
if (! $this->option('skip-failing')) {
|
if (! $this->option('skip-failing')) {
|
||||||
throw $th;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->components->warn("Migration failed for tenant {$tenant->getTenantKey()}: {$e->getMessage()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Concerns;
|
namespace Stancl\Tenancy\Concerns;
|
||||||
|
|
||||||
use Illuminate\Support\Collection;
|
use Exception;
|
||||||
use Stancl\Tenancy\Contracts\Tenant;
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
|
|
||||||
trait DealsWithTenantSymlinks
|
trait DealsWithTenantSymlinks
|
||||||
|
|
@ -13,31 +13,44 @@ trait DealsWithTenantSymlinks
|
||||||
* Get all possible tenant symlinks, existing or not (array of ['public path' => 'storage path']).
|
* 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.
|
* 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.
|
* 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('filesystems.disks');
|
||||||
$disks = config('tenancy.filesystem.root_override');
|
$urlOverrides = config('tenancy.filesystem.url_override');
|
||||||
$suffixBase = config('tenancy.filesystem.suffix_base');
|
$rootOverrides = config('tenancy.filesystem.root_override');
|
||||||
|
|
||||||
$tenantKey = $tenant->getTenantKey();
|
$tenantKey = $tenant->getTenantKey();
|
||||||
|
$tenantStoragePath = tenancy()->run($tenant, fn () => storage_path());
|
||||||
|
|
||||||
/** @var Collection<array<string, string>> $symlinks */
|
/** @var array<string, string> $symlinks */
|
||||||
$symlinks = collect([]);
|
$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);
|
$publicPath = str_replace('%tenant%', (string) $tenantKey, $publicPath);
|
||||||
|
$storagePath = str_replace('%storage_path%', $tenantStoragePath, $rootOverrides[$disk]);
|
||||||
|
|
||||||
tenancy()->central(function () use ($symlinks, $publicPath, $storagePath) {
|
$symlinks[public_path($publicPath)] = $storagePath;
|
||||||
$symlinks->push([public_path($publicPath) => storage_path($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. */
|
/** Determine if the provided path is an existing symlink. */
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ trait HasTenantOptions
|
||||||
protected function getOptions()
|
protected function getOptions()
|
||||||
{
|
{
|
||||||
return array_merge([
|
return array_merge([
|
||||||
['tenants', null, InputOption::VALUE_IS_ARRAY|InputOption::VALUE_OPTIONAL, '', null],
|
['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'],
|
['with-pending', null, InputOption::VALUE_NONE, 'Include pending tenants in query'],
|
||||||
], parent::getOptions());
|
], parent::getOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ class InitializeTenancyByDomainOrSubdomain extends InitializeTenancyBySubdomain
|
||||||
if ($this->isSubdomain($domain)) {
|
if ($this->isSubdomain($domain)) {
|
||||||
$domain = $this->makeSubdomain($domain);
|
$domain = $this->makeSubdomain($domain);
|
||||||
|
|
||||||
if (is_object($domain) && $domain instanceof Exception) {
|
if ($domain instanceof Exception) {
|
||||||
$onFail = static::$onFail ?? function ($e) {
|
$onFail = static::$onFail ?? function ($e) {
|
||||||
throw $e;
|
throw $e;
|
||||||
};
|
};
|
||||||
|
|
@ -36,7 +36,7 @@ class InitializeTenancyByDomainOrSubdomain extends InitializeTenancyBySubdomain
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a Response instance was returned, we return it immediately.
|
// If a Response instance was returned, we return it immediately.
|
||||||
if (is_object($domain) && $domain instanceof Response) {
|
if ($domain instanceof Response) {
|
||||||
return $domain;
|
return $domain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class InitializeTenancyBySubdomain extends InitializeTenancyByDomain
|
||||||
|
|
||||||
$subdomain = $this->makeSubdomain($this->getDomain($request));
|
$subdomain = $this->makeSubdomain($this->getDomain($request));
|
||||||
|
|
||||||
if (is_object($subdomain) && $subdomain instanceof Exception) {
|
if ($subdomain instanceof Exception) {
|
||||||
$onFail = static::$onFail ?? function ($e) {
|
$onFail = static::$onFail ?? function ($e) {
|
||||||
throw $e;
|
throw $e;
|
||||||
};
|
};
|
||||||
|
|
@ -46,7 +46,7 @@ class InitializeTenancyBySubdomain extends InitializeTenancyByDomain
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a Response instance was returned, we return it immediately.
|
// If a Response instance was returned, we return it immediately.
|
||||||
if (is_object($subdomain) && $subdomain instanceof Response) {
|
if ($subdomain instanceof Response) {
|
||||||
return $subdomain;
|
return $subdomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class Tenancy
|
||||||
/** Initialize tenancy for the passed tenant. */
|
/** Initialize tenancy for the passed tenant. */
|
||||||
public function initialize(Tenant|int|string $tenant): void
|
public function initialize(Tenant|int|string $tenant): void
|
||||||
{
|
{
|
||||||
if (! is_object($tenant)) {
|
if (! $tenant instanceof Tenant) {
|
||||||
$tenantId = $tenant;
|
$tenantId = $tenant;
|
||||||
$tenant = $this->find($tenantId);
|
$tenant = $this->find($tenantId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ class TenancyServiceProvider extends ServiceProvider
|
||||||
$this->app->singleton('globalUrl', function ($app) {
|
$this->app->singleton('globalUrl', function ($app) {
|
||||||
if ($app->bound(FilesystemTenancyBootstrapper::class)) {
|
if ($app->bound(FilesystemTenancyBootstrapper::class)) {
|
||||||
$instance = clone $app['url'];
|
$instance = clone $app['url'];
|
||||||
$instance->setAssetRoot($app[FilesystemTenancyBootstrapper::class]->originalPaths['asset_url']);
|
$instance->setAssetRoot($app[FilesystemTenancyBootstrapper::class]->originalAssetUrl);
|
||||||
} else {
|
} else {
|
||||||
$instance = $app['url'];
|
$instance = $app['url'];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
test
2
test
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# --columns doesn't seem to work at the moment, so we're setting it using an environment variable
|
# --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 "$@"
|
||||||
|
|
|
||||||
|
|
@ -50,11 +50,15 @@ use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\BroadcastChannelPrefixBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\BroadcastChannelPrefixBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\Integrations\FortifyRouteBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\Integrations\FortifyRouteBootstrapper;
|
||||||
|
|
||||||
|
// todo refactor this file -- too much stuff happening here
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
$this->mockConsoleOutput = false;
|
$this->mockConsoleOutput = false;
|
||||||
|
|
||||||
config(['cache.default' => $cacheDriver = 'redis']);
|
config([
|
||||||
CacheTenancyBootstrapper::$tenantCacheStores = [$cacheDriver];
|
'cache.default' => 'redis',
|
||||||
|
'tenancy.cache.stores' => ['redis'],
|
||||||
|
]);
|
||||||
// Reset static properties of classes used in this test file to their default values
|
// Reset static properties of classes used in this test file to their default values
|
||||||
BroadcastingConfigBootstrapper::$credentialsMap = [];
|
BroadcastingConfigBootstrapper::$credentialsMap = [];
|
||||||
TenancyBroadcastManager::$tenantBroadcasters = ['pusher', 'ably'];
|
TenancyBroadcastManager::$tenantBroadcasters = ['pusher', 'ably'];
|
||||||
|
|
@ -74,7 +78,6 @@ beforeEach(function () {
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
// Reset static properties of classes used in this test file to their default values
|
// Reset static properties of classes used in this test file to their default values
|
||||||
RootUrlBootstrapper::$rootUrlOverride = null;
|
RootUrlBootstrapper::$rootUrlOverride = null;
|
||||||
CacheTenancyBootstrapper::$tenantCacheStores = [];
|
|
||||||
TenancyBroadcastManager::$tenantBroadcasters = ['pusher', 'ably'];
|
TenancyBroadcastManager::$tenantBroadcasters = ['pusher', 'ably'];
|
||||||
BroadcastingConfigBootstrapper::$credentialsMap = [];
|
BroadcastingConfigBootstrapper::$credentialsMap = [];
|
||||||
TenancyUrlGenerator::$prefixRouteNames = false;
|
TenancyUrlGenerator::$prefixRouteNames = false;
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ beforeEach(function () {
|
||||||
],
|
],
|
||||||
'cache.default' => $cacheDriver = 'redis',
|
'cache.default' => $cacheDriver = 'redis',
|
||||||
'cache.stores.' . $secondCacheDriver = 'redis2' => config('cache.stores.redis'),
|
'cache.stores.' . $secondCacheDriver = 'redis2' => config('cache.stores.redis'),
|
||||||
|
'tenancy.cache.stores' => [$cacheDriver, $secondCacheDriver],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
CacheTenancyBootstrapper::$tenantCacheStores = [$cacheDriver, $secondCacheDriver];
|
|
||||||
CacheTenancyBootstrapper::$prefixGenerator = null;
|
CacheTenancyBootstrapper::$prefixGenerator = null;
|
||||||
|
|
||||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||||
|
|
@ -29,7 +29,6 @@ beforeEach(function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
CacheTenancyBootstrapper::$tenantCacheStores = [];
|
|
||||||
CacheTenancyBootstrapper::$prefixGenerator = null;
|
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 () {
|
test('specific central cache store can be used inside a service', function () {
|
||||||
// Make sure 'redis' (the default store) is the only prefixed store
|
// 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)
|
// Name of the non-default, central cache store that we'll use using cache()->store($cacheStore)
|
||||||
$cacheStore = 'redis2';
|
$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 () {
|
test('only the stores specified in tenantCacheStores get prefixed', function () {
|
||||||
// Make sure the currently used store ('redis') is the only store in $tenantCacheStores
|
// 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';
|
$centralValue = 'central-value';
|
||||||
$assertStoreIsNotPrefixed = function (string $unprefixedStore) use ($prefixedStore, $centralValue) {
|
$assertStoreIsNotPrefixed = function (string $unprefixedStore) use ($prefixedStore, $centralValue) {
|
||||||
|
|
@ -24,6 +24,7 @@ use Stancl\Tenancy\Events\TenancyInitialized;
|
||||||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||||
|
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Database\Exceptions\TenantDatabaseDoesNotExistException;
|
use Stancl\Tenancy\Database\Exceptions\TenantDatabaseDoesNotExistException;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
|
@ -298,6 +299,8 @@ test('run command with array of tenants works', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('link command works', function() {
|
test('link command works', function() {
|
||||||
|
config(['tenancy.bootstrappers' => [FilesystemTenancyBootstrapper::class]]);
|
||||||
|
|
||||||
$tenantId1 = Tenant::create()->getTenantKey();
|
$tenantId1 = Tenant::create()->getTenantKey();
|
||||||
$tenantId2 = Tenant::create()->getTenantKey();
|
$tenantId2 = Tenant::create()->getTenantKey();
|
||||||
pest()->artisan('tenants:link')
|
pest()->artisan('tenants:link')
|
||||||
|
|
@ -318,6 +321,8 @@ test('link command works', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('link command works with a specified tenant', function() {
|
test('link command works with a specified tenant', function() {
|
||||||
|
config(['tenancy.bootstrappers' => [FilesystemTenancyBootstrapper::class]]);
|
||||||
|
|
||||||
$tenantKey = Tenant::create()->getTenantKey();
|
$tenantKey = Tenant::create()->getTenantKey();
|
||||||
|
|
||||||
pest()->artisan('tenants:link', [
|
pest()->artisan('tenants:link', [
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
|
||||||
use Stancl\Tenancy\Database\Concerns\HasDatabase;
|
use Stancl\Tenancy\Database\Concerns\HasDatabase;
|
||||||
use Stancl\Tenancy\Database\Concerns\HasDomains;
|
use Stancl\Tenancy\Database\Concerns\HasDomains;
|
||||||
use Stancl\Tenancy\Database\Concerns\HasPending;
|
use Stancl\Tenancy\Database\Concerns\HasPending;
|
||||||
use Stancl\Tenancy\Database\Concerns\MaintenanceMode;
|
|
||||||
use Stancl\Tenancy\Database\Models;
|
use Stancl\Tenancy\Database\Models;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -18,7 +17,7 @@ class Tenant extends Models\Tenant implements TenantWithDatabase
|
||||||
{
|
{
|
||||||
public static array $extraCustomColumns = [];
|
public static array $extraCustomColumns = [];
|
||||||
|
|
||||||
use HasDatabase, HasDomains, HasPending, MaintenanceMode;
|
use HasDatabase, HasDomains, HasPending;
|
||||||
|
|
||||||
public static function getCustomColumns(): array
|
public static function getCustomColumns(): array
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Stancl\Tenancy\Overrides\CacheManager;
|
|
||||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||||
use Illuminate\Support\Facades\Event;
|
use Illuminate\Support\Facades\Event;
|
||||||
use Stancl\Tenancy\Events\TenancyEnded;
|
use Stancl\Tenancy\Events\TenancyEnded;
|
||||||
|
|
@ -14,17 +13,15 @@ use Stancl\Tenancy\Bootstrappers\CacheTagsBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
config(['cache.default' => $cacheDriver = 'redis']);
|
config([
|
||||||
CacheTenancyBootstrapper::$tenantCacheStores = [$cacheDriver];
|
'cache.default' => 'redis',
|
||||||
|
'tenancy.cache.stores' => ['redis'],
|
||||||
|
]);
|
||||||
|
|
||||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||||
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
|
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
CacheTenancyBootstrapper::$tenantCacheStores = [];
|
|
||||||
});
|
|
||||||
|
|
||||||
test('global cache manager stores data in global cache', function (string $bootstrapper) {
|
test('global cache manager stores data in global cache', function (string $bootstrapper) {
|
||||||
config(['tenancy.bootstrappers' => [$bootstrapper]]);
|
config(['tenancy.bootstrappers' => [$bootstrapper]]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,10 @@ use Stancl\Tenancy\Middleware\CheckTenantForMaintenanceMode;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
config(['tenancy.models.tenant' => MaintenanceTenant::class]);
|
||||||
|
});
|
||||||
|
|
||||||
test('tenants can be in maintenance mode', function () {
|
test('tenants can be in maintenance mode', function () {
|
||||||
Route::get('/foo', function () {
|
Route::get('/foo', function () {
|
||||||
return 'bar';
|
return 'bar';
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
||||||
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\UrlGeneratorBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\UrlGeneratorBootstrapper;
|
||||||
use Stancl\Tenancy\Controllers\TenantAssetController;
|
use Stancl\Tenancy\Controllers\TenantAssetController;
|
||||||
|
use Stancl\Tenancy\Events\TenancyEnded;
|
||||||
|
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||||
use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
|
use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
|
@ -32,6 +34,7 @@ beforeEach(function () {
|
||||||
$cloneAction->handle(Route::getRoutes()->getByName('stancl.tenancy.asset'));
|
$cloneAction->handle(Route::getRoutes()->getByName('stancl.tenancy.asset'));
|
||||||
|
|
||||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
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 () {
|
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);
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
expect(asset('foo'))->toBe(route('stancl.tenancy.asset', ['path' => 'foo']));
|
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 () {
|
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);
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
expect(asset('foo'))->toBe("https://an-s3-bucket/tenant{$tenant->id}/foo");
|
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) {
|
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,
|
'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'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue