3030 * By providing a callback to shouldClone(), you can change how it's determined if a route should be cloned if you don't want to use middleware flags.
3131 *
3232 * Cloned routes are prefixed with '/{tenant}', flagged with 'tenant' middleware, and have their names prefixed with 'tenant.'.
33+ *
34+ * The addition of the tenant parameter can be controlled using addTenantParameter(true|false). Note that if you decide to disable
35+ * tenant parameter addition, the routes MUST differ in domains. This can be controlled using the domain(string|null) method. The
36+ * default behavior is to NOT set any domains on cloned routes -- unless specified otherwise using that method.
37+ *
3338 * The parameter name and prefix can be changed e.g. to `/{team}` and `team.` by configuring the path resolver (tenantParameterName and tenantRouteNamePrefix).
3439 * Routes with names that are already prefixed won't be cloned - but that's just the default behavior.
3540 * The cloneUsing() method allows you to completely override the default behavior and customize how the cloned routes will be defined.
4348 *
4449 * Example usage:
4550 * ```
46- * Route::get('/foo', fn () => true )->name('foo')->middleware('clone');
47- * Route::get('/bar', fn () => true )->name('bar')->middleware('universal');
51+ * Route::get('/foo', ... )->name('foo')->middleware('clone');
52+ * Route::get('/bar', ... )->name('bar')->middleware('universal');
4853 *
4954 * $cloneAction = app(CloneRoutesAsTenant::class);
5055 *
5459 * // Clone bar route as /{tenant}/bar and name it tenant.bar ('universal' middleware won't be present in the cloned route)
5560 * $cloneAction->cloneRoutesWithMiddleware(['universal'])->handle();
5661 *
57- * Route::get('/baz', fn () => true )->name('baz');
62+ * Route::get('/baz', ... )->name('baz');
5863 *
5964 * // Clone baz route as /{tenant}/bar and name it tenant.baz ('universal' middleware won't be present in the cloned route)
6065 * $cloneAction->cloneRoute('baz')->handle();
66+ *
67+ * Route::domain('central.localhost')->get('/no-tenant-parameter', ...)->name('no-tenant-parameter')->middleware('clone');
68+ *
69+ * // Clone baz route as /no-tenant-parameter and name it tenant.no-tenant-parameter (the route won't have the tenant parameter)
70+ * // This can be useful with domain identification. Importantly, the original route MUST have a domain set. The domain of the
71+ * // cloned route can be customized using domain(string|null). By default, the cloned route will not be scoped to a domain,
72+ * // unless a domain() call is used. It's important to keep in mind that:
73+ * // 1. When addTenantParameter(false) is used, the paths will be the same, thus domains must differ.
74+ * // 2. If the original route (with the same path) has no domain, the cloned route will never be used due to registration order.
75+ * $cloneAction->addTenantParameter(false)->cloneRoutesWithMiddleware(['clone'])->cloneRoute('no-tenant-parameter')->handle();
6176 * ```
6277 *
6378 * Calling handle() will also clear the $routesToClone array.
7085class CloneRoutesAsTenant
7186{
7287 protected array $ routesToClone = [];
88+ protected bool $ addTenantParameter = true ;
89+ protected string |null $ domain = null ;
7390 protected Closure |null $ cloneUsing = null ; // The callback should accept Route instance or the route name (string)
7491 protected Closure |null $ shouldClone = null ;
7592 protected array $ cloneRoutesWithMiddleware = ['clone ' ];
@@ -78,6 +95,7 @@ public function __construct(
7895 protected Router $ router ,
7996 ) {}
8097
98+ /** Clone routes. This resets routesToClone() but not other config. */
8199 public function handle (): void
82100 {
83101 // If no routes were specified using cloneRoute(), get all routes
@@ -102,36 +120,64 @@ public function handle(): void
102120 $ route = $ this ->router ->getRoutes ()->getByName ($ route );
103121 }
104122
105- $ this ->copyMiscRouteProperties ( $ route , $ this -> createNewRoute ($ route) );
123+ $ this ->createNewRoute ($ route );
106124 }
107125
108126 // Clean up the routesToClone array after cloning so that subsequent calls aren't affected
109127 $ this ->routesToClone = [];
110128
111129 $ this ->router ->getRoutes ()->refreshNameLookups ();
130+ $ this ->router ->getRoutes ()->refreshActionLookups ();
112131 }
113132
133+ /**
134+ * Should a tenant parameter be added to the cloned route.
135+ *
136+ * The name of the parameter is controlled using PathTenantResolver::tenantParameterName().
137+ */
138+ public function addTenantParameter (bool $ addTenantParameter ): static
139+ {
140+ $ this ->addTenantParameter = $ addTenantParameter ;
141+
142+ return $ this ;
143+ }
144+
145+ /** The domain the cloned route should use. Set to null if it shouldn't be scoped to a domain. */
146+ public function domain (string |null $ domain ): static
147+ {
148+ $ this ->domain = $ domain ;
149+
150+ return $ this ;
151+ }
152+
153+ /** Provide a custom callback for cloning routes, instead of the default behavior. */
114154 public function cloneUsing (Closure |null $ cloneUsing ): static
115155 {
116156 $ this ->cloneUsing = $ cloneUsing ;
117157
118158 return $ this ;
119159 }
120160
161+ /** Specify which middleware should serve as "flags" telling this action to clone those routes. */
121162 public function cloneRoutesWithMiddleware (array $ middleware ): static
122163 {
123164 $ this ->cloneRoutesWithMiddleware = $ middleware ;
124165
125166 return $ this ;
126167 }
127168
169+ /**
170+ * Provide a custom callback for determining whether a route should be cloned.
171+ * Overrides the default middleware-based detection.
172+ * */
128173 public function shouldClone (Closure |null $ shouldClone ): static
129174 {
130175 $ this ->shouldClone = $ shouldClone ;
131176
132177 return $ this ;
133178 }
134179
180+ /** Clone an individual route. */
135181 public function cloneRoute (Route |string $ route ): static
136182 {
137183 $ this ->routesToClone [] = $ route ;
@@ -171,28 +217,31 @@ protected function createNewRoute(Route $route): Route
171217 $ action ->put ('as ' , PathTenantResolver::tenantRouteNamePrefix () . $ name );
172218 }
173219
174- $ action
175- ->put ('middleware ' , $ middleware )
176- ->put ('prefix ' , $ prefix . '/{ ' . PathTenantResolver::tenantParameterName () . '} ' );
220+ if ($ this ->domain ) {
221+ $ action ->put ('domain ' , $ this ->domain );
222+ } elseif ($ action ->offsetExists ('domain ' )) {
223+ $ action ->offsetUnset ('domain ' );
224+ }
225+
226+ $ action ->put ('middleware ' , $ middleware );
227+
228+ if ($ this ->addTenantParameter ) {
229+ $ action ->put ('prefix ' , $ prefix . '/{ ' . PathTenantResolver::tenantParameterName () . '} ' );
230+ }
177231
178232 /** @var Route $newRoute */
179233 $ newRoute = $ this ->router ->$ method ($ uri , $ action ->toArray ());
180234
181- return $ newRoute ;
182- }
183-
184- /**
185- * Copy misc properties of the original route to the new route.
186- */
187- protected function copyMiscRouteProperties (Route $ originalRoute , Route $ newRoute ): void
188- {
235+ // Copy misc properties of the original route to the new route.
189236 $ newRoute
190- ->setBindingFields ($ originalRoute ->bindingFields ())
191- ->setFallback ($ originalRoute ->isFallback )
192- ->setWheres ($ originalRoute ->wheres )
193- ->block ($ originalRoute ->locksFor (), $ originalRoute ->waitsFor ())
194- ->withTrashed ($ originalRoute ->allowsTrashedBindings ())
195- ->setDefaults ($ originalRoute ->defaults );
237+ ->setBindingFields ($ route ->bindingFields ())
238+ ->setFallback ($ route ->isFallback )
239+ ->setWheres ($ route ->wheres )
240+ ->block ($ route ->locksFor (), $ route ->waitsFor ())
241+ ->withTrashed ($ route ->allowsTrashedBindings ())
242+ ->setDefaults ($ route ->defaults );
243+
244+ return $ newRoute ;
196245 }
197246
198247 /** Removes top-level cloneRoutesWithMiddleware and adds 'tenant' middleware. */
0 commit comments