Přeskočit obsah
V
Pro vývojáře
Architektura, konvence, jádro systému a bezpečnost
Jádro / Routování

Routování#

Jak se z adresy stane presenter a z parametrů zpátky hezká adresa. Jádro: app/Core/Routers/.

Tahle kapitola je návod: v první polovině je, jak to funguje, ve druhé postupy pro tři nejčastější úkoly — přidat routu, přidat hezký slug a najít, proč odkaz vede jinam, než měl.

Dvě poloviny routeru#

Část Kde se definuje Příklad
Statické sekce v kódu, přes factory v DI /admin/…, /api/…, /cron/…, /script/…
Dynamické routy frontu v databázi (cms_system_routes) /inzeraty/<kategorie>, /clanky/<slug>

Masky frontových rout NEJSOU v kódu

Tohle je první věc, o kterou se člověk uhodí. Když hledáte, proč má výpis inzerátů zrovna takovou adresu, v app/ to nenajdete — je to řádek v databázi. V kódu je jen továrna, která z masky postaví routu a doplní jí překlady.

Router z databáze nečte přímo: postaví se snapshot tabulky rout (RouteTableBuilder) a ten se cachuje. Při zásahu do cache je sestavení routeru bez jediného SQL dotazu.

požadavek
RouterDispatcher ──► statické sekce (factory z DI)
   └─────────────► dynamické routy z RouteTableSnapshot
                        ├─ cache-hit  → 0 SQL
                        └─ miss/off   → RouteTableBuilder → DB

Co dělá továrna routy#

Maska sama o sobě neumí hezké adresy — jen parametry. Továrna k ní přidá překlady:

$route = new RestrictedTranslatableRoute($mask, [
    'module' => $module, 'presenter' => $presenter, 'action' => $action,
]);

// id stránky ⇄ slug v adrese
$route->addTranslation('slug',
    fn(int $id, string $loc) => $this->pageSlugTranslator->idToSlug($id, $loc),
    fn(string $slug, string $loc) => $this->pageSlugTranslator->slugToId($slug, $loc),
);

// `name` se v adrese objeví, ale odvozuje se z `id` — do odkazu se nepředává
$route->addDerivedParameter('name', 'id',
    fn(int $id) => $this->advertNameTranslator->idToName($id),
);
Prvek K čemu je
addTranslation obousměrný převod parametru: id → slug při skládání odkazu, slug → id při čtení adresy
addDerivedParameter parametr, který se do odkazu nepředává — dopočítá se z jiného (typicky název z id)
applyWhitelist omezí routu na stránky, kterým patří

Odvozený parametr = kratší volání odkazu

Šablona linkuje jen id; název inzerátu v adrese doplní router sám. Kdyby se předával, musela by ho každá šablona odněkud vzít — a při přejmenování by se rozešly.

Postup: přidávám novou frontovou routu#

  1. Řádek do cms_system_routes s maskou, modulem, presenterem a akcí. Masku piš tak, jak má vypadat adresa: inzeraty/<kategorie>[/p-<page>].
  2. Zvol továrnu. Když stačí prosté parametry, použij existující obecnou; když má být v adrese slug nebo název, potřebuješ továrnu s překladem (app/Core/Routers/Factories/<Modul>/).
  3. SQL do docs/sql/ jako idempotentní skript — routa je konfigurace, ne migrace schématu, ale patří k nasazení.
  4. Smaž cache rout (nebo počkej na TTL) — jinak jede pořád starý snapshot.
  5. Prokliknij obojí směry: adresu zadanou ručně (čte se) i odkaz vygenerovaný šablonou (skládá se). Nestačí jedno.

Výchozí hodnota v masce se z adresy VYPUSTÍ

o-<order=0> znamená, že /o-0 v adrese nikdy neuvidíte — Nette segment s výchozí hodnotou odstraní a příchozí /o-0 přesměruje (301) na tvar bez něj. Je to užitečné (jde tím vyjádřit „výchozí řazení = relevance“), ale kdo to neví, hledá chybu v presenteru.

