From ab02d2885c48d86f403dc5e4093b4fe087ac28a8 Mon Sep 17 00:00:00 2001 From: lukinovec Date: Thu, 29 Aug 2024 15:58:50 +0200 Subject: [PATCH 01/14] Make RootUrlBootstrapper run ONLY in CLI by default (add $rootUrlOverrideInTests), work with resolved UrlGenerator --- src/Bootstrappers/RootUrlBootstrapper.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Bootstrappers/RootUrlBootstrapper.php b/src/Bootstrappers/RootUrlBootstrapper.php index 6a5236736..bd49feda9 100644 --- a/src/Bootstrappers/RootUrlBootstrapper.php +++ b/src/Bootstrappers/RootUrlBootstrapper.php @@ -36,20 +36,24 @@ class RootUrlBootstrapper implements TenancyBootstrapper protected string|null $originalRootUrl = null; + public static bool $rootUrlOverrideInTests = false; + public function __construct( - protected UrlGenerator $urlGenerator, protected Repository $config, protected Application $app, ) {} public function bootstrap(Tenant $tenant): void { - if ($this->app->runningInConsole() && static::$rootUrlOverride) { - $this->originalRootUrl = $this->urlGenerator->to('/'); + $shouldRunInTests = ! app()->runningUnitTests() || static::$rootUrlOverrideInTests; + $shouldRun = $this->app->runningInConsole() && $shouldRunInTests && static::$rootUrlOverride; + + if ($shouldRun) { + $this->originalRootUrl = $this->app['url']->to('/'); $newRootUrl = (static::$rootUrlOverride)($tenant, $this->originalRootUrl); - $this->urlGenerator->forceRootUrl($newRootUrl); + $this->app['url']->forceRootUrl($newRootUrl); $this->config->set('app.url', $newRootUrl); } } @@ -57,7 +61,7 @@ public function bootstrap(Tenant $tenant): void public function revert(): void { if ($this->originalRootUrl) { - $this->urlGenerator->forceRootUrl($this->originalRootUrl); + $this->app['url']->forceRootUrl($this->originalRootUrl); $this->config->set('app.url', $this->originalRootUrl); } } From 2266c47722b9708f57673d4ce14ff38fa6c053ad Mon Sep 17 00:00:00 2001 From: lukinovec Date: Thu, 29 Aug 2024 16:02:28 +0200 Subject: [PATCH 02/14] Make resolving 'url' return a pre-created generator instance instead of creating it on every app('url') call --- .../UrlGeneratorBootstrapper.php | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/Bootstrappers/UrlGeneratorBootstrapper.php b/src/Bootstrappers/UrlGeneratorBootstrapper.php index e3bb4a99b..15116760d 100644 --- a/src/Bootstrappers/UrlGeneratorBootstrapper.php +++ b/src/Bootstrappers/UrlGeneratorBootstrapper.php @@ -37,7 +37,7 @@ public function bootstrap(Tenant $tenant): void public function revert(): void { - $this->app->bind('url', fn () => $this->originalUrlGenerator); + $this->app->extend('url', fn () => $this->originalUrlGenerator); } /** @@ -47,24 +47,22 @@ public function revert(): void */ protected function useTenancyUrlGenerator(): void { - $this->app->extend('url', function (UrlGenerator $urlGenerator, Application $app) { - $newGenerator = new TenancyUrlGenerator( - $app['router']->getRoutes(), - $urlGenerator->getRequest(), - $app['config']->get('app.asset_url'), - ); + $newGenerator = new TenancyUrlGenerator( + $this->app['router']->getRoutes(), + $this->originalUrlGenerator->getRequest(), + $this->app['config']->get('app.asset_url'), + ); - $newGenerator->defaults($urlGenerator->getDefaultParameters()); + $newGenerator->defaults($this->originalUrlGenerator->getDefaultParameters()); - $newGenerator->setSessionResolver(function () { - return $this->app['session'] ?? null; - }); - - $newGenerator->setKeyResolver(function () { - return $this->app->make('config')->get('app.key'); - }); + $newGenerator->setSessionResolver(function () { + return $this->app['session'] ?? null; + }); - return $newGenerator; + $newGenerator->setKeyResolver(function () { + return $this->app->make('config')->get('app.key'); }); + + $this->app->extend('url', fn () => $newGenerator); } } From 42e7ec329c40ad907e7f02451b695b3b2063ef80 Mon Sep 17 00:00:00 2001 From: lukinovec Date: Thu, 29 Aug 2024 16:09:59 +0200 Subject: [PATCH 03/14] Take care of doubling tenant keys in TenancyUrlGenerator, add regression test for using UrlGenerator and RootUrl bootstrappers together --- src/Overrides/TenancyUrlGenerator.php | 19 +++++++++- .../Bootstrappers/RootUrlBootstrapperTest.php | 38 ++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/Overrides/TenancyUrlGenerator.php b/src/Overrides/TenancyUrlGenerator.php index af3070216..274930b89 100644 --- a/src/Overrides/TenancyUrlGenerator.php +++ b/src/Overrides/TenancyUrlGenerator.php @@ -53,7 +53,24 @@ public function route($name, $parameters = [], $absolute = true) { [$name, $parameters] = $this->prepareRouteInputs($name, Arr::wrap($parameters)); - return parent::route($name, $parameters, $absolute); + $url = parent::route($name, $parameters, $absolute); + + if (isset($parameters[PathTenantResolver::tenantParameterName()])) { + // Ensure the tenant key is present in the URL just once + // This is necessary when using UrlGeneratorBootstrapper with RootUrlBootstrapper + $tenantId = $parameters[PathTenantResolver::tenantParameterName()]; + $afterTenant = str($url)->afterLast($tenantId)->toString(); + $beforeTenant = str($url)->before($tenantId)->toString(); + + if (! $absolute && str(url('/'))->contains($tenantId)) { + // If the URL should be relative and the tenant key is already present in the full URL, don't add it again + return $afterTenant; + } + + return $beforeTenant . $tenantId . $afterTenant; + } + + return $url; } /** diff --git a/tests/Bootstrappers/RootUrlBootstrapperTest.php b/tests/Bootstrappers/RootUrlBootstrapperTest.php index ee17a802e..91ab608db 100644 --- a/tests/Bootstrappers/RootUrlBootstrapperTest.php +++ b/tests/Bootstrappers/RootUrlBootstrapperTest.php @@ -10,18 +10,23 @@ use Stancl\Tenancy\Listeners\RevertToCentralContext; use Stancl\Tenancy\Bootstrappers\RootUrlBootstrapper; use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain; +use Stancl\Tenancy\Bootstrappers\UrlGeneratorBootstrapper; +use Stancl\Tenancy\Middleware\InitializeTenancyByPath; +use Stancl\Tenancy\Overrides\TenancyUrlGenerator; beforeEach(function () { Event::listen(TenancyInitialized::class, BootstrapTenancy::class); Event::listen(TenancyEnded::class, RevertToCentralContext::class); RootUrlBootstrapper::$rootUrlOverride = null; + RootUrlBootstrapper::$rootUrlOverrideInTests = true; }); afterEach(function () { RootUrlBootstrapper::$rootUrlOverride = null; + RootUrlBootstrapper::$rootUrlOverrideInTests = false; }); -test('root url bootstrapper overrides the root url when tenancy gets initialized and reverts the url to the central one after tenancy ends', function() { +test('root url bootstrapper overrides the root url when tenancy gets initialized and reverts the url to the central one when ending tenancy', function() { config(['tenancy.bootstrappers' => [RootUrlBootstrapper::class]]); Route::group([ @@ -65,3 +70,34 @@ expect(URL::to('/'))->toBe($baseUrl); expect(config('app.url'))->toBe($baseUrl); }); + +test('root url bootstrapper can be used with url generator bootstrapper', function() { + config(['tenancy.bootstrappers' => [RootUrlBootstrapper::class, UrlGeneratorBootstrapper::class]]); + + TenancyUrlGenerator::$prefixRouteNames = true; + TenancyUrlGenerator::$passTenantParameterToRoutes = true; + + Route::get('/', function () { + return true; + })->name('home'); + + Route::get('/{tenant}', function () { + return true; + })->name('tenant.home')->middleware(InitializeTenancyByPath::class); + + $rootUrlOverride = function (Tenant $tenant, string $originalRootUrl) { + return str($originalRootUrl)->beforeLast($tenant->getTenantKey())->toString() . '/' . $tenant->getTenantKey(); + }; + + $tenant = Tenant::create(); + + $tenantUrl = $rootUrlOverride($tenant, url('/')); + + RootUrlBootstrapper::$rootUrlOverride = $rootUrlOverride; + + expect(route('home'))->toBe(url('/')); + + tenancy()->initialize($tenant); + + expect(route('home'))->toBe($tenantUrl); +}); From 9f09c9e6dd3049b4111a9755cae48672a2169269 Mon Sep 17 00:00:00 2001 From: PHP CS Fixer Date: Thu, 29 Aug 2024 14:10:45 +0000 Subject: [PATCH 04/14] Fix code style (php-cs-fixer) --- src/Bootstrappers/RootUrlBootstrapper.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Bootstrappers/RootUrlBootstrapper.php b/src/Bootstrappers/RootUrlBootstrapper.php index bd49feda9..a42b6ae6f 100644 --- a/src/Bootstrappers/RootUrlBootstrapper.php +++ b/src/Bootstrappers/RootUrlBootstrapper.php @@ -7,7 +7,6 @@ use Closure; use Illuminate\Config\Repository; use Illuminate\Contracts\Foundation\Application; -use Illuminate\Routing\UrlGenerator; use Stancl\Tenancy\Contracts\TenancyBootstrapper; use Stancl\Tenancy\Contracts\Tenant; From 4e126846f9c27720f36a2d876c3ab836e5c90c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Thu, 29 Aug 2024 22:31:08 +0200 Subject: [PATCH 05/14] refactor RootUrlBootstrapper --- src/Bootstrappers/RootUrlBootstrapper.php | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/Bootstrappers/RootUrlBootstrapper.php b/src/Bootstrappers/RootUrlBootstrapper.php index a42b6ae6f..c6a044344 100644 --- a/src/Bootstrappers/RootUrlBootstrapper.php +++ b/src/Bootstrappers/RootUrlBootstrapper.php @@ -44,17 +44,24 @@ public function __construct( public function bootstrap(Tenant $tenant): void { - $shouldRunInTests = ! app()->runningUnitTests() || static::$rootUrlOverrideInTests; - $shouldRun = $this->app->runningInConsole() && $shouldRunInTests && static::$rootUrlOverride; - - if ($shouldRun) { - $this->originalRootUrl = $this->app['url']->to('/'); + if (static::$rootUrlOverride === null) { + return; + } - $newRootUrl = (static::$rootUrlOverride)($tenant, $this->originalRootUrl); + if (! $this->app->runningInConsole()) { + return; + } - $this->app['url']->forceRootUrl($newRootUrl); - $this->config->set('app.url', $newRootUrl); + if (app()->runningUnitTests() && ! static::$rootUrlOverrideInTests) { + return; } + + $this->originalRootUrl = $this->app['url']->to('/'); + + $newRootUrl = (static::$rootUrlOverride)($tenant, $this->originalRootUrl); + + $this->app['url']->forceRootUrl($newRootUrl); + $this->config->set('app.url', $newRootUrl); } public function revert(): void From e0cfdf09cb5363cbedf1579740dd31cf80b8a496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Thu, 19 Dec 2024 18:58:36 +0100 Subject: [PATCH 06/14] add docblock --- src/Bootstrappers/RootUrlBootstrapper.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Bootstrappers/RootUrlBootstrapper.php b/src/Bootstrappers/RootUrlBootstrapper.php index c6a044344..1d7542e68 100644 --- a/src/Bootstrappers/RootUrlBootstrapper.php +++ b/src/Bootstrappers/RootUrlBootstrapper.php @@ -35,6 +35,12 @@ class RootUrlBootstrapper implements TenancyBootstrapper protected string|null $originalRootUrl = null; + /** + * You may want to selectively enable or disable this bootstrapper in specific tests. + * For instance, when using `Livewire::test()` this bootstrapper may cause problems, + * while in tests that are generating URLs in things like mails the bootstrapper should + * be used just like in any queued job. + */ public static bool $rootUrlOverrideInTests = false; public function __construct( From 4f94ccb5f70d813014b43667d2b5d2133accde55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Thu, 19 Dec 2024 21:50:59 +0100 Subject: [PATCH 07/14] clarify docblock --- src/Bootstrappers/RootUrlBootstrapper.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Bootstrappers/RootUrlBootstrapper.php b/src/Bootstrappers/RootUrlBootstrapper.php index 1d7542e68..3ccfef959 100644 --- a/src/Bootstrappers/RootUrlBootstrapper.php +++ b/src/Bootstrappers/RootUrlBootstrapper.php @@ -37,9 +37,10 @@ class RootUrlBootstrapper implements TenancyBootstrapper /** * You may want to selectively enable or disable this bootstrapper in specific tests. - * For instance, when using `Livewire::test()` this bootstrapper may cause problems, - * while in tests that are generating URLs in things like mails the bootstrapper should - * be used just like in any queued job. + * For instance, when using `Livewire::test()` this bootstrapper can cause problems, + * due to an internal Livewire route, so you may want to disable it, while in tests + * that are generating URLs in things like mails, the bootstrapper should be used + * just like in any queued job. */ public static bool $rootUrlOverrideInTests = false; From 354ec1fb2a16b7331ae85efea096a6603e80446a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Fri, 20 Dec 2024 03:55:10 +0100 Subject: [PATCH 08/14] simplify test: use concrete values instead of overly dynamic code --- tests/Bootstrappers/RootUrlBootstrapperTest.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/Bootstrappers/RootUrlBootstrapperTest.php b/tests/Bootstrappers/RootUrlBootstrapperTest.php index 91ab608db..9f9d3b2a2 100644 --- a/tests/Bootstrappers/RootUrlBootstrapperTest.php +++ b/tests/Bootstrappers/RootUrlBootstrapperTest.php @@ -85,19 +85,17 @@ return true; })->name('tenant.home')->middleware(InitializeTenancyByPath::class); - $rootUrlOverride = function (Tenant $tenant, string $originalRootUrl) { - return str($originalRootUrl)->beforeLast($tenant->getTenantKey())->toString() . '/' . $tenant->getTenantKey(); + $rootUrlOverride = function (Tenant $tenant) { + return 'http://localhost/' . $tenant->getTenantKey(); }; - $tenant = Tenant::create(); - - $tenantUrl = $rootUrlOverride($tenant, url('/')); + $tenant = Tenant::create(['id' => 'acme']); RootUrlBootstrapper::$rootUrlOverride = $rootUrlOverride; - expect(route('home'))->toBe(url('/')); + expect(route('home'))->toBe('http://localhost'); tenancy()->initialize($tenant); - expect(route('home'))->toBe($tenantUrl); + expect(route('home'))->toBe('http://localhost/acme'); }); From 4d8b092e56381a542ecc5c78611604654384f11f Mon Sep 17 00:00:00 2001 From: lukinovec Date: Fri, 20 Dec 2024 11:22:27 +0100 Subject: [PATCH 09/14] Fix bootstrapper order in test, add url('/') assertion --- tests/Bootstrappers/RootUrlBootstrapperTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Bootstrappers/RootUrlBootstrapperTest.php b/tests/Bootstrappers/RootUrlBootstrapperTest.php index 9f9d3b2a2..9128307b3 100644 --- a/tests/Bootstrappers/RootUrlBootstrapperTest.php +++ b/tests/Bootstrappers/RootUrlBootstrapperTest.php @@ -72,7 +72,7 @@ }); test('root url bootstrapper can be used with url generator bootstrapper', function() { - config(['tenancy.bootstrappers' => [RootUrlBootstrapper::class, UrlGeneratorBootstrapper::class]]); + config(['tenancy.bootstrappers' => [UrlGeneratorBootstrapper::class, RootUrlBootstrapper::class]]); TenancyUrlGenerator::$prefixRouteNames = true; TenancyUrlGenerator::$passTenantParameterToRoutes = true; @@ -98,4 +98,5 @@ tenancy()->initialize($tenant); expect(route('home'))->toBe('http://localhost/acme'); + expect(url('/'))->toBe('http://localhost/acme'); }); From 5c8933bd14a3c1f999c52a479307bd8ce577fbdd Mon Sep 17 00:00:00 2001 From: lukinovec Date: Mon, 23 Dec 2024 12:58:28 +0100 Subject: [PATCH 10/14] Use $this->app instead of app() --- src/Bootstrappers/RootUrlBootstrapper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bootstrappers/RootUrlBootstrapper.php b/src/Bootstrappers/RootUrlBootstrapper.php index 3ccfef959..3a6501690 100644 --- a/src/Bootstrappers/RootUrlBootstrapper.php +++ b/src/Bootstrappers/RootUrlBootstrapper.php @@ -59,7 +59,7 @@ public function bootstrap(Tenant $tenant): void return; } - if (app()->runningUnitTests() && ! static::$rootUrlOverrideInTests) { + if ($this->app->runningUnitTests() && ! static::$rootUrlOverrideInTests) { return; } From 81917b86fb4c2720cf16f96308d8c57cf43d37f1 Mon Sep 17 00:00:00 2001 From: lukinovec Date: Mon, 23 Dec 2024 15:08:41 +0100 Subject: [PATCH 11/14] Improve TenancyUrlGenerator and RootUrlBootstrapperTest clarity --- src/Overrides/TenancyUrlGenerator.php | 33 +++++++++----- .../Bootstrappers/RootUrlBootstrapperTest.php | 43 +++++++++++-------- 2 files changed, 49 insertions(+), 27 deletions(-) diff --git a/src/Overrides/TenancyUrlGenerator.php b/src/Overrides/TenancyUrlGenerator.php index 7c21ce3f6..8f7e86fee 100644 --- a/src/Overrides/TenancyUrlGenerator.php +++ b/src/Overrides/TenancyUrlGenerator.php @@ -62,18 +62,31 @@ public function route($name, $parameters = [], $absolute = true) $url = parent::route($name, $parameters, $absolute); if (isset($parameters[PathTenantResolver::tenantParameterName()])) { - // Ensure the tenant key is present in the URL just once - // This is necessary when using UrlGeneratorBootstrapper with RootUrlBootstrapper - $tenantId = $parameters[PathTenantResolver::tenantParameterName()]; - $afterTenant = str($url)->afterLast($tenantId)->toString(); - $beforeTenant = str($url)->before($tenantId)->toString(); - - if (! $absolute && str(url('/'))->contains($tenantId)) { - // If the URL should be relative and the tenant key is already present in the full URL, don't add it again - return $afterTenant; + /** + * Ensure the tenant key appears in the final URL only once. + * This adjustment is necessary when RootUrlBootstrapper is enabled (and used as intended). + * + * When RootUrlBootstrapper adds the tenant key to the root URL: + * - The root URL includes the tenant key (http://localhost/tenantfoo). + * - Passing the tenant key as a parameter to `parent::route()` adds it again, causing duplication. + * + * To fix this: + * - For relative URLs: Include only the part AFTER the tenant key. + * - For absolute URLs: Rebuild the URL so that the tenant key is included exactly once. + */ + $tenantKey = $parameters[PathTenantResolver::tenantParameterName()]; + + // Separate the URL into parts before the first and after the last tenant key + $urlBeforeTenantKey = str($url)->before($tenantKey)->toString(); // e.g. "http://localhost/" + $urlAfterTenantKey = str($url)->afterLast($tenantKey)->toString(); // e.g. "/home" + + if (! $absolute && str(url('/'))->contains($tenantKey)) { + // For relative URLs, return only the part after the tenant key + return $urlAfterTenantKey; } - return $beforeTenant . $tenantId . $afterTenant; + // Reconstruct the URL with the tenant key appearing exactly once + return $urlBeforeTenantKey . $tenantKey . $urlAfterTenantKey; } return $url; diff --git a/tests/Bootstrappers/RootUrlBootstrapperTest.php b/tests/Bootstrappers/RootUrlBootstrapperTest.php index 9128307b3..2269d5204 100644 --- a/tests/Bootstrappers/RootUrlBootstrapperTest.php +++ b/tests/Bootstrappers/RootUrlBootstrapperTest.php @@ -72,31 +72,40 @@ }); test('root url bootstrapper can be used with url generator bootstrapper', function() { + /** + * Order matters when combining these two bootstrappers. + * Before overriding the URL generator's root URL, we need to bind TenancyUrlGenerator. + * Otherwise (when using RootUrlBootstrapper BEFORE UrlGeneratorBootstrapper), + * the original URL generator's root URL will be changed, and only after that will the TenancyUrlGenerator bound, + * ultimately making the root URL override pointless. + */ config(['tenancy.bootstrappers' => [UrlGeneratorBootstrapper::class, RootUrlBootstrapper::class]]); TenancyUrlGenerator::$prefixRouteNames = true; TenancyUrlGenerator::$passTenantParameterToRoutes = true; + RootUrlBootstrapper::$rootUrlOverride = fn (Tenant $tenant, string $originalRootUrl) => $originalRootUrl . '/' . $tenant->getTenantKey(); - Route::get('/', function () { - return true; - })->name('home'); + Route::get('/home', fn () => 'home')->name('home'); + Route::get('/{tenant}/home', fn () => 'tenant.home')->name('tenant.home')->middleware(InitializeTenancyByPath::class); - Route::get('/{tenant}', function () { - return true; - })->name('tenant.home')->middleware(InitializeTenancyByPath::class); + expect(url('/home'))->toBe('http://localhost/home'); - $rootUrlOverride = function (Tenant $tenant) { - return 'http://localhost/' . $tenant->getTenantKey(); - }; - - $tenant = Tenant::create(['id' => 'acme']); + expect(route('home'))->toBe('http://localhost/home'); + expect(route('home', absolute: false))->toBe('/home'); - RootUrlBootstrapper::$rootUrlOverride = $rootUrlOverride; + tenancy()->initialize(Tenant::create(['id' => 'acme'])); - expect(route('home'))->toBe('http://localhost'); - - tenancy()->initialize($tenant); + // The url() helper should generate the full URL containing the tenant key + expect(url('/home'))->toBe('http://localhost/acme/home'); - expect(route('home'))->toBe('http://localhost/acme'); - expect(url('/'))->toBe('http://localhost/acme'); + /** + * The absolute path should return the correct absolute path, containing just one tenant key, + * and the relative path should still be /home. + * + * We use string manipulation in the route() method override for this to behave correctly. + * + * @see TenancyUrlGenerator + */ + expect(route('home'))->toBe('http://localhost/acme/home'); + expect(route('home', absolute: false))->toBe('/home'); }); From c6a524f171f7454c8d2f9f82cfd107a305edecc6 Mon Sep 17 00:00:00 2001 From: lukinovec Date: Fri, 27 Dec 2024 08:54:14 +0100 Subject: [PATCH 12/14] Revert attempt to maintain compatibility between the two bootstrappers --- src/Overrides/TenancyUrlGenerator.php | 32 +-------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/src/Overrides/TenancyUrlGenerator.php b/src/Overrides/TenancyUrlGenerator.php index 8f7e86fee..7c0a7879c 100644 --- a/src/Overrides/TenancyUrlGenerator.php +++ b/src/Overrides/TenancyUrlGenerator.php @@ -59,37 +59,7 @@ public function route($name, $parameters = [], $absolute = true) [$name, $parameters] = $this->prepareRouteInputs($name, Arr::wrap($parameters)); // @phpstan-ignore argument.type - $url = parent::route($name, $parameters, $absolute); - - if (isset($parameters[PathTenantResolver::tenantParameterName()])) { - /** - * Ensure the tenant key appears in the final URL only once. - * This adjustment is necessary when RootUrlBootstrapper is enabled (and used as intended). - * - * When RootUrlBootstrapper adds the tenant key to the root URL: - * - The root URL includes the tenant key (http://localhost/tenantfoo). - * - Passing the tenant key as a parameter to `parent::route()` adds it again, causing duplication. - * - * To fix this: - * - For relative URLs: Include only the part AFTER the tenant key. - * - For absolute URLs: Rebuild the URL so that the tenant key is included exactly once. - */ - $tenantKey = $parameters[PathTenantResolver::tenantParameterName()]; - - // Separate the URL into parts before the first and after the last tenant key - $urlBeforeTenantKey = str($url)->before($tenantKey)->toString(); // e.g. "http://localhost/" - $urlAfterTenantKey = str($url)->afterLast($tenantKey)->toString(); // e.g. "/home" - - if (! $absolute && str(url('/'))->contains($tenantKey)) { - // For relative URLs, return only the part after the tenant key - return $urlAfterTenantKey; - } - - // Reconstruct the URL with the tenant key appearing exactly once - return $urlBeforeTenantKey . $tenantKey . $urlAfterTenantKey; - } - - return $url; + return parent::route($name, $parameters, $absolute); } /** From 9a2d3bdb80f6eb4412cb5426fff2671a9c52aa81 Mon Sep 17 00:00:00 2001 From: lukinovec Date: Fri, 27 Dec 2024 09:02:32 +0100 Subject: [PATCH 13/14] Delete bootstrapper combining test --- .../Bootstrappers/RootUrlBootstrapperTest.php | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/tests/Bootstrappers/RootUrlBootstrapperTest.php b/tests/Bootstrappers/RootUrlBootstrapperTest.php index 2269d5204..c25a8bae0 100644 --- a/tests/Bootstrappers/RootUrlBootstrapperTest.php +++ b/tests/Bootstrappers/RootUrlBootstrapperTest.php @@ -70,42 +70,3 @@ expect(URL::to('/'))->toBe($baseUrl); expect(config('app.url'))->toBe($baseUrl); }); - -test('root url bootstrapper can be used with url generator bootstrapper', function() { - /** - * Order matters when combining these two bootstrappers. - * Before overriding the URL generator's root URL, we need to bind TenancyUrlGenerator. - * Otherwise (when using RootUrlBootstrapper BEFORE UrlGeneratorBootstrapper), - * the original URL generator's root URL will be changed, and only after that will the TenancyUrlGenerator bound, - * ultimately making the root URL override pointless. - */ - config(['tenancy.bootstrappers' => [UrlGeneratorBootstrapper::class, RootUrlBootstrapper::class]]); - - TenancyUrlGenerator::$prefixRouteNames = true; - TenancyUrlGenerator::$passTenantParameterToRoutes = true; - RootUrlBootstrapper::$rootUrlOverride = fn (Tenant $tenant, string $originalRootUrl) => $originalRootUrl . '/' . $tenant->getTenantKey(); - - Route::get('/home', fn () => 'home')->name('home'); - Route::get('/{tenant}/home', fn () => 'tenant.home')->name('tenant.home')->middleware(InitializeTenancyByPath::class); - - expect(url('/home'))->toBe('http://localhost/home'); - - expect(route('home'))->toBe('http://localhost/home'); - expect(route('home', absolute: false))->toBe('/home'); - - tenancy()->initialize(Tenant::create(['id' => 'acme'])); - - // The url() helper should generate the full URL containing the tenant key - expect(url('/home'))->toBe('http://localhost/acme/home'); - - /** - * The absolute path should return the correct absolute path, containing just one tenant key, - * and the relative path should still be /home. - * - * We use string manipulation in the route() method override for this to behave correctly. - * - * @see TenancyUrlGenerator - */ - expect(route('home'))->toBe('http://localhost/acme/home'); - expect(route('home', absolute: false))->toBe('/home'); -}); From 470b84d1721b7f9787607b38dc2df685be5ab48a Mon Sep 17 00:00:00 2001 From: PHP CS Fixer Date: Thu, 16 Jan 2025 09:25:44 +0000 Subject: [PATCH 14/14] Fix code style (php-cs-fixer) --- src/Concerns/ParallelCommand.php | 4 ++-- src/Resolvers/DomainTenantResolver.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Concerns/ParallelCommand.php b/src/Concerns/ParallelCommand.php index aad7a1eca..e8f87ec99 100644 --- a/src/Concerns/ParallelCommand.php +++ b/src/Concerns/ParallelCommand.php @@ -70,7 +70,7 @@ protected function sysctlGetLogicalCoreCount(bool $darwin): int // perflevel0 refers to P-cores on M-series, and the entire CPU on Intel Macs if ($darwin && $ffi->sysctlbyname('hw.perflevel0.logicalcpu', FFI::addr($cores), FFI::addr($size), null, 0) === 0) { return $cores->cdata; - } else if ($darwin) { + } elseif ($darwin) { // Reset the size in case the pointer got written to (likely shouldn't happen) $size->cdata = FFI::sizeof($cores); } @@ -109,7 +109,7 @@ protected function getProcesses(): int if ($processes === null) { // This is used when the option is set but *without* a value (-p). $processes = $this->getLogicalCoreCount(); - } else if ((int) $processes === -1) { + } elseif ((int) $processes === -1) { // Default value we set for the option -- this is used when the option is *not set*. $processes = 1; } else { diff --git a/src/Resolvers/DomainTenantResolver.php b/src/Resolvers/DomainTenantResolver.php index 4d34811e8..b5767172a 100644 --- a/src/Resolvers/DomainTenantResolver.php +++ b/src/Resolvers/DomainTenantResolver.php @@ -7,12 +7,12 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; +use Illuminate\Support\Str; use Stancl\Tenancy\Contracts\Domain; use Stancl\Tenancy\Contracts\SingleDomainTenant; use Stancl\Tenancy\Contracts\Tenant; use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException; use Stancl\Tenancy\Tenancy; -use Illuminate\Support\Str; class DomainTenantResolver extends Contracts\CachedTenantResolver {