Fáze požadavku: 5. Middleware Pipeline
Modul 3 · Lekce 2

Middleware pipeline: Jak filtrovat a obohacovat HTTP požadavky

💡 Shrnutí lekce

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átit abort(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!

💻 Klíčová konstrukce z praxe
<?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.

Otázka 1 z 3

Který návrhový vzor (Design Pattern) je základem zpracování požadavků v Laravel Middleware?

Otázka 2 z 3

Kde se v Laravelu 12 registrují vlastní middleware do výchozí webové skupiny?

Otázka 3 z 3

Co se stane, když v metodě handle() zavoláte $response = $next($request); a až poté modifikujete $response?

Zodpovězeno: 0 / 3
🔬 Živá anatomie v této aplikaci
2 soubory
Na co se v kódu zaměřit: V app/Http/Middleware/EnsureStudentProfile.php si všimni metody handle(). Před voláním $next($request) middleware načte profil studenta a sdílí jej přes View::share('currentStudent', $profile), a po návratu odpovědi do ní vloží identifikační cookie s roční platností.
cesta: app/Http/Middleware/EnsureStudentProfile.php
50 řádků · 1.3 KB
Middleware zajišťující anonymní UUID profil studenta v cookie s trvalým sledováním pokroku kurzem.
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
cesta: bootstrap/app.php
23 řádků · 0.7 KB
Nové jádro Laravel 12 pro registraci webových i konzolových rout, middleware pipeline a zachytávání výjimek bez Http Kernelu.
1 <?php
2
3 declare(strict_types=1);
4
5 use App\Http\Middleware\EnsureStudentProfile;
6 use Illuminate\Foundation\Application;
7 use Illuminate\Foundation\Configuration\Exceptions;
8 use Illuminate\Foundation\Configuration\Middleware;
9
10 return Application::configure(basePath: dirname(__DIR__))
11 ->withRouting(
12 web: __DIR__.'/../routes/web.php',
13 commands: __DIR__.'/../routes/console.php',
14 health: '/up',
15 )
16 ->withMiddleware(function (Middleware $middleware): void {
17 $middleware->web(append: [
18 EnsureStudentProfile::class,
19 ]);
20 })
21 ->withExceptions(function (Exceptions $exceptions): void {
22 //
23 })->create();
24
Zdroj: skutečné soubory této aplikace Plný průzkumník anatomie →