Kenneth_H's avatar

Slug with multiple endpoints

Hi So after reading around the web, I understand that it is not a good idea to create something like a SlugResolveController for handling this kind of route:

Route::get('{slug}', 'SlugResolveController@resolve');

Although the controller is still needed, but from what I have read, the actual logic should seperated into a Job/Command in Laravel 5.1 For my code I have created a pattern for my various content. I have a Page model and a PageController Anything that is saved in the pages table can be accessed by visting example.com/page/123, where 123 is the page_id. For a user, which is provided by the User model through the UserController i would be example.com/user/123 And so on.

Currently I have a slug mapping table called url_alias, which holds the slug and a "system" path like the above. In a custom CMS, that I did from scratch, this was done by putting a layer in between the "wildcard" route and the controllers, so that this layer would find out which controller to call based on the "system" path.

How would I accomplish making this in Laravel? My idea would be to create something that could create an instance of a controller to then process the request and return the correct view. Can I do this directly in code or should I use a specific package?

0 likes
6 replies
Kenneth_H's avatar

Well, that would be a way of bringing down the length of my routes.php, but not quite what I am after. Perhaps I have explained my issue incorrect. What I want is a way to just put a slug and then have a mapping table where I can find the information about which controller should handle the request. So I might have a URL like this:

http://example.com/my-custom-page

Now this might be some kind of content page, so in the mapping table, this slug would resolve to page/123, which is then spilt and then sent to PageController@resolve

bobbybouwmann's avatar

Oke, in that case I would just define the routes in the routes file. I always see my routes.php as the sitemap of my application. If I join a project, it's the first file I look at to see which routes are available and what the application will handle for me!

Kenneth_H's avatar

Yes. and this works if part of the route is static. But since I try to develop a CMS, I have the above route as the last entry in my routes.php file. This way I can get past all the predefined routes and then have the "wildcard" route as the last point before outputting a 404 error. If you look in a CMS like wordpress or Drupal, they can both define a slug for the user to type in the browser and then get it to work. Currently mine is working, but only for content that can be provided through the PageController, as I have changed the route to this:

Route::get('{slug}', 'PageController@resolve');

The method resolve() contains the logic needed to resolve the slug to a page/123 and then extract the ID, get the Page using the Page model and then pass it to a show method in the PageController.

How would I accomplish to make the above route a true wildcard route, that can resolve to anything as long as what it resolves to is matching the defined pattern of {model}/{id}?

Kenneth_H's avatar

So I have been digging around while this thread has been active to see if I somehow could make this work without having to call another controller within my controller and also keep the amount of logic in that controller to a minimum. I thought that I found the solution in the findRoute method of Illuminate\Routing\Router, as that would basically do what I want. Unfortunately it is a protected method, which means I cannot do this:

app('router')->findRoute($request);

Since that method is called by another method called dispatchToRoute in the same class, I could use that. However for this to work and pass on the request object to this method, I need to modify it so that the path which it should resolve against the routes.php file will be the system path. So if my slug resolves to this:

page/123

I need to make the request object contain this new path, but for this to happen I need to modify a protected field in the object and there is no method to do so. If I could get it to work, I would basically be able to skip the controller and in my routes.php create a closure like this:

Route::get('{slug}', function(){
/*Code to evaluate URL and modify request path/uri goes here*/
return app('router')->dispatchToRoute($request);
});

Does anyone know how to modify the request path/uri without having to call redirect('my/new/path')?

Kenneth_H's avatar
Kenneth_H
OP
Best Answer
Level 16

For anyone having the same issue, I want to save you the trouble and a lot of gray hairs. I have found a usable solution So I had this route:

Route::get('{slug}', 'PageController@resolve');

I also have a model for my Slug. this is called PathAlias So first I have the Route-Model-Binding:

Route::bind('path_alias', function ($value) {
    return App\PathAlias::where('alias', $value)->first();
});

Then I have the above route in the bottom of my routes.php, but with a small modification:

Route::get('{path_alias}', function(\App\PathAlias $path_alias){
    return Route::dispatchToRoute(\Illuminate\Http\Request::create($path_alias->source));
});

What I have done was going through the sourcecode and within the SymfonyRequest class there was a static method to create a request. So without having to redirect to the resolved path, I could dispatch a new request directly to the routing layer and then it would find the route for the controller responsible for handling the request. With this solution there is no need to create routes following a specific pattern, although I recommend it, which means as long as there is a route for the source of my slug, it can also display the results. Of course this is just created with a closure, but shows the basic way of doing it. A production ready solution would be able to handle 404 etc. therefore it should be in a controller.

2 likes

Please or to participate in this conversation.