Aug 26, 2013
Table of contents:
An application’s router is one of the most fundamental components in the architecture of a software project. The router takes URLs requests and instructs the application what resources are required.
In theory, this doesn’t sound very complicated at all. If you enter the URL /dogs its pretty obvious that you want the DogsController.php
, right?
In a very simple project you can get away with having a very simple router. However, in nearly all bigger projects, you are going to have to handle a wide range of requests and take actions under some very specific circumstances.
A while ago I started building a RESTful router package, but I ended up giving up because I discovered Laravel’s amazing router. Laravel’s router already did everything that I wanted, so there was no point in me reinventing the wheel.
In this post I’m going to go through the Laravel router, show you what is possible with it and how you can go about implementing it in your project. Laravel’s router is one of the best that I’ve ever worked with and it makes building simple or complicated routing patterns extremely easy.
Let’s take a look!
As I’ve mentioned in many of the previous “Building Cribbb” tutorials, the app/routes.php
is where you define your routes. This ensures you keep all your route definitions in one place so whenever you need to update them you know exactly where to look.
There is also a very hand routes command that gives you information on all of your defined routes. Open up Terminal and enter:
$ php artisan routes
This is extremely helpful when you need to quickly remind yourself of what routes are available in the application.
There are basically two ways that you can route requests in Laravel.
As I’ve shown in previous tutorials, you can simply define a resource in the routes.php
and Laravel will automatically direct traffic to that RESTful controller for you.
Route::resource("users", "UserController");
Alternatively, you can also define a RESTful route and controller. This means that the methods on the controller will be named according to the RESTful method that is used.
For example:
Route::controller("users", "UserController");
Your Controller would then look like this:
class UserController extends BaseController
{
public function getIndex()
{
//
}
public function postProfile()
{
//
}
}
You can also manually route to Controller actions like this:
Route::get("user/{id}", "UserController@showProfile");
Another way of dealing with your routing logic is to keep it all within the routes.php
file. This method is popular for smaller projects where the amount of Controller logic is very simple, for example:
Route::get("user/{id}", function ($id) {
return User::find($id);
});
For the most part you will want to direct traffic to your Controller to handle the logic, rather than dealing with it in the routes file. Its fine to experiment in the routes file, but eventually your project will become a mess if you don’t make the separation of concerns.
In this tutorial I will be showing you the basic functionality of the Route class as simply the method and the action that you would typically use if all of your logic was to be kept in the routes file. However, at the end of this post I will show you how to implement this functionality in your Controllers too.
By default, Laravel will ship with one route already set up:
Route::get("/", function () {
return View::make("hello");
});
As you can probably guess, this simply responds to the root of the domain and returns the app/views/hello.php
file. If you remember back to Building out RESTful Controller methods in Laravel 4, you will know that GET
is the method you would use when you are retrieving a resource. In this example, we are simply getting the hello.php
file so we use the Route::get();
method on the Route
class.
The get();
method accepts two arguments, a path and a closure. The path is simply the URL which this method should respond to. The closure is what you want to happen. If you remember back to my What are PHP Lambdas and Closures? tutorial, a closure is basically just an anonymous function that you can pass into another function as an argument.
To make a POST request, you can simply use the post();
method.
Route::post("user/store", function () {
return User::create(Input::all());
});
If you want the route to respond to any HTTP verb, you can use the any();
method:
Route::any("hello", function () {
return "Hello World";
});
And if you want to force a route to be served over HTTPS, you can pass an array as the second argument with the closure as the second argument of the array:
Route::get("secure", [
"https",
function () {
return "Must be over HTTPS";
},
]);
Notice in all of these examples you only need to have the leading slash on the root URL, you should not use it on any other URLs.
Unless your application is extremely simple, you will find that at some point you need to have URLs that have parameters. Examples of these types of URLs are /users/1
or /2013/07/01/name-of-post
. These types of URLs are important because they allow you to pass that parameter into Laravel so the exact record of the resource can be found in the database.
To expect a parameter you simply add it to the URL in curly braces:
Route::get("user/{id}", function ($id) {
return User::find($id);
});
Remember, a closure is an anonymous function that can accept variables from outside of the scope that it was created. So you can pass the parameter into the closure to retrieve the record from the database.
If you want the parameter to be optional, you can simply add a question mark to the placeholder in the route:
Route::get("user/{id?}", function ($id = null) {
if ($id) {
return User::find($id);
}
return User::all();
});
You can also use regular expressions to restrict the accepted parameters, for examples:
Route::get('user/{id}', function($id)
{
return User:find($id);
})
->where('id', '[0-9]+');
If you have ever experienced the pain of having to changing URLs in an application, you will appreciate the ability to name routes. This basically means that you can refer to routes in your application as names that you define, rather than the explicit URL. So if you need to change the URL, you only have to do it once and the rest of your code is none the wiser.
To set a name for your route, simply pass the second argument as an array with an “as” key:
Route::get("user/profile", [
"as" => "profile",
function () {
//
},
]);
Now whenever you refer to this URL in your application, you can use the name that you set:
$url = URL::route("profile");
$redirect = Redirect::route("profile");
But if you want to change the URL of the route at some point in the future, you can simply update the route and none of your code will break.
Last week I talked about using the CSRF filter in your forms. Often you will want to apply a filter to many routes. Instead of attaching the filter to each individual route, you can assign it to many routes at the same time as a group:
Route::group(["before" => "csrf"], function () {
Route::post("/user", function () {});
Route::post("post", function () {});
});
In this example I’m protecting many routes against CSRF attacks (What is a CSRF attack?) using a filter and a group.
If you want to prefix a group of URLs, this can easily be achieved by passing a prefix array. For example, if you wanted to namespace your API using version numbers, you could do something like this:
Route::group(["prefix" => "api/v1"], function () {
// Api V1 Controllers
});
Now in the future if you wanted to roll out Version 2 of your API which would break for users currently using Version 1, you could simply create a new set of routes that direct traffic to your Version 2 API without breaking Version 1:
Route::group(["prefix" => "api/v2"], function () {
// Api V2 Controllers
});
As I mentioned at the top of this post, unless your project is very small, you will usually always want to route to a Controller so you can separate the logic from the routes.
To route to a Controller, simply pass the name of the Controller and the associated method as the second parameter instead of the closure:
Route::get("user/{id}", "UserController@showProfile");
If you want to namespace your Controllers, you can simply pass the fully qualified name. This is useful for versioning your API:
Route::get("user/{id}", 'api\v1\UserController@showProfile');
And you can also define route names by passing a third argument:
Route::get("user/profile", [
"uses" => "UserController@showProfile",
"as" => "profile",
]);
If you want to use filters you can define it in much the same way as above:
Route::get("user/profile", [
"before" => "auth",
"uses" => "UserController@showProfile",
]);
However, personally, I like to deal with this in the constructor method of the Controller:
class UserController extends BaseController
{
/**
* Constructor
*/
public function __construct()
{
$this->beforeFilter("auth");
}
}
I won’t go into too much detail on Filters as I will save that for a dedicated post.
Routing in Laravel 4 is a breeze and allows you to quickly and easily set up all kinds of URLs for your application.
Routing is a major component of an application, but is something that hopefully you should never have to think about. Believe me, building a router to handle all of these different circumstances and abilities is no easy thing. Fortunately Laravel has this fantastic inbuilt router that makes the whole process incredibly easy.
This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.