Router a routování - Petrovo\Route
Petrovo CMS používá vlastní lightweight registry-based router (Petrovo\Route) bez Siler/Laminas overhead.
Bootstrap Flow
app/bootstrap.php provádí path-based pre-dispatch - podle prefixu URL routuje na správný route soubor:
// app/bootstrap.php
$requestPath = path($request); // Get URI path
if (str_starts_with($requestPath, '/getData/')) {
$response = require 'routes/data.php';
} elseif (str_starts_with($requestPath, '/getFile/')) {
$response = require 'routes/file.php';
} elseif (str_contains($requestPath, '/RUN/')) {
$response = require 'routes/run.php';
} elseif (str_starts_with($requestPath, '/CRON/')) {
$response = require 'routes/cron.php';
} elseif (str_starts_with($requestPath, '/ADMIN')) {
$response = require 'routes/app-backend.php';
} else {
$response = require 'routes/app-frontend.php';
}
if (!$response) {
$response = html($_SERVER['REQUEST_URI'] . " — not found", 404);
}
emit($response);
Každý route soubor pak:
- Zaregistruje middleware do pipelines (
pipe()) - Zaregistruje routes (
get(),post(),any()) - Zavolá
dispatch()aby matchoval a executnul
Route Registration
V route souborech registruješ routes pomocí get(), post(), any():
use function Petrovo\Route\{get, post, any, dispatch};
use function Petrovo\Middleware\pipe;
use function Petrovo\Http\{request};
// 1. Zaregistruj middleware
pipe(proxyMiddleware, 'frontend');
pipe(sanitizationMiddleware, 'frontend');
// 2. Zaregistruj routes
get('/', __DIR__ . '/../Controllers/home.php', 'frontend');
get('/{slug}', __DIR__ . '/../Controllers/slug.php', 'frontend');
post('/search', [SearchController::class, 'handle'], 'frontend');
// 3. Dispatchuj
$response = dispatch(request());
// 4. Fallback 404
if (!$response) {
$response = html('Not found', 404);
}
return $response;
Důležité: Routes se zaregistrují globálně (v $_ROUTES) a dispatch je matchuje postupně. Prvá matchnuta route vyhraje.
Tři Typy Route Handleru
Petrovo Route dispatcher podporuje tři typy handleru:
1. File Path Handler
Handler je cesta k PHP souboru - bude vyžadován s dostupnými parametry:
use function Petrovo\Route\get;
get('/{slug}', __DIR__ . '/../Controllers/slug.php', 'frontend');
get('/user/{id}', __DIR__ . '/../Controllers/user-detail.php', 'frontend');
V Controllers/slug.php jsou dostupné parametry z route:
<?php
// $slug je dostupný automaticky z route parametru
// $request je PSR-7 ServerRequestInterface
// $params je array s route parametry ['slug' => 'some-page']
$page = fetchPage($slug);
return html("<h1>{$page->title}</h1>");
2. Closure Handler
Handler je inline funkce - má přístup k $request a $params:
use function Petrovo\Route\post;
use function Petrovo\Http\json;
post('/search', function($request, $params) {
$query = $request->getQueryParams()['q'] ?? '';
$results = searchDatabase($query);
return json(['results' => $results]);
}, 'api');
3. Array Notation: [Class::method] - NOVÝ
Handler je [ClassName::class, 'methodName'] - třída se automaticky instancuje:
use function Petrovo\Route\get;
use function Petrovo\Route\post;
use App\Admin\Controllers\AdminsController;
get('/ADMIN/admins', [AdminsController::class, 'index'], 'admin');
post('/ADMIN/admins', [AdminsController::class, 'store'], 'admin');
get('/ADMIN/admins/{id}', [AdminsController::class, 'show'], 'admin');
get('/ADMIN/admins/{id}/edit', [AdminsController::class, 'edit'], 'admin');
V App\Admin\Controllers\AdminsController:
<?php
namespace App\Admin\Controllers;
use function Petrovo\Http\json;
class AdminsController {
public function index($request, $params) {
$admins = $this->getAllAdmins();
return json(['data' => $admins]);
}
public function store($request, $params) {
$data = $request->getParsedBody();
$admin = $this->createAdmin($data);
return json(['id' => $admin->id], 201);
}
public function show($request, $params) {
$adminId = $params['id'] ?? null;
$admin = $this->getAdmin($adminId);
return json(['data' => $admin]);
}
}
Výhody array notation:
- ✅ IDE autocomplete pro názvy metod
- ✅ Static analysis (PHPStan, psalm)
- ✅ Explicitní a čitelný kód
- ✅ Třída se instancuje pouze při matchnutí route
HTTP Metody
use function Petrovo\Route\{get, post, put, delete, patch, any};
get('/path', $handler, 'pipeline'); // GET
post('/path', $handler, 'pipeline'); // POST
put('/path', $handler, 'pipeline'); // PUT
delete('/path', $handler, 'pipeline'); // DELETE
patch('/path', $handler, 'pipeline'); // PATCH
any('/path', $handler, 'pipeline'); // Jakýkoliv HTTP method
URL Parametry
Pojmenované parametry
Základní syntaxe - složené závorky {param} zachytí jakýkoliv řetězec (kromě /):
use function Petrovo\Route\get;
get('/{slug}', ...); // /some-page, /about, /contact
get('/user/{id}', ...); // /user/123, /user/john-doe
get('/blog/{year}/{month}/{day}', ...); // /blog/2025/02/01
Parametry jsou dostupné v handleru:
get('/{slug}', function($request, $params) {
$slug = $params['slug']; // 'some-page'
// ...
}, 'frontend');
Wildcard routes
Prefix matching pro fallback routy:
get('/.*', $handler, 'frontend'); // Matchuje cokoliv
any('/ADMIN.*', $handler, 'admin'); // /ADMIN, /ADMIN/users, atd.
Pořadí routů: Rooty se matchují v pořadí registrace. Zvláštní routy dej dřív, obecné dál.
// ✅ Správný pořadí
get('/user/profile', ...); // Specifické
get('/user/{id}', ...); // Obecné
get('/.*', ...); // Fallback
// ❌ Špatný pořadí - /.*` by matchlo všechno
get('/.*', ...);
get('/user/{id}', ...); // Nikdy se nevykonná!
Middleware Pipeline
Middleware se zaregistrují do pojmenované pipeline a aplikují se na všechny routy v té pipeline:
use function Petrovo\Route\{get, post, dispatch};
use function Petrovo\Middleware\pipe;
// Zaregistruj middleware do 'admin' pipeline
pipe(proxyMiddleware, 'admin');
pipe(sessionExpirationAdminMiddleware, 'admin');
pipe(sanitizationMiddleware, 'admin');
pipe(headersMiddleware, 'admin');
// Všechny routy v 'admin' pipeline projdou middleware
get('/ADMIN/users', [UsersController::class, 'index'], 'admin');
post('/ADMIN/users', [UsersController::class, 'store'], 'admin');
get('/ADMIN/users/{id}', [UsersController::class, 'show'], 'admin');
// dispatch() aplikuje middleware pipeline na matchnutou routu
$response = dispatch(request());
Pořadí middleware: Middleware se spouští v pořadí registrace. Middleware se volá:
- Request → Middleware 1 → Middleware 2 → ... → Handler → Response
- Handler vrátí response
- Response zpět: ... → Middleware 2 → Middleware 1 → emit()
Named pipelines: Každá pipeline je nezávislá - můžeš mít různá middleware pro frontend/admin/api:
// Frontend pipeline - light weight
pipe(proxyMiddleware, 'frontend');
pipe(sanitizationMiddleware, 'frontend');
// Admin pipeline - heavy weight
pipe(proxyMiddleware, 'admin');
pipe(sessionExpirationAdminMiddleware, 'admin');
pipe(sanitizationMiddleware, 'admin');
pipe(loggingAdminMiddleware, 'admin');
// API pipeline - minimal
pipe(corsMiddleware, 'api');
pipe(sanitizationMiddleware, 'api');