From adf3daa0222c90b4bd39fb26c24798b8c4591362 Mon Sep 17 00:00:00 2001 From: Martin Vlcek Date: Mon, 19 Jul 2021 22:38:33 +0000 Subject: [PATCH] This adds support for tenancy aware Storage::url() method --- assets/config.php | 10 +++ .../FilesystemTenancyBootstrapper.php | 27 +++++++- src/Commands/Link.php | 69 +++++++++++++++++++ src/Jobs/CreateStorageSymlinks.php | 46 +++++++++++++ src/TenancyServiceProvider.php | 1 + tests/BootstrapperTest.php | 27 ++++++++ tests/CommandsTest.php | 19 ++++- 7 files changed, 196 insertions(+), 3 deletions(-) create mode 100644 src/Commands/Link.php create mode 100644 src/Jobs/CreateStorageSymlinks.php diff --git a/assets/config.php b/assets/config.php index 029591ad..d127445f 100644 --- a/assets/config.php +++ b/assets/config.php @@ -116,6 +116,16 @@ return [ 'public' => '%storage_path%/app/public/', ], + /* + * Use this to support Storage url method on local driver disks. + * You should create a symbolic link which points to the public directory using command: artisan tenants:link + * Then you can use tenant aware Storage url: Storage::disk('public')->url('file.jpg'); + */ + 'url_override' => [ + // The array key is local disk (must exist in root_override) and value is public directory (%tenant_id% will be replaced with actual tenant id). + 'public' => 'storage-%tenant_id%', + ], + /** * Should storage_path() be suffixed. * diff --git a/src/Bootstrappers/FilesystemTenancyBootstrapper.php b/src/Bootstrappers/FilesystemTenancyBootstrapper.php index d5ae2d50..322a82c9 100644 --- a/src/Bootstrappers/FilesystemTenancyBootstrapper.php +++ b/src/Bootstrappers/FilesystemTenancyBootstrapper.php @@ -7,6 +7,7 @@ namespace Stancl\Tenancy\Bootstrappers; use Illuminate\Contracts\Foundation\Application; use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Support\Facades\Storage; +use League\Flysystem\Adapter\Local as LocalAdapter; use Stancl\Tenancy\Contracts\TenancyBootstrapper; use Stancl\Tenancy\Contracts\Tenant; @@ -57,7 +58,7 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper foreach ($this->app['config']['tenancy.filesystem.disks'] as $disk) { /** @var FilesystemAdapter $filesystemDisk */ $filesystemDisk = Storage::disk($disk); - $this->originalPaths['disks'][$disk] = $filesystemDisk->getAdapter()->getPathPrefix(); + $this->originalPaths['disks']['path'][$disk] = $filesystemDisk->getAdapter()->getPathPrefix(); if ($root = str_replace( '%storage_path%', @@ -71,6 +72,22 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper } $this->app['config']["filesystems.disks.{$disk}.root"] = $finalPrefix; + + // Storage Url + if ($filesystemDisk->getAdapter() instanceof LocalAdapter) { + $config = $filesystemDisk->getDriver()->getConfig(); + $this->originalPaths['disks']['url'][$disk] = $config->has('url') + ? $config->get('url') + : null; + + if ($url = str_replace( + '%tenant_id%', + $tenant->getTenantKey(), + $this->app['config']["tenancy.filesystem.url_override.{$disk}"] ?? '' + )) { + $config->set('url', url($url)); + } + } } } @@ -88,10 +105,16 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper /** @var FilesystemAdapter $filesystemDisk */ $filesystemDisk = Storage::disk($disk); - $root = $this->originalPaths['disks'][$disk]; + $root = $this->originalPaths['disks']['path'][$disk]; $filesystemDisk->getAdapter()->setPathPrefix($root); $this->app['config']["filesystems.disks.{$disk}.root"] = $root; + + // Storage Url + if ($filesystemDisk->getAdapter() instanceof LocalAdapter && ! is_null($this->originalPaths['disks']['url'])) { + $config = $filesystemDisk->getDriver()->getConfig(); + $config->set('url', $this->originalPaths['disks']['url']); + } } } } diff --git a/src/Commands/Link.php b/src/Commands/Link.php new file mode 100644 index 00000000..5c483728 --- /dev/null +++ b/src/Commands/Link.php @@ -0,0 +1,69 @@ +getTenants() + ->map(function (Tenant $tenant) use ($suffix_base, $disk_urls, $disks) { + + $map = []; + + foreach ($disk_urls as $disk => $public_path) { + $storage_path = str_replace('%storage_path%', $suffix_base . $tenant['id'], $disks[$disk]); + $storage_path = storage_path($storage_path); + + $public_path = str_replace('%tenant_id%', $tenant['id'], $public_path); + $public_path = public_path($public_path); + + // make sure storage path exist before we create symlink + if (! is_dir($storage_path)) { + mkdir($storage_path, 0777, true); + } + + $map[] = [$public_path => $storage_path]; + } + + return $map; + + })->flatten(1) + ->mapWithKeys(fn ($item) => $item) + ->all(); + } +} diff --git a/src/Jobs/CreateStorageSymlinks.php b/src/Jobs/CreateStorageSymlinks.php new file mode 100644 index 00000000..38523e98 --- /dev/null +++ b/src/Jobs/CreateStorageSymlinks.php @@ -0,0 +1,46 @@ +tenant = $tenant; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + Artisan::call('tenants:link', [ + '--tenants' => [$this->tenant->getTenantKey()], + ]); + } +} diff --git a/src/TenancyServiceProvider.php b/src/TenancyServiceProvider.php index cf326792..1e77f441 100644 --- a/src/TenancyServiceProvider.php +++ b/src/TenancyServiceProvider.php @@ -82,6 +82,7 @@ class TenancyServiceProvider extends ServiceProvider { $this->commands([ Commands\Run::class, + Commands\Link::class, Commands\Seed::class, Commands\Install::class, Commands\Migrate::class, diff --git a/tests/BootstrapperTest.php b/tests/BootstrapperTest.php index 1b0c880d..cd7e5207 100644 --- a/tests/BootstrapperTest.php +++ b/tests/BootstrapperTest.php @@ -210,5 +210,32 @@ class BootstrapperTest extends TestCase } } + /** @test */ + public function filesystem_local_storage_has_own_public_url() + { + config([ + 'tenancy.bootstrappers' => [ + FilesystemTenancyBootstrapper::class, + ], + 'tenancy.filesystem.root_override.public' => '%storage_path%/app/public/', + 'tenancy.filesystem.url_override.public' => 'storage-%tenant_id%' + ]); + + $tenant1 = Tenant::create(); + $tenant2 = Tenant::create(); + + tenancy()->initialize($tenant1); + $this->assertEquals( + 'http://localhost/storage-'.$tenant1->getTenantKey().'/', + Storage::disk('public')->url('') + ); + + tenancy()->initialize($tenant2); + $this->assertEquals( + 'http://localhost/storage-'.$tenant2->getTenantKey().'/', + Storage::disk('public')->url('') + ); + } + // for queues see QueueTest } diff --git a/tests/CommandsTest.php b/tests/CommandsTest.php index d7da0cab..ac6beb91 100644 --- a/tests/CommandsTest.php +++ b/tests/CommandsTest.php @@ -31,7 +31,11 @@ class CommandsTest extends TestCase config(['tenancy.bootstrappers' => [ DatabaseTenancyBootstrapper::class, - ]]); + ], + 'tenancy.filesystem.suffix_base' => 'tenant-', + 'tenancy.filesystem.root_override.public' => '%storage_path%/app/public/', + 'tenancy.filesystem.url_override.public' => 'storage-%tenant_id%' + ]); Event::listen(TenancyInitialized::class, BootstrapTenancy::class); Event::listen(TenancyEnded::class, RevertToCentralContext::class); @@ -202,4 +206,17 @@ class CommandsTest extends TestCase ->expectsOutput('Tenant: ' . $tenantId1) ->expectsOutput('Tenant: ' . $tenantId2); } + + /** @test */ + public function link_command_works() + { + $tenantId1 = Tenant::create()->getTenantKey(); + $tenantId2 = Tenant::create()->getTenantKey(); + Artisan::call('tenants:link'); + + $this->assertDirectoryExists(storage_path("tenant-$tenantId1/app/public")); + $this->assertDirectoryExists(public_path("storage-$tenantId1")); + $this->assertDirectoryExists(storage_path("tenant-$tenantId2/app/public")); + $this->assertDirectoryExists(public_path("storage-$tenantId2")); + } }