mirror of
https://github.com/archtechx/tenancy.git
synced 2026-02-04 16:24:03 +00:00
174 lines
7.1 KiB
PHP
174 lines
7.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Stancl\Tenancy\Overrides;
|
|
|
|
use BackedEnum;
|
|
use Illuminate\Routing\UrlGenerator;
|
|
use Illuminate\Support\Arr;
|
|
use InvalidArgumentException;
|
|
use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
|
|
|
/**
|
|
* This class is used in place of the default UrlGenerator when UrlGeneratorBootstrapper is enabled.
|
|
*
|
|
* TenancyUrlGenerator does two extra things:
|
|
*
|
|
* - Autofills the tenant parameter in the tenant context with the current tenant if $passTenantParameterToRoutes is enabled.
|
|
* With path identification, this is required to be done by URL::defaults() -- setting
|
|
* UrlGeneratorBootstrapper::$addTenantParameterToDefaults (which is false by default) to true handles that.
|
|
* Enabling $passTenantParameterToRoutes is preferable with query string identification.
|
|
*
|
|
* - Prepends the route name passed to route() and URL::temporarySignedRoute()
|
|
* with `tenant.` (or the configured prefix) if $prefixRouteNames is enabled (disabled by default).
|
|
* Primarily intended to be used with path identification.
|
|
*
|
|
* This behavior can be bypassed by passing the $bypassParameter to the mentioned methods (`['central' => true]` by default).
|
|
*/
|
|
class TenancyUrlGenerator extends UrlGenerator
|
|
{
|
|
/**
|
|
* Parameter which bypasses the behavior modification of route() and temporarySignedRoute().
|
|
*
|
|
* For example, in tenant context:
|
|
* Route::get('/', ...)->name('home');
|
|
* Route::get('/tenant', ...)->middleware(InitializeTenancyByRequestData::class)->name('tenant.home');
|
|
* - route('home') => app.test/tenant?tenant=tenantKey
|
|
* - route('home', [$bypassParameter => true]) => app.test/
|
|
* - route('tenant.home', [$bypassParameter => true]) => app.test/tenant -- query string identification (no query string passed)
|
|
*
|
|
* With path identification, it is recommended to pass the tenant parameter automatically by setting
|
|
* UrlGeneratorBootstrapper::$addTenantParameterToDefaults to true,
|
|
* and in that case, this affects only the automatic route name prefixing,
|
|
* and the forceful route name overrides set in the $override property.
|
|
*
|
|
* @see UrlGeneratorBootstrapper
|
|
*/
|
|
public static string $bypassParameter = 'central';
|
|
|
|
/**
|
|
* Determine if the route names passed to `route()` or `temporarySignedRoute()`
|
|
* should get prefixed with the tenant route name prefix.
|
|
*
|
|
* This is useful when using path identification with packages that generate URLs,
|
|
* like Jetstream, so that you don't have to manually prefix route names passed to each route() call.
|
|
*/
|
|
public static bool $prefixRouteNames = false;
|
|
|
|
/**
|
|
* Determine if the tenant parameter should get passed
|
|
* to the links generated by `route()` or `temporarySignedRoute()` whenever available.
|
|
* This is primarily intended to be used with query string identification.
|
|
*
|
|
* With path identification, the tenant parameter is passed by URL::defaults()
|
|
* when UrlGeneratorBootstrapper::$addTenantParameterToDefaults is true (which is the default).
|
|
*
|
|
* @see UrlGeneratorBootstrapper
|
|
*/
|
|
public static bool $passTenantParameterToRoutes = false;
|
|
|
|
/**
|
|
* Route names that should always be overridden.
|
|
* This behavior can still be bypassed by passing the bypass parameter.
|
|
*
|
|
* For example, Jetstream integration:
|
|
* [
|
|
* 'profile.show' => 'tenant.profile.show',
|
|
* 'two-factor.login' => 'tenant.two-factor.login',
|
|
* ]
|
|
*
|
|
* `route('profile.show')` will return an URL as if you called `route('tenant.profile.show')`.
|
|
* `route('profile.show', ['central' => true])` will return an URL as if you called `route('profile.show')`.
|
|
*/
|
|
public static array $override = [];
|
|
|
|
/**
|
|
* Override the route() method so that the route name gets prefixed
|
|
* and the tenant parameter gets added when in tenant context.
|
|
*/
|
|
public function route($name, $parameters = [], $absolute = true)
|
|
{
|
|
if ($name instanceof BackedEnum && ! is_string($name = $name->value)) { // @phpstan-ignore function.impossibleType
|
|
throw new InvalidArgumentException('Attribute [name] expects a string backed enum.');
|
|
}
|
|
|
|
[$name, $parameters] = $this->prepareRouteInputs($name, Arr::wrap($parameters)); // @phpstan-ignore argument.type
|
|
|
|
return parent::route($name, $parameters, $absolute);
|
|
}
|
|
|
|
/**
|
|
* Override the temporarySignedRoute() method so that the route name gets prefixed
|
|
* and the tenant parameter gets added when in tenant context.
|
|
*/
|
|
public function temporarySignedRoute($name, $expiration, $parameters = [], $absolute = true)
|
|
{
|
|
if ($name instanceof BackedEnum && ! is_string($name = $name->value)) { // @phpstan-ignore function.impossibleType
|
|
throw new InvalidArgumentException('Attribute [name] expects a string backed enum.');
|
|
}
|
|
|
|
[$name, $parameters] = $this->prepareRouteInputs($name, Arr::wrap($parameters)); // @phpstan-ignore argument.type
|
|
|
|
return parent::temporarySignedRoute($name, $expiration, $parameters, $absolute);
|
|
}
|
|
|
|
/**
|
|
* Return bool indicating if the bypass parameter was in $parameters.
|
|
*/
|
|
protected function routeBehaviorModificationBypassed(mixed $parameters): bool
|
|
{
|
|
if (isset($parameters[static::$bypassParameter])) {
|
|
return (bool) $parameters[static::$bypassParameter];
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Takes a route name and an array of parameters to return the prefixed route name
|
|
* and the route parameters with the tenant parameter added.
|
|
*
|
|
* To skip these modifications, pass the bypass parameter in route parameters.
|
|
* Before returning the modified route inputs, the bypass parameter is removed from the parameters.
|
|
*/
|
|
protected function prepareRouteInputs(string $name, array $parameters): array
|
|
{
|
|
if (! $this->routeBehaviorModificationBypassed($parameters)) {
|
|
$name = $this->routeNameOverride($name) ?? $this->prefixRouteName($name);
|
|
$parameters = $this->addTenantParameter($parameters);
|
|
}
|
|
|
|
// Remove bypass parameter from the route parameters
|
|
unset($parameters[static::$bypassParameter]);
|
|
|
|
return [$name, $parameters];
|
|
}
|
|
|
|
/**
|
|
* If $prefixRouteNames is true, prefix the passed route name.
|
|
*/
|
|
protected function prefixRouteName(string $name): string
|
|
{
|
|
$tenantPrefix = PathTenantResolver::tenantRouteNamePrefix();
|
|
|
|
if (static::$prefixRouteNames && ! str($name)->startsWith($tenantPrefix)) {
|
|
$name = str($name)->after($tenantPrefix)->prepend($tenantPrefix)->toString();
|
|
}
|
|
|
|
return $name;
|
|
}
|
|
|
|
/**
|
|
* If `tenant()` isn't null, add tenant parameter to the passed parameters.
|
|
*/
|
|
protected function addTenantParameter(array $parameters): array
|
|
{
|
|
return tenant() && static::$passTenantParameterToRoutes ? array_merge($parameters, [PathTenantResolver::tenantParameterName() => tenant()->getTenantKey()]) : $parameters;
|
|
}
|
|
|
|
protected function routeNameOverride(string $name): string|null
|
|
{
|
|
return static::$override[$name] ?? null;
|
|
}
|
|
}
|