Skip to content

PSR-7 a PSR-15: HTTP Messages a Middleware

Petrovo CMS používá standardy PSR-7 (HTTP Message Interface) a PSR-15 (HTTP Server Request Handler) prostřednictvím knihoven Laminas Diactoros a Laminas Stratigility.

PSR-7: HTTP Messages

PSR-7 definuje standardní objekty pro práci s HTTP requesty a responses.

Request vs. Response

Request (ServerRequest):

  • Reprezentuje příchozí HTTP požadavek
  • Obsahuje: URL, metodu (GET/POST), headers, body, query parametry
  • V Petrovo CMS se vytvoří z superglobálních proměnných ($_GET, $_POST, atd.)

Response:

  • Reprezentuje odchozí HTTP odpověď
  • Obsahuje: status code, headers, body
  • V Petrovo CMS se obvykle vrací jako string s exit

Vytvoření Request objektu

use Siler\Diactoros;

// Vytvoří RequestInterface ze superglobálních proměnných
$request = Diactoros\request();

Interně:

$_GET       // Uloženo v request query attributes
$_POST      // Uloženo v request parsed body
$_SERVER    // Uloženo v request server params
$_COOKIE    // Uloženo v request cookies
// ...

Response helpers

Siler poskytuje helpery pro vytváření Response objektů:

use Siler\Diactoros;

// HTML response
Diactoros\html('<p>Obsah</p>');

// JSON response
Diactoros\json(['data' => 'value']);

// Textová response
Diactoros\text('Text');

PSR-15: Middleware Pipeline

PSR-15 definuje standardní middleware interface. Middleware je vrstva, která:

  1. Obdrží request
  2. Provede nějakou logiku (autentifikace, validace, atd.)
  3. Předá request dalšímu middleware v řetězu
  4. Přijme response zpět a provede finální úpravy

Middleware v Sileru

Middleware v Sileru jsou funkce/closures, které přijímají:

  • $request - PSR-7 ServerRequest
  • $handler - Callable pro předání dalšímu middleware
  • Vrací: PSR-7 Response
$middleware = function($request, $handler) {
    // 1. Logika PŘED dalším middleware
    echo "Middleware start\n";

    // 2. Předá request dál
    $response = $handler($request);

    // 3. Logika PO dalším middleware
    echo "Middleware end\n";

    // 4. Vrátí response
    return $response;
};

Registrace middleware - pipe()

Middleware se registrují do pojmenované pipeline pomocí pipe():

use function Siler\Stratigility\pipe;

// Registrace do pipeline 'frontend'
pipe($middleware1, 'frontend');
pipe($middleware2, 'frontend');
pipe($middleware3, 'frontend');

Pořadí je důležité - middleware se spouští v pořadí registrace.

Spuštění pipeline - handle()

Middleware pipeline se spustí pomocí handle():

use function Siler\Stratigility\handle;

$response = handle($request, 'frontend');

handle() spustí celou pipeline a vrátí finální response.

Výstup - sapi_emit()

sapi_emit() pošle response zpět do prohlížeče:

use Siler\HttpHandlerRunner;

HttpHandlerRunner\sapi_emit($response);

Vzory v Petrovo CMS

Vzor 1: DATA endpointy (s handle())

V app/routes/data.php se používá celá pipeline s handle():

// 1. Registrace middleware
pipe(proxyMiddleware, 'data');
pipe(defineWebMiddleware, 'data');
pipe(sanitizationMiddleware, 'data');
pipe(corsMiddleware, 'data');
pipe(finalHandler, 'data');  // Final middleware

// 2. Vytvoření request
$request = Diactoros\request();

// 3. Spuštění CELÉ pipeline
$response = handle($request, 'data');

// 4. Odeslání response
HttpHandlerRunner\sapi_emit($response);

// 5. Routování v pipeline
Route\any($path, DATA_DIR . '...php');

Důvod: DATA endpointy potřebují konzistentní middleware chování (CORS, sanitizace).

Vzor 2: Frontend routy (s process())

V app/routes/app-frontend.php se používá process() s jednotlivými routami:

// 1. Registrace middleware
pipe(staticBlockerMiddleware, 'frontend');
pipe(proxyMiddleware, 'frontend');
// ... více middleware

// 2. Vytvoření request
$request = Diactoros\request();

// 3. Handler pro konkrétní routu
$handler = fn($request, $params) => require_fn(__DIR__ . '/../Controllers/home.php')($params);

// 4. Route s `process()` - aplikuje middleware POUZE na tuto routu
Route\get('/', process($request, 'frontend')($handler), $request);

process() vrací middleware wrapper okolo handleru - aplikuje middleware jen na jednu routu.

Vzor 3a: RUN (s handle())

RUN routy mají vlastní pipeline s handle():

pipe(proxyMiddleware, 'run');
pipe(defineWebMiddleware, 'run');
pipe(sanitizationMiddleware, 'run');
pipe(csrfMiddleware, 'run');
pipe(corsMiddleware, 'run');
pipe(settingMiddleware, 'run');
pipe(finalHandler, 'run');
$response = handle($request, 'run');

Route\post('/RUN/action', __DIR__ . '/../tags/action.php');

Vzor 3b: CRON (s process())

CRON routy používají process() - handler je součást middleware stacku:

