Beware the Route to Evil

Posted: 2013-07-23
Category: PHP

As programmers we should all be used to the fact that our personal preferences and habits are all subject to change. I used to be a big fan of Alman and tabs > spaces, now I follow PSR-2 happily which contradicts those two rules and couldn't care less.

Another contradiction to my personal preferences has been "automagical routing" verses "verbose routing". CodeIgniter used to guess the route from a URL, and I loved that. All I'd need to get a controller working was make the file, add a method, call it up in the browser - it seemed so simple!

Then I worked on the CodeIgniter Rest-Server, which allowed users to suffix their methods with HTTP verbs, so GET /users/profile/1 would go to public function profile_get(), and a profile could be updated by posting to it, the method of course being public function profile_post(). Everyone was happy, I used it to make about 5 million API's, the UN have code running on it, are doing something with it, unicorns and rainbows everywhere.

BUT, when you start to rely on this automagical logic it can be a huge ball-ache down the line. Now, every single endpoint in my entire application has its own route, which I've created myself, specified the HTTP verb it should attach to (one at a time) and maybe even named it if I'm using something cool like Laravel 4.

Here are some reasons why relying on automagical route guesswork in your framework of choice is probably a bad idea.

Project Inheritance

I've taken over projects using this sort of logic. Not just in CodeIgniter, but Kohana, Fuel and Laravel too. Because I am taking over from somebody else, first I need to work out all the URLs. Sometimes this would be an API with some outdated endpoint documentation, but more commonly it would be random applications with admin panels, AJAX endpoints, user dashboards, search forms and whatever else.

When you get to this sort of project having auto-guesswork routes means you have to go through all the controllers, pick apart which endpoints are navigated to via the auto-guesswork and which have their own custom route (because the client wanted /users/profiles/philstugeon to be /p/philsturgeon). Yay.

Duplicate Endpoints

Whenever you make a custom route like /p/philsturgeon, you still have /users/profiles/philstugeon active. As a "backwards compatibility" thing that might seem like a nice idea, but you don't want both URLs active indefinitely. In other examples you probably wont EVER want both to be active, so now your only real option is to specifically route /users/profiles/{any} to the toilet, which is growing your routes file for something you don't actually want…

Route Evaluation Performance

Go and look at the CodeIgniter router and tell me if you think the number of file_exists() and is_dir() checks it performs to maintain that level of guesswork is a good idea. Even if they threw a cache in there, its complicated as f**k and totally unnecessary. With static routes you are saying "take this pattern, and send it to that action" which is super lightweight, compared to: "Is the first segment a directory, or a file? Is the second a directory, or a file, or a method in a file? Madness.

Renaming Controllers

You want that whole thing to be called "channel" instead of "categories"? If your auto-guesswork was getting you to the "categories" controller, then you're faced with two options. A) Rage-quit B) Tell your boss to pick the right nomenclature first, then do A, C) Rename everything so the guesswork works, or D) Add some static routes, which you're starting to realize maybe you should have done in the first place.

Resource RESTful Controllers

Even Laravel 4 is a little guilty of this, but you can do it either way. Every day I see people using their RESTful Controllers (which are incredibly similar to the Rest Server controllers I put together) which combine the HTTP verb and the URL segment to map to a method, then try to call it.

This sounds great until people want to start using named routes, reverse routing, etc and all of a sudden the conventions clash. The answer in most cases is "Stop trying to be clever and manually list your routes.", and it works.

Sub-Resources are impossible

Your magical route logic is never going to make this work:

Route::get('users/{id}/friends', 'UsersController@friends');

So you're going to need to half and half, which is confusing.

routes.php is Documentation

The advantages of manually specifying your routes is that you avoid all of the above issues, but another advantage is a simple one: Your routes.php is documentation. A developer can pick it up, know exactly what endpoints go where, what controllers are active and what is old junk.

It's even been suggested that you have the slight potential performance tweak of putting your most popular URLs at the top of the routes.php file, so you can ditch out of your router ASAP. I'm not 100% sold on the importance of that, but sometimes microseconds count.

As for the cost of developer time all you're really losing here is the 5 seconds it takes you to write:

Route::post('me/settings', 'SettingsController@update');

It's not hard, and it avoids trying to mess around making subdirectories called "me" and renaming your controllers to try and match the URL.


Daniel Petrie


I absolutely hated specifying routes at first. It was actually one of the things I disliked about Laravel when I switched over to it. Now I love it and see the benefits/advantages of doing so. Pretty much agree with everything here.

Dex Barrett


I remember sometime when I logged into Laravel IRC asking about Resource Controllers and Phil telling me not to bother about them. Wise words, indeed.

What a Resource Controller may be making easy at first becomes a pain in terms of maintainability.

Cody Covey


I think in the Resource Controllers section above you really meant Restful Controllers when mentioning Laravel's implementation?

Scott Robertson


I used to be exactly the same, when i used to use CakePHP (ha) i loved the fact that i did not have to touch routes at all. But now that doesn't make any sense at all to me.

I use Silex now mainly for personal projects, and i specify every endpoint, which gives you a lot better control over what is happening. It does also help when coming back to old projects and you can see all of the urls in one place.



This article is clearly mistaking Resource and Restful controllers. Resource controllers are a known entity - you know exactly which routes you are getting, they are all named, etc, etc. Restful routes on the other hand...yeah; that can be painful.



Completely agree. I too was a tabs guy (and know you strongly objected to spaces on twitter a few times :P) and switched when I started using Laravel, to keep up with the PSR standards.

Sublime helped loads with this as I'm sure you're already aware.

Routing wise I really HATED the way Laravel did routing, until I started using it. It makes a lot of sense. Sure, its still a massive ballache initially, but once its done it's done, and you've got a crapload of freedom on how your URL structure works. The only thing I'd love now is a PyroRoutes style addon that can modify the routes file from my admin area. May have to look into making one for Laravel.

The Shift Exchange


Another reason (at least for Laravel 4) - you need to explicitly bind routes to use Route::model() - you can not use it in conjunction with Route::resource()

Jonathan D. Johnson


I think your final point is the biggest gain here. With so many codebases that struggle to produce even the slightest bit of documentation, having a single file that produces a literal map of the application is a huge win.

The Angry Web Developer


I onloy started using this approach with Laravel 4 and I did hate it at first, missing how CodeIgniter guessed everything. The more I used Laravel 4 the more sense it made specificying all endpoints, even for bigger projects. I'm in full agreement with the last point as I've never thought of a list of routes as a form of documentation, which they essentially are.

Sanjin Celeski


I never though about routes as documentation. It totally makes sense! :-)

Posting comments after one month has been disabled.