1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-12 11:14:04 +00:00

Refactor early identification (#47)

* Make universal route logic part of tbe early ID trait

* Add requstHasTenant to prevent access MW, add todo@samuel

* Delete PathIdentificationManager, move the used methods appropriately

* Correct and refactor code related to the deleted PathIdentificationManager class

* Add docblock

* Fix code style (php-cs-fixer)

* refactor globalStackMiddleware()

* remove todos [ci skip]

* refactor routeMiddleware()

* revert bool assertions

* revert more changes

---------

Co-authored-by: PHP CS Fixer <phpcsfixer@example.com>
Co-authored-by: Samuel Štancl <samuel@archte.ch>
This commit is contained in:
lukinovec 2024-04-22 11:30:58 +02:00 committed by GitHub
parent b70cd0e531
commit 4e51cdbacb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 95 additions and 256 deletions

View file

@ -193,12 +193,11 @@ class TenancyServiceProvider extends ServiceProvider
* return RouteFacade::get('/livewire/livewire.js', $handle)->middleware(['universal']);
* });
*/
if (InitializeTenancyByRequestData::inGlobalStack()) {
TenancyUrlGenerator::$prefixRouteNames = false;
}
if (InitializeTenancyByPath::inGlobalStack()) {
if (tenancy()->globalStackHasMiddleware(config('tenancy.identification.path_identification_middleware'))) {
TenancyUrlGenerator::$prefixRouteNames = true;
/** @var CloneRoutesAsTenant $cloneRoutes */

View file

@ -87,7 +87,17 @@ return [
Middleware\InitializeTenancyByDomainOrSubdomain::class,
],
// todo@lukas docblock
/**
* Identification middleware tenancy recognizes as path identification middleware.
*
* This is used during determining whether whether a path identification is used
* during operations specific to path identification, e.g. forgetting the tenant parameter in ForgetTenantParameter.
*
* If you're using a custom path identification middleware, add it here.
*
* @see \Stancl\Tenancy\Actions\CloneRoutesAsTenant
* @see \Stancl\Tenancy\Listeners\ForgetTenantParameter
*/
'path_identification_middleware' => [
Middleware\InitializeTenancyByPath::class,
],

View file

@ -3,14 +3,14 @@
declare(strict_types=1);
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\PathIdentificationManager;
use Stancl\Tenancy\Controllers\TenantAssetController;
use Stancl\Tenancy\Resolvers\PathTenantResolver;
Route::get('/tenancy/assets/{path?}', TenantAssetController::class)
->where('path', '(.*)')
->name('stancl.tenancy.asset');
Route::prefix('/{' . PathIdentificationManager::getTenantParameterName() . '}/')->get('/tenancy/assets/{path?}', TenantAssetController::class)
Route::prefix('/{' . PathTenantResolver::tenantParameterName() . '}/')->get('/tenancy/assets/{path?}', TenantAssetController::class)
->where('path', '(.*)')
->middleware('tenant')
->name(PathIdentificationManager::getTenantRouteNamePrefix() . 'stancl.tenancy.asset');
->name(PathTenantResolver::tenantRouteNamePrefix() . 'stancl.tenancy.asset');

View file

@ -10,7 +10,7 @@ use Illuminate\Routing\Router;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Stancl\Tenancy\Enums\RouteMode;
use Stancl\Tenancy\PathIdentificationManager;
use Stancl\Tenancy\Resolvers\PathTenantResolver;
/**
* The CloneRoutesAsTenant action clones
@ -75,7 +75,7 @@ class CloneRoutesAsTenant
protected function getRoutesToClone(): Collection
{
$tenantParameterName = PathIdentificationManager::getTenantParameterName();
$tenantParameterName = PathTenantResolver::tenantParameterName();
/**
* Clone all routes that:
@ -95,9 +95,10 @@ class CloneRoutesAsTenant
return false;
}
$routeHasPathIdentificationMiddleware = PathIdentificationManager::pathIdentificationOnRoute($route);
$pathIdentificationMiddlewareInGlobalStack = PathIdentificationManager::pathIdentificationInGlobalStack();
$pathIdentificationMiddleware = config('tenancy.identification.path_identification_middleware');
$routeHasPathIdentificationMiddleware = tenancy()->routeHasMiddleware($route, $pathIdentificationMiddleware);
$routeHasNonPathIdentificationMiddleware = tenancy()->routeHasIdentificationMiddleware($route) && ! $routeHasPathIdentificationMiddleware;
$pathIdentificationMiddlewareInGlobalStack = tenancy()->globalStackHasMiddleware($pathIdentificationMiddleware);
/**
* The route should get cloned if:
@ -163,7 +164,7 @@ class CloneRoutesAsTenant
->filter(fn (string $middleware) => ! in_array($middleware, ['universal', 'clone']))
->toArray();
$tenantRouteNamePrefix = PathIdentificationManager::getTenantRouteNamePrefix();
$tenantRouteNamePrefix = PathTenantResolver::tenantRouteNamePrefix();
// Make sure the route name has the tenant route name prefix
$newRouteNamePrefix = $route->getName()
@ -173,7 +174,7 @@ class CloneRoutesAsTenant
return $action
->put('as', $newRouteNamePrefix)
->put('middleware', $newRouteMiddleware)
->put('prefix', '/{' . PathIdentificationManager::getTenantParameterName() . '}/' . $route->getPrefix());
->put('prefix', '/{' . PathTenantResolver::tenantParameterName() . '}/' . $route->getPrefix());
})->toArray();
/** @var Route $newRoute */

View file

@ -14,6 +14,9 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Route as RouteFacade;
use Stancl\Tenancy\Enums\RouteMode;
/**
* @mixin \Stancl\Tenancy\Tenancy
*/
trait DealsWithRouteContexts
{
/**
@ -107,46 +110,14 @@ trait DealsWithRouteContexts
}
/**
* Check if the passed route has the passed middleware
* three layers deep explained in the annotation of getRouteMiddleware().
* Checks whether any of the passed middleware are present in the route's middleware stack.
*/
public static function routeHasMiddleware(Route $route, string $middleware): bool
public static function routeHasMiddleware(Route $route, string|array $middlewares): bool
{
return in_array($middleware, static::getRouteMiddleware($route));
}
$routeMiddleware = static::getRouteMiddleware($route);
public function routeIdentificationMiddleware(Route $route): string|null
{
foreach (static::getRouteMiddleware($route) as $routeMiddleware) {
if (in_array($routeMiddleware, static::middleware())) {
return $routeMiddleware;
}
}
return null;
}
public static function kernelIdentificationMiddleware(): string|null
{
/** @var Kernel $kernel */
$kernel = app(Kernel::class);
foreach (static::middleware() as $identificationMiddleware) {
if ($kernel->hasMiddleware($identificationMiddleware)) {
return $identificationMiddleware;
}
}
return null;
}
/**
* Check if a route has identification middleware.
*/
public static function routeHasIdentificationMiddleware(Route $route): bool
{
foreach (static::getRouteMiddleware($route) as $middleware) {
if (in_array($middleware, static::middleware())) {
foreach (Arr::wrap($middlewares) as $middleware) {
if (in_array($middleware, $routeMiddleware)) {
return true;
}
}
@ -155,22 +126,34 @@ trait DealsWithRouteContexts
}
/**
* Check if route uses kernel identification (identification middleare is in the global stack and the route doesn't have route-level identification middleware).
* Check if a route has identification middleware.
*/
public static function routeUsesKernelIdentification(Route $route): bool
public static function routeHasIdentificationMiddleware(Route $route): bool
{
return ! static::routeHasIdentificationMiddleware($route) && static::kernelIdentificationMiddleware();
return static::routeHasMiddleware($route, static::middleware());
}
/**
* Check if a route uses domain identification.
* Check if route uses kernel identification (identification middleware is in the global stack and the route doesn't have route-level identification middleware).
*/
public static function routeHasDomainIdentificationMiddleware(Route $route): bool
public static function routeUsesKernelIdentification(Route $route): bool
{
$routeMiddleware = static::getRouteMiddleware($route);
return ! static::routeHasIdentificationMiddleware($route) &&
tenancy()->globalStackHasMiddleware(static::middleware());
}
foreach (config('tenancy.identification.domain_identification_middleware') as $middleware) {
if (in_array($middleware, $routeMiddleware)) {
/**
* Checks whether any of the passed middleware are present in the global middleware stack.
*
* @param class-string|array $identificationMiddleware
*/
public static function globalStackHasMiddleware(string|array $identificationMiddleware = []): bool
{
/** @var Kernel $kernel */
$kernel = app(Kernel::class);
foreach (Arr::wrap($identificationMiddleware) as $middleware) {
if ($kernel->hasMiddleware($middleware)) {
return true;
}
}

View file

@ -4,12 +4,10 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Concerns;
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
use Stancl\Tenancy\Enums\Context;
use Stancl\Tenancy\Enums\RouteMode;
use Stancl\Tenancy\Exceptions\MiddlewareNotUsableWithUniversalRoutesException;
use Stancl\Tenancy\Middleware\IdentificationMiddleware;
use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
@ -27,6 +25,19 @@ use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
*/
trait UsableWithEarlyIdentification
{
/**
* Determine if the tenant is present in the incoming request.
*
* Because universal routes can be in any context (central/tenant),
* we use this to determine the context. We can't just check for
* the route's middleware to determine the route's context.
*
* For example, route '/foo' has the 'universal' and InitializeTenancyByRequestData middleware.
* When visiting the route, we should determine the context by the presence of the tenant payload.
* The context is tenant if the tenant parameter is present (e.g. '?tenant=foo'), otherwise the context is central.
*/
abstract public function requestHasTenant(Request $request): bool;
/**
* Skip middleware if the route is universal and uses path identification or if the route is universal and the context should be central.
* Universal routes using path identification should get cloned using CloneRoutesAsTenant.
@ -36,9 +47,6 @@ trait UsableWithEarlyIdentification
protected function shouldBeSkipped(Route $route): bool
{
if (tenancy()->routeIsUniversal($route) && $this instanceof IdentificationMiddleware) {
/** @phpstan-ignore-next-line */
throw_unless($this instanceof UsableWithUniversalRoutes, MiddlewareNotUsableWithUniversalRoutesException::class);
return $this->determineUniversalRouteContextFromRequest(request()) === Context::CENTRAL;
}
@ -51,10 +59,11 @@ trait UsableWithEarlyIdentification
// Now that we're sure the MW isn't used in the global MW stack, we determine whether to skip it
if ($this instanceof PreventAccessFromUnwantedDomains) {
// Skip access prevention if the route directly uses a non-domain identification middleware
return tenancy()->routeHasIdentificationMiddleware($route) && ! tenancy()->routeHasDomainIdentificationMiddleware($route);
return tenancy()->routeHasIdentificationMiddleware($route) &&
! tenancy()->routeHasMiddleware($route, config('tenancy.identification.domain_identification_middleware'));
}
return $this->shouldIdentificationMiddlewareBeSkipped($route);
return $this->shouldSkipIdentificationMiddleware($route);
}
protected function determineUniversalRouteContextFromRequest(Request $request): Context
@ -66,7 +75,6 @@ trait UsableWithEarlyIdentification
$globalIdentificationUsed = ! tenancy()->routeHasIdentificationMiddleware($route) && static::inGlobalStack();
$routeLevelIdentificationUsed = tenancy()->routeHasMiddleware($route, static::class);
/** @var UsableWithUniversalRoutes $this */
if (($globalIdentificationUsed || $routeLevelIdentificationUsed) && $this->requestHasTenant($request)) {
return Context::TENANT;
}
@ -74,9 +82,9 @@ trait UsableWithEarlyIdentification
return Context::CENTRAL;
}
protected function shouldIdentificationMiddlewareBeSkipped(Route $route): bool
protected function shouldSkipIdentificationMiddleware(Route $route): bool
{
if (! static::inGlobalStack()) {
if (! tenancy()->globalStackHasMiddleware(static::class)) {
return false;
}
@ -109,6 +117,6 @@ trait UsableWithEarlyIdentification
public static function inGlobalStack(): bool
{
return app(Kernel::class)->hasMiddleware(static::class);
return tenancy()->globalStackHasMiddleware(static::class);
}
}

View file

@ -1,26 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Concerns;
use Illuminate\Http\Request;
/**
* Identification middleware has to implement this in order to make universal routes work with it,.
*/
interface UsableWithUniversalRoutes
{
/**
* Determine if the tenant is present in the incoming request.
*
* Because universal routes can be in any context (central/tenant),
* we use this to determine the context. We can't just check for
* the route's middleware to determine the route's context.
*
* For example, route '/foo' has the 'universal' and InitializeTenancyByRequestData middleware.
* When visiting the route, we should determine the context by the presence of the tenant payload.
* The context is tenant if the tenant parameter is present (e.g. '?tenant=foo'), otherwise the context is central.
*/
public function requestHasTenant(Request $request): bool;
}

View file

@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Exceptions;
use Exception;
class MiddlewareNotUsableWithUniversalRoutesException extends Exception
{
public function __construct(string $message = '')
{
parent::__construct($message ?: 'Universal routes are usable only with identification middleware that implements UsableWithUniversalRoutes.');
}
}

View file

@ -6,7 +6,7 @@ namespace Stancl\Tenancy\Listeners;
use Illuminate\Routing\Events\RouteMatched;
use Stancl\Tenancy\Enums\RouteMode;
use Stancl\Tenancy\PathIdentificationManager;
use Stancl\Tenancy\Resolvers\PathTenantResolver;
/**
* Remove the tenant parameter from the matched route when path identification is used globally.
@ -26,12 +26,13 @@ class ForgetTenantParameter
{
public function handle(RouteMatched $event): void
{
$kernelPathIdentificationUsed = PathIdentificationManager::pathIdentificationInGlobalStack() && ! tenancy()->routeHasIdentificationMiddleware($event->route);
$pathIdentificationInGlobalStack = tenancy()->globalStackHasMiddleware(config('tenancy.identification.path_identification_middleware'));
$kernelPathIdentificationUsed = $pathIdentificationInGlobalStack && ! tenancy()->routeHasIdentificationMiddleware($event->route);
$routeMode = tenancy()->getRouteMode($event->route);
$routeModeIsTenantOrUniversal = $routeMode === RouteMode::TENANT || ($routeMode === RouteMode::UNIVERSAL && $event->route->hasParameter(PathIdentificationManager::getTenantParameterName()));
$routeModeIsTenantOrUniversal = $routeMode === RouteMode::TENANT || ($routeMode === RouteMode::UNIVERSAL && $event->route->hasParameter(PathTenantResolver::tenantParameterName()));
if ($kernelPathIdentificationUsed && $routeModeIsTenantOrUniversal) {
$event->route->forgetParameter(PathIdentificationManager::getTenantParameterName());
$event->route->forgetParameter(PathTenantResolver::tenantParameterName());
}
}
}

View file

@ -7,11 +7,10 @@ namespace Stancl\Tenancy\Middleware;
use Closure;
use Illuminate\Http\Request;
use Stancl\Tenancy\Concerns\UsableWithEarlyIdentification;
use Stancl\Tenancy\Concerns\UsableWithUniversalRoutes;
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
use Stancl\Tenancy\Tenancy;
class InitializeTenancyByDomain extends IdentificationMiddleware implements UsableWithUniversalRoutes
class InitializeTenancyByDomain extends IdentificationMiddleware
{
use UsableWithEarlyIdentification;

View file

@ -7,16 +7,14 @@ namespace Stancl\Tenancy\Middleware;
use Closure;
use Illuminate\Http\Request;
use Stancl\Tenancy\Concerns\UsableWithEarlyIdentification;
use Stancl\Tenancy\Concerns\UsableWithUniversalRoutes;
use Stancl\Tenancy\Exceptions\RouteIsMissingTenantParameterException;
use Stancl\Tenancy\PathIdentificationManager;
use Stancl\Tenancy\Resolvers\PathTenantResolver;
use Stancl\Tenancy\Tenancy;
/**
* @see Stancl\Tenancy\Listeners\ForgetTenantParameter
*/
class InitializeTenancyByPath extends IdentificationMiddleware implements UsableWithUniversalRoutes
class InitializeTenancyByPath extends IdentificationMiddleware
{
use UsableWithEarlyIdentification;
@ -55,6 +53,6 @@ class InitializeTenancyByPath extends IdentificationMiddleware implements Usable
*/
public function requestHasTenant(Request $request): bool
{
return tenancy()->getRoute($request)->hasParameter(PathIdentificationManager::getTenantParameterName());
return tenancy()->getRoute($request)->hasParameter(PathTenantResolver::tenantParameterName());
}
}

View file

@ -7,13 +7,12 @@ namespace Stancl\Tenancy\Middleware;
use Closure;
use Illuminate\Http\Request;
use Stancl\Tenancy\Concerns\UsableWithEarlyIdentification;
use Stancl\Tenancy\Concerns\UsableWithUniversalRoutes;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByRequestDataException;
use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
use Stancl\Tenancy\Resolvers\RequestDataTenantResolver;
use Stancl\Tenancy\Tenancy;
class InitializeTenancyByRequestData extends IdentificationMiddleware implements UsableWithUniversalRoutes
class InitializeTenancyByRequestData extends IdentificationMiddleware
{
use UsableWithEarlyIdentification;

View file

@ -67,4 +67,10 @@ class PreventAccessFromUnwantedDomains
{
return in_array($request->getHost(), config('tenancy.identification.central_domains'), true);
}
// todo@samuel
public function requestHasTenant(Request $request): bool
{
return false;
}
}

View file

@ -6,7 +6,7 @@ namespace Stancl\Tenancy\Overrides;
use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\Arr;
use Stancl\Tenancy\PathIdentificationManager;
use Stancl\Tenancy\Resolvers\PathTenantResolver;
/**
* This class is used in place of the default UrlGenerator when UrlGeneratorBootstrapper is enabled.
@ -104,7 +104,7 @@ class TenancyUrlGenerator extends UrlGenerator
*/
protected function prefixRouteName(string $name): string
{
$tenantPrefix = PathIdentificationManager::getTenantRouteNamePrefix();
$tenantPrefix = PathTenantResolver::tenantRouteNamePrefix();
if (static::$prefixRouteNames && ! str($name)->startsWith($tenantPrefix)) {
$name = str($name)->after($tenantPrefix)->prepend($tenantPrefix)->toString();
@ -118,6 +118,6 @@ class TenancyUrlGenerator extends UrlGenerator
*/
protected function addTenantParameter(array $parameters): array
{
return tenant() && static::$passTenantParameterToRoutes ? array_merge($parameters, [PathIdentificationManager::getTenantParameterName() => tenant()->getTenantKey()]) : $parameters;
return tenant() && static::$passTenantParameterToRoutes ? array_merge($parameters, [PathTenantResolver::tenantParameterName() => tenant()->getTenantKey()]) : $parameters;
}
}

View file

@ -1,60 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy;
use Closure;
use Illuminate\Routing\Route;
use Stancl\Tenancy\Resolvers\PathTenantResolver;
// todo a lot of duplicate logic with PathTenantResolver, ideally remove this class
class PathIdentificationManager
{
public static Closure|null $tenantParameterName = null;
public static Closure|null $tenantRouteNamePrefix = null;
/**
* Get the tenant parameter name using the static property.
* Default to PathTenantResolver::tenantParameterName().
*/
public static function getTenantParameterName(): string
{
return static::$tenantParameterName ? (static::$tenantParameterName)() : PathTenantResolver::tenantParameterName();
}
/**
* Get the tenant route name prefix using the static property.
* Default to PathTenantResolver::tenantRouteNamePrefix().
*/
public static function getTenantRouteNamePrefix(): string
{
return static::$tenantRouteNamePrefix ? (static::$tenantRouteNamePrefix)() : PathTenantResolver::tenantRouteNamePrefix();
}
public static function pathIdentificationOnRoute(Route $route): bool
{
return static::checkPathIdentificationMiddleware(fn ($middleware) => tenancy()->routeHasMiddleware($route, $middleware));
}
public static function pathIdentificationInGlobalStack(): bool
{
return static::checkPathIdentificationMiddleware(fn ($middleware) => $middleware::inGlobalStack());
}
protected static function checkPathIdentificationMiddleware(Closure $closure): bool
{
foreach (static::getPathIdentificationMiddleware() as $middleware) {
if ($closure($middleware)) {
return true;
}
}
return false;
}
protected static function getPathIdentificationMiddleware(): array
{
return config('tenancy.identification.path_identification_middleware');
}
}

View file

@ -4,15 +4,14 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Resolvers;
use Stancl\Tenancy\Tenancy;
use Stancl\Tenancy\Contracts\Domain;
use Stancl\Tenancy\Contracts\Tenant;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Stancl\Tenancy\Contracts\SingleDomainTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;
use Stancl\Tenancy\Contracts\Domain;
use Stancl\Tenancy\Contracts\SingleDomainTenant;
use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
use Stancl\Tenancy\Tenancy;
class DomainTenantResolver extends Contracts\CachedTenantResolver
{

View file

@ -286,7 +286,7 @@ function getResolverArgument(string $resolver, string $tenantKey): string|Route
// Make the 'tenant' route parameter the tenant key
// Setting the parameter on the $route->parameters property is required
// Because $route->setParameter() throws an exception when $route->parameters is not set yet
$route->parameters[PathIdentificationManager::getTenantParameterName()] = $tenantKey;
$route->parameters[PathTenantResolver::tenantParameterName()] = $tenantKey;
// Return the route instance with the tenant key as the 'tenant' parameter
return $route;

View file

@ -74,7 +74,7 @@ test('domain identification middleware is configurable', function() {
config(['tenancy.identification.domain_identification_middleware' => []]);
expect(tenancy()->routeHasDomainIdentificationMiddleware($route))->toBeFalse();
expect(tenancy()->routeHasMiddleware($route, config('tenancy.identification.domain_identification_middleware')))->toBeFalse();
// Set domain identification middleware list back to default
config(['tenancy.identification.domain_identification_middleware' => [
@ -83,5 +83,5 @@ test('domain identification middleware is configurable', function() {
InitializeTenancyByDomainOrSubdomain::class,
]]);
expect(tenancy()->routeHasDomainIdentificationMiddleware($route))->toBeTrue();
expect(tenancy()->routeHasMiddleware($route, config('tenancy.identification.domain_identification_middleware')))->toBeTrue();
});

View file

@ -21,7 +21,6 @@ use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
use Stancl\Tenancy\Exceptions\MiddlewareNotUsableWithUniversalRoutesException;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByRequestDataException;
test('a route can be universal using domain identification', function (array $routeMiddleware, array $globalMiddleware) {
@ -321,29 +320,6 @@ test('tenant resolver methods return the correct names for configured values', f
['tenant_route_name_prefix', 'prefix']
]);
test('identification middleware works with universal routes only when it implements MiddlewareUsableWithUniversalRoutes', function () {
$tenantKey = Tenant::create()->getTenantKey();
$routeAction = fn () => tenancy()->initialized ? $tenantKey : 'Tenancy is not initialized.';
// Route with the package's request data identification middleware implements MiddlewareUsableWithUniversalRoutes
RouteFacade::get('/universal-route', $routeAction)->middleware(['universal', InitializeTenancyByRequestData::class]);
// Routes with custom request data identification middleware does not implement MiddlewareUsableWithUniversalRoutes
RouteFacade::get('/custom-mw-universal-route', $routeAction)->middleware(['universal', CustomMiddleware::class]);
RouteFacade::get('/custom-mw-tenant-route', $routeAction)->middleware(['tenant', CustomMiddleware::class]);
// Ensure the custom identification middleware works with non-universal routes
// This is tested here because this is the only test where the custom MW is used
// No exception is thrown for this request since the route uses the TENANT middleware, not the UNIVERSAL middleware
pest()->get('http://localhost/custom-mw-tenant-route?tenant=' . $tenantKey)->assertOk()->assertSee($tenantKey);
pest()->get('http://localhost/universal-route')->assertOk();
pest()->get('http://localhost/universal-route?tenant=' . $tenantKey)->assertOk()->assertSee($tenantKey);
pest()->expectException(MiddlewareNotUsableWithUniversalRoutesException::class);
$this->withoutExceptionHandling()->get('http://localhost/custom-mw-universal-route');
});
foreach ([
'domain identification types' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomain::class],
'subdomain identification types' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyBySubdomain::class],
@ -370,45 +346,6 @@ foreach ([
]);
}
class CustomMiddleware extends IdentificationMiddleware
{
use UsableWithEarlyIdentification;
public static string $header = 'X-Tenant';
public static string $cookie = 'X-Tenant';
public static string $queryParameter = 'tenant';
public function __construct(
protected Tenancy $tenancy,
protected RequestDataTenantResolver $resolver,
) {
}
/** @return \Illuminate\Http\Response|mixed */
public function handle(Request $request, Closure $next): mixed
{
if ($this->shouldBeSkipped(tenancy()->getRoute($request))) {
// Allow accessing central route in kernel identification
return $next($request);
}
return $this->initializeTenancy($request, $next, $this->getPayload($request));
}
protected function getPayload(Request $request): string|array|null
{
if (static::$header && $request->hasHeader(static::$header)) {
return $request->header(static::$header);
} elseif (static::$queryParameter && $request->has(static::$queryParameter)) {
return $request->get(static::$queryParameter);
} elseif (static::$cookie && $request->hasCookie(static::$cookie)) {
return $request->cookie(static::$cookie);
}
return null;
}
}
class Controller extends BaseController
{
public function __invoke()