From 3103ff60c0c7361a68ccf51371b193e48278a43d Mon Sep 17 00:00:00 2001 From: lukinovec Date: Mon, 9 Mar 2026 11:12:21 +0100 Subject: [PATCH] Add `toRoute()` override to TenancyUrlGenerator Also update `route()` override since `parent::route()` calls `toRoute()` under the hood (similarly to how `parent::temporarySignedRoute()` calls `route()`) --- src/Overrides/TenancyUrlGenerator.php | 29 ++++++++++++++++++- .../UrlGeneratorBootstrapperTest.php | 22 ++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/Overrides/TenancyUrlGenerator.php b/src/Overrides/TenancyUrlGenerator.php index f7ed9a84..d89117fb 100644 --- a/src/Overrides/TenancyUrlGenerator.php +++ b/src/Overrides/TenancyUrlGenerator.php @@ -114,7 +114,15 @@ class TenancyUrlGenerator extends UrlGenerator throw new InvalidArgumentException('Attribute [name] expects a string backed enum.'); } - [$name, $parameters] = $this->prepareRouteInputs($name, Arr::wrap($parameters)); // @phpstan-ignore argument.type + $wrappedParameters = Arr::wrap($parameters); + + [$name, $parameters] = $this->prepareRouteInputs($name, $wrappedParameters); // @phpstan-ignore argument.type + + if (isset($wrappedParameters[static::$bypassParameter])) { + // If the bypass parameter was passed, we need to add it back to the parameters after prepareRouteInputs() removes it, + // so that the underlying toRoute() call in parent::route() can bypass the behavior modification as well. + $parameters[static::$bypassParameter] = $wrappedParameters[static::$bypassParameter]; + } return parent::route($name, $parameters, $absolute); } @@ -142,6 +150,25 @@ class TenancyUrlGenerator extends UrlGenerator return parent::temporarySignedRoute($name, $expiration, $parameters, $absolute); } + /** + * Override the toRoute() method so that the route name gets prefixed + * and the tenant parameter gets added when in tenant context. + */ + public function toRoute($route, $parameters, $absolute) + { + $name = $route->getName(); + + if ($name) { + [$prefixedName, $parameters] = $this->prepareRouteInputs($name, Arr::wrap($parameters)); // @phpstan-ignore argument.type + + if ($prefixedName !== $name && $tenantRoute = $this->routes->getByName($prefixedName)) { + $route = $tenantRoute; + } + } + + return parent::toRoute($route, $parameters, $absolute); + } + /** * Return bool indicating if the bypass parameter was in $parameters. */ diff --git a/tests/Bootstrappers/UrlGeneratorBootstrapperTest.php b/tests/Bootstrappers/UrlGeneratorBootstrapperTest.php index f089207a..97da0d07 100644 --- a/tests/Bootstrappers/UrlGeneratorBootstrapperTest.php +++ b/tests/Bootstrappers/UrlGeneratorBootstrapperTest.php @@ -401,3 +401,25 @@ test('the bypass parameter works correctly with temporarySignedRoute', function( ->toContain('localhost/foo') ->not()->toContain('central='); // Bypass parameter gets removed from the generated URL }); + +test('the toRoute method can automatically prefix the passed route name', function () { + config(['tenancy.bootstrappers' => [UrlGeneratorBootstrapper::class]]); + + Route::get('/central/home', fn () => 'central')->name('home'); + Route::get('/tenant/home', fn () => 'tenant')->name('tenant.home'); + + TenancyUrlGenerator::$prefixRouteNames = true; + + $tenant = Tenant::create(); + + tenancy()->initialize($tenant); + + $centralRoute = Route::getRoutes()->getByName('home'); + + // url()->toRoute() prefixes the name of the passed route ('home') with the tenant prefix + // and generates the URL for the tenant route (as if the 'tenant.home' route was passed to the method) + expect(url()->toRoute($centralRoute, [], true))->toBe('http://localhost/tenant/home'); + + // Passing the bypass parameter skips the name prefixing, so the method returns the central route URL + expect(url()->toRoute($centralRoute, ['central' => true], true))->toBe('http://localhost/central/home'); +});