Middleware pipeline: Jak filtrovat a obohacovat HTTP požadavky
Koncept „cibulového“ řetězce (Pipeline) v HTTP vrstvě. Jak Middleware zpracovává požadavek před a po předání do aplikace na příkladu EnsureStudentProfile.
Co je to Middleware a jak funguje HTTP Pipeline
Middleware představuje ochranný mechanismus a filtrační vrstvu, kterou musí projít každý příchozí HTTP požadavek předtím, než dorazí k aplikační logice v Controlleru. Stejně tak výstupní HTTP odpověď (Response) prochází toutéž pipeline opačným směrem zpět ke klientovi.
Tento koncept vychází z návrhového vzoru Pipeline (nebo Chain of Responsibility), často přirovnávaného ke slupkám cibule:
[ Příchozí Request ]
│
▼
┌──────────────────────────────────────────────┐
│ EnsureStudentProfile (Ověření identity) │
│ ┌────────────────────────────────────────┐ │
│ │ VerifyCsrfToken / EncryptCookies │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ CourseController::index() │ │ │
│ │ │ (Generování pohledu / dat) │ │ │
│ │ └──────────────────────────────────┘ │ │
│ │ Zpětný průchod: Šifrování odpovědi │ │
│ └────────────────────────────────────────┘ │
│ Zpětný průchod: Připojení cookie studenta │
└──────────────────────────────────────────────┘
│
▼
[ Odchozí Response s HTTP hlavičkami ]
1. Anatomie Middleware třídy v PHP 8.4
V moderním Laravelu 12 píšeme middleware jako neměnnou třídu se striktním typováním:
final readonly class EnsureStudentProfile
{
public function __construct(
private StudentProgressService $progressService,
) {}
public function handle(Request $request, Closure $next): Response
{
// 1. Předání řízení dovnitř aplikace (Before middleware fáze)
$rawToken = $request->cookie(StudentProgressService::COOKIE_NAME);
$profile = $this->progressService->resolveProfile($rawToken);
$request->attributes->set('studentProfile', $profile);
View::share('currentStudent', $profile);
// 2. Volání další vrstvy v řetězci ($next)
$response = $next($request);
// 3. Obohacení odchozí odpovědi (After middleware fáze)
$cookie = cookie(
name: StudentProgressService::COOKIE_NAME,
value: $profile->token,
minutes: 525600, // Platnost 1 rok
);
$response->headers->setCookie($cookie);
return $response;
}
}
Klíčové fáze metody handle():
- Před voláním
$next($request): Zde můžete požadavek validovat, odmítnout (např. vrátitabort(403)), vyčistit data nebo injektovat dodatečné atributy přes$request->attributes->set(). - Volání
$next($request): Tímto krokem middleware předává řízení dalšímu prvku pipeline (dalšímu middlewaru nebo samotnému controlleru). - Po volání
$next($request): Zde již držíte hotový objekt$response. Můžete upravovat HTTP hlavičky, připojovat cookies nebo měnit stavový kód.
2. Registrace Middleware v Laravelu 12 (bootstrap/app.php)
V Laravelu 12 došlo k revoluci v konfiguraci. Starý soubor app/Http/Kernel.php byl zrušen a veškerá konfigurace middleware se nyní provádí přímo ve fluent bootstrap souboru bootstrap/app.php:
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
$middleware->web(append: [
EnsureStudentProfile::class,
]);
})
->create();
Metoda $middleware->web(append: [...]) zařadí náš middleware na konec výchozí skupiny web (za inicializaci session, šifrování cookies a ochranu CSRF). Díky tomu má náš middleware okamžitě k dispozici dekódované cookies!
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use App\Services\StudentProgressService;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\View;
use Symfony\Component\HttpFoundation\Response;
final readonly class EnsureStudentProfile
{
public function __construct(
private StudentProgressService $progressService,
) {}
/**
* Zpracuje příchozí požadavek, ověří a předá identitu anonymního studenta.
*
* @param Closure(Request): Response $next
*/
public function handle(Request $request, Closure $next): Response
{
// Krok 1: Extrakce tokenu z podepsané cookie
$rawToken = $request->cookie(StudentProgressService::COOKIE_NAME);
$token = is_string($rawToken) ? $rawToken : null;
// Krok 2: Nalezení nebo automatické vytvoření anonymního profilu v PostgreSQL
$profile = $this->progressService->resolveProfile($token);
// Krok 3: Sdílení profilu do atributů požadavku a globálního Blade View kontextu
$request->attributes->set('studentProfile', $profile);
View::share('currentStudent', $profile);
// Krok 4: Spuštění dalšího middleware a Controlleru
$response = $next($request);
// Krok 5: Připojení trvalé cookie s tokenem do odchozí HTTP odpovědi
$cookie = cookie(
name: StudentProgressService::COOKIE_NAME,
value: $profile->token,
minutes: 525600,
);
if (method_exists($response, 'withCookie')) {
$response->withCookie($cookie);
} else {
$response->headers->setCookie($cookie);
}
return $response;
}
}
🎯 Ověření znalostí z lekce
Kvíz otestuje pochopení architektury a reálného kódu probíraného v této lekci.
Který návrhový vzor (Design Pattern) je základem zpracování požadavků v Laravel Middleware?
Kde se v Laravelu 12 registrují vlastní middleware do výchozí webové skupiny?
Co se stane, když v metodě handle() zavoláte $response = $next($request); a až poté modifikujete $response?
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace App\Http\Middleware; |
| 6 | |
| 7 | use App\Services\StudentProgressService; |
| 8 | use Closure; |
| 9 | use Illuminate\Http\Request; |
| 10 | use Illuminate\Support\Facades\View; |
| 11 | use Symfony\Component\HttpFoundation\Response; |
| 12 | |
| 13 | final readonly class EnsureStudentProfile |
| 14 | { |
| 15 | public function __construct( |
| 16 | private StudentProgressService $progressService, |
| 17 | ) {} |
| 18 | |
| 19 | /** |
| 20 | * Zpracuje příchozí požadavek, ověří a předá identitu anonymního studenta. |
| 21 | * |
| 22 | * @param Closure(Request): Response $next |
| 23 | */ |
| 24 | public function handle(Request $request, Closure $next): Response |
| 25 | { |
| 26 | $rawToken = $request->cookie(StudentProgressService::COOKIE_NAME); |
| 27 | $token = is_string($rawToken) ? $rawToken : null; |
| 28 | |
| 29 | $profile = $this->progressService->resolveProfile($token); |
| 30 | |
| 31 | $request->attributes->set('studentProfile', $profile); |
| 32 | View::share('currentStudent', $profile); |
| 33 | |
| 34 | $response = $next($request); |
| 35 | |
| 36 | $cookie = cookie( |
| 37 | name: StudentProgressService::COOKIE_NAME, |
| 38 | value: $profile->token, |
| 39 | minutes: 525600, |
| 40 | ); |
| 41 | |
| 42 | if (method_exists($response, 'withCookie')) { |
| 43 | $response->withCookie($cookie); |
| 44 | } else { |
| 45 | $response->headers->setCookie($cookie); |
| 46 | } |
| 47 | |
| 48 | return $response; |
| 49 | } |
| 50 | } |
| 51 | |