pipe(proxyMiddleware, 'cron');
pipe(defineWebMiddleware, 'cron');
pipe(ipFilteringMiddleware, 'cron');

$cronHandler = function ($request, $params) {
    $script = __DIR__ . '/../cron/' . $params['script'] . '.php';
    if (is_file($script)) {
        require $script;  // Cron script se spustí UVNITŘ handleru
    }
};

Route\get(CONFIG['dirPrefix'] . '/CRON/{script}', process($request, 'cron')($cronHandler), $request);

Klíč: Cron script běží UVNITŘ handleru, takže je součást middleware stacku. Handler vrací (implicitně) Diactoros\none() nebo zavolá exit.

Vzor 3c: FILE (s handle())

FILE routy mají vlastní pipeline s handle():

// app/routes/file.php
$path = Http\path();
if (!str_contains($path, GET_FILE_URL . '/')) {
    return;  // Pokud to není /getFile/*, necháme další route
}

pipe(proxyMiddleware, 'file');
pipe(defineWebMiddleware, 'file');
pipe(corsMiddleware, 'file');
pipe(finalHandler, 'file');
$response = handle($request, 'file');

// Routování se definuje PŘED handle():
// Route\get('/getFile/...', $handler);

exit(0);  // Bez sapi_emit()! Middleware stack emituje automaticky

Klíč: FILE routy jsou výjimka - používají handle() ale NEJSOU to DATA endpointy. Response emituje middleware stack automaticky.

Co se liší - handle() vs process()?

handle() process()
Použití DATA, RUN, FILE Frontend, Admin, CRON routy
Co vrací ResponseInterface objekt Closure (middleware wrapper)
Jak se emituje Explicitně sapi_emit() Automaticky middleware stackem
Route registrace MIMO route V route definici
Handler je Součást middleware stacku Část route parametrů

Praktický flow - DATA endpoint

1. Browser odešle GET /getData/Users/list
                       ↓
2. app/bootstrap.php zavolá app/routes/data.php
                       ↓
3. routes/data.php zaregistruje middleware do 'data' pipeline
                       ↓
4. Vytvoří $request z $_GET, $_POST, atd.
                       ↓
5. handle($request, 'data') spustí middleware pipeline:
   - proxyMiddleware
   - defineWebMiddleware
   - sanitizationMiddleware
   - corsMiddleware
   - finalHandler (vrátí response)
                       ↓
6. routes/data.php matchuje routu
   Route\any('/getData/Users/list', 'app/DATA/Users/list.php')
                       ↓
7. app/DATA/Users/list.php se spustí
   - Vrátí JSON nebo HTML response
   - V response je uložena data
                       ↓
8. HttpHandlerRunner\sapi_emit($response)
   - Pošle headers
   - Vypíše body
   - Skončí

Praktický flow - Frontend stránka

1. Browser odešle GET /some-page
                       ↓
2. app/bootstrap.php zavolá app/routes/app-frontend.php
                       ↓
3. routes/app-frontend.php zaregistruje middleware
   + Vytvoří $request
   + Definuje handler (route callbackem)
                       ↓
4. Route\get('/some-page', process($request, 'frontend')($handler))
   - process() vrací middleware-wrapped handler
   - Handler se zavolá s aplikovaným middleware
                       ↓
5. Handler (Controller) se spustí:
   - app/Controllers/slug.php
   - Vrátí HTML string nebo zavolá echo/exit
                       ↓
6. Router automaticky hlásí response
   - Header 200 OK
   - Body = vyprintovaný HTML

Kde se vrací response?

Typ Jak se vrací
DATA endpoint outputJson() nebo Json::output()
Frontend controller echo, html() nebo přímý výstup
RUN tag echo nebo přímý výstup
Middleware return $response

Shrnutí

  • PSR-7 = standardní objekty pro HTTP request/response
  • PSR-15 = standardní middleware interface
  • Laminas Diactoros = implementace PSR-7
  • Laminas Stratigility = implementace PSR-15 + pipeline
  • Siler = wrappery pro Laminas pro snazší použití

Petrovo CMS používá:

  • Diactoros\request() - vytvoří request
  • pipe() - zaregistruje middleware
  • handle() - spustí middleware pipeline a vrátí ResponseInterface (DATA/RUN/FILE)
  • process() - wrappuje middleware okolo single route (Frontend/Admin/CRON)
  • sapi_emit() - Explicitně emituje response (DATA/RUN - ne FILE!)
  • Middleware stack emituje response automaticky (Frontend/Admin/CRON/FILE)
  • Přímo echo/exit pro jednoduché výstupy

Klíčový rozdíl: Emitování Response

handle() vrací objekt, ale JEN DATA a RUN endpointy explicitně emitují sapi_emit():

// app/routes/data.php - Explicitně emituje
$response = handle($request, 'data');
HttpHandlerRunner\sapi_emit($response);  // ← Musíme poslat

// app/routes/file.php - Middleware stack emituje automaticky
$response = handle($request, 'file');
exit(0);  // ← Middleware stack se postará o emitování

process() vrací closure, kterou Router zavolá a response se emituje automaticky:

// app/routes/app-frontend.php
Route\get('/', process($request, 'frontend')($handler), $request);
// ← Router zavolá closuру, middleware stack emituje response automaticky

Proč je FILE jinak? Middleware stack Laminas Stratigility má vestavěný SAPI emitter, který se používá při process(). U handle() je to na nás.