PSR-7 a PSR-15: HTTP Messages a Middleware
Petrovo CMS implementuje standardy PSR-7 (HTTP Message Interface) a PSR-15 (HTTP Server Request Handler) prostřednictvím:
- HttpSoft - PSR-7 implementace (Request/Response objekty)
- Core\Http - Funkcionální API pro PSR-7 (Request + Response helpery)
- Core\Middleware - Lightweight PSR-15 pipeline s pojmenovanými kanály
- Core\Route - Registry-based router s automatickou middleware integrací
Jde o custom lightweight stack bez Laminas/Siler overhead — čistě funkcionální design optimalizovaný pro malé týmy a vysokou čitelnost.
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
- Vytvoří se z superglobálních proměnných (
$_GET,$_POST, atd.)
Response:
- Reprezentuje odchozí HTTP odpověď
- Obsahuje: status code, headers, body
- V Petrovo CMS se vytváří přes helpery (
html(),json(), atd.) a odesílá přesemit()
Vytvoření Request objektu
use function Core\Http\request;
// Vytvoří ServerRequestInterface ze superglobálních proměnných
$request = request();
Interně zpracuje:
$_GET // Query parametry
$_POST // Parsed body
$_SERVER // Server params + HTTP headers
$_COOKIE // Cookies
$_FILES // Konvertováno na UploadedFileInterface objekty
Proxy support: Detekuje reverse proxy (nginx, Caddy, Cloudflare) a správně extrahuje IP a hostname z X-Forwarded-* headerů.
Request helpery
use function Core\Http\{
request, path, method, query, body, field,
with_attribute, get_attribute, attributes,
header, has_header, raw, is_json, parse_json,
authorization_header, bearer, raw2json,
get_host_from_request
};
$request = request();
path($request); // '/some-page'
method($request); // 'GET', 'POST', ...
query($request, 'id'); // $_GET['id']
query($request, 'page', 1); // s výchozí hodnotou
body($request); // $_POST (array|object|null)
field($request, 'name'); // $_POST['name']
field($request, 'email', ''); // s výchozí hodnotou
header($request, 'Content-Type'); // hodnota headeru
has_header($request, 'Authorization'); // bool
raw($request); // raw php://input
is_json($request); // bool (Content-Type: application/json)
parse_json($request); // decoded JSON body
raw2json($request); // JSON body pokud je JSON, jinak null
authorization_header($request); // 'Bearer xyz...' | null
bearer($request); // 'xyz...' | null (extrahuje token)
get_host_from_request(); // hostname s proxy podporou
Atributy (komunikace mezi middleware):
$request = with_attribute($request, 'user_id', 42);
$userId = get_attribute($request, 'user_id');
$all = attributes($request);
Response helpery
use function Core\Http\{
html, json, translatedJson, text, binary,
redirect, no_content,
with_header, with_headers, with_status,
emit
};
// HTML response
html('<p>Obsah</p>');
html('<p>Chyba</p>', 500);
// JSON response
json(['data' => 'value']);
json(['error' => 'Not found'], 404);
// Přeložená JSON response (i18n)
translatedJson(true, 'save_success'); // {status: 'ok', message: '...'}
translatedJson(false, 'validation_error', [], 'cs', 422);
// Textová response
text('Plain text');
// Binární response (obrázky, soubory)
binary($imageData, 'image/jpeg');
binary($pdfData, 'application/pdf');
// Redirect
redirect('/new-url');
redirect('/gone', 301);
// Empty response (204 No Content)
no_content();
// Modifikátory (immutable — vrátí nový objekt)
$response = with_header($response, 'X-Custom', 'value');
$response = with_headers($response, ['X-A' => '1', 'X-B' => '2']);
$response = with_status($response, 201);
// Odeslání klientovi
emit($response);
Ostatní Http helpery
use function Core\Http\{isSecure, HTTPStatus};
isSecure(); // bool — detekuje HTTPS včetně proxy headerů
HTTPStatus(404); // ['code' => 404, 'error' => 'HTTP/1.1 404 Not Found']
// + pošle header()
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 Petrovo
Middleware v Petrovo jsou funkce/closures, které přijímají:
$request- PSR-7 ServerRequestInterface$handler- RequestHandlerInterface pro předání dalšímu middleware- Vrací: PSR-7 ResponseInterface
$middleware = function($request, $handler) {
// 1. Logika PŘED dalším middleware
error_log("Request: " . $request->getMethod() . " " . $request->getUri()->getPath());
// 2. Předá request dál
$response = $handler->handle($request);
// 3. Logika PO dalším middleware (úprava response)
$response = $response->withHeader('X-Custom-Header', 'value');
// 4. Vrátí response
return $response;
};
Registrace middleware — pipe()
Middleware se registrují do pojmenované pipeline pomocí pipe():
use function Core\Middleware\pipe;
pipe($middleware1, 'website');
pipe($middleware2, 'website');
pipe($middleware3, 'website');
Pořadí je důležité — middleware se spouští v pořadí registrace. Thread-safe: Používá statickou storage namísto globálů — funguje v FrankenPHP, Swoole, RoadRunner.
Spuštění pipeline — process() a handle()
process() — Wrappuje middleware okolo route handleru
use function Core\Middleware\process;
$handler = fn($request) => html('<p>OK</p>');
$middlewareHandler = process($request, 'website')($handler);
$response = $middlewareHandler->handle($request);
process() vrací closure, která přijme $handler a vrátí RequestHandlerInterface.
handle() — Spustí middleware pipeline bez handleru
use function Core\Middleware\handle;
$response = handle($request, 'website');
Všechna middleware se spustí a vrátí finální response.
Utility funkce
use function Core\Middleware\{wrap_handler, reset, get_pipelines};
// Převede RequestHandlerInterface na callable (pro routing)
$callable = wrap_handler($handler);
// Vymaže všechny pipelines (pro testování)
reset();
// Vrátí registrované pipelines (pro debugging)
$pipelines = get_pipelines();
Výstup — emit()
use function Core\Http\emit;
emit($response);
Odešle status code, headers a body klientovi.
Architekt Petrovo — Custom PSR Stack
Petrovo CMS nahradil Laminas/Siler vlastní lightweight implementací:
┌────────────────────────────────────────┐
│ App Bootstrap (app/bootstrap.php) │
│ └─ Path-based dispatcher │
│ (podle /getData/, /ADMIN, atd.) │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ Route Files (app/routes/*.php) │
│ ├─ Zaregistrují middleware │
│ │ (pipe → Core\Middleware) │
│ ├─ Zaregistrují routes │
│ │ (get/post/any → Core\Route) │
│ └─ Zavolají dispatch() │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ Route Dispatcher (core/Route/Route.php)│
│ ├─ Matchuje patterns │
│ ├─ Extrahuje parametry │
│ └─ Aplikuje middleware pipeline │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ Middleware Pipeline (core/Middleware) │
│ ├─ Named pipelines (frontend/admin) │
│ └─ Spouští middleware v pořadí │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ Handler (Controller/Tag/Script) │
│ └─ Vrátí PSR-7 Response │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ HTTP Response (core/Http/Response.php) │
│ └─ emit() → Output na klient │
└────────────────────────────────────────┘
Tři typy Route Handleru
Petrovo Route dispatcher podporuje tři typy handleru — file paths, closures a class methods:
1. File Path Handler
use function Core\Route\get;
get('/{slug}', __DIR__ . '/../Controllers/slug.php', 'website');
V Controllers/slug.php:
<?php
// Route params dostupné jako $params array i jako proměnné ($slug, atd.)
$params = $request->getAttributes();
return html("<h1>$slug</h1>");
2. Closure Handler
use function Core\Route\post;
post('/search', function($request, $params) {
$query = $params['query'] ?? null;
$results = searchDatabase($query);
return json($results);
}, 'api');
3. Array Notation — [ClassName::class, 'method']
use function Core\Route\{get, post, put, delete};
use App\Modules\Admins\Controller;
get('/ADMIN/admins', [Controller::class, 'index'], 'admin');
post('/ADMIN/admins', [Controller::class, 'store'], 'admin');
put('/ADMIN/admins/{id}', [Controller::class, 'update'], 'admin');
delete('/ADMIN/admins/{id}',[Controller::class, 'destroy'],'admin');
V Controller:
class Controller {
public function index($request, $params): Response { ... }
public function store($request, $params): Response { ... }
public function update($request, $params): Response { ... }
public function destroy($request, $params): Response { ... }
}
Výhody array notation:
- IDE autocomplete a static analysis (PHPStan)
- Explicitní a čitelný kód
- Automatická instanciace třídy
Registrace HTTP metod
use function Core\Route\{get, post, put, patch, delete, any};
get('/path', $handler, 'pipeline');
post('/path', $handler, 'pipeline');
put('/path/{id}', $handler, 'pipeline');
patch('/path/{id}', $handler, 'pipeline');
delete('/path/{id}', $handler, 'pipeline');
any('/path', $handler, 'pipeline'); // Matchuje všechny metody
Route patterns
/exact/path - přesná shoda
/path/{id} - povinný parametr (non-empty)
/path/{id}? - volitelný parametr (může být prázdný)
/prefix/.* - wildcard prefix (matchuje vše od /prefix/)
Utility funkce
use function Core\Route\{dispatch, reset, routes, matched_route};
$response = dispatch($request); // Matchuje routes a spustí handler
reset(); // Vymaže routes (pro testování)
$all = routes(); // Vrátí registrované routes (pro debugging)
$matched = matched_route(); // Vrátí naposledy matchovanou route (pro Tracy panel)
Praktický flow — Request Processing
1. Frontend Page Request
GET /some-page
↓ app/bootstrap.php (path-based dispatcher)
└─> /some-page → require 'website.php'
↓ app/routes/website.php
├─ pipe() zaregistruje middleware
├─ any('/{slug}', controller, 'frontend') zaregistruje route
└─ dispatch(request())
↓ core/Route/Route.php: dispatch()
├─ Matchuje pattern: /{slug}
├─ Extrahuje: slug = 'some-page'
└─ Aplikuje middleware: process($request, 'frontend')($handler)
↓ core/Middleware/Pipeline.php
├─ Spouští middleware v pořadí:
│ 1. proxyMiddleware
│ 2. defineWebMiddleware
│ 3. csrfMiddleware
│ 4. headersMiddleware
└─ Zavolá handler
↓ Controller/slug.php
└─ return html("<h1>$slug</h1>")
↓ Response zpět middleware stackem → emit()
2. Admin Controller Request
GET /ADMIN/admins
↓ app/routes/admin.php
├─ pipe() zaregistruje admin middleware
├─ get('/ADMIN/admins', [Controller::class, 'index'], 'admin')
└─ dispatch(request())
↓ dispatch() → instanciuje Controller, zavolá index($request, $params)
↓ Middleware: auth, session, permissions
↓ Controller::index() → return json([...])
↓ emit($response)
Architekturní Srovnání
| Aspekt | OLD (Laminas/Siler) | NEW (Petrovo) |
|---|---|---|
| Request | Siler\Diactoros\request() | Core\Http\request() |
| Response | Siler\Diactoros helpers | Core\Http{html, json, binary, ...} |
| Middleware | Siler\Stratigility\pipe() | Core\Middleware\pipe() |
| Pipeline | Siler\Stratigility\process() | Core\Middleware\process() |
| Routing | Siler\Route | Core\Route |
| Emit | HttpHandlerRunner\sapi_emit() | Core\Http\emit() |
| Handler Types | File path, Closure | File path, Closure, [Class::method] |
| HTTP Methods | get, post, any | get, post, put, patch, delete, any |
Shrnutí — Petrovo Custom Stack
Core\Http (core/Http/):
request()- PSR-7 ServerRequest z globals- Request helpery:
path(),method(),query(),body(),field(),header(),bearer(), ... - Response helpery:
html(),json(),translatedJson(),text(),binary(),redirect(),no_content() - Response modifikátory:
with_header(),with_headers(),with_status() emit()- Send response to clientisSecure(),HTTPStatus()- HTTP utility funkce
Core\Middleware (core/Middleware/Pipeline.php):
pipe()- Registrace middleware do named pipelineprocess()- Wrap middleware okolo handleruhandle()- Spuštění pipeline bez handleruwrap_handler()- Převod RequestHandlerInterface na callablereset(),get_pipelines()- Utility (testing, debugging)
Core\Route (core/Route/Route.php):
get(),post(),put(),patch(),delete(),any()- Registrace routesdispatch()- Match routes a spuštění s middleware- Tři handler types: file path, closure, [Class::method]
- Pattern matching s cachováním
matched_route(),reset(),routes()- Utility (debugging, testing)
Výsledek: Lightweight, funkcionální, bez Laminas/Siler overhead
- Vyšší výkon (žádné zabalování)
- Lepší čitelnost (vždy vidíš co se děje)
- Menší složitost (méně vrstvení)
- Ideální pro malé týmy s full codebase knowledge