devdev / in the loop
PHP PHP

Guida a Laravel 10
Middleware

Uno dei concetti basilari di Laravel, oltre alle Routes, sono i Middleware. Come suggerisce il loro nome, i Middleware sono strumenti che si frappongono tra la richiesta HTTP e la risposta, quindi tra Request e Response.

L’esempio più classico è quello dell’autenticazione dell’utente. In sostanza, cioè che si frappone alla visualizzazione di una data pagina che deve essere protetta da autenticazione è appunto un qualche tipo di Middleware che controlla se l’utente (nella sessione attuale) è correttamente autenticato. Se non lo fosse, il middleware stesso farebbe un redirect verso la pagina di login (o di errore). Se invece l’utente fosse correttamente loggato, l’esecuzione continuerebbe normalmente.

Oltre a quelli che troviamo inclusi nello scaffolding originale di Laravel, potremo creare ogni tipo di Middleware che possa servire al nostro progetto. Un esempio di middleware può essere quello che effettua il logging di alcune richieste o che redireziona l’utente in base al suo status, alla sua località, etc.

Tutti i middleware sono posizionati nella directory app/Http/Middleware della struttura (scaffolding) del progetto Laravel.

 

Creazione di un Middleware

Pre creare un Middleware, il modo più semplice è affidarsi al comando Artisan. Ce n’è uno apposta:

php artisan make:middleware RichiestaValida

Questo comando creerà un nuovo file con il nome RichiestaValida.php in app/Http/Middleware. Quello che faremo è controllare se la richiesta HTTP in ingresso contiene un dato parametro POST e se questo è uguale ad una nostra stringa di controllo. Se lo è, l’esecuzione deve procedere normalmente; se invece non lo fosse l’utente verrebbe reindirizzato verso una pagina specifica. Andiamo ad editare il file appena creato dal comando Artisan:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class RichiestaValida
{
    /**
    * Handle an incoming request.
    *
    * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
    */
    public function handle(Request $request, Closure $next): Response
    {
        if ($request->input('token') == 'abc123') {
            return $next($request);
        } else {
            return redirect('errore');
        }
    }
}

Troveremo che il metodo handle() è già presente, perché è lui l’entry point standard del nostro Middleware. Di default, come possiamo notare, ha già due parametri: rispettivamente uno per la richiesta Request e uno di tipo Closure (quindi una funzione callable, chiamabile), che chiariremo in seguito.

All’interno del metodo controlliamo che il campo POST token sia uguale ad una nostra stringa di controllo. Se non lo fosse, eseguiamo il redirect verso la Route errore.

Da notare subito che nel caso positivo, quando vogliamo cioè che l’esecuzione continui, dobbiamo chiamare necessariamente $next passandogli la richiesta. Questo è fondamentale e serve per proseguire con la “catena delle richieste” perché i middleware possono essere sovrapposti, formando una serie di livelli. Questo meccanismo quindi assicura che la richiesta continui verso il prossimo middleware.

Registriamo il middleware

Abbiamo due scelte per mettere in campo il nostro middleware: eseguirlo ad ogni richiesta HTTP della nostra applicazione, oppure utilizzarlo sulle Route.

Se ci interessa creare un middleware globale, è necessario aggiungere il nome della classe nella proprietà $middleware nella classe presente in app/Http/Kernel.php.

Se invece volessimo assegnare il middleware ad una route, che è il caso sicuramente più diffuso, proseguiamo.

Assegnare il middleware ad una route

Abbiamo già visto a fondo le Route in Laravel nei capitoli precedenti, quindi possiamo riutilizzare uno degli esempi che abbiamo già analizzato. Possiamo quindi pensare di aggiungere un middleware di controllo alla route che si occupa di visualizzare gli articoli di un blog, per aggiungere una funzionalità simil paywall, quindi per permettere che alcuni articoli possano essere letti soltanto dagli abbonati, come fa il New York Times.

use App\Models\Articolo;

Route::get('/articolo/{articolo}', function (Articolo $articolo) {
    return $articolo->titolo;
})->middleware(PayWall::class);

