Oct 10, 2014 | laravel, 5.0, laravel 5

Laravel 5.0 - Middleware (Filter-style)

Series

This is a series of posts on New Features in Laravel 5.0.

!
Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

If you've been following along with my previous blog posts about Laravel 5.0, you may have noticed that route filters were first moved to be their own directory and class structure, and then eventually they mysteriously disappeared. You may have even noticed that references to Middleware showed up in their place.

Adding custom middleware to your Laravel app has actually been around for a while. For a great introduction to middleware, and how middleware worked in Laravel 4.1, check out Chris Fidao's HTTP Middleware in Laravel 4.1.

NOTE: Filters still exist in the codebase, so you can still use them, but middleware is becoming the preferred practice and way of thinking about decorating your routes.

What is middleware?

Middleware is actually a little hard. Take a look at the graphic below, from StackPHP. If your application--your routing, your controllers, your business logic--is the green circle in the center, you can see that the user's request passes through several middleware layers, hits your app, and then passes out through more middleware layers. Any given middleware can operate before the application logic, after it, or both.

So, middleware is a series of wrappers around your application that decorate the requests and the responses in a way that isn't a part of your application logic.

StackPHP Onion (image attribution StackPHP.com)

The way this works is that middleware implements a decorator pattern: it takes the request, does something, and returns another request object to the next layer of the stack.

Laravel uses middleware by default to handle encrypting/decrypting and queueing cookies, and reading and writing sessions, but you can also use it to add any sort of layer you'd like to your request/response cycle: rate limiting, custom request parsing, and much more.

How do I write middleware?

artisan make:middleware MyMiddleware

This will generate a simple middleware file:

<?php namespace App\Http\Middleware;

use Closure;
use Illuminate\Contracts\Routing\Middleware;

class MyMiddleware implements Middleware {

    /**
    * Handle an incoming request.
    *
    * @param  \Illuminate\Http\Request  $request
    * @param  \Closure  $next
    * @return mixed
    */
    public function handle($request, Closure $next)
    {
        //
    }

}

As you can see, the foundation of any middleware is the handle method, which takes two parameters: $request, which is an Illuminate Request object, and $next, which is a Closure (anonymous function) that runs the request through the rest of the middleware stack.

Remember my absurd example of a ValidatesWhenResolved object that blocks odd request ports? Well, we're bringing it back, Middleware-style.

<?php namespace App\Http\Middleware;

use Closure;
use Illuminate\Contracts\Routing\Middleware;

class MyMiddleware implements Middleware {

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        // Test for an even vs. odd remote port
        if (($request->server->get('REMOTE_PORT') / 2) % 2 > 0)
        {
            throw new \Exception("WE DON'T LIKE ODD REMOTE PORTS");
        }

        return $next($request);
    }

}

How do I use middleware?

There are two primary ways to bind middleware in Laravel 5. Both start with App\Http\Kernel.

You'll notice that this new Kernel class has two properties: $middleware and $routeMiddleware. Both are arrays of middleware; the middlewares in $middleware run on every request and the middlewares in $routeMiddleware have to be enabled.

At the time of this writing, five middlewares run by default:

    protected $middleware = [
        'Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode',
        'Illuminate\Cookie\Middleware\EncryptCookies',
        'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse',
        'Illuminate\Session\Middleware\StartSession',
        'Illuminate\View\Middleware\ShareErrorsFromSession',
        'Illuminate\Foundation\Http\Middleware\VerifyCsrfToken',
    ];

and three are available as optional:

    protected $routeMiddleware = [
        'auth' = 'App\Http\Middleware\Authenticate',
        'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth',
        'guest' => 'App\Http\Middleware\RedirectIfAuthenticated',
    ];

As you can see, the optional routes that are available by default are the same as the filters that were optional by default, except that--importantly--CSRF protection has now been enabled by default for al routes..

Running middleware on every request

So, let's start by running our middleware on every request. Simple add it to $middleware:

    protected $middleware = [
        'App\Http\Middleware\MyMiddleware',
        'Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode',
        'Illuminate\Cookie\Middleware\EncryptCookies',
        'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse',
        'Illuminate\Session\Middleware\StartSession',
        'Illuminate\View\Middleware\ShareErrorsFromSession',
        'Illuminate\Foundation\Http\Middleware\VerifyCsrfToken',
    ];

... and now it'll run on every request.

Running middleware on specific routes

