Introduzione
Quando un’applicazione PHP riceve una richiesta HTTP, spesso deve eseguire alcune operazioni comuni prima di raggiungere il codice principale: verificare un token, aggiungere intestazioni di sicurezza, misurare i tempi di risposta oppure gestire gli errori in modo uniforme. Inserire tutta questa logica nei controller rende il progetto difficile da leggere e da testare.
Un approccio più professionale consiste nell’utilizzare i middleware. Un middleware è un componente che riceve una richiesta, può modificarla o interromperne l’elaborazione e, se necessario, inoltrarla al componente successivo. Questo modello è definito principalmente da PSR-7, che descrive le interfacce per richieste e risposte HTTP, e da PSR-15, che definisce le interfacce per middleware e handler HTTP.
In questo tutorial costruiremo una piccola pipeline composta da:
- un middleware che aggiunge intestazioni di sicurezza;
- un middleware che controlla un’intestazione di autenticazione;
- un handler finale che produce una risposta JSON;
- un dispatcher responsabile dell’esecuzione ordinata dei componenti.
L’esempio segue anche principi di coding standard importanti: nomi descrittivi, responsabilità singole, tipizzazione esplicita, metodi brevi e dipendenze dichiarate tramite interfacce.
Codice completo
Per un progetto reale è consigliabile installare le interfacce PSR con Composer:
composer require psr/http-message psr/http-server-handler psr/http-server-middleware Il seguente esempio utilizza implementazioni molto semplici di richiesta e risposta, utili per comprendere il flusso senza introdurre subito un framework:
<?php
declare(strict_types=1);
namespace AppHttp;
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
use PsrHttpServerRequestHandlerInterface;
use PsrHttpServerMiddlewareInterface;
use LaminasDiactorosResponseJsonResponse;
/**
* Handler finale dell´applicazione.
*/
final class HomeHandler implements RequestHandlerInterface
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
return new JsonResponse([
´success´ => true,
´message´ => ´Richiesta elaborata correttamente´,
]);
}
}
/**
* Aggiunge intestazioni HTTP di sicurezza.
*/
final class SecurityHeadersMiddleware implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$response = $handler->handle($request);
return $response
->withHeader(´X-Content-Type-Options´, ´nosniff´)
->withHeader(´X-Frame-Options´, ´DENY´)
->withHeader(´Referrer-Policy´, ´no-referrer´);
}
}
/**
* Verifica la presenza di un token di accesso.
*/
final class AuthenticationMiddleware implements MiddlewareInterface
{
public function __construct(
private readonly string $expectedToken
) {
}
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$authorization = $request->getHeaderLine(´Authorization´);
$expectedHeader = ´Bearer ´ . $this->expectedToken;
if ($authorization !== $expectedHeader) {
return new JsonResponse(
[´error´ => ´Autenticazione richiesta´],
401
);
}
return $handler->handle($request);
}
}
/**
* Esegue i middleware in sequenza.
*/
final class MiddlewareDispatcher implements RequestHandlerInterface
{
/**
* @param list<MiddlewareInterface> $middlewares
*/
public function __construct(
private readonly array $middlewares,
private readonly RequestHandlerInterface $finalHandler
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
return $this->createHandler(0)->handle($request);
}
private function createHandler(int $index): RequestHandlerInterface
{
if (!isset($this->middlewares[$index])) {
return $this->finalHandler;
}
$middleware = $this->middlewares[$index];
$nextHandler = $this->createHandler($index + 1);
return new class($middleware, $nextHandler)
implements RequestHandlerInterface {
public function __construct(
private readonly MiddlewareInterface $middleware,
private readonly RequestHandlerInterface $nextHandler
) {
}
public function handle(
ServerRequestInterface $request
): ResponseInterface {
return $this->middleware->process(
$request,
$this->nextHandler
);
}
};
}
} Una possibile configurazione dell’applicazione è la seguente:
<?php
use AppHttpAuthenticationMiddleware;
use AppHttpHomeHandler;
use AppHttpMiddlewareDispatcher;
use AppHttpSecurityHeadersMiddleware;
$application = new MiddlewareDispatcher(
middlewares: [
new SecurityHeadersMiddleware(),
new AuthenticationMiddleware(´token-di-esempio´),
],
finalHandler: new HomeHandler()
);
$response = $application->handle($request); Spiegazione
PSR-7: richieste e risposte immutabili
PSR-7 stabilisce interfacce comuni per rappresentare messaggi HTTP. Una caratteristica importante è l’immutabilità. Metodi come withHeader() non modificano direttamente l’oggetto originale, ma restituiscono una nuova risposta con l’intestazione richiesta.
Questo comportamento riduce gli effetti collaterali. Un middleware può quindi creare una nuova versione della risposta senza alterare in modo imprevedibile quella ricevuta da un altro componente.
PSR-15: middleware e handler
Un middleware PSR-15 implementa il metodo:
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface Il parametro $handler rappresenta il componente successivo nella pipeline. Il middleware può:
- chiamare
$handler->handle($request)e continuare l’elaborazione; - restituire subito una risposta, ad esempio in caso di autenticazione fallita;
- modificare la risposta dopo l’esecuzione del componente successivo.
Nel codice, AuthenticationMiddleware applica una regola di accesso. Se il token non è valido, restituisce una risposta con stato HTTP 401 e non chiama il gestore successivo. In questo modo la logica di autenticazione rimane separata dal controller.
Ordine della pipeline
L’ordine dei middleware è significativo. Nell’esempio, il middleware di sicurezza viene eseguito prima dell’autenticazione. Dopo l’esecuzione dell’handler finale, la risposta risale la pipeline in ordine inverso: il middleware di autenticazione termina per primo e quello delle intestazioni aggiunge gli header alla risposta.
In un progetto più completo, il dispatcher dovrebbe essere affidato a un pacchetto già collaudato, come quelli presenti nell’ecosistema PSR-15. L’implementazione mostrata serve soprattutto a comprendere il meccanismo.
Best practice
- Una responsabilità per middleware: separa autenticazione, autorizzazione, CORS, logging e gestione degli errori.
- Programma verso interfacce: usa
MiddlewareInterface,RequestHandlerInterfaceeResponseInterfaceinvece di dipendere da classi concrete. - Non inserire segreti nel codice: il token dovrebbe provenire da variabili d’ambiente o da un gestore sicuro della configurazione.
- Mantieni l’immutabilità: assegna il risultato dei metodi
withHeader(),withAttribute()e simili. - Usa tipi rigorosi:
declare(strict_types=1), proprietàreadonlye tipi di ritorno rendono più chiaro il contratto delle classi. - Evita middleware enormi: un componente che autentica, registra log e modifica la risposta viola il principio di responsabilità singola.
- Testa ogni componente isolatamente: verifica sia il caso di successo sia quello in cui il middleware interrompe la pipeline.
- Centralizza gli errori: un middleware dedicato può intercettare eccezioni e trasformarle in risposte JSON coerenti.
Riepilogo
PSR-7 e PSR-15 permettono di progettare una gestione HTTP standardizzata e indipendente dal framework. Le richieste e le risposte vengono trattate tramite interfacce comuni, mentre i middleware organizzano le operazioni trasversali in una pipeline leggibile.
Questo approccio migliora la manutenibilità perché ogni componente ha uno scopo preciso, può essere riutilizzato in più endpoint e può essere testato senza avviare l’intera applicazione. Applicare contemporaneamente PSR, tipizzazione rigorosa e buone regole di formattazione porta a un codice PHP più prevedibile e professionale.
