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 - nejrychlejší PSR-7 implementace (Request/Response objekty)
- Petrovo\Http - Funkcionální API pro PSR-7 (helpers bez overhead)
- Petrovo\Middleware - Lightweight PSR-15 pipeline s pojmenovanými kanály
- Petrovo\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
- 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 function Petrovo\Http\request;
// Vytvoří RequestInterface ze superglobálních proměnných
// Automaticky zpracovává proxy headers (X-Forwarded-*)
$request = request();
Interně:
$_GET // Uloženo v request query parameters
$_POST // Uloženo v request parsed body
$_SERVER // Uloženo v request server params
$_COOKIE // Uloženo v request 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ů.
Response helpers
Petrovo poskytuje funkcionální helpery pro vytváření Response objektů:
use function Petrovo\Http\{html, json, text, redirect, no_content, emit};
// HTML response
html('<p>Obsah</p>');
// JSON response
json(['data' => 'value']);
// Textová response
text('Plain text');
// Redirect
redirect('/new-url', 301);
// Empty response (204 No Content)
no_content();
// Emit response to client
emit($response);
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 Petrovo\Middleware\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. Thread-safe: Používá statickou storage namísto globálů - funguje v FrankenPHP, Swoole, RoadRunner.
Spuštění pipeline - process() a handle()
Existují dva způsoby spuštění middleware:
process() - Wrappuje middleware okolo route handleru
use function Petrovo\Middleware\process;
$handler = fn($request, $params) => require __DIR__ . '/controller.php';
$middlewareWrappedHandler = process($request, 'frontend')($handler);
$response = $middlewareWrappedHandler->handle($request);
Middleware se aplikují pouze na konkrétní route.
handle() - Spustí middleware pipeline bez handleru
use function Petrovo\Middleware\handle;
$response = handle($request, 'frontend');
Všechna middleware se spustí a vrátí finální response.
Výstup - emit()
emit() pošle response zpět do prohlížeče:
use function Petrovo\Http\emit;
emit($response);
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 → Petrovo\Middleware) │
│ ├─ Zaregistrují routes │
│ │ (get/post/any → Petrovo\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 Petrovo\Route\get;
// Handler je soubor - bude vyžadován s dostupnými parametry
get('/{slug}', __DIR__ . '/../Controllers/slug.php', 'frontend');
V Controllers/slug.php:
<?php
// $slug parametr je dostupný z route
// Vrátit Response objekt
return html("<h1>$slug</h1>");
2. Closure Handler
use function Petrovo\Route\post;
// Handler je inline funkce
post('/search', function($request, $params) {
$query = $params['query'] ?? null;
$results = searchDatabase($query);
return json($results);
}, 'api');
3. Array Notation (Class::method) - NOVÝ
use function Petrovo\Route\get;
use App\Admin\Controllers\AdminsController;
// Handler je [ClassName::class, 'methodName']
// Třída se automaticky instancuje
get('/ADMIN/admins', [AdminsController::class, 'index'], 'admin');
post('/ADMIN/admins', [AdminsController::class, 'store'], 'admin');
V App\Admin\Controllers\AdminsController:
<?php
class AdminsController {
public function index($request, $params) {
return json($this->getAllAdmins());
}
public function store($request, $params) {
$data = $request->getParsedBody();
return $this->createAdmin($data);
}
}
Výhody array notation:
- ✅ IDE autocomplete
- ✅ Static analysis (PHPStan)
- ✅ Explicitní a čitelný kód
- ✅ Bez require_fn() wrapperu
Praktický flow - Request Processing
1. Frontend Page Request
GET /some-page
↓ app/bootstrap.php (path-based dispatcher)
└─> /some-page → require 'app-frontend.php'
↓ app/routes/app-frontend.php
├─ pipe() zaregistruje middleware
├─ any('/{slug}', controller, 'frontend') zaregistruje route
└─ dispatch(request()) // Router: matchuje, aplikuje middleware
↓ core/Route/Route.php: dispatch()
├─ Matchuje pattern: /{slug}
├─ Extrahuje: slug = 'some-page'
└─ Wrappuje handler: process($request, 'frontend')($handler)
↓ core/Middleware/Pipeline.php
├─ Spouští middleware v pořadí:
│ 1. proxyMiddleware
│ 2. defineWebMiddleware
│ 3. sanitizationMiddleware
│ 4. csrfMiddleware
│ 5. headersMiddleware
└─ Zavolá handler
↓ Controllers/slug.php
└─ return html("<h1>$slug</h1>") // Response
↓ Response zpět middleware stackem
└─ Middleware stack automaticky emituje: headers + body
2. Data Endpoint Request
GET /getData/Users/list?id=123
↓ app/bootstrap.php
└─> /getData/... → require 'app/routes/data.php'
↓ app/routes/data.php
├─ pipe() zaregistruje middleware (bez CSRF, jen CORS)
├─ any('/getData/.*', closure, 'data')
└─ dispatch(request())
↓ core/Route/Route.php: dispatch()
├─ Matchuje pattern: /getData/.*
└─ Wrappuje handler: process($request, 'data')($handler)
↓ Middleware stack
├─ proxyMiddleware
├─ defineWebMiddleware
├─ sanitizationMiddleware
└─ Zavolá handler
↓ Closure handler
├─ Mapuje URL → app/DATA/Users/list.php
└─ require → vrátí JSON response
↓ Response zpět middleware + emit()
3. CRON Request
GET /CRON/send-emails
↓ app/bootstrap.php
└─> /CRON/... → require 'app/routes/cron.php'
↓ app/routes/cron.php
├─ pipe() zaregistruje middleware (IP filtering)
├─ get('/CRON/{script}', function(...), 'cron')
└─ dispatch(request())
↓ core/Route/Route.php
├─ Matchuje: /CRON/{script}
└─ Wrappuje handler: process($request, 'cron')($handler)
↓ Middleware + Closure handler
└─ require "app/cron/send-emails.php"
↓ Response se automaticky emituje
Architekturní Srovnění
| Aspekt | OLD (Laminas) | NEW (Petrovo) |
|---|---|---|
| Request | Siler\Diactoros\request() | Petrovo\Http\request() |
| Response | Siler\Diactoros helpers | Petrovo\Http helpers |
| Middleware | Siler\Stratigility\pipe() | Petrovo\Middleware\pipe() |
| Pipeline | Siler\Stratigility\process() | Petrovo\Middleware\process() |
| Routing | Siler\Route | Petrovo\Route |
| Emit | HttpHandlerRunner\sapi_emit() | Petrovo\Http\emit() |
| Handler Types | File path, Closure | File path, Closure, [Class::method] |
Shrnutí - Petrovo Custom Stack
Petrovo CMS vlastní implementace:
✅ Petrovo\Http (core/Http/):
request()- PSR-7 ServerRequest z globalshtml(),json(),text(),redirect()- Response helpersemit()- Send response to client- Proxy header support (X-Forwarded-*)
✅ Petrovo\Middleware (core/Middleware/Pipeline.php):
pipe()- Register middleware to named pipelineprocess()- Wrap middleware around handlerhandle()- Execute middleware pipeline directly- Thread-safe storage (static, ne global)
✅ Petrovo\Route (core/Route/Route.php):
get(),post(),any()- Register routesdispatch()- Match routes and execute with middleware- Pattern matching with caching
- Tři handler types: file path, closure, [Class::method]
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