This title was written for Laravel version 4. If you're looking for material on the latest version of Laravel, then please check out Code Smart.
I recall a couple of years back when Jesse O'brien was planning a private event where he and his buddies could watch the local hockey team play their latest match against the Laravel Pandas.
Now we all know that the mighty Laravel Pandas could never be beaten by the London Knights, but Jesse refused to listen. Insisting that this could be the start of the road to glory for the Knights.
The event was planned to take place at the Hoser Hut in central London. A friendly welcoming place for anyone born in 'very-north' America. (Maple syrup land.)
Unfortunately the Hoser Hut had a reputation for being not so welcoming to those who visit from over the border. It was a known fact that Americans were thrown out of the windows of the Hoser Hut on a regular basis. It was for that reason that Jesse decided he needed some kind of door filter to keep the nasty Americans out. Of course the good ol' british chap Dayle Rees was always welcome at the Hoser Hut. He was welcome anywhere.
Jesse employed a bouncer to stand at the front of the Hoser Hut and ask to see ID to confirm whether the guest visiting the hut was Canadian or not.
You see, what Jesse did was implement a filter. Those passing the requirements of the filter would be granted entrance to the warm and cozy Hoser Hut to watch the Laravel Pandas destroy the London Knights. However Americans attempting to enter the bar would not meet the criteria of the filter, and would be shown the shiny side of a boot.
Let's leave Jesse to his game and see how we can use filters to protect our application routes.
Basic Filters
Filters are certain sets of rules or actions that can be applied to a route. They can be performed before or after a route’s logic is executed, however, you will find before filters to be more useful. Using before filters we can alter the flow of the application if a certain set of rules or criteria are not met. It’s a great way of protecting our routes.
As always, an example speaks a thousand words. Let’s have a look at a filter, but first we need something else. Let’s see:
<!-- app/views/birthday.php -->
<h1>Happy Birthday!</h1>
<p>Happy birthday to Dayle, hurray!</p>
Great! Now that we have a happy birthday view, we can create our first filter. Here we go:
<?php
// app/filters.php
Route::filter('birthday', function()
{
if (date('d/m/y') == '12/12/84') {
return View::make('birthday');
}
});
Here we have our first filter. Laravel has provided a file at app/filters.php
as a generic location for our filters, but we can actually put them wherever we like.
We use the Route::filter()
method to create a new filter. The first parameter is a friendly name that we will later use to assign the filter to a route. In this example I have named the filter ‘birthday’. The second parameter to the route is a callback, which in the example is a Closure.
The callback is a function that is called when the filter is executed. If it returns a response type object, just like those that we use within our route logic, then that response will be returned and will be served instead of the result of the route logic. If no response is returned from the filter callback, then the routes logic will continue as normal.
This gives us a great deal of power, so go ahead and practice your evil mastermind laugh. Seriously, this is important business.
Muahahahah!
Well, I suppose that will have to do. You see we can either alter the flow of the application, or perform an action and allow the route logic to continue its execution. For example, we might want to only show a certain type of content on our website, to a certain type of user. This would mean returning a redirect response to another page. Alternatively, we could write a log each time the filter is executed, to see which pages have been visited. Perhaps I am getting ahead of myself, let’s have another look at our example filter.
<?php
// app/filters.php
Route::filter('birthday', function()
{
if (date('d/m') == '12/12') {
return View::make('birthday');
}
});
Looking closely at our closure, we can see that we have a condition, and a response. In our filter, if the current date is equal to ’12/12/84’, which is of course the date that the most important person in the universe was born, then the closure will return the response. If the response is returned from the Closure, then we will be redirected to the happy birthday view. Otherwise our route logic will continue as normal.
Of course, before the filter will become useful we need to attach it to a route. However, before we can do that we need to change the route’s structure a little. Do you remember how I told you that routing methods accept a closure as the second parameter? Well I told a little white lie again. Sorry.
You see the route methods can also accept an array as the second parameter. We can use this array to assign additional parameters to the route. Let’s have a look at how a route looks with an array as a second parameter.
<?php
// app/routes.php
Route::get('/', array(function()
{
return View::make('hello');
}));
You see, it’s pretty similar. What we have done is shifted the Closure into the array. It functions just as it did before. In fact, as long as we keep the closure in the array, we can include other values. That’s how we are going to attach our filter. Let’s start by taking a look at the ‘before’ filter option.
<?php
// app/routes.php
Route::get('/', array(
'before' => 'birthday',
function()
{
return View::make('hello');
}
));
As you can see, we have created another option within our array. The index ‘before’ tells the framework that we want to run our ‘birthday’ filter before the routes logic is executed. The value ‘birthday’ matches up with the nickname that we gave to our filter.
Let’s go ahead and execute our route by visiting /
. Now, assuming today isn’t the 12th of December then you will see the Laravel welcome page. This is because the filter conditional logic failed, and no response was returned.
Right, so let’s wait until the 12th of December so that we can see what happens when the filters condition passes and the response is returned.
Just kidding, let’s change the filter to force it to pass. We can change the condition to the boolean value true
.
<?php
// app/filters.php
Route::filter('birthday', function()
{
if (true) {
return View::make('birthday');
}
});
There we go, now let’s visit /
to see if anything has changed. Hurray, it’s my birthday! Let’s all sing happy birthday to me. Actually, let’s just wait until December. So we can see that the birthday filters logic has succeeded, and the happy birthday view has been returned.
We can attach a filter using the ‘after’ option of a route array, this way the filter will be executed after your route logic. Here’s an example:
<?php
// app/routes.php
Route::get('/', array(
'after' => 'birthday',
function()
{
return View::make('hello');
}
));
You need to remember, however, that the after filter cannot replace the response. Thus, our birthday filter is a little pointless when using ‘after’. You could however perform some logging, or a cleanup operation. Just remember that it’s there if you need it!
Multiple Filters
One other thing you should know is that you can apply as many filters as you like to a route. Let’s have a look at some examples of this in action. First, let’s attach multiple before filters:
<?php
// app/routes.php
Route::get('/', array(
'before' => 'birthday|christmas',
function()
{
return View::make('hello');
}
));
Here we have attached both the ‘birthday’ and ‘christmas’ before filters to the route. I’ll let your imagination decide what the ‘christmas’ filter does, but make sure it’s something magical.
The pipe |
character can be used to separate a list of filters. They will be executed from left to right, and the first that returns a response will end the request, and that response will be delivered as the result.
If you like, you can use an array instead to provide your multiple filters. this might strike you as being more ‘phpish’.
<?php
// app/routes.php
Route::get('/', array(
'before' => array('birthday', 'christmas'),
function()
{
return View::make('hello');
}
));
Use whichever suits the way you code, personally I like the arrays. If you want, you can also assign a ‘before’ and ‘after’ filter at the same time, like this:
<?php
// app/routes.php
Route::get('/', array(
'before' => 'birthday',
'after' => 'christmas',
function()
{
return View::make('hello');
}
));
Naturally, the before filter will run first, then the route logic, and finally the after filter.
So, you think you’re done with filters? Don’t get carried away!
Filter Parameters
Just like PHP functions, filters can accept parameters. This is a great way to avoid repetition, and allow for increased flexibility. Let’s lead with an example, as always.
<?php
// app/filters.php
// before
Route::filter('test', function($route, $request)
{
});
// after
Route::filter('test', function($route, $request, $response)
{
});
Wait, why are there two filters?
Well spotted! Well, actually they are the same filter, but still, your question is valid. You see, Laravel provides a different set of parameters to ‘before’ and ‘after’ filters. You will notice that both filters receive $route
and $request
variables. You can actually call them whatever you like, but I named them this way for a reason.
If you were to var_dump()
the first parameter, you will see that it is an instance of Illuminate\Routing\Route
. You will remember that ‘Illuminate’ is the codename used for Laravel 4 components. The ‘Route’ class represents a route used by the routing layer. This instance represents the current route that is being executed. Clever right? The ‘Route’ instance is gigantic, go ahead and var_dump()
it if you don’t believe this shifty welshman. You could interrogate it for the detailed information contained within, or even alter some of its values to manipulate the framework. However, this is an advanced topic, and outside the scope of this chapter, so let’s look at the next parameter instead.
As you might have guessed, the next parameter is an instance of the current request object. The Illuminate\Http\Request
instance represents the state of the request being sent to your web server. It contains information about the URL, and data passed with the request, along with a great wealth of additional information.
The after filter receives an additional parameter, an instance of response object that is returned from the route the filter is acting on. This instance is due to be served as the response of the current request.
Right, those parameters Laravel gave us might be useful to advanced users of the framework, but wouldn’t it be great if we could provide our own parameters to our route filters? Let’s take a look at how we can do that.
First we need to add a placeholder variable to our filter Closure, it should come after the ones that laravel provides, like this:
<?php
// app/filters.php
Route::filter('birthday', function($route, $request, $date)
{
if (date('d/m') == $date) {
return View::make('birthday');
}
});
Our birthday filter has been altered to accept a $date
parameter. If the current date matches the date provided then the birthday filter is executed.
Now all we need to know is how to provide the parameters to our route filter. Let’s take a look.
<?php
// app/routes.php
Route::get('/', array(
'before' => 'birthday:12/12',
function()
{
return View::make('hello');
}
));
The parameter we pass to our filter comes after the colon :
character when we assign it to the route. Go ahead and test it, change the date to the current day and watch the filter fire.
If we want to provide additional parameters, then we need to provide extra placeholder variables with the Closure. It will look something like this.
<?php
// app/filters.php
Route::filter('birthday', function($route, $request, $first, $second, $third)
{
return "{$first} - {$second} - {$third}";
});
We can accept as many parameters as we like. To provide multiple parameters we must first add a colon :
between the filter name and its parameters. The parameters themselves must be separated by a comma ,
. Here’s an example:
<?php
// app/routes.php
Route::get('/', array(
'before' => 'birthday:foo,bar,baz',
function()
{
return View::make('hello');
}
));
The values ‘foo’, ‘bar’, and ‘baz’ will be passed to the placeholders we added to the filter. It’s worth noting that just like functions, filter parameters can be assigned default values, making them optional. Here’s an example:
<?php
// app/filter.php
Route::filter('example', function($route, $request, $optional = 'Yep!')
{
return $optional;
});
Provide the optional parameter or don’t. It’s up to you, it’s your framework!
Feel free to use as many parameters as you like to make your filter more efficient. Take advantage of this great feature.
Filter Classes
Closures are great. They are really convenient, and work great in my examples. However, they are strapped to the logic that we are writing. We can’t instantiate them, this makes them difficult to test.
For that reason, any Laravel feature that requires a Closure will also have an alternative. A PHP Class. Let’s have a look at how we can use a class to represent our filters.
Before we make the class, we need somewhere to put it. Let’s create a new folder in /app
called filters
, and update our composer.json
classmap to include the new folder.
"autoload": {
"classmap": [
"app/commands",
"app/controllers",
"app/models",
"app/filters",
"app/database/migrations",
"app/database/seeds",
"app/tests/TestCase.php"
]
}
Now let’s create a new class for our birthday filter. Here we go:
<?php
// app/filters/Birthday.php
class BirthdayFilter
{
public function filter($route, $request, $date)
{
if (date('d/m') == $date) {
return View::make('birthday');
}
}
}
I have called my class ‘BirthdayFilter’, you don’t need the ‘Filter’ suffix, but I like to do it anyway, it’s up to you. What you do need however, is the filter()
method. It works just like the Closure. In fact, because it works just like the Closure I don’t need to explain it again. Instead, let’s take a look at how we can hook up a filter to a route.
First we need to create a filter alias, once again we will use the Route::filter()
method. However, this time we will pass a string instead of a closure as the second parameter. Like this:
<?php
// app/routes.php
Route::filter('birthday', 'BirthdayFilter');
The second parameter for the method is a string that identifies the filter class to use. If the filter class is located within a namespace, go ahead and supply the namespace too.
Now that the filter alias has been created, we can add it to the route just as we did before.
<?php
// app/routes.php
Route::get('/', array(
'before' => 'birthday',
function()
{
return View::make('hello');
}
));
Remember that you will need to run composer dump-autoload
before Composer, and Laravel, will be able to find our filter class.
If you intend to fully test your code, then writing filters as classes is the best way to go about your business. We’ll discover more about testing in a later chapter.
Global Filters
If you take a look inside /app/filters.php
you will notice two strange looking filters. These are the global filters and are executed before, and after, every request to your application.
<?php
// app/filters.php
App::before(function($request)
{
//
});
App::after(function($request, $response)
{
//
});
They work exactly like normal filters, except that they apply to all routes by default. This means there is no need to add them to the before
and after
array indexes of our routes.
Default Filters
In the app/filters.php
there are some filters that have already been created for you. Let’s have a look at the first three.
<?php
// app/filters.php
Route::filter('auth', function()
{
if (Auth::guest()) return Redirect::guest('login');
});
Route::filter('auth.basic', function()
{
return Auth::basic();
});
Route::filter('guest', function()
{
if (Auth::check()) return Redirect::to('/');
});
All of these filters relate to the authentication layer of Laravel. They can be used to restrict route access to users who are, or are not, logged in to the web application at present.
In a later chapter we will be taking a closer look at the authentication layer, and the content of these filters will make more sense. For now, just know that they are there waiting for you!
The fourth filter is the cross site request forgery filter, and looks like this:
<?php
// app/filters.php
Route::filter('csrf', function()
{
if (Session::token() != Input::get('_token'))
{
throw new Illuminate\Session\TokenMismatchException;
}
});
You can attach it to your routes to protect requests being posted from an origin other than your own application. This is a very useful security measure that is used primarily to protect routes that are the target of forms, or data submission.
Feel free to take advantage of the filters Laravel has provided, they have been put there to save you time.
Pattern Filters
Don’t want to attach the filter manually to all your routes? No I can’t blame you. Tired fingers happen, I’m writing a book, I know this. Let’s try to find a way to save your poor little phalanges some effort. Here’s a pattern filter.
The pattern filter will allow you to match a before filter to a number of routes by supplying a routing pattern with a wildcard. Let’s see this in action.
<?php
// app/routes.php
Route::when('profile/*', 'birthday');
The Route::when()
method above, will run the ‘birthday’ filter on all route URIs that start with ‘profile/‘. The star within the first parameter will act as a wildcard. This is a great way of attaching a before filter to a number of different routes at once.
My books are available online for free to encourage learning. However, if you'd like for me to keep writing, then please consider buying a digital copy over at Leanpub.com.
It's available in PDF, ePub, and Kindle format, and contains a bunch of extras that you won't find on the site. I have a full-time job, and I write my books in my spare time. Please consider buying a copy so that I can continue to write new books from the comfort of my sofa!