Middleware for Laravel 5

Hi, I'm Steven Maguire

  • I've been building software since 2004.
  • Contribute to open source.
  • Author courses for Pluralsight.com.
  • Build product development teams in Chicago.
  • Tweet from @stevenmaguire.

The Plan

  • Middleware as a Concept
  • PSR 7, Middleware, and You Caring
  • Laravel and Middleware
  • Demo and Code sample
    (Content Security Policy Middleware)

Middleware as a Concept

Definition

mid·dle·ware, noun

general term for software that serves to "glue together" separate, often complex and already existing, programs 

bit.ly/middleware-definition

  1. receive a thing.
  2. change something
    about the thing.
  3. send the thing along.

Found in books

bit.ly/middleware-in-books

Found in the wild

Task Automation

  1. receive a task.
  2. change something
    internal about the taskor write to the file system.
  3. send the task along to next pipe.

Message Queues

  1. receive a message.
  2. change something
    about the message, or conduct some other business with message information.
  3. send the message along to next worker.

Halloween

  1. receive a trick-or-treater.
  2. add candy to trick-or-treater bag.
  3. send the trick-or-treater to next house.

Easy, eh?

Let's look at something familiar, yet more complicated.

Web Request

Web Request

  1. receive a request.
  2. change something about the request.
  3. send request to next middleware or application.
  1. create response.
  1. receive response.
  2. change something about the response.
  3. send the response to next middleware.

Inbound middleware

Application

Outbound middleware

It's not scary!

It's becoming quite common.

Frameworks with Middleware Support

  • Django
  • Drupal
  • Enterprise JavaBeans
  • Laravel
  • Node JS
    (gulp, grunt, and many more)
  • Ruby on Rails (Rack)
  • Silex
  • Slim
  • Stack PHP
  • Symfony
  • Yii (extension)
  • Zend Framework 3

Let's talk about PHP

PSR 7, Middleware, and You Caring

A Brief History of
PSR 7

(very)

^

Web Request

Web Request

Pre-PSR 7 PHP

Web Request

Pre-PSR 7 PHP

  • Request information stored in predefined variables
    • $_SERVER, $_COOKIE, $_FILES, $_GET, $_POST, etc.
  • Each framework maintain unique Request and Response objects, different APIs
    • Framework specific adapters required
  • Middleware responsible for "transforming" incoming data
    • More code bloat and "not invented here" practices

Whatever shall we do?

Fix it!

:cough: with standards :cough:

Who will fix it?

PHP Framework Interop Group

php-fig.org

It's kind of like the United Nations of PHP

How will we fix it?

The Solution

  • We like OOP
  • We like interfaces
  • We like package
    management

Let's design widely accepted interfaces for HTTP Requests and HTTP Responses and use them in our projects!

Web Request

Post-PSR 7 PHP

  • Request information still stored in predefined variables
    • $_SERVER, $_COOKIE, $_FILES, $_GET, $_POST, etc.
  • Each framework maintain unique Request and Response objects, same APIs
    • Framework specific adapters no longer required
  • Middleware responsible for "manipulating" incoming data
    • Less code bloat

SAME

API

!!!

Web Request

Your Middleware

<?php namespace App\Http\Middleware;

use Psr\Http\Message\RequestInterface;

class MyMiddleware
{
    /**
     * Handle request.
     * @param  Psr\Http\Message\RequestInterface $request
     * @return Psr\Http\Message\RequestInterface 
     */  
    public function doThatSweetMagic(RequestInterface $request)
    {
        // Do something awesome to the request        

        return $request;
    }
}

Study PSR 7

  • php-fig.org/psr/psr-7
  • github.com/php-fig/http-message
  • packagist.org/packages/psr/http-message

The Community is Already Adopting Support

Laravel 5 and Middleware!

PSR 7 Middleware Support :(

That's ok!

@todo

  • Define middleware
  • Describe middleware types
  • Register middleware
  • Make middleware work

"HTTP middleware provide a convenient mechanism for filtering HTTP requests entering your application."

laravel.com/docs/5.1/middleware

Laravel Middleware is

Any class that implements a public method named handle that accepts a Request object, a Closure object, and returns a Response object

The handle method

/**
 * @param  Request  $request
 * @param  Closure  $next
 *
 * @return Response
 */
public function handle($request, Closure $next);

Laravel Middleware is

"chained"

The hard part is done for you.

The "next" closure

<?php

namespace App\Http\Middleware;

use Closure;

class MyVeryOwnMiddleware
{
    public function handle($request, Closure $next)
    {
        // Perform action

        return $next($request);
    }
}

@todo

  • Define middleware
  • Describe middleware types
  • Register middleware
  • Make middleware work
  • Before
  • After
  • Terminable

Laravel Middleware is

The Before

<?php

namespace App\Http\Middleware;

use Closure;

class BeforeMiddleware
{
    public function handle($request, Closure $next)
    {
        // Perform action

        return $next($request);
    }
}

The After

<?php

namespace App\Http\Middleware;

use Closure;

class AfterMiddleware
{
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        // Perform action

        return $response;
    }
}

