Router a routování
Petrovo CMS používá vlastní registry-based router Core\Route.
Bootstrap flow
app/bootstrap.php nejdřív:
- načte prostředí a konfiguraci
- detekuje jazyk z URL prefixu
- pustí early blocking ochranu
- podle cesty vybere správný route soubor
Aktuální pre-dispatch:
if (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, '/ADMIN')) {
$response = require 'routes/admin.php';
} else {
$response = require 'routes/website.php';
}
Každý route soubor pak:
- zaregistruje middleware do pipelines (
pipe()) - zaregistruje routes (
get(),post(),any(),delete(), ...) - Zavolá
dispatch()aby matchoval a executnul
Route Registration
V route souborech registruješ routes pomocí get(), post(), any():
use function Core\Route\{get, post, any, dispatch};
use function Core\Middleware\pipe;
use function Core\Http\{request};
// 1. Zaregistruj middleware
pipe(proxyMiddleware, 'website');
// 2. Zaregistruj routes
get('/', __DIR__ . '/../website/slug.php', 'website');
get('/newsletter-email-activation', __DIR__ . '/../website/tags/newsletter/add-email/activation.php', 'website');
post('/cookieBar/updateLog', [CookieBarController::class, 'updateLog'], 'website');
// 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ě a dispatch je matchuje postupně. První match vyhraje.
Tři Typy Route Handleru
Petrovo Route dispatcher podporuje tři typy handleru:
1. File path handler
Handler je cesta k PHP souboru:
use function Core\Route\get;
get('/', __DIR__ . '/../website/slug.php', 'website');
get('/newsletter-email-activation', __DIR__ . '/../website/tags/newsletter/add-email/activation.php', 'website');
V includovaném souboru jsou dostupné route parametry i request kontext.
<?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 Core\Route\post;
use function Core\Http\json;
post('/search', function($request, $params) {
$query = $request->getQueryParams()['q'] ?? '';
$results = searchDatabase($query);
return json(['results' => $results]);
}, 'api');
3. Array notation [Class::class, 'method']
Handler je [ClassName::class, 'methodName'] a třída se automaticky instancuje:
use function Core\Route\get;
use function Core\Route\post;
use App\Modules\Admin\User\UserController;
get('/ADMIN/admins', [UserController::class, 'list'], 'admin');
post('/ADMIN/admins/filter', [UserController::class, 'filter'], 'admin');
get('/ADMIN/admins/{id}', [UserController::class, 'show'], 'admin');
post('/ADMIN/admins/{id}/invite', [UserController::class, 'invite'], '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 Core\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 Core\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'
// ...
}, 'website');
Wildcard routes
Prefix matching pro fallback routy:
get('/.*', $handler, 'website'); // 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 Core\Route\{get, post, dispatch};
use function Core\Middleware\pipe;
// Zaregistruj middleware do 'admin' pipeline
pipe(proxyMiddleware, 'admin');
pipe(sessionExpirationAdminMiddleware, '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()
Route soubory v projektu
Projekt běžně kombinuje:
- top-level
app/routes/*.php - modulové
app/Modules/*/router.php - podmodulové
app/Modules/*/*/routes.php
Příklady:
app/routes/admin.phpapp/routes/website.phpapp/routes/run.phpapp/Modules/Admin/router.phpapp/Modules/Menu/router.phpapp/Modules/Admin/User/routes.phpapp/Modules/Menu/Node/routes.php
To umožňuje držet routy blízko konkrétní business logiky.
Named pipelines: Každá pipeline je nezávislá.
// Frontend pipeline - light weight
pipe(proxyMiddleware, 'website');
// User pipeline - heavy weight
pipe(proxyMiddleware, 'admin');
pipe(sessionExpirationAdminMiddleware, 'admin');
pipe(loggingAdminMiddleware, 'admin');
// API pipeline - minimal
pipe(corsMiddleware, 'api');