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á:
- Obdrží request
- Provede nějakou logiku (autentifikace, validace, atd.)
- Předá request dalšímu middleware v řetězu
- 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ří requestpipe()- zaregistruje middlewarehandle()- 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/exitpro 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.