Terminable Middleware is

Any Middleware class that implements a public method named terminate that accepts a Request object, a Response object, and has no return

The Terminable

<?php

namespace Illuminate\Session\Middleware;

use Closure;

class StartSession
{
    public function handle($request, Closure $next)
    {
        return $next($request);
    }

    public function terminate($request, $response)
    {
        // Store the session data...
    }
}

Web Request

@todo

  • Define middleware
  • Describe middleware types
  • Register middleware
  • Make middleware work
  • Global
    (Always on)
  • Route
    (Routes/Controllers)

Register Middleware as

Salute the kernel

// app/Http/Kernel.php

<?php 

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * @var array
     */
    protected $middleware = [];

    /**
     * The application's route middleware.
     *
     * @var array
     */
    protected $routeMiddleware = [];
}

Global middleware

// Within App\Http\Kernel Class...

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

Route middleware

// Within App\Http\Kernel Class...

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

Requires unique key, used as reference in other parts of application

@todo

  • Define middleware
  • Describe middleware types
  • Register middleware
  • Make middleware work

Global Middleware

will always execute and in the order registered within the Kernel

Global middleware execution

// Within app/Http/Kernel.php

protected $middleware = [
    //
    \App\Http\Middleware\MyMiddleware1::class,
    \App\Http\Middleware\MyMiddleware2::class,
    \App\Http\Middleware\MyMiddleware3::class,
    //
];

Multiple middleware executed globally

// MyMiddleware1 executed
// MyMiddleware2 executed
// MyMiddleware3 executed

Global middleware execution

// Within app/Http/Kernel.php

protected $middleware = [
    //
    \App\Http\Middleware\MyMiddleware2::class,
    \App\Http\Middleware\MyMiddleware3::class,
    \App\Http\Middleware\MyMiddleware1::class,
    //
];

Multiple middleware executed globally

// MyMiddleware2 executed
// MyMiddleware3 executed
// MyMiddleware1 executed

Route Middleware

can be assigned to routes, route groups, and controllers, which will control execution order

Route middleware execution

// Within app/Http/Kernel.php

protected $routeMiddleware = [
    //
    'mine' => \App\Http\Middleware\MyMiddleware::class,
    //
];

// Within app/Http/routes.php

Route::get('profile', ['middleware' => 'mine', function () {
    //
}]);

Single middleware executed by route

// MyMiddleware executed

Route middleware execution

// Within app/Http/Kernel.php

protected $routeMiddleware = [
    //
    'mine.one' => \App\Http\Middleware\MyMiddleware1::class,
    'mine.two' => \App\Http\Middleware\MyMiddleware2::class,
    'mine.three' => \App\Http\Middleware\MyMiddleware3::class,
    //
];

// Within app/Http/routes.php

Route::get('profile', ['middleware' => [
    'mine.one','mine.two','mine.three'], function () {
    //
}]);

Multiple middleware executed by route

// MyMiddleware1 executed
// MyMiddleware2 executed
// MyMiddleware3 executed

Route middleware execution

// Within app/Http/Kernel.php

protected $routeMiddleware = [
    //
    'mine.one' => \App\Http\Middleware\MyMiddleware1::class,
    'mine.two' => \App\Http\Middleware\MyMiddleware2::class,
    'mine.three' => \App\Http\Middleware\MyMiddleware3::class,
    //
];

// Within app/Http/routes.php

Route::get('profile', ['middleware' => [
    'mine.two','mine.three','mine.one'], function () {
    //
}]);

Multiple middleware executed by route

// MyMiddleware2 executed
// MyMiddleware3 executed
// MyMiddleware1 executed

Route middleware execution

// Within app/Http/Kernel.php

protected $routeMiddleware = [
    //
    'mine.one' => \App\Http\Middleware\MyMiddleware1::class,
    'mine.two' => \App\Http\Middleware\MyMiddleware2::class,
    'mine.three' => \App\Http\Middleware\MyMiddleware3::class,
    //
];

// Within app/Http/routes.php

Route::group(['middleware' => ['mine.two','mine.three','mine.one']], function () {
    Route::get('profile', function () {
        //
    });
});

Multiple middleware executed by route group

// MyMiddleware2 executed
// MyMiddleware3 executed
// MyMiddleware1 executed

Route middleware execution

// Within app/Http/Kernel.php

protected $routeMiddleware = [
    //
    'mine.one' => \App\Http\Middleware\MyMiddleware1::class,
    'mine.two' => \App\Http\Middleware\MyMiddleware2::class,
    'mine.three' => \App\Http\Middleware\MyMiddleware3::class,
    //
];

