Laravel 5: Middleware

Sinds begin februari 2015 is een nieuwe versie van het populaire Laravel framework uitgekomen; Laravel 5. Laravel 5 komt weer met een heleboel mooie nieuwe features, waarvan ik er één wil behandelen in deze blog post; Middleware. Tijdens het schrijven van dit blog ga ik ervan uit dat er een algemene kennis is van MVC en PHP.

Wat is Middleware?

Middleware is wat de naam al aangeeft, iets wat in het `midden` wordt uitgevoerd. Middleware wordt uitgevoerd voordat een bepaalde controller of actie wordt opgestart. Dit stelt je dus in staat bepaalde acties uit te voeren, bijvoorbeeld controleren of een gebruiker is ingelogd, statistieken wegschrijven, enz. Ontzettend handig dus. Middleware vervangt filters in Laravel 4.

Hoe ziet Middleware eruit?

Laravel 5 komt “out of the box” met een handige commandline tool: Artisan.
Als je Artisan goed gebruikt, scheelt dit enorm veel tijd! Artisan genereert kant en klare objecten. In ons voorbeeld willen we controleren of een gebruiker zijn profiel wel volledig heeft ingevuld voordat hij een nieuw bericht mag plaatsen.

We laten Artisan een middleware object voor ons genereren met het volgende commando:
php artisan make:middleware CheckProfileCompletion

Artisan heeft voor ons een CheckProfileCompletion.php aangemaakt in App\Http\Middleware. Laten we deze eens bekijken:

namespace App\Http\Middleware;

use Closure;

class CheckProfileCompletion {

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

}

Elk Middleware object heeft een verplichte handle method, die de huidige request weer doorgeeft aan het volgende object.

class CheckProfileCompletion {
    
    protected $redirect;
     
    public function __construct(Redirector $redirector)
    {
        $this->redirect = $redirector;
    }    

	/**
	 * Handle an incoming request.
	 *
	 * @param  \Illuminate\Http\Request  $request
	 * @param  \Closure  $next
	 * @return mixed
	 */
	public function handle($request, Closure $next)
	{
        
       
            if(Auth::user()->profile->isComplete())
            {
                return $next($request);
            }          
       
                        
        return $this->redirect->route('profile.edit')->with('message', new MessageBag(['Je moet eerst je profiel aanvullen']));    
        		
	}

}

In bovenstaand voorbeeld maken we gebruik van de krachtige IOC container van Laravel, door het Redirector object te injecteren. Vervolgens controleren we in de `handle` method of ons profiel compleet is, zo niet sturen we deze terug naar de pagina om het profiel te bewerken.

Nu denk je misschien, wat als de gebruiker niet is ingelogd, krijg je dan geen foutmeldingen omdat er geen gebruiker object is? Klopt, maar ook daarvoor kunnen we gebruik maken van Middleware.

Hoe pas ik Middleware toe?

We hebben ons Middleware object, nu moeten we alleen nog zorgen dat deze ook daadwerkelijk wordt uitgevoerd.
In de App directory staat een bestand Kernel.php, hierin registeren we onze nieuw gemaakte middleware.

In dit bestand zien we 2 arrays: $middleware en $routeMiddleware.
$middleware is een lijstje met Middleware dat op elk request wordt uitgevoerd.
$routeMiddleware hierin kunnen we een naam geven aan onze Middleware, zodat we deze elders kunnen gebruiken. Kernel.php moet er in ons geval zo uitzien:

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 = [
		'Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode',
		'Illuminate\Cookie\Middleware\EncryptCookies',
		'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse',
		'Illuminate\Session\Middleware\StartSession',
		'Illuminate\View\Middleware\ShareErrorsFromSession',
		'App\Http\Middleware\VerifyCsrfToken',
	];

	/**
	 * The application's route middleware.
	 *
	 * @var array
	 */
	protected $routeMiddleware = [
		'auth' => 'App\Http\Middleware\Authenticate',
		'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth',
		'guest' => 'App\Http\Middleware\RedirectIfAuthenticated',
        // Our newly added profile completion middleware
        'check.profile.completion' => 'App\Http\Middleware\CheckProfileCompletion'
	];

}

Nu hebben we 2 keuzes om onze Middleware aan te roepen, direct in de controller of specifiek per route. Ik zelf ben erg voorstander van het aangeven in de routes file, maar dit is een stukje persoonlijke voorkeur.

Middleware in de controller

Als het goed is extend je controller de Laravel controller. Hierdoor wordt de Middleware() method beschikbaar.
In onderstaand voorbeeld checken we voor elke controller method of het profiel van onze gebruiker wel compleet is:

namespace App\Http\Controllers;

use App\Http\Requests;

class MessagesController extends Controller {

    public function __construct()
    {
        $this->middleware('check.profile.completion');
    }	
	public function create()
	{
		return view('messages.create');
	}

Indien je de Middleware niet op elke request uitgevoerd wil hebben, dan is het ook mogelijk om $this->middleware() in de betreffende method te plaatsen.

Middleware in routes

Deze methode heeft mijn persoonlijke voorkeur. Ik ben van mening dat een controller niet hoeft te weten wat er van te voren wordt uitgevoerd, en op deze manier laten we de controller buiten beschouwing.

Open App\routes.php
Stel we hebben een route om een nieuw bericht aan te maken:

Route::get('messages.create', ['uses' => 'App\Http\Controllers\MessagesController@create']);

Dan kunnen we deze uitbreiden met onze Middleware op de volgende manier:

Route::get('messages.create', ['uses' => 'App\Http\Controllers\MessagesController@create', 'middleware' => 'check.profile.completion']);

check.profile.completion is de naam die we aan onze Middleware hebben gegeven in kernel.php.

Meerdere middlewares op een route is natuurlijk ook mogelijk, deze voegen we toe als een array:

Route::get('messages.create', ['uses' => 'App\Http\Controllers\MessagesController@create', 'middleware' => ['auth', 'check.profile.completion']])

 

In bovenstaand voorbeeld kijken we eerst of de gebruiker is ingelogd, en daarna of het profiel compleet is.
Ook kan je Middleware meegeven aan een route group, voordeel is hier dat je het maar één keer hoeft mee te geven:


Route::group(['middleware' => ['auth', 'check.profile.completion']], function()
{
    Route::get('messages.create', ['uses' => 'App\Http\Controllers\MessagesController@create']);
});

Wij zijn fan van Laravel 5 en opties als Middleware. De volgende blogpost zal gaan over FormRequests!