For those of you that frequent the Laravel irc channel on freenode, you may have been witness to a recurring conversation surrounding Laravel routing, namely, using http methods other than POST & GET, and the use of Route::controller() & Route::resource(). Well this article plans to cover those bases as well as some added fun regarding Laravel routes, that may help.
If you’ve read my previous Laravel article, A Simplified Laravel ACL, you’ll be familiar with the way in which I leverage named routes to provide a basic route based ACL, but it seems that people are still neglecting this particular feature, in favour of using relative paths.
Why are named routes important? Think of named routes as way of abstracting out the paths for various parts of the system, providing that you’ve named them sensibly, you can simply refer to the particular route using any of the available methods, and should a time arise where you need to change the path for the route, you don’t need to go and track down every reference and update. This obviously doesn’t help if the route name changes, but as I said, providing that you’re naming them sensibly, there shouldn’t be an issue.
Below is an extract of a routes file for a personal project that I’ve been working on;
As you can see here, I make full use of named routes and name everything sensibly, with a somewhat pseudo hierarchical structure. You’ll also notice a few other things, such as the groups, namespace declarations and the naming of my controller methods, which all segway nicely into the rest of this article.
Route groups are again something that I frequently see overlooked, which I think may be down to a combination of confusion and lack of exposure. As you can see in the above example, I use route groups to separate my routes into logical chunks, sure the routes file can get a bit lengthy, but it reads well, each definition is not too long and contains only the minimum amount of information required to process this route.
This particular little trick is something that appears in the documentation, but is again, an often under-utilised feature. This allows me to specify the base namespace for all routes within this group. So with my routes that are set to use ‘User\UserController@index’ for example, the routing system will prefix with ‘ModBin\Controllers’, which saves the hassle of lengthy namespace/class references.
In my first example you’ll see that I have a secondary group that contains the prefix for all of the /user routes. Much like the namespace declaration in the previous section, that allows the router to append ‘/user’ to the start of the route paths, defined within this group. You may also notice, that unlike the original example, this group has a namespace declaration as well. This is just to show you how the namespace part of the route group works, so now every route I define within the groups has the class reference prefixed with ‘ModBin\Controllers\User’.
And the Rest
You can pretty much use most of the options available for individual route declarations in the groups, and it’d take too long to list them all. If you’re curious about what you can and can’t do, have some trial and error or perhaps go through the Laravel core classes to see exactly how it works.
Non POST or GET for Browsers
This is the main bit right here, the core of the article, the primary purpose for its existence. As you can see from Section 9 of the Hypertext Transfer Protocol — HTTP/1.1 RFC there are a few HTTP methods that can be used with HTTP/1.1. Right now, you don’t need to worry about them all, just the few I have listed below; (Feel free to scroll past this)
The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI. If the Request-URI refers to a data-producing process, it is the produced data which shall be returned as the entity in the response and not the source text of the process, unless that text happens to be the output of the process.
The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line. POST is designed to allow a uniform method to cover the following functions:
- Annotation of existing resources;
- Posting a message to a bulletin board, newsgroup, mailing list, or similar group of articles;
- Providing a block of data, such as the result of submitting a form, to a data-handling process;
- Extending a database through an append operation.
The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI. If a new resource is created, the origin server MUST inform the user agent via the 201 (Created) response. If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to indicate successful completion of the request. If the resource could not be created or modified with the Request-URI, an appropriate error response SHOULD be given that reflects the nature of the problem. The recipient of the entity MUST NOT ignore any Content-* (e.g. Content-Range) headers that it does not understand or implement and MUST return a 501 (Not Implemented) response in such cases.
The DELETE method requests that the origin server delete the resource identified by the Request-URI. This method MAY be overridden by human intervention (or other means) on the origin server. The client cannot be guaranteed that the operation has been carried out, even if the status code returned from the origin server indicates that the action has been completed successfully. However, the server SHOULD NOT indicate success unless, at the time the response is given, it intends to delete the resource or move it to an inaccessible location.
Chances are, you’ve used all of these methods before, but until now you hadn’t seen a description of the individual methods in this sort of context, instead, the information you have regarding these methods is likely something that was inferred from practise and seeing others use it. That’s not a huge issue, to be honest, you probably don’t need to know all of the above, and I’ve only included it to help explain my point (which is coming, don’t worry).
These methods allow you to have a nice little API for interaction with your systems resources (I mean API in the sense of instructions of how to use the system, rather than a RESTful API), for example, consider the below routes file;
Here we have 4 individual routes, all with the same URI, but utilising the individual http methods which are available to us, allowing us to react accordingly and guarantee that the right action is performed for the right request. In the instance where you’re making an API (An actual API such as a RESTful one), and that the communication between the implementation and your system is machine to machine, rather than browser to machine (I know that’s still technically machine to machine), the above routes file works perfectly.
If you are however creating a system that will primarily be available via the browser, then the above routes file should not be used. Allow me to present the following two reasons as to why this is a bad idea.
- HTML Forms do not support any methods outside of POST & GET.
- User facing systems should have descriptive and easily identifiable URIs.
Since the second reason is somewhat preference based I’ll focus on the first.
HTML Form Support
At the time of writing this, HTML Forms only support methods such as POST & GET, and although HTML 5 did add PUT/DELETE at one point, it was later dropped. The reason for this is that by the definition set out in the RFC, the methods would clash with the way that the browser works.
To give you an example, a DELETE method should return no content, as the resource was deleted, and although you can use the status code 200, a 204 (No Content) would be far more applicable.
As for PUT, I’m unable to find much information regarding exactly why it wasn’t added, but if I had to make a guess (and this is just a pure guess), I’d say that it’s to do with the fact that it is in most ways, identical to POST, and although the definitions differ, the cases in which they’d be used are almost always the same. It’s worth noting that the XMLHttpRequest spec supports both PUT and DELETE.
But Laravel has a Workaround?
The workaround that Laravel provides, in my mind, is pointless. These methods are not available as part of the HTML Form spec, which is a decision, not an oversight, made by the people responsible for the entire specification, so I trust their decision and reasoning. I completely appreciate that this allows you to have a much nicer routes files and allows you to declare everything in a nice fashion, but its simply encouraging developers to go against the standards to make their life a tiny insignificant amount easier, which is no different to any of the other corner cutting and bad conventions that the community is actively trying to prevent.
Descriptive and Identifiable URIs
Chances are, your users won’t be remembering the URI and some of them, if not most, won’t even look at the address bar (unless of course there’s no padlock, then shit really hits the fan), but I believe in the general rule that my URIs should be descriptive, and at a glance, I should be able to identify exactly what the system is doing, without having to load the page. As I said, this particular reason is largely preference based so take what you will from this, but as my Dad would say;
It’s my opinion and everyone is entitled to it.
Route::resource() and Route::controller()
Well here we are, the main purpose for writing this article, the source of the raging fire that I struggle to contain whenever I see mention of these being used. In fact, while writing this article I managed to start another discussion in #laravel regarding the pointless nature of these methods, with Route::resource() being the main topic.
I entirely appreciate that this is somewhat to do with preference and could very well be down to the fact that I’m a stickler for doing things correctly, even if it takes longer. Now, don’t get me wrong, these two methods are great during early development, or proof of concept testing (ignoring the invalid http methods that resource uses), but once those two situations have ended, so does their usefulness.
This method, this god damn method right here is just pure laziness personified. If you’re hacking together a small system, sure, but 99 times out of 100, the systems I’m working with are typically large scale bespoke systems, and I keep seeing this thing used left, right and centre. Essentially, this method promotes lazy behaviour as people will just direct everything to this and define the routes in the controller, which is lazy, unorganised and just horrible to read if you’re another developer, no one wants to sift through hundreds of lines of crap to find out what the routes are.
In short, DON’T USE IT!
While I don’t particularly like the controller method, this method I absolutely loathe. I have however made a short list below of the key reasons.
- It’s lazy
- It uses HTTP methods that as I’ve listed above, HTML forms do not support
- It allows little to no customisation of your routes, thus taking the control away from you
- You should be in the habit of explicitly defining every action
I’m not going to cover the HTTP methods point as I think I already covered that in the prior section, but I’m going to focus on the third and fourth as I feel that they are the key here.
One of the main reasons that people use Laravel, besides its simplicity and the fact it’s easy to use, is that it does its utmost to give you complete control of absolutely everything, from the individual components to such things as the inversion of control container, and to use something like Route::resource() is the throw that all away and laugh in its face. If you want to add extra methods to controllers generated and/or referenced in this way, you have to go and create separate route definitions for this, which just makes your routes.php look horrible, it’s hard to follow and just looks incomplete.
One of the things you as a developer should be doing, is securing your application and making sure it works exactly how you want, when you want, after all, was that the reason you were hired? Sure it’s tedious and sure the file becomes long, but it’s a process you rarely have to repeat during the development cycle, and even adding extra routes is no real issue if you have an editor that allows you to collapse sections (you should be using Route::group()).
I realise that I could go on and on and on about Route::resource() and I’d just be repeating myself. For now, I’m going to leave what I have and I’ve made a note to approach this specific method in another article sometime in the future, but I’m always in the #laravel irc channel so feel free to discuss this topic and share your opinion, but be warned, I can be just as loud and abusive as you can.
Routes are a very important core part of any system/application and the power that Laravel gives you is as exceptional as it is underutilised. With everything however, it does have its downsides, but if you’ve read all the way through this lengthy article and you’d like to be exempt from the first wave of attacks once I get planning permission to launch an orbital ion cannon, then follow these simple rules:
- Make your routes file look nice, if it gets too long, oh well, it’s better to make sense.
- Name your routes, you never know when this will come in handy, especially in situations where you need to refactor the path.
- Group your routes, shorten your routes file by passing attributes to the group rather than each individual definition.
- Namespace your code and make full use of that with route groups.
- Do not use HTTP methods other than POST or GET when working with HTML forms.
- Avoid the controller method for medium/large applications.
- Outside of initial POC testing, do not use resource controller.
- Outside of initial POC testing, do not use resource controller.
- Outside of initial POC testing, do not use resource controller. (No that’s not a mistake, I did write that three times)
Anyway, I hope you enjoyed reading this as much as I enjoyed writing it, I plan to write many more articles in the coming months. Happy development.