Well I would add an identifier path to make it easier to distinguish between sub-paths:
/{city}/companies/{companies}
/{city}/regions/{region}
/{city}/at/{street}
Then have each path linked to a different controller where you already know what type the second parameter is.
But as you said it is a client requirement you can try this:
1 - Use explicit binding
Easiest to code, but worst in performance, you can add an explicit binding.
Add this to your app's RouteServiceProvider boot method:
Route::bind('location', function ($slug) {
return Company::where('slug', $slug)->first()
?? Region::where('slug', $slug)->first()
?? Address::where('slug', $slug)->first();
});
Reference: https://laravel.com/docs/8.x/routing#explicit-binding
I am assuming you have a separate model to each entity on your path. This will execute at most 3 queries, one for each entity, and use the null-coalesce operator to try the next one if no record was found.
Then you can have only one path:
/{city}/{location}
In your controller you won't typehint the parameter, but Laravel will know from the explicit binding what to pass to it if the location binding name matches the parameter name:
public function index(City $city, $location)
{
// ...
}
2 - Use an auxiliary table to keep all slugs.
To avoid issuing 3 queries, you can create a polymorphic table that saves and indexes all the slugs and relates to the correct model.
Then you can use implicit model binding as you would use for the City model.
This will always make 2 queries (one for the polymorphic table, and one for the related model).
Read about polymorphic models in the docs here:
https://laravel.com/docs/8.x/eloquent-relationships#polymorphic-relationships
Hope this helps.