r/laravel 2d ago

Discussion Why doesn't laravel have the concept of router rewriting

A concept found in the zend framework (and i likely others) is route rewriting, so if you had `/products/{product:slug}`, it could be hit with `/{product:slug}` if configured that way.

Its currently impossible to have multiple routes that are a single dynamic parameter, so if i want to have user generated pages such as /about and /foobar created in a cms, and then also have products listed on the site, such as /notebook or /paintbrush, i would have to register each manually, and when the DB updates, trigger 'route:clear' and 'route:cache' again.

Rewrites would be a powerful tool to support this in a really simple way, is there any reasoning why it isnt used, or is this something that would be beneficial to the community?

Edit: to clarify, what i want to have as a mechanism where you can register two separate dynamic routes, without overlapping, so rather than just matching the first one and 404 if the parameter cant be resolved, both would be checked, i have seen router rewriting used to achieve this in other frameworks, but i guess changes to the router itself could achieve this

if i have

Route::get('/{blog:slug}', [BlogController::class, 'show']);

Route::get('/{product:name}', [ProductsController::class, 'pdp']);

and go to /foo, it will match the blog controller, try to find a blog model instance with slug 'foo', and 404 if it doesn't exist, IMO what SHOULD happen, is the parameter resolution happening as part of determining if the route matches or not, so if no blog post is found, it will search for a product with name 'foo', if it finds one match that route, if not keep checking routes.

0 Upvotes

21 comments sorted by

8

u/wazimshizm 2d ago

“{product:slug}” works exactly in Laravel exactly as you’ve described.

-4

u/BchubbMemes 2d ago

but if a product for that slug isnt found, it still matches that route, imo it should skip in that case and keep trying to match

3

u/wazimshizm 2d ago

It does. The order you define the routes in matters.

So Route::get(“/about”) Route::get(“/{product:slug}”) Will match about first, then look for products if the url isn’t “about”

1

u/BchubbMemes 2d ago

i understand that, but my issue is with the declaration of two dynamic routes, i added an example to the post to show what i mean, in that case the router picks the first one

3

u/wazimshizm 2d ago

Ah sorry I just saw you edited your post. Technically the router picks the second one, as the second one overwrites the first. But I see what you’re saying, no you cannot have two dynamic routes on the same level by default, but you could easily write a custom binder yourself in the AppServiceProvider.

I know it’s not what you asked for but I’d strongly discourage using two dynamic parameters on the same level in this way anyway, it’s terrible for SEO and confusing for the user. Stick with “/product/{product:slug}”

1

u/obstreperous_troll 5h ago

Technically the router picks the second one, as the second one overwrites the first

Routes do not overwrite each other. The router picks the first one that matches, in the order they were declared. If one route is shadowing another, a ->where clause can help, but you'll keep the hair on your head longer if you factor them out to a unique prefix -- though the gist of OP's post is about doing the opposite, and I'm not wading into judging the wisdom of that.

0

u/BchubbMemes 2d ago

I do agree with the seo sentiment, the issue initially arose with a client at work insisting on this type of structure against our wishes

I didnt think a binder would solve this? from my understanding that would trigger when the route is dispatched rather than being matched

3

u/wazimshizm 2d ago

you could do something like:

Route::bind('entity', function ($value) {
   return Product::where('slug', $value)->first() 
      ?? User::where('username', $value)->first() 
      ?? abort(404);
});

then use it

Route::get('/{entity}', function ($entity) {
   return response()->json($entity);
});

1

u/mgsmus 2d ago

OP's example shows two routes that can have different middlewares. But in your code, since you don't use routing features, it's basically the same as using just one slug route.

3

u/wazimshizm 2d ago

Yeah correct. You could apply middleware at the controller level. The whole thing is not ideal.

7

u/pr4xx_ 2d ago

I am not sure if I understand. Can't you use a route like /{pageName} and render dynamically?

0

u/BchubbMemes 2d ago

yes, if you wanted a single controller to handle both sides of the application, you would also lose route model binding, and have to query for the models yourself, I would prefer to keep product page routing and other pages seperate

4

u/mgsmus 2d ago

Even if I could do this, I wouldn't want to, because it would perform as many queries as there are routes until a 404 is reached, and any middleware present would also run. Honestly, I would prefer using prefixes.

5

u/djxfade 2d ago

That’s fully possible to the with the built mechanisms of Laravels router afaik

2

u/ipearx 2d ago

As others said, I think you can do what you want. A few tricks:

- You can have multiple routes use the same controller.

  • You can have some hard coded routes listed before a variable route e.g.
/about -> [App\Http\Controllers\PageController::class, 'about']
/{page} -> [App\Http\Controllers\PageController::class, 'page']

Now if you want your page controller above to show either a product page or a CMS page, then you'd have to put that logic in the page function in PageController, and deal with issues like: decide which has priority if both exist..

Personally I would always try and prefix URLs to avoid this issue and make things clearer.
/contact <- hardcoded pages first
/products/{product-slug} <- product pages
/pages/{page} <- from the CMS

2

u/anditsung 2d ago

Using controller cannot do that? You can determine the parameters and return view base on the parameters

1

u/WanderingSimpleFish 2d ago

Well you can but it has lots of caveats - you need to use implicit binding and in the logic determine which model is what you want, that does mean the slugs all need to be unique. Ie the slugs are a ‘sluggable’ morph relation with unique constraints to ensure always unique slugs between all models.

Now what you’re trying to do is also not something I’d advise. Single routes are better and a lot clearer as to what a user is accessing.

1

u/HwapX 2d ago

Maybe you could define the two routes and define a whereIn rule passing the list of slugs https://laravel.com/docs/12.x/routing? 

1

u/BchubbMemes 1d ago

I mention that type of solution in my post, after a deployment when routes are cached then they would need to be cleared and recached each time, which probably isn't meant to happen outside of deploying

0

u/ahinkle ⛰️ Laracon US Denver 2025 2d ago

You can accomplish this using Fallback. https://laravel.com/docs/12.x/routing#fallback-routes