Skip to content

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á:

  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 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 globals
  • html(), json(), text(), redirect() - Response helpers
  • emit() - Send response to client
  • Proxy header support (X-Forwarded-*)

Petrovo\Middleware (core/Middleware/Pipeline.php):

  • pipe() - Register middleware to named pipeline
  • process() - Wrap middleware around handler
  • handle() - Execute middleware pipeline directly
  • Thread-safe storage (static, ne global)

Petrovo\Route (core/Route/Route.php):

  • get(), post(), any() - Register routes
  • dispatch() - 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