OK, now let's move our custom middleware to the optional stack, with a key:

    protected $routeMiddleware = [
        'auth' = 'App\Http\Middleware\Authenticate',
        'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth',
        'guest' => 'App\Http\Middleware\RedirectIfAuthenticated',
        'absurd' => 'App\Http\Middleware\MyMiddleware',
    ];

And now we can apply it using the $this->middleware() method on the base Controller or in routes.php.

Middleware route annotation (removed)

Note: Annotations are no longer a part of Laravel 5 core, so middleware route annotation is no longer supported without using an external package.

You can annotate a controller or a route to use specific middleware:

/**
 * @Resource("foobar/photos")
 * @Middleware("auth")
 * @Middleware("absurd", except={"update"})
 * @Middleware("csrf", only={"index"})
 */
class FoobarPhotosController
{}

You can annotate a single controller method:

/**
 * @Middleware("auth.basic")
 */
public function index() {}

$this->middleware()

Or, you can use the $this->middleware() method on any controller (or its methods) if the controller extends the base controller:

...
use Illuminate\Routing\Controller;

class AwesomeController extends Controller {

    public function __construct()
    {
        $this->middleware('csrf');
        $this->middleware('auth', ['only' => 'update'])
    }

}

Adding middleware in routes.php

You can also assign middleware to run on a route in routes.php:

// Routes.php

// Single route
$router->get("/awesome/sauce", "AwesomeController@sauce", ['middleware' => 'auth']);

// Route group
$router->group(['middleware' => 'auth'], function() {
    // lots of routes that require auth middleware
});

How do I implement before vs. after filters in middleware?

It took me a minute to follow this, but Taylor pointed out that the difference between a "before" middleware and an "after" middleware is based on whether the middleware's action happens before or after the request it's passed:

...
class BeforeMiddleware implements Middleware {

    public function handle($request, Closure $next)
    {
        // Do Stuff
        return $next($request);
    }

}
...
class AfterMiddleware implements Middleware {

    public function handle($request, Closure $next)
    {
        $response = $next($request);
        // Do stuff
        return $response;
    }

}

As you can see, the before middleware operates and then passes on the request. The after middleware, on the other hand, allows the request to be processed, and then operates on it.

01000011 01101111 01101110 01100011 01101100 01110101 01110011 01101001 01101111 01101110

If you're not familiar with it, it might take a minute to get your head wrapped around the concept of middleware. Filters are a little easier a jump from our normal thinking about controller route requests. But middleware—the concept of the stack which passes along only a request, allowing it to be decorated piece by piece--is cleaner, simpler, and more flexible.

Not only that, but middleware is just one more way of working with your request in a way that is both powerfully effective in your Laravel apps, but plays nicely elsewhere else. The Laravel 5.0 middleware syntax isn't perfectly compatible with StackPHP syntax, but if you structure your request/response stack along the organizational structure of middlewares it's a further work in the direction of separation of concerns--and modifying a Laravel Middleware to work in a separate, StackPHP-style syntax, would take minimal effort.

Questions? Comments? I'm @stauffermatt on Twitter.


Comments? I'm @stauffermatt on Twitter


Tags: laravel  •  5.0  •  laravel 5


This is part of a series of posts on New Features in Laravel 5.0:

  1. Sep 10, 2014 | laravel, 5.0, laravel 5
  2. Sep 10, 2014 | laravel, 5.0, laravel 5
  3. Sep 12, 2014 | laravel, laravel 5, 5.0
  4. Sep 20, 2014 | laravel, 5.0, laravel 5
  5. Sep 28, 2014 | laravel, laravel 5, 5.0
  6. Sep 30, 2014 | laravel, 5.0, laravel 5
  7. Oct 9, 2014 | laravel, 5.0, laravel 5
  8. Oct 10, 2014 | laravel, 5.0, laravel 5
  9. Oct 10, 2014 | laravel, 5.0, laravel 5
  10. Nov 20, 2014 | laravel, 5.0, laravel 5
  11. Jan 2, 2015 | laravel, 5.0, commands, laravel 5
  12. Jan 16, 2015 | laravel, laravel 5
  13. Jan 19, 2015 | laravel 5, laravel
  14. Jan 21, 2015 | laravel, events, 5.0, laravel 5
  15. Jan 26, 2015 | laravel, laravel 5
  16. Feb 1, 2015 | laravel, laravel 5
  17. Feb 14, 2015 | laravel 5, laravel, eloquent

Subscribe

For quick links to fresh content, and for more thoughts that don't make it to the blog.