Middleware

Introduction

I middleware forniscono un comodo meccanismo per ispezionare e filtrare le richieste HTTP che entrano nella tua applicazione. Ad esempio, Laravel include un middleware che verifica se l’utente dell’applicazione è autenticato. Se l’utente non è autenticato, il middleware reindirizzerà l’utente alla schermata di login dell’applicazione. Tuttavia, se l’utente è autenticato, il middleware consentirà alla richiesta di procedere ulteriormente nell’applicazione.

È possibile scrivere middleware aggiuntivi per eseguire una varietà di compiti oltre all’autenticazione. Ad esempio, un middleware di logging potrebbe registrare tutte le richieste in arrivo nella tua applicazione. Ci sono diversi middleware inclusi nel framework Laravel, tra cui middleware per l’autenticazione e la protezione CSRF. Tutti questi middleware si trovano nella directory app/Http/Middleware.

Defining Middleware

Per creare un nuovo middleware, utilizza il comando Artisan make:middleware:

php artisan make:middleware EnsureTokenIsValid

Questo comando creerà una nuova classe EnsureTokenIsValid nella directory app/Http/Middleware. In questo middleware, consentiremo l’accesso alla route solo se il token fornito corrisponde a un valore specificato. In caso contrario, reindirizzeremo gli utenti alla home URI.

<?php
 
namespace App\Http\Middleware;
 
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
 
class EnsureTokenIsValid
{
    /**
     * 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') !== 'my-secret-token') {
            return redirect('home');
        }
 
        return $next($request);
    }
}

Come puoi vedere, se il token fornito non corrisponde al nostro token segreto, il middleware restituirà una redirect HTTP al client; altrimenti, la richiesta verrà passata ulteriormente nell’applicazione. Per permettere al middleware di “passare”, dovresti chiamare la funzione $next con il parametro $request.

È meglio immaginare il middleware come una serie di “strati” che le richieste HTTP devono attraversare prima di raggiungere la tua applicazione. Ogni strato può esaminare la richiesta e anche rifiutarla completamente.

Tutti i middleware vengono risolti attraverso il container dei servizi, quindi puoi definire dipendenze tramite type-hint nel costruttore di un middleware.

Middleware e Risposte

Naturalmente, un middleware può svolgere attività prima o dopo di far passare la richiesta più in profondità nell’applicazione. Ad esempio, il seguente middleware eseguirebbe alcune operazioni prima che la richiesta venga gestita dall’applicazione:

<?php
 
namespace App\Http\Middleware;
 
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
 
class BeforeMiddleware
{
    public function handle(Request $request, Closure $next): Response
    {
        // Perform action
 
        return $next($request);
    }
}

Tuttavia, questo middleware svolgerebbe la sua attività dopo che la richiesta è stata gestita dall’applicazione:

<?php
 
namespace App\Http\Middleware;
 
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
 
class AfterMiddleware
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);
 
        // Perform action
 
        return $response;
    }
}

Registering Middleware

Global Middleware

Se desideri che un middleware venga eseguito durante ogni richiesta HTTP alla tua applicazione, elenca la classe del middleware nella proprietà $middleware della classe app/Http/Kernel.php.

Assigning Middleware To Routes

Se desideri assegnare middleware a specifiche rotte, puoi invocare il metodo middleware durante la definizione della rotta:

use App\Http\Middleware\Authenticate;
 
Route::get('/profile', function () {
    // ...
})->middleware(Authenticate::class);

Puoi assegnare più middleware alla rotta passando un array di nomi di middleware al metodo middleware:

Route::get('/', function () {
    // ...
})->middleware([First::class, Second::class]);

Per comodità, puoi assegnare degli alias ai middleware nel file app/Http/Kernel.php della tua applicazione. Per default, la proprietà $middlewareAliases di questa classe contiene voci per i middleware inclusi in Laravel. Puoi aggiungere i tuoi middleware a questa lista e assegnare loro un alias a tua scelta:

// Within App\Http\Kernel class...
 
protected $middlewareAliases = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
    'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
    'can' => \Illuminate\Auth\Middleware\Authorize::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];

Una volta che l’alias del middleware è stato definito nel kernel HTTP, puoi utilizzare l’alias quando assegni il middleware alle rotte:

Route::get('/profile', function () {
    // ...
})->middleware('auth');

Escluso il middleware

Quando assegni middleware a un gruppo di rotte, potresti occasionalmente voler impedire che il middleware venga applicato a una singola rotta all’interno del gruppo. Puoi farlo utilizzando il metodo withoutMiddleware:

use App\Http\Middleware\EnsureTokenIsValid;
 
Route::middleware([EnsureTokenIsValid::class])->group(function () {
    Route::get('/', function () {
        // ...
    });
 
    Route::get('/profile', function () {
        // ...
    })->withoutMiddleware([EnsureTokenIsValid::class]);
});

Puoi anche escludere un insieme di middleware da un intero gruppo di definizioni di rotte:

use App\Http\Middleware\EnsureTokenIsValid;
 
Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () {
    Route::get('/profile', function () {
        // ...
    });
});

Il metodo withoutMiddleware può rimuovere solo il middleware delle rotte e non si applica al middleware globale.

Middleware Groups

A volte potresti voler raggruppare diversi middleware sotto una singola chiave per facilitarne l’assegnazione alle rotte. Puoi farlo utilizzando la proprietà $middlewareGroups del tuo kernel HTTP.Laravel include gruppi di middleware predefiniti come “web” e “api” che contengono middleware comuni da applicare alle rotte web e API. Ricorda che questi gruppi di middleware vengono automaticamente applicati dal provider di servizi App\Providers\RouteServiceProvider del tuo’applicazione alle rotte all’interno dei rispettivi file delle rotte “web” e “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' => [
        \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];

I gruppi di middleware possono essere assegnati alle rotte e alle azioni del controller utilizzando la stessa sintassi dei singoli middleware. Ancora una volta, i gruppi di middleware rendono più conveniente l’assegnazione di molti middleware a una singola rotta:

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

Di default, i gruppi di middleware web e api vengono automaticamente applicati ai file corrispondenti routes/web.php e routes/api.php dell’applicazione tramite il provider App\Providers\RouteServiceProvider.

Sorting Middleware

Raramente, potresti avere bisogno che i tuoi middleware vengano eseguiti in un ordine specifico ma non avere il controllo sull’ordine quando vengono assegnati alla route. In questo caso, puoi specificare la priorità dei tuoi middleware usando la proprietà $middlewarePriority del file app/Http/Kernel.php. Questa proprietà potrebbe non esistere nel tuo HTTP kernel per impostazione predefinita. Se non esiste, puoi copiare la sua definizione predefinita qui sotto:

/**
 * The priority-sorted list of middleware.
 *
 * This forces non-global middleware to always be in the given order.
 *
 * @var string[]
 */
protected $middlewarePriority = [
    \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
    \Illuminate\Cookie\Middleware\EncryptCookies::class,
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
    \Illuminate\Routing\Middleware\ThrottleRequests::class,
    \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
    \Illuminate\Contracts\Session\Middleware\AuthenticatesSessions::class,
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
    \Illuminate\Auth\Middleware\Authorize::class,
];

Middleware Parameters

I middleware possono anche ricevere parametri aggiuntivi. Ad esempio, se la tua applicazione ha bisogno di verificare che l’utente autenticato abbia un determinato “ruolo” prima di eseguire un’azione specifica, potresti creare un middleware EnsureUserHasRole che riceva il nome del ruolo come argomento aggiuntivo.

I parametri aggiuntivi del middleware verranno passati dopo l’argomento $next:

<?php
 
namespace App\Http\Middleware;
 
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
 
class EnsureUserHasRole
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next, string $role): Response
    {
        if (! $request->user()->hasRole($role)) {
            // Redirect...
        }
 
        return $next($request);
    }
 
}

I parametri del middleware possono essere specificati durante la definizione della route separando il nome del middleware e i parametri con due punti : . I parametri multipli dovrebbero essere delimitati da virgole:

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

Terminable Middleware

A volte un middleware potrebbe dovere eseguire alcune operazioni dopo che la risposta HTTP è stata inviata al browser. Se definisci un metodo terminate nel tuo middleware e il tuo server web sta utilizzando FastCGI, il metodo terminate verrà automaticamente chiamato dopo che la risposta è stata inviata al browser:

<?php
 
namespace Illuminate\Session\Middleware;
 
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
 
class TerminatingMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        return $next($request);
    }
 
    /**
     * Handle tasks after the response has been sent to the browser.
     */
    public function terminate(Request $request, Response $response): void
    {
        // ...
    }
}

Il metodo terminate dovrebbe ricevere sia la richiesta (request) che la risposta (response). Una volta che hai definito un middleware terminabile, dovresti aggiungerlo all’elenco dei middleware delle route o globali nel file app/Http/Kernel.php.

Quando chiami il metodo terminate sul tuo middleware, Laravel risolverà un’istanza fresca del middleware dal container dei servizi. Se desideri utilizzare la stessa istanza del middleware quando vengono chiamati i metodi handle e terminate, registra il middleware nel container utilizzando il metodo singleton del container. Di solito questo dovrebbe essere fatto nel metodo register del tuo AppServiceProvider:

use App\Http\Middleware\TerminatingMiddleware;
 
/**
 * Register any application services.
 */
public function register(): void
{
    $this->app->singleton(TerminatingMiddleware::class);
}