Middleware
Il middleware è generalmente utilizzato per intercettare richieste o risposte. Ad esempio, per convalidare l'identità dell'utente in modo uniforme prima di eseguire il controller, come reindirizzarlo alla pagina di accesso se l'utente non è connesso, o per aggiungere un certo header a una risposta. Può anche servire a statistiche della quota di richieste a un certo URI, ecc.
Modello Onion del Middleware
            ┌──────────────────────────────────────────────────────┐
            │                     middleware1                      │ 
            │     ┌──────────────────────────────────────────┐     │
            │     │               middleware2                │     │
            │     │     ┌──────────────────────────────┐     │     │
            │     │     │         middleware3          │     │     │        
            │     │     │     ┌──────────────────┐     │     │     │
            │     │     │     │                  │     │     │     │
   ── Reqeust ───────────────────> Controller ── Response ───────────────────────────> Client
            │     │     │     │                  │     │     │     │
            │     │     │     └──────────────────┘     │     │     │
            │     │     │                              │     │     │
            │     │     └──────────────────────────────┘     │     │
            │     │                                          │     │
            │     └──────────────────────────────────────────┘     │
            │                                                      │
            └──────────────────────────────────────────────────────┘
Il middleware e il controller formano un classico modello onion, dove il middleware è simile a strati esterni di una cipolla e il controller è il nucleo. Come mostrato nella figura, la richiesta attraversa come una freccia i middleware 1, 2 e 3 per arrivare al controller, che restituisce una risposta, e poi la risposta attraversa di nuovo i middleware in ordine inverso (3, 2, 1) per tornare al client. In altre parole, in ciascun middleware possiamo accedere sia alla richiesta che alla risposta.
Intercettazione della Richiesta
A volte non vogliamo che una certa richiesta raggiunga il livello del controller. Ad esempio, se in middleware2 scopriamo che l'utente attuale non è connesso, possiamo intercettare direttamente la richiesta e restituire una risposta di login. Quindi il processo è simile a quanto segue:
            ┌────────────────────────────────────────────────────────────┐
            │                         middleware1                        │ 
            │     ┌────────────────────────────────────────────────┐     │
            │     │                   middleware2                  │     │
            │     │          ┌──────────────────────────────┐      │     │
            │     │          │        middleware3           │      │     │       
            │     │          │    ┌──────────────────┐      │      │     │
            │     │          │    │                  │      │      │     │
   ── Reqeust ─────────┐     │    │    Controller    │      │      │     │
            │     │ Response │    │                  │      │      │     │
   <───────────────────┘     │    └──────────────────┘      │      │     │
            │     │          │                              │      │     │
            │     │          └──────────────────────────────┘      │     │
            │     │                                                │     │
            │     └────────────────────────────────────────────────┘     │
            │                                                            │
            └────────────────────────────────────────────────────────────┘
Come mostrato nella figura, una volta che la richiesta raggiunge middleware2, viene generata una risposta di login, e questa risposta attraversa il middleware1 prima di tornare al client.
Interfaccia del Middleware
Il middleware deve implementare l'interfaccia Webman\MiddlewareInterface.
interface MiddlewareInterface
{
    /**
     * Process an incoming server request.
     *
     * Processes an incoming server request in order to produce a response.
     * If unable to produce the response itself, it may delegate to the provided
     * request handler to do so.
     */
    public function process(Request $request, callable $handler): Response;
}
Ciò significa che deve implementare il metodo process, che deve restituire un oggetto support\Response. Per impostazione predefinita, questo oggetto è generato da $handler($request) (la richiesta continuerà a attraversare il nucleo della cipolla) oppure può essere una risposta generata da funzioni helper come response(), json(), xml(), redirect(), ecc. (la richiesta smette di continuare a attraversare il nucleo della cipolla).
Ottenere Richiesta e Risposta nel Middleware
Nel middleware possiamo ottenere la richiesta e anche la risposta dopo l'esecuzione del controller, quindi il middleware può essere suddiviso in tre parti.
- Fase di attraversamento della richiesta, ovvero la fase prima del trattamento della richiesta
 - Fase di trattamento della richiesta da parte del controller
 - Fase di attraversamento della risposta, ovvero la fase dopo il trattamento della richiesta
 