// Within app/Http/routes.php

Route::get('profile', 'MainController@profile');


// Within app/Http/Controllers/MainController.php

public function __construct()
{
    $this->middleware('mine.two');
    $this->middleware('mine.three');
    $this->middleware('mine.one');
}

Multiple middleware executed by controller

// MyMiddleware2 executed
// MyMiddleware3 executed
// MyMiddleware1 executed

Route middleware execution

// Within app/Http/routes.php

Route::get('hello', 'MainController@hello');
Route::get('goodbye', 'MainController@goodbye');

// Within app/Http/Controllers/MainController.php

public function __construct()
{
    $this->middleware('mine.two');
    $this->middleware('mine.three');
    $this->middleware('mine.one');
}

public function hello()
{
    return 'hello';
}

public function goodbye()
{
    return 'goodbye';
}

Multiple middleware executed by controller

// MyMiddleware2 executed
// MyMiddleware3 executed
// MyMiddleware1 executed
// MyMiddleware2 executed
// MyMiddleware3 executed
// MyMiddleware1 executed

Route middleware execution

// Within app/Http/routes.php

Route::get('hello', 'MainController@hello');
Route::get('goodbye', 'MainController@goodbye');

// Within app/Http/Controllers/MainController.php

public function __construct()
{
    $this->middleware('mine.two', ['only' => ['hello']]);
    $this->middleware('mine.three', ['except' => ['hello']]);
    $this->middleware('mine.one');
}

public function hello()
{
    return 'hello';
}

public function goodbye()
{
    return 'goodbye';
}

Multiple middleware executed by controller

// MyMiddleware2 executed
// MyMiddleware1 executed
// MyMiddleware3 executed
// MyMiddleware1 executed

Route Middleware

can be passed parameters!

Route middleware parameters

// Within app/Http/Kernel.php

protected $routeMiddleware = [
    //
    'mine' => \App\Http\Middleware\MyMiddleware::class,
    //
];

// Within app/Http/routes.php

Route::get('profile', ['middleware' => 'mine:foo', function () {
    //
}]);

// Within app/Http/Controllers/MainController.php

public function __construct()
{
    $this->middleware('mine:bar');
}

Pass single parameter to route middleware

Route middleware parameters

// Within app/Http/Kernel.php

protected $routeMiddleware = [
    //
    'mine' => \App\Http\Middleware\MyMiddleware::class,
    //
];

// Within app/Http/routes.php

Route::get('profile', ['middleware' => 'mine:foo,bar', function () {
    //
}]);

// Within app/Http/Controllers/MainController.php

public function __construct()
{
    $this->middleware('mine:bar,foo');
}

Pass multiple parameters to route middleware

Route middleware parameters

Receive middleware parameters

// Within app/Http/Middleware/BeforeMiddleware.php

public function handle($request, Closure $next, $param1, $param2)
{
    \Log::info($param1);
    \Log::info($param2);

    // Perform action

    return $next($request);
}

// Within app/Http/routes.php

Route::get('profile', ['middleware' => 'mine:foo,bar', function () { }]);
// local.INFO: foo
// local.INFO: bar 

Route middleware parameters

Middleware parameters may be required or optional

// Within app/Http/Middleware/BeforeMiddleware.php

public function handle($request, Closure $next, $param1, $param2)
{
    \Log::info($param1);
    \Log::info($param2);

    // Perform action

    return $next($request);
}

// Within app/Http/routes.php

Route::get('profile', ['middleware' => 'mine:foo', function () { }]);
// ErrorException in BeforeMiddleware.php line 16:
// Missing argument 4 for App\Http\Middleware\BeforeMiddleware::handle()

Route middleware parameters

Middleware parameters may be required or optional

// Within app/Http/Middleware/BeforeMiddleware.php

public function handle($request, Closure $next, $param1, $param2 = null)
{
    \Log::info($param1);
    \Log::info($param2);

    // Perform action

    return $next($request);
}

// Within app/Http/routes.php

Route::get('profile', ['middleware' => 'mine:foo', function () { }]);
// local.INFO: foo
// local.INFO: 

@todo

  • Define middleware
  • Describe middleware types
  • Register middleware
  • Make middleware work

Demo

github.com/stevenmaguire/middle-earth-ware

(bit.ly/laravel-middleware-demo)

Questions?

Thank you!

@stevenmaguire
on twitter

stevenmaguire@gmail.com
on electronic mail

stevenmaguire
on github

Middleware for Laravel 5

By Steven Maguire

Middleware for Laravel 5

Deck originally created for a presentation to a gathering of the Chicago Laravel Meetup group - bit.ly/laravel-middleware

  • 5,333