Literál shodný s výchozí hodnotou routa spolkne

Když maska má <akce=detail> a někdo chce adresu …/detail, segment zmizí. Řeší se to jinou výchozí hodnotou, ne jinou šablonou.

Postup: chci v adrese hezký název místo id#

  1. Napiš překladač do app/Core/Routers/Translators/<Modul>/ — podědí AbstractTranslator a umí idToName(), případně obousměrně idToSlug()/slugToId().
  2. Zaregistruj ho v továrně routy: v konstruktoru $translator->addTargetFactory(self::class) a v create() ho zapoj přes addTranslation() nebo addDerivedParameter().
  3. Ověř obě strany. Skládání odkazu (id → text) selže tiše — vznikne adresa s číslem místo názvu. Čtení (text → id) selže hlasitě, 404.

Překladač musí umět i neznámou hodnotu

Smazaný nebo přejmenovaný záznam znamená, že starý odkaz odněkud zvenčí dorazí s hodnotou, kterou překladač nezná. Vrátit null je správně (skončí to 404); spadnout na výjimce ne.

Předehřátí překladače před linkováním

Komponenta, která skládá hodně odkazů, si překladač předehřeje — jinak se sáhne do databáze pro každý řádek výpisu zvlášť.

Postup: odkaz vede jinam, než jsem čekal#

Od nejlevnějšího:

  1. Podívej se na masku v databázi, ne do kódu. Devět z deseti překvapení je tam.
  2. Zkontroluj výchozí hodnoty v masce — segment s výchozí hodnotou se vypouští a příchozí adresa se na kanonický tvar přesměrovává.
  3. Ověř, jestli nepřebíjí jiná routa. Pořadí rozhoduje; routa s obecnější maskou a nižším pořadím spolkne adresu dřív, než se dostane na tu tvou.
  4. Smaž cache rout a zkus znovu — po zásahu do tabulky jede starý snapshot až do vypršení TTL.
  5. Teprve pak hledej v presenteru.

Osiřelá routa s obecnou maskou spolkne KAŽDOU adresu

Routa bez navázaných stránek dostane prázdný whitelist. Když má obecnou masku a nízké pořadí, chytí všechno na webu — a projeví se to jako „náhodně padají různé stránky", ne jako chyba routování.

Persistentní parametry se propisují do odkazů

Parametr označený jako persistentní se objeví i v odkazech, kde ho nikdo nechtěl. Vypadá to jako chyba routy, ale je to vlastnost presenteru.

Kanonizace a jazyk#

Věc Chování
Adresa v jiném než kanonickém tvaru 301 na kanonický (výchozí hodnoty vypuštěné)
Jazyk LocaleResolver + mapa locale → jazyk ze snapshotu
Hledaný výraz je v cestě (/clanky/s-<dotaz>), ne v query stringu

?search=… router zahodí

Přesměruje dřív, než se dostane k presenteru. Kdo přidává hledání, musí ho dát do masky — jinak parametr zmizí a nikde se to nezaloguje.

Cache rout#

Vlastní vypínač routerEnabled v config/Shared/cache.neon, nezávislý na hlavním vypínači cache. TTL routerTtl (výchozí 300 s) s rozptylem ±10 %.

TTL je strop staleness, ne řešení

Zápis do tabulky rout mimo aplikaci (ruční SQL) snapshotem nehne. Cache proto drží nanejvýš do vypršení TTL — a po ruční změně je čistší ji smazat, než čekat.

Kam sáhnout#

Chci Kde
změnit tvar adresy řádek v cms_system_routes
přidat překlad id ⇄ text app/Core/Routers/Translators/<Modul>/
zapojit překlad do routy app/Core/Routers/Factories/<Modul>/
statické sekce (admin/api/cron) app/Core/Routers/Factories/*RouteFactory.php
chování cache rout config/Shared/cache.neonrouterEnabled, routerTtl