Tramite il metodo middleware() stiamo dicendo a Laravel che le richieste verso /articolo/* vanno “passate” (o sarebbe meglio dire filtrate) dal middleware PayWall. Se volessimo aggiungere più di un middleware, possiamo indicare un’array in questo modo:

use App\Models\Articolo;

Route::get('/articolo/{articolo}', function (Articolo $articolo) {
    return $articolo->titolo;
})->middleware([Autenticazione::class, PayWall::class]);

Per semplificarci un po’ la vita, potremmo creare un alias per i nostri middleware e renderli disponibili ovunque facilmente all’interno del nostro progetto. Per farlo, possiamo aggiungere un nuovo elemento a $middlewareAliases presente nel file app/Http/Kernel.php e dargli appunto un nome più breve (e memorabile):

// app/Http/Kernel.php

protected $middlewareAliases = [
    'paywall' => \App\Http\Middleware\PayWall::class,
    'autenticazione' => \App\Http\Middleware\Autenticazione::class,
    // .. altri già presenti
];

Una volta salvato, la nostra route precedente diventerebbe così:

use App\Models\Articolo;

Route::get('/articolo/{articolo}', function (Articolo $articolo) {
    return $articolo->titolo;
})->middleware('paywall');

 

 

Gruppi di middleware

In uno degli esempi precedenti abbiamo utilizzato due middleware su una route, quello Autenticazione e quello PayWall, per ipotizzare appunto che la visualizzazione di /articolo/* fosse vincolata a questi due, rispettivamente per l’autenticazione e per controllare che l’utente abbia un abbonamento attivo. Potremmo pensare di raggrupparli in modo da farli lavorare sempre assieme e richiamarli con un solo alias. Per farlo, possiamo aggiungere un elemento a $middlewareGroups, presente nello stesso file che abbiamo visto prima, app/Http/Kernel.php. Qui troveremo già i gruppi web e api che vengono applicati alle route omonime.

// app/Http/Kernel.php

/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
    'web' => [
        // ...
    ],

    'api' => [
        // ...
    ],
    'utente_pagante' => [
        \App\Http\Middleware\Autenticazione::class,
        \App\Http\Middleware\PayWall::class,
    ]
];

Assegnarlo ad una route è analogo a quanto visto prima: basterà indicare il nome del gruppo.

use App\Models\Articolo;

Route::get('/articolo/{articolo}', function (Articolo $articolo) {
    return $articolo->titolo;
})->middleware(['utente_pagante']);

Per assegnare un middleware (o un gruppo di middleware) ad un gruppo di route:

use App\Models\Articolo;

Route::middleware(['utente_pagante'])->group(function() {
    // ...
});

Middleware e parametri

Negli esempi precedenti abbiamo visto che il metodo handle() di un middleware ha inizialmente 2 parametri, uno con la Request “in ingresso” e l’altro che in sostanza è la chiamata al prossimo “strato” di middleware. I middleware possono in realtà anche ricevere parametri aggiuntivi. Sempre continuando sul nostro esempio del Paywall, potremmo passare il parametro $tipoabbonamento per capire appunto che tipo di sottoscrizione ha l’utente. Per farlo creiamo il middleware UtenteHaAbbonamento e, analogamente a prima, lavoriamo sul metodo handle():

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class UtenteHaAbbonamento
{
    /**
    * Handle an incoming request.
    *
    * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
    */
    public function handle(Request $request, Closure $next, string $tipoabbonamento): Response
    {
        if (Abbonamento::where('user', 1)->tipo === $tipoabbonamento) {
            return $next($request);
        } else {
            // errore
        }
    }
}

Passiamo questo parametro aggiuntivo in una route indicando il valore del parametro preceduto da :

Route::get('/articolo/{articolo}', function (Articolo $articolo) {
    return $articolo->titolo;
})->middleware('UtenteHaAbbonamento:gold');

 

Capitolo successivo → About
Precedente ← Routes e Model binding
if (weekend) {
    relax();
}
la nostra newsletter, ogni tanto.