Le tre fasi sono rappresentate nel middleware come segue:
<?php
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class Test implements MiddlewareInterface
{
    public function process(Request $request, callable $handler) : Response
    {
        echo 'Questa è la fase di attraversamento della richiesta, ovvero prima del trattamento della richiesta';
        $response = $handler($request); // Continua a attraversare il nucleo della cipolla fino a ottenere la risposta dal controller
        echo 'Questa è la fase di attraversamento della risposta, ovvero dopo il trattamento della richiesta';
        return $response;
    }
}
Esempio: Middleware di Autenticazione
Crea il file app/middleware/AuthCheckTest.php (crea il directory se non esiste) come segue:
<?php
namespace app\middleware;
use ReflectionClass;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class AuthCheckTest implements MiddlewareInterface
{
    public function process(Request $request, callable $handler) : Response
    {
        if (session('user')) {
            // Già autenticato, la richiesta continua a attraversare il nucleo della cipolla
            return $handler($request);
        }
        // Utilizza la riflessione per ottenere quali metodi del controller non richiedono autenticazione
        $controller = new ReflectionClass($request->controller);
        $noNeedLogin = $controller->getDefaultProperties()['noNeedLogin'] ?? [];
        // La funzione richiesta richiede autenticazione
        if (!in_array($request->action, $noNeedLogin)) {
            // Intercetta la richiesta, restituisce una risposta di reindirizzamento, la richiesta smette di attraversare il nucleo della cipolla
            return redirect('/user/login');
        }
        // Non richiede autenticazione, la richiesta continua a attraversare il nucleo della cipolla
        return $handler($request);
    }
}
Crea il controller app/controller/UserController.php
<?php
namespace app\controller;
use support\Request;
class UserController
{
    /**
     * Metodi che non richiedono autenticazione
     */
    protected $noNeedLogin = ['login'];
    public function login(Request $request)
    {
        $request->session()->set('user', ['id' => 10, 'name' => 'webman']);
        return json(['code' => 0, 'msg' => 'login ok']);
    }
    public function info()
    {
        return json(['code' => 0, 'msg' => 'ok', 'data' => session('user')]);
    }
}
Nota
$noNeedLoginregistra i metodi del controller che possono essere accessibili senza autenticazione.
Aggiungi il middleware globale in config/middleware.php come segue:
return [
    // Middleware globale
    '' => [
        // ... altri middleware omessi here
        app\middleware\AuthCheckTest::class,
    ]
];
Con il middleware di autenticazione, possiamo concentrarci sulla scrittura del codice di business nel livello del controller senza preoccuparci se l'utente è connesso o meno.
Esempio: Middleware di Richiesta Cross-Origin
Crea il file app/middleware/AccessControlTest.php (crea il directory se non esiste) come segue:
<?php
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class AccessControlTest implements MiddlewareInterface
{
    public function process(Request $request, callable $handler) : Response
    {
        // Se la richiesta è di tipo options, restituisce una risposta vuota; altrimenti continua a attraversare il nucleo della cipolla e ottiene una risposta
        $response = $request->method() == 'OPTIONS' ? response('') : $handler($request);
        // Aggiunge headers http relativi al cross-origin alla risposta
        $response->withHeaders([
            'Access-Control-Allow-Credentials' => 'true',
            'Access-Control-Allow-Origin' => $request->header('origin', '*'),
            'Access-Control-Allow-Methods' => $request->header('access-control-request-method', '*'),
            'Access-Control-Allow-Headers' => $request->header('access-control-request-headers', '*'),
        ]);
        return $response;
    }
}
Suggerimento
Il cross-origin può generare richieste OPTIONS, e non vogliamo che le richieste OPTIONS arrivino al controller, quindi rispondiamo direttamente con una risposta vuota (response('')) per implementare l'intercettazione della richiesta.
Se la tua interfaccia ha bisogno di configurare il routing, utilizzaRoute::any(..)oRoute::add(['POST', 'OPTIONS'], ..)per impostare.
Aggiungi il middleware globale in config/middleware.php come segue:
return [
    // Middleware globale
    '' => [
        // ... altri middleware omessi here
        app\middleware\AccessControlTest::class,
    ]
];
Nota
Se una richiesta ajax ha headers personalizzati, è necessario aggiungere questo header personalizzato nel campoAccess-Control-Allow-Headersnel middleware, altrimenti si otterrà il messaggioRequest header field XXXX is not allowed by Access-Control-Allow-Headers in preflight response.
Spiegazioni
- I middleware si dividono in middleware globali, middleware dell'applicazione (questi ultimi sono efficaci solo in modalità multi-app, vedi multi-app) e middleware delle route.
 - La posizione del file di configurazione del middleware è 
config/middleware.php. - Il middleware globale è configurato nella chiave 
''. - Il middleware dell'applicazione è configurato sotto il nome dell'app specifica, per esempio:
 
return [
    // Middleware globale
    '' => [
        app\middleware\AuthCheckTest::class,
        app\middleware\AccessControlTest::class,
    ],
    // Middleware dell'app API (il middleware dell'app è valido solo in modalità multi-app)
    'api' => [
        app\middleware\ApiOnly::class,
    ]
];
Middleware del Controller e Middleware dei Metodi
Utilizzando le annotazioni, possiamo impostare middleware per un certo controller o per un certo metodo di un controller.
<?php
namespace app\controller;
use app\middleware\Controller1Middleware;
use app\middleware\Controller2Middleware;
use app\middleware\Method1Middleware;
use app\middleware\Method2Middleware;
use support\annotation\Middleware;
use support\Request;
#[Middleware(Controller1Middleware::class, Controller2Middleware::class)]
class IndexController
{
    #[Middleware(Method1Middleware::class, Method2Middleware::class)]
    public function index(Request $request): string
    {
        return 'hello';
    }
}
Middleware delle Rotte
Possiamo impostare middleware per una certa rotta o per un gruppo di rotte.
Per esempio, aggiungi la seguente configurazione in config/route.php:
<?php
use support\Request;
use Webman\Route;
Route::any('/admin', [app\admin\controller\IndexController::class, 'index'])->middleware([
    app\middleware\MiddlewareA::class,
    app\middleware\MiddlewareB::class,
]);
Route::group('/blog', function () {
   Route::any('/create', function () {return response('create');});
   Route::any('/edit', function () {return response('edit');});
   Route::any('/view/{id}', function ($r, $id) {response("view $id");});
})->middleware([
    app\middleware\MiddlewareA::class,
    app\middleware\MiddlewareB::class,
]);
Passare Parametri al Costruttore del Middleware
Il file di configurazione supporta la creazione diretta di middleware o funzioni anonime, permettendo di passare facilmente parametri al middleware tramite il costruttore.
Ad esempio, in config/middleware.php si può anche configurare in questo modo:
return [
    // Middleware globale
    '' => [
        new app\middleware\AuthCheckTest($param1, $param2, ...),
        function(){
            return new app\middleware\AccessControlTest($param1, $param2, ...);
        },
    ],
    // Middleware dell'app API (il middleware dell'app è valido solo in modalità multi-app)
    'api' => [
        app\middleware\ApiOnly::class,
    ]
];
Allo stesso modo, i middleware delle rotte possono anche ricevere parametri dal costruttore, ad esempio in config/route.php:
Route::any('/admin', [app\admin\controller\IndexController::class, 'index'])->middleware([
    new app\middleware\MiddlewareA($param1, $param2, ...),
    function(){
        return new app\middleware\MiddlewareB($param1, $param2, ...);
    },
]);
Esempio di utilizzo dei parametri nel middleware:
<?php
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class MiddlewareA implements MiddlewareInterface
{
    protected $param1;
    protected $param2;
    public function __construct($param1, $param2)
    {
        $this->param1 = $param1;
        $this->param2 = $param2;
    }
    public function process(Request $request, callable $handler) : Response
    {
        var_dump($this->param1, $this->param2);
        return $handler($request);
    }
}
Sequenza di Esecuzione del Middleware
- La sequenza di esecuzione del middleware è 
middleware globale->middleware dell'app->middleware del controller->middleware delle rotte->middleware dei metodi. - Quando ci sono più middleware nello stesso livello, vengono eseguiti nell'ordine di configurazione effettivo dei middleware in quello stesso livello.
 - Le richieste 404 non attivano di default alcun middleware (sebbene possa ancora essere aggiunto un middleware tramite 
Route::fallback(function(){})->middleware()). 
Passare Parametri al Middleware tramite la Rotta (route->setParams)
Configurazione della rotta config/route.php
<?php
use support\Request;
use Webman\Route;
Route::any('/test', [app\controller\IndexController::class, 'index'])->setParams(['some_key' =>'some value']);
Middleware (presumendo sia un middleware globale)
<?php
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class Hello implements MiddlewareInterface
{
    public function process(Request $request, callable $handler) : Response
    {
        // Per impostazione predefinita, $request->route è nullo, quindi è necessario verificare se $request->route è vuoto
        if ($route = $request->route) {
            $value = $route->param('some_key');
            var_export($value);
        }
        return $handler($request);
    }
}
Passare Dati dal Middleware al Controller
A volte un controller ha bisogno di utilizzare i dati generati nel middleware; in questo caso possiamo passare dati al controller aggiungendo proprietà all'oggetto $request. Per esempio:
Middleware
<?php
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class Hello implements MiddlewareInterface
{
    public function process(Request $request, callable $handler) : Response
    {
        $request->data = 'some value';
        return $handler($request);
    }
}
Controller:
<?php
namespace app\controller;
use support\Request;
class FooController
{
    public function index(Request $request)
    {
        return response($request->data);
    }
}
Ottenere Informazioni della Rotta Corrente nel Middleware
Possiamo utilizzare $request->route per ottenere l'oggetto rotta e utilizzare i metodi corrispondenti per raccogliere informazioni pertinenti.
Configurazione della rotta
<?php
use support\Request;
use Webman\Route;
Route::any('/user/{uid}', [app\controller\UserController::class, 'view']);
Middleware
<?php
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class Hello implements MiddlewareInterface
{
    public function process(Request $request, callable $handler) : Response
    {
        $route = $request->route;
        // Se la richiesta non ha corrisposto a nessuna rotta (eccetto le rotte di default), allora $request->route è nullo
        // Supponendo che il browser acceda a /user/111, stamperà le seguenti informazioni
        if ($route) {
            var_export($route->getPath());       // /user/{uid}
            var_export($route->getMethods());    // ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD','OPTIONS']
            var_export($route->getName());       // user_view
            var_export($route->getMiddleware()); // []
            var_export($route->getCallback());   // ['app\\controller\\UserController', 'view']
            var_export($route->param());         // ['uid'=>111]
            var_export($route->param('uid'));    // 111 
        }
        return $handler($request);
    }
}
Nota
Ottenere Eccezioni nel Middleware
Durante il trattamento del business, potrebbero esserci eccezioni; per ottenere l'eccezione nel middleware utilizziamo $response->exception().
Configurazione della rotta
<?php
use support\Request;
use Webman\Route;
Route::any('/user/{uid}', function (Request $request, $uid) {
    throw new \Exception('exception test');
});
Middleware:
<?php
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class Hello implements MiddlewareInterface
{
    public function process(Request $request, callable $handler) : Response
    {
        $response = $handler($request);
        $exception = $response->exception();
        if ($exception) {
            echo $exception->getMessage();
        }
        return $response;
    }
}
Middleware Globale Superiore
Il middleware globale del progetto principale influisce solo sul progetto principale e non sui plugin dell'app. A volte vogliamo aggiungere un middleware che influisca globalmente su tutti i plugin, quindi possiamo utilizzare un middleware globale superiore.
Configura in config/middleware.php come segue:
return [
    '@' => [ // Aggiunge middleware globale al progetto principale e a tutti i plugin
        app\middleware\MiddlewareGlobl::class,
    ], 
    '' => [], // Aggiunge middleware globale solo al progetto principale
];
Suggerimento
Il middleware globale@non può essere configurato solo nel progetto principale, ma anche in uno dei plugin; per esempio, configurando@nel fileplugin/ai/config/middleware.phpinfluenzerà sia il progetto principale che tutti i plugin.
Aggiungere Middleware a un Plugin
A volte vogliamo aggiungere un middleware a un certo plugin dell'app senza modificare il codice del plugin (perché le modifiche verrebbero sovrascritte durante l'aggiornamento), quindi possiamo configurare il middleware nel progetto principale.
Configura in config/middleware.php come segue:
return [
    'plugin.ai' => [], // Aggiunge middleware al plugin ai
    'plugin.ai.admin' => [], // Aggiunge middleware al modulo admin del plugin ai (directory plugin\ai\app\admin)
];
Suggerimento
Naturalmente, è possibile aggiungere configurazioni simili in un altro plugin per influenzarne altri; per esempio, aggiungendo la configurazione sopra nel fileplugin/foo/config/middleware.php, influenzerà il plugin ai.