mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-14 03:14:03 +00:00
* Give universal flag the highest priority (wip) * Stop forgetting tenant parameter when route-level path ID is used * Fix PHPStan errors * Simplify annotation * Fix comment * Correct annotations * Improve requestHasTenant comment * Make cloning logic only clone universal routes, delete the universal flag from the new (tenant) route * Delete APP_DEBUG * make if condition easier to read * Update DealsWithRouteContexts.php * Fix test * Fix code style (php-cs-fixer) * Move tests * Delete incorrectly committed file * Cloning routes update wip * Route cloning rework WIP * Add todo to clone routes * Fix code style (php-cs-fixer) * Route cloning fix WIP * Set CloneRoutesAsTenant::$tenantMiddleware to ID MW * Revert CloneRoutesAsTenant::$tenantMiddleware-related changes * Simplify requestHasTenant * Add and test 'ckone' flag * Delete setting $skippedRoutes from CloneRoutesAsTenant * Fix code style (php-cs-fixer) * make config key used for testing distinct from normal tenancy config keys * Update src/Actions/CloneRoutesAsTenant.php * Move 'path identification types' dataset to CloneActionTest --------- Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com> Co-authored-by: Samuel Štancl <samuel@archte.ch> Co-authored-by: PHP CS Fixer <phpcsfixer@example.com>
199 lines
7.8 KiB
PHP
199 lines
7.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Stancl\Tenancy\Actions;
|
|
|
|
use Closure;
|
|
use Illuminate\Routing\Route;
|
|
use Illuminate\Routing\Router;
|
|
use Illuminate\Support\Collection;
|
|
use Stancl\Tenancy\Enums\RouteMode;
|
|
use Stancl\Tenancy\PathIdentificationManager;
|
|
|
|
/**
|
|
* The CloneRoutesAsTenant action clones
|
|
* routes flagged with the 'universal' middleware,
|
|
* all routes without a flag if the default route mode is universal,
|
|
* and routes that directly use the InitializeTenancyByPath middleware.
|
|
*
|
|
* The main purpose of this action is to make the integration
|
|
* of packages (e.g., Jetstream or Livewire) easier with path-based tenant identification.
|
|
*
|
|
* By default, universal routes are cloned as tenant routes (= they get flagged with the 'tenant' middleware)
|
|
* and prefixed with the '/{tenant}' path prefix. Their name also gets prefixed with the tenant name prefix.
|
|
*
|
|
* Routes with the path identification middleware get cloned similarly, but only if they're not universal at the same time.
|
|
* Unlike universal routes, these routes don't get the tenant flag,
|
|
* because they don't need it (they're not universal, and they have the identification MW, so they're already considered tenant).
|
|
*
|
|
* You can use the `cloneUsing()` hook to customize the route definitions,
|
|
* and the `skipRoute()` method to skip cloning of specific routes.
|
|
* You can also use the $tenantParameterName and $tenantRouteNamePrefix
|
|
* static properties to customize the tenant parameter name or the route name prefix.
|
|
*
|
|
* Note that routes already containing the tenant parameter or prefix won't be cloned.
|
|
*/
|
|
class CloneRoutesAsTenant
|
|
{
|
|
protected array $cloneRouteUsing = [];
|
|
protected array $skippedRoutes = [
|
|
'stancl.tenancy.asset',
|
|
];
|
|
|
|
public function __construct(
|
|
protected Router $router,
|
|
) {
|
|
}
|
|
|
|
public function handle(): void
|
|
{
|
|
$this->router
|
|
->prefix('/{' . PathIdentificationManager::getTenantParameterName() . '}/')
|
|
->group(fn () => $this->getRoutesToClone()->each(fn (Route $route) => $this->cloneRoute($route)));
|
|
|
|
$this->router->getRoutes()->refreshNameLookups();
|
|
}
|
|
|
|
/**
|
|
* Make the action clone a specific route using the provided callback instead of the default one.
|
|
*/
|
|
public function cloneUsing(string $routeName, Closure $callback): static
|
|
{
|
|
$this->cloneRouteUsing[$routeName] = $callback;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Skip a route's cloning.
|
|
*/
|
|
public function skipRoute(string $routeName): static
|
|
{
|
|
$this->skippedRoutes[] = $routeName;
|
|
|
|
return $this;
|
|
}
|
|
|
|
protected function getRoutesToClone(): Collection
|
|
{
|
|
$tenantParameterName = PathIdentificationManager::getTenantParameterName();
|
|
|
|
/**
|
|
* Clone all routes that:
|
|
* - don't have the tenant parameter
|
|
* - aren't in the $skippedRoutes array
|
|
* - are using path identification (kernel or route-level).
|
|
*
|
|
* Non-universal cloned routes will only be available in the tenant context,
|
|
* universal routes will be available in both contexts.
|
|
*/
|
|
return collect($this->router->getRoutes()->get())->filter(function (Route $route) use ($tenantParameterName) {
|
|
if (
|
|
tenancy()->routeHasMiddleware($route, 'tenant') ||
|
|
in_array($route->getName(), $this->skippedRoutes, true) ||
|
|
in_array($tenantParameterName, $route->parameterNames(), true)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
$routeHasPathIdentificationMiddleware = PathIdentificationManager::pathIdentificationOnRoute($route);
|
|
$pathIdentificationMiddlewareInGlobalStack = PathIdentificationManager::pathIdentificationInGlobalStack();
|
|
$routeHasNonPathIdentificationMiddleware = tenancy()->routeHasIdentificationMiddleware($route) && ! $routeHasPathIdentificationMiddleware;
|
|
|
|
/**
|
|
* The route should get cloned if:
|
|
* - it has route-level path identification middleware, OR
|
|
* - it uses kernel path identification (it doesn't have any route-level identification middleware) and the route is tenant or universal.
|
|
*
|
|
* The route is considered tenant if:
|
|
* - it's flagged as tenant, OR
|
|
* - it's not flagged as tenant or universal, but it has the identification middleware
|
|
*
|
|
* The route is considered universal if it's flagged as universal, and it doesn't have the tenant flag
|
|
* (it's still considered universal if it has route-level path identification middleware + the universal flag).
|
|
*
|
|
* If the route isn't flagged, the context is determined using the default route mode.
|
|
*/
|
|
$pathIdentificationUsed = (! $routeHasNonPathIdentificationMiddleware) &&
|
|
($routeHasPathIdentificationMiddleware || $pathIdentificationMiddlewareInGlobalStack);
|
|
|
|
if (
|
|
$pathIdentificationUsed &&
|
|
(tenancy()->getRouteMode($route) === RouteMode::UNIVERSAL || tenancy()->routeHasMiddleware($route, 'clone'))
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Clone a route using a callback specified in the $cloneRouteUsing property (using the cloneUsing method).
|
|
* If there's no callback specified for the route, use the default way of cloning routes.
|
|
*/
|
|
protected function cloneRoute(Route $route): void
|
|
{
|
|
$routeName = $route->getName();
|
|
|
|
// If the route's cloning callback exists
|
|
// Use the callback to clone the route instead of the default way of cloning routes
|
|
if ($routeName && $customRouteCallback = data_get($this->cloneRouteUsing, $routeName)) {
|
|
$customRouteCallback($route);
|
|
|
|
return;
|
|
}
|
|
|
|
$newRoute = $this->createNewRoute($route);
|
|
|
|
if (! tenancy()->routeHasMiddleware($route, 'tenant')) {
|
|
$newRoute->middleware('tenant');
|
|
}
|
|
|
|
$this->copyMiscRouteProperties($route, $newRoute);
|
|
}
|
|
|
|
protected function createNewRoute(Route $route): Route
|
|
{
|
|
$method = strtolower($route->methods()[0]);
|
|
$routeName = $route->getName();
|
|
$tenantRouteNamePrefix = PathIdentificationManager::getTenantRouteNamePrefix();
|
|
|
|
/** @var Route $newRoute */
|
|
$newRoute = $this->router->$method($route->uri(), $route->action);
|
|
|
|
// Delete middleware from the new route and
|
|
// Add original route middleware to ensure there's no duplicate middleware
|
|
unset($newRoute->action['middleware']);
|
|
|
|
// Exclude `universal` and `clone` middleware from the new route -- it should specifically be a tenant route
|
|
$newRoute->middleware(array_filter(
|
|
tenancy()->getRouteMiddleware($route),
|
|
fn (string $middleware) => ! in_array($middleware, ['universal', 'clone'])
|
|
));
|
|
|
|
if ($routeName && ! $route->named($tenantRouteNamePrefix . '*')) {
|
|
// Clear the route name so that `name()` sets the route name instead of suffixing it
|
|
unset($newRoute->action['as']);
|
|
|
|
$newRoute->name($tenantRouteNamePrefix . $routeName);
|
|
}
|
|
|
|
return $newRoute;
|
|
}
|
|
|
|
/**
|
|
* Copy misc properties of the original route to the new route.
|
|
*/
|
|
protected function copyMiscRouteProperties(Route $originalRoute, Route $newRoute): void
|
|
{
|
|
$newRoute
|
|
->setBindingFields($originalRoute->bindingFields())
|
|
->setFallback($originalRoute->isFallback)
|
|
->setWheres($originalRoute->wheres)
|
|
->block($originalRoute->locksFor(), $originalRoute->waitsFor())
|
|
->withTrashed($originalRoute->allowsTrashedBindings())
|
|
->setDefaults($originalRoute->defaults);
|
|
}
|
|
}
|