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); } }