1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-12 18:44:03 +00:00

Create tenant storage directories in FilesystemTenancyBootstrapper (#1410)

This is because the CreateTenantStorage listener only runs when
a tenant is created, but in multi-server setups the directory may
need to be created each time a tenant is *used*, not just created.

Also changed the listeners to use TenantEvent instead of specific
events, to make it possible to use them with other events, such as
TenancyBootstrapped.

Also update permission bits in a few mkdir() calls to better scope
data to the current OS user.

Also fix a typo in CacheTenancyBootstrapper (exception message).
This commit is contained in:
Samuel Štancl 2025-11-04 21:16:39 +01:00 committed by GitHub
parent 0ef4dfd230
commit cab8ecebec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 45 additions and 7 deletions

View file

@ -108,7 +108,7 @@ class CacheTenancyBootstrapper implements TenancyBootstrapper
// Previously we just silently ignored this, however since session scoping is of high importance // Previously we just silently ignored this, however since session scoping is of high importance
// in production, we make sure to notify the developer, by throwing an exception, that session // in production, we make sure to notify the developer, by throwing an exception, that session
// scoping isn't happening as expected/configured due to an incompatible session driver. // scoping isn't happening as expected/configured due to an incompatible session driver.
throw new Exception('Session driver [' . $this->config->get('session.driver') . '] cannot be scoped by tenancy.cache.scope_session'); throw new Exception('Session driver [' . $this->config->get('session.driver') . '] cannot be scoped by tenancy.cache.scope_sessions');
} }
} else { } else {
// Scoping sessions using this bootstrapper implicitly adds the session store to $names // Scoping sessions using this bootstrapper implicitly adds the session store to $names

View file

@ -78,6 +78,15 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
return; return;
} }
$path = $suffix
? $this->tenantStoragePath($suffix) . '/framework/cache'
: $this->originalStoragePath . '/framework/cache';
if (! is_dir($path)) {
// Create tenant framework/cache directory if it does not exist
mkdir($path, 0750, true);
}
if ($suffix === false) { if ($suffix === false) {
$this->app->useStoragePath($this->originalStoragePath); $this->app->useStoragePath($this->originalStoragePath);
} else { } else {
@ -211,7 +220,7 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
if (! is_dir($path)) { if (! is_dir($path)) {
// Create tenant framework/sessions directory if it does not exist // Create tenant framework/sessions directory if it does not exist
mkdir($path, 0755, true); mkdir($path, 0750, true);
} }
$this->app['config']['session.files'] = $path; $this->app['config']['session.files'] = $path;

View file

@ -4,18 +4,25 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Listeners; namespace Stancl\Tenancy\Listeners;
use Stancl\Tenancy\Events\TenantCreated; use Stancl\Tenancy\Events\Contracts\TenantEvent;
/**
* Can be used to manually create framework directories in the tenant storage when storage_path() is scoped.
*
* Useful when using real-time facades which use the framework/cache directory.
*
* Generally not needed anymore as the directory is also created by the FilesystemTenancyBootstrapper.
*/
class CreateTenantStorage class CreateTenantStorage
{ {
public function handle(TenantCreated $event): void public function handle(TenantEvent $event): void
{ {
$storage_path = tenancy()->run($event->tenant, fn () => storage_path()); $storage_path = tenancy()->run($event->tenant, fn () => storage_path());
$cache_path = "$storage_path/framework/cache"; $cache_path = "$storage_path/framework/cache";
if (! is_dir($cache_path)) { if (! is_dir($cache_path)) {
// Create the tenant's storage directory and /framework/cache within (used for e.g. real-time facades) // Create the tenant's storage directory and /framework/cache within (used for e.g. real-time facades)
mkdir($cache_path, 0777, true); mkdir($cache_path, 0750, true);
} }
} }
} }

View file

@ -5,11 +5,11 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Listeners; namespace Stancl\Tenancy\Listeners;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Stancl\Tenancy\Events\DeletingTenant; use Stancl\Tenancy\Events\Contracts\TenantEvent;
class DeleteTenantStorage class DeleteTenantStorage
{ {
public function handle(DeletingTenant $event): void public function handle(TenantEvent $event): void
{ {
$path = tenancy()->run($event->tenant, fn () => storage_path()); $path = tenancy()->run($event->tenant, fn () => storage_path());

View file

@ -200,3 +200,24 @@ test('tenant storage can get deleted after the tenant when DeletingTenant listen
expect(File::isDirectory($tenantStoragePath))->toBeFalse(); expect(File::isDirectory($tenantStoragePath))->toBeFalse();
}); });
test('the framework/cache directory is created when storage_path is scoped', function (bool $suffixStoragePath) {
config([
'tenancy.bootstrappers' => [
FilesystemTenancyBootstrapper::class,
],
'tenancy.filesystem.suffix_storage_path' => $suffixStoragePath
]);
$centralStoragePath = storage_path();
tenancy()->initialize($tenant = Tenant::create());
if ($suffixStoragePath) {
expect(storage_path('framework/cache'))->toBe($centralStoragePath . "/tenant{$tenant->id}/framework/cache");
expect(is_dir($centralStoragePath . "/tenant{$tenant->id}/framework/cache"))->toBeTrue();
} else {
expect(storage_path('framework/cache'))->toBe($centralStoragePath . '/framework/cache');
expect(is_dir($centralStoragePath . "/tenant{$tenant->id}/framework/cache"))->toBeFalse();
}
})->with([true, false]);

View file

@ -56,6 +56,7 @@ test('file sessions are separated', function (bool $scopeSessions) {
if ($scopeSessions) { if ($scopeSessions) {
expect($sessionPath())->toBe(storage_path('tenant' . $tenant->getTenantKey() . '/framework/sessions')); expect($sessionPath())->toBe(storage_path('tenant' . $tenant->getTenantKey() . '/framework/sessions'));
expect(is_dir(storage_path('tenant' . $tenant->getTenantKey() . '/framework/sessions')))->toBeTrue();
} else { } else {
expect($sessionPath())->toBe(storage_path('framework/sessions')); expect($sessionPath())->toBe(storage_path('framework/sessions'));
} }