From 3c0e21b726c9a17ce27f1a8ea18a227c854a7c26 Mon Sep 17 00:00:00 2001 From: Victor R <39545521+viicslen@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:17:11 -0500 Subject: [PATCH] [4.x] Filesystem bootstrapper: scoped disk support (#1402) Fixes #1401 --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: lukinovec Co-authored-by: Samuel Stancl --- composer.json | 3 +- .../FilesystemTenancyBootstrapper.php | 13 ++++++- .../FilesystemTenancyBootstrapperTest.php | 36 +++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 393d0d8a..b03e1b2f 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,8 @@ "doctrine/dbal": "^3.6.0", "spatie/valuestore": "^1.2.5", "pestphp/pest": "^3.0", - "larastan/larastan": "^3.0" + "larastan/larastan": "^3.0", + "league/flysystem-path-prefixing": "^3.0" }, "autoload": { "psr-4": { diff --git a/src/Bootstrappers/FilesystemTenancyBootstrapper.php b/src/Bootstrappers/FilesystemTenancyBootstrapper.php index faa02de7..af2b809f 100644 --- a/src/Bootstrappers/FilesystemTenancyBootstrapper.php +++ b/src/Bootstrappers/FilesystemTenancyBootstrapper.php @@ -114,7 +114,18 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper protected function forgetDisks(): void { - Storage::forgetDisk($this->app['config']['tenancy.filesystem.disks']); + $tenantDisks = $this->app['config']['tenancy.filesystem.disks']; + $scopedDisks = []; + + foreach ($this->app['config']['filesystems.disks'] as $name => $disk) { + if (isset($disk['driver'], $disk['disk']) + && $disk['driver'] === 'scoped' + && in_array($disk['disk'], $tenantDisks, true)) { + $scopedDisks[] = $name; + } + } + + Storage::forgetDisk(array_merge($tenantDisks, $scopedDisks)); } protected function diskRoot(string $disk, Tenant|false $tenant): void diff --git a/tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php b/tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php index 706a7882..628b974e 100644 --- a/tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php +++ b/tests/Bootstrappers/FilesystemTenancyBootstrapperTest.php @@ -221,3 +221,39 @@ test('the framework/cache directory is created when storage_path is scoped', fun expect(is_dir($centralStoragePath . "/tenant{$tenant->id}/framework/cache"))->toBeFalse(); } })->with([true, false]); + +test('scoped disks are scoped per tenant', function () { + config([ + 'tenancy.bootstrappers' => [ + FilesystemTenancyBootstrapper::class, + ], + 'filesystems.disks.scoped_disk' => [ + 'driver' => 'scoped', + 'disk' => 'public', + 'prefix' => 'scoped_disk_prefix', + ], + ]); + + $tenant = Tenant::create(); + + Storage::disk('scoped_disk')->put('foo.txt', 'central'); + expect(Storage::disk('scoped_disk')->get('foo.txt'))->toBe('central'); + expect(file_get_contents(storage_path() . "/app/public/scoped_disk_prefix/foo.txt"))->toBe('central'); + + tenancy()->initialize($tenant); + + expect(Storage::disk('scoped_disk')->get('foo.txt'))->toBe(null); + Storage::disk('scoped_disk')->put('foo.txt', 'tenant'); + expect(file_get_contents(storage_path() . "/app/public/scoped_disk_prefix/foo.txt"))->toBe('tenant'); + expect(Storage::disk('scoped_disk')->get('foo.txt'))->toBe('tenant'); + + tenancy()->end(); + + expect(Storage::disk('scoped_disk')->get('foo.txt'))->toBe('central'); + Storage::disk('scoped_disk')->put('foo.txt', 'central2'); + expect(Storage::disk('scoped_disk')->get('foo.txt'))->toBe('central2'); + + expect(file_get_contents(storage_path() . "/app/public/scoped_disk_prefix/foo.txt"))->toBe('central2'); + expect(file_get_contents(storage_path() . "/tenant{$tenant->id}/app/public/scoped_disk_prefix/foo.txt"))->toBe('tenant'); +}); +