diff --git a/src/Commands/Link.php b/src/Commands/Link.php index e08fa52c..fabde169 100644 --- a/src/Commands/Link.php +++ b/src/Commands/Link.php @@ -4,8 +4,12 @@ declare(strict_types=1); namespace Stancl\Tenancy\Commands; +use Exception; use Illuminate\Console\Command; +use Illuminate\Support\LazyCollection; use Stancl\Tenancy\Concerns\HasATenantsOption; +use Stancl\Tenancy\CreateStorageSymlinksAction; +use Stancl\Tenancy\RemoveStorageSymlinksAction; class Link extends Command { @@ -27,7 +31,7 @@ class Link extends Command * * @var string */ - protected $description = 'Create symbolic links for tenants.'; + protected $description = 'Create or remove tenant symbolic links.'; /** * Execute the console command. @@ -36,102 +40,48 @@ class Link extends Command */ public function handle() { - $tenants = collect($this->option('tenants')) ?? $this->getTenants()->map->getTenantKey(); - $links = $tenants->flatMap(fn ($tenantKey) => $this->getPossibleTenantSymlinks($tenantKey)) - ->mapWithKeys(fn ($item) => $item) - ->all(); + $tenants = $this->getTenants(); - if ($this->option('remove')) { - $this->removeLinks($links); - } else { - $this->createLinks($links); + try { + if ($this->option('remove')) { + $this->removeLinks($tenants); + } else { + $this->createLinks($tenants); + } + } catch (Exception $exception) { + $this->error($exception->getMessage()); + + return 1; } } - protected function removeLinks(array $links) + /** + * @param LazyCollection $tenants + * @return void + */ + protected function removeLinks($tenants) { - foreach ($links as $publicPath => $storagePath) { - $this->removeLink($publicPath); - } + RemoveStorageSymlinksAction::handle( + $tenants, + afterLinkRemoval: fn($publicPath) => $this->info("The [$publicPath] link has been removed.") + ); $this->info('The links have been removed.'); } - protected function createLinks(array $links) + /** + * @param LazyCollection $tenants + * @return void + */ + protected function createLinks($tenants) { - foreach ($links as $link => $storagePath) { - $this->createLink($link, $storagePath); - } + CreateStorageSymlinksAction::handle( + $tenants, + $this->option('relative') ?? false, + $this->option('force') ?? false, + afterLinkCreation: fn($publicPath, $storagePath) => $this->info("The [$publicPath] link has been connected to [$storagePath].") + ); $this->info('The links have been created.'); } - - protected function removeLink(string $publicPath) - { - if ($this->symlinkExists($publicPath)) { - $this->laravel->make('files')->delete($publicPath); - - $this->info("The [$publicPath] link has been removed."); - } - } - - protected function createLink(string $publicPath, string $storagePath) - { - if ($this->symlinkExists($publicPath)) { - // If the 'force' option isn't passed, don't overwrite the existing symlink - if (! $this->option('force')) { - $this->error("The [$publicPath] link already exists."); - - return; - } - - $this->laravel->make('files')->delete($publicPath); - } - - // Make sure the storage path exists before we create a symlink - if (! is_dir($storagePath)) { - mkdir($storagePath, 0777, true); - } - - if ($this->option('relative')) { - $this->laravel->make('files')->relativeLink($storagePath, $publicPath); - } else { - $this->laravel->make('files')->link($storagePath, $publicPath); - } - - $this->info("The [$publicPath] link has been connected to [$storagePath]."); - } - - /** - * Get all possible tenant symlinks, existing or not (array of ['public path' => 'storage path']). - * - * @return array - */ - protected function getPossibleTenantSymlinks(int|string $tenantKey) - { - $diskUrls = config('tenancy.filesystem.url_override'); - $disks = config('tenancy.filesystem.root_override'); - $suffixBase = config('tenancy.filesystem.suffix_base'); - $symlinks = []; - - foreach ($diskUrls as $disk => $publicPath) { - $storagePath = str_replace('%storage_path%', $suffixBase . $tenantKey, $disks[$disk]); - $storagePath = storage_path($storagePath); - - $publicPath = str_replace('%tenant_id%', $tenantKey, $publicPath); - $publicPath = public_path($publicPath); - - $symlinks[] = [$publicPath => $storagePath]; - } - - return $symlinks; - } - - /** - * Determine if the provided path is an existing symlink. - */ - protected function symlinkExists(string $link): bool - { - return file_exists($link) && is_link($link); - } } diff --git a/src/Concerns/DealsWithTenantSymlinks.php b/src/Concerns/DealsWithTenantSymlinks.php new file mode 100644 index 00000000..b0aa052d --- /dev/null +++ b/src/Concerns/DealsWithTenantSymlinks.php @@ -0,0 +1,43 @@ + 'storage path']). + * + * @return array + */ + protected static function possibleTenantSymlinks(Tenant $tenant): Collection + { + $diskUrls = config('tenancy.filesystem.url_override'); + $disks = config('tenancy.filesystem.root_override'); + $suffixBase = config('tenancy.filesystem.suffix_base'); + $symlinks = []; + $tenantKey = $tenant->getTenantKey(); + + foreach ($diskUrls as $disk => $publicPath) { + $storagePath = str_replace('%storage_path%', $suffixBase . $tenantKey, $disks[$disk]); + $storagePath = storage_path($storagePath); + + $publicPath = str_replace('%tenant_id%', $tenantKey, $publicPath); + $publicPath = public_path($publicPath); + + $symlinks[] = [$publicPath => $storagePath]; + } + + return collect($symlinks)->mapWithKeys(fn($item) => $item); + } + + /** + * Determine if the provided path is an existing symlink. + */ + protected static function symlinkExists(string $link): bool + { + return file_exists($link) && is_link($link); + } +} diff --git a/src/CreateStorageSymlinksAction.php b/src/CreateStorageSymlinksAction.php new file mode 100644 index 00000000..508f8993 --- /dev/null +++ b/src/CreateStorageSymlinksAction.php @@ -0,0 +1,54 @@ + $storagePath) { + static::createLink($publicPath, $storagePath, $tenant, $relativeLink, $force, $afterLinkCreation); + } + } + } + + protected static function createLink(string $publicPath, string $storagePath, Tenant $tenant, bool $relativeLink, bool $force, Closure|null $afterLinkCreation) + { + event(new CreatingStorageSymlink($tenant)); + + if (static::symlinkExists($publicPath)) { + // If the 'force' option isn't passed, don't overwrite the existing symlink + throw_if(! $force, new Exception("The [$publicPath] link already exists.")); + + app()->make('files')->delete($publicPath); + } + + // Make sure the storage path exists before we create a symlink + if (! is_dir($storagePath)) { + mkdir($storagePath, 0777, true); + } + + if ($relativeLink) { + app()->make('files')->relativeLink($storagePath, $publicPath); + } else { + app()->make('files')->link($storagePath, $publicPath); + } + + event((new StorageSymlinkCreated($tenant))); + + $afterLinkCreation($publicPath, $storagePath); + } +} diff --git a/src/Jobs/CreateStorageSymlinks.php b/src/Jobs/CreateStorageSymlinks.php index 490e7b2d..a2fcb45a 100644 --- a/src/Jobs/CreateStorageSymlinks.php +++ b/src/Jobs/CreateStorageSymlinks.php @@ -9,10 +9,8 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Artisan; use Stancl\Tenancy\Contracts\Tenant; -use Stancl\Tenancy\Events\CreatingStorageSymlink; -use Stancl\Tenancy\Events\StorageSymlinkCreated; +use Stancl\Tenancy\CreateStorageSymlinksAction; class CreateStorageSymlinks implements ShouldQueue { @@ -37,12 +35,6 @@ class CreateStorageSymlinks implements ShouldQueue */ public function handle() { - event(new CreatingStorageSymlink($this->tenant)); - - Artisan::call('tenants:link', [ - '--tenants' => [$this->tenant->getTenantKey()], - ]); - - event(new StorageSymlinkCreated($this->tenant)); + CreateStorageSymlinksAction::handle($this->tenant); } } diff --git a/src/Jobs/RemoveStorageSymlinks.php b/src/Jobs/RemoveStorageSymlinks.php index c6ba71b4..9d2c700e 100644 --- a/src/Jobs/RemoveStorageSymlinks.php +++ b/src/Jobs/RemoveStorageSymlinks.php @@ -5,14 +5,12 @@ declare(strict_types=1); namespace Stancl\Tenancy\Jobs; use Illuminate\Bus\Queueable; +use Stancl\Tenancy\Contracts\Tenant; +use Illuminate\Queue\SerializesModels; +use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Artisan; -use Stancl\Tenancy\Contracts\Tenant; -use Stancl\Tenancy\Events\RemovingStorageSymlink; -use Stancl\Tenancy\Events\StorageSymlinkRemoved; +use Stancl\Tenancy\RemoveStorageSymlinksAction; class RemoveStorageSymlinks implements ShouldQueue { @@ -37,13 +35,6 @@ class RemoveStorageSymlinks implements ShouldQueue */ public function handle() { - event(new RemovingStorageSymlink($this->tenant)); - - Artisan::call('tenants:link', [ - '--remove' => true, - '--tenants' => [$this->tenant->getTenantKey()], - ]); - - event(new StorageSymlinkRemoved($this->tenant)); + RemoveStorageSymlinksAction::handle($this->tenant); } } diff --git a/src/RemoveStorageSymlinksAction.php b/src/RemoveStorageSymlinksAction.php new file mode 100644 index 00000000..f01437c3 --- /dev/null +++ b/src/RemoveStorageSymlinksAction.php @@ -0,0 +1,42 @@ + $storagePath) { + static::removeLink($publicPath, $tenant, $afterLinkRemoval); + } + } + } + + protected static function removeLink(string $publicPath, Tenant $tenant, Closure|null $afterLinkRemoval) + { + if (static::symlinkExists($publicPath)) { + event(new RemovingStorageSymlink($tenant)); + + app()->make('files')->delete($publicPath); + + event(new StorageSymlinkRemoved($tenant)); + + if ($afterLinkRemoval) { + $afterLinkRemoval($publicPath); + } + } + } +}