Skip to content

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:

  1. Zaregistruje middleware do pipelines (pipe())
  2. Zaregistruje routes (get(), post(), any())
  3. 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á:

  1. Request → Middleware 1 → Middleware 2 → ... → Handler → Response
  2. Handler vrátí response
  3. 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');