Laravel: les middlewares
Tags: laravel, php, french
Published: 2020-05-08 12:58:04

Aujourd’hui on va voir un élément clef de tous les projets web : les middlewares parfois appelés filters. Simples à comprendre pour les développeurs un peu aguerris, ils représentent une difficulté pour les débutants.

## C’est quoi un middleware ?
Pour faire simple, ce sont des fonctions qui permettent d’enrichir ou de vérifier une requête avant qu’elle arrive à votre controller. 

Plusieurs middlewares sont fournis de base avec Laravel comme `auth` pour autoriser l'accès à la route seulement aux requêtes liées à un utilisateur connecté.

## Middlewares par défaut

Laravel inclut les middlewares suivant dans chaque nouveau projet Laravel :
- Authenticate : renvoie une 401 si la requête ne contient pas d'Authorization
- CheckForMaintenanceMode : Si l'application est en maintenance, vérifie si l'IP ou la requête est autorisée pour l'exécuter malgré la maintenance
- EncryptCookies : Encrypte les cookies
- RedirectIfAuthenticated : Redirige sur la route définie comme Home si la requête est un utilisateur connecté
- TrimStrings : Trim chaque champ du body de la requête
- TrustProxies : Liste des proxies qui envoie les requêtes à votre server
- VerifyCsrfToken : Vérifie le token Csrf fournit par la requête

Le framework lui même en contient de nombreux autres :
- AuthenticateWithBasicAuth
- Authorize
- EnsureEmailsVerified
- RequiredPassword
- AddQueuedCookiesToResponse
- ConvertEmptyStringToNull
- TransformRequest
- ValidatePostSize
- CheckResponseForModifications
- FrameGuard
- SetCacheHeadersd
- SubstituteBindings
- Throttle requests
- ThrottleRequestsWithRedis
- ValidateSignature
- AuthenticateSession
- StartSession
- ShareErrorsFromSession

## Créer votre middleware
Avec artisan, il est possible de créer votre propre middleware avec la commande :

```sh
php artisan make:middleware CheckAge
```

Pour Lumen, il est possible de créer directement le fichier dans `app/Http/Middleware`.

```‌php
<?php
namespace App\Http\Middleware;
use Closure;
class CheckAge
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($request->age <= 200) {
            return redirect('home');
        }

        return $next($request);
    }
}
```

Cette classe dispose d'une fonction `handle` qui sera appelé par le framework quand vous allez utiliser votre middleware. Le plus important est l'appel au callback `next` qui permet de continuer la requête.

Il est possible d'ajouter des paramètres à la fonction `handle` que vous pourrez définir quand vous l'appelerez.

`‌``‌
<?php

namespace App\Http\Middleware;

use Closure;

class CheckRole
{
    /**
     * Handle the incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string  $role
     * @return mixed
     */
    public function handle($request, Closure $next, $role)
    {
        if (! $request->user()->hasRole($role)) {
            // Redirect...
        }

        return $next($request);
    }

}
``‌`

Une fois votre middleware créé, il ne nous reste plus qu'à l'enregistrer pour qu'il soit reconnu par Laravel.

## Enregistrer votre middleware

En fonction de votre besoin, il existe plusieurs manières d'enregistrer votre middleware :
- pour toute votre application
- pour une seule route
- dans un groupe de middleware

### Enregistrer un middleware pour toute votre application

Pour qu'il se déclenche à chaque requête, il faut l'enregistrer dans l'attribut `$middleware` dans le fichier `app/Http/Kernel.php`.

### Enregistrer un middleware pour une seule route

La liste des middlewares utilisable pour vos routes ou groupes de route se trouve dans l'attribut `$routeMiddleware` de la classe `App\Http\Kernel`. Elle prend la forme d'un tableau associatif où la clef est une chaîne qui va servir d'identifiant et la valeur est la classe de votre middleware.

Ensuite on peut l'utiliser directement lorsqu'on enregistre nos routes.

`‌``‌
Route::get('admin/profile', "MonController@fonction")->middleware('auth');
``‌`

Il est possible d'utiliser le nom de la classe plutôt que la clé du tableau pour bénéficier de l'autocompletion.

`‌``‌
Route::get('admin/profile', "MonController@fonction")->middleware(CheckAge::class);
``‌`

Il est également possible d'enregistrer plusieurs middlewares pour une même route.

`‌``‌
Route::get('/', function () {})->middleware('first', 'second');
``‌`

Enfin, il est possible d'empêcher l'exécution d'un middleware d'un groupe pour une de ses routes.

`‌``‌
Route::middleware([CheckAge::class])->group(function () {
    Route::get('/', function () {});

    Route::get('admin/profile', function () {})->withoutMiddleware([CheckAge::class]);
});
``‌`

### Enregistrer un middleware pour un groupe de requêtes

Dans le cas de middlewares que vous allez utiliser ensemble, il est possible de créer un groupe de middlewares dans la propriété `$middlewareGroups`. Il vous suffira ensuite d'appeler uniquement ce groupe pour appliquer tous ses middlewares.

C'est ce que fait Laravel par défaut avec les groupes `web` et `api`.

`‌``‌
/**
 * The application's route middleware groups.
 *
 * @var array
 */
protected $middlewareGroups = [
    'web' => [
        \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,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],

    'api' => [
        'throttle:60,1',
        'auth:api',
    ],
];
``‌`

### Passer des arguments à votre middleware
L'ensemble des arguments se passe à votre middleware avec un `:` après l'identifiant du middleware. S'il y a plusieurs arguments, on les sépare par des `,`.

`‌``‌
Route::put('post/{id}', function ($id) {})->middleware('role:editor');
``‌`