Tak jsem si jednou zase řekl, že se porozhlédnu po PHP frameworcích – jestli se něco nezměnilo, jestli nemá cenu něco z toho, co tu máme, začít používat atp. A narazil jsem na jednu věc, jejíž řešení se mi moc nelíbilo, resp. se mi zdálo a zdá zbytečně složitě dělané a nepružné. Z nadpisu je určitě poznat, o co se jedná – ano, routing, routování atp.
Např. takový Zend na to hned vytahuje celou mašinerii objektů nejrůznějšího druhu, pro každý druh cesty má svou svou vlastní třídu apod. Někdo by mohl říct, že je to tak správně – je tu mnoho voleb, co zvolit pro jaký případ routy apod. Ale já, jak jinak, si to nemyslím. PHP je už tak pomalé a zatěžovat ho při každém požadavku prací, myslím že i navíc, jen abychom mohli spustit funkční kód, je podle mě napováženou.
Nebyl bych to já, kdybych hned nepřišel s něčím „lepším“ :o) A v čem je celý ten fígl? Použití pár regulárních výrazů, při čemž se využijí všechny jejich možnosti. Na POSIXové regulární výrazy tedy rovnou zapomeňte (funkce jako ereg(), ereg_replace() nechme dětem), protože k tomuhle budou potřeba Perl-compatibile výrazy, které nabízejí mnohem širší možnosti. K seznámení doporučuji seriál o PCRE na intervalu.
Tak ale vrhněme se již na to. Routy jsou většinou zapisovány jako sekvence znaků, kde některé sekvence mohou mít speciální význam. Např. takové :nazev značí, že tady bude nějaká proměnná část. Hvězdička může sloužit k tomu, aby řekla, že tady může být cokoli. Prvního se budeme držet i my. Hvězdičku jsem v životě nevyužil, takže to vynechám, ale „doimplementace“ by byla jednoduchá. Přidáme však ještě jedno rozšíření, které jsem našel u Háefka a je mi velice sympatické. A to syntaxe :nazev{[a-z]+}, kde se ve složených závorkách za „proměnnou“ může udat regulární výraz, místo toho, aby bylo pole s mapováním regulárních výrazů a názvů někde vedle. Jen bych ji pro jednoduchost implementace trochu pozměnil – místo složených závorek použil třebas špičaté (< a >). Hlavním důvodem je to, že špičaté nejsou žádným řídícím znakem v regulárních výrazech (no, dobře v konstrukcích (?<=) a (?>!) se vyskytují :o)), zato složené se používají pro určení počtu opakování (jehož použití bude nejspíš častější než ta tvrzení o předchozím), a taky se v URL moc nevyskytují, takže se ani moc nebudou vyskytovat v routách, a tedy budou opravdu sloužit k označené regulárních výrazu za proměnnou. Takže nová verze je :nazev<[a-z]+>. A nějaká komplexnější routa by mohla vypadat následovně:
/moje/projekty/blog/:lang<[a-zA-Z]{2}>/:module/:controller/:action/:page<\d+>
Abychom s takovou routou mohli něco dělat, musíme ji nejdříve rozparsovat. Nejdříve jsem pro to chtěl napsat nějaký jednoduchý parser (resp. jsem ho i napsal :o)) ale pak jsem si vzpomněl na poučku, kterou jsem si slíbil, že se budu řídit: „PHP je šnek, proto co můžeš přenechat k udělání něčemu jinému, tak to tomu taky nech.“ Takže použijeme regulární výrazy:
<?php
function parse_route($route)
{
return explode("\036", trim(
preg_replace(
'~:([A-Za-z0-9_]+)(?:<(.+?)>)?~',
"\036\$1\037\$2\036",
$route
), "\036")
);
}
Řekněme, že chceme routu /clanek/:id<\d+>-:clanek a hle, tohle nepůjde. A ne, nechceme použít /clanek/:id<\d+>/:clanek ;o)
Nyní se routa rozpadla na části. Některé frameworky routu podobně „porcují“, ale dělají to podle lomítek (/). A jestli se jedná o „proměnnou“ část odvozují z toho, jestli má část na začátku :, což se mi nelíbí :o)
Tady taky routu naporcujeme, ale již podle toho, jestli se skutečně jedná o „proměnnou“, či „statickou“ část. Využíváme pro to řídící znak číslo 30 (osmičkově 36) – tzv. „record separator“. Regulární výrazy jsou pak odděleny od názvu proměnné pomocí znako numero 31 (osmičkově 37) – „unit separator“. Pokud by bylo potřeba tyto řídící znaky v routě použít pro něco jiného, lze samozřejmě využít nějaké jiné (i když, co by pohledávaly tyhle separátory v URL?). Navíc takto rozparsovaná routa má tu hezkou vlastnost, že proměnné části jsou v poli vždycky na sudé pozici (druhé, čtvrté…), resp. na lichém klíči (kvůli číslování od nuly).
A teď přijde to hlavní. Jak udělat jednoduché ověřování, jestli naše URI odpovídá téhle routě společně s jednoduchým vytáhnutím dat? Celé vylepšení spočívá v tom, že zatímco ostatní dělají různá mapování mezi závorkami a klíči, my využijeme klíče rovnou.
<?php
function create_route_regex($route)
{
$regex = '~^';
foreach (parse_route($route) as $x => $part) {
if ($x % 2 === 0) { // static part
$regex .= preg_quote($part, '~');
} else { // variable part
list($name, $part_regex) = explode("\31", $part);
$regex .= '(?<' . $name . '>' .
(empty($part_regex) ? '[A-Za-z0-9-_\\.]+' : $part_regex) .
')';
}
}
$regex .= '$~';
return $regex;
}
Použijeme PCRE závorkovou konstrukci (?<nazev>…), která závorku pojmenuje, čímž se vyhneme nutnosti dodatečného mapování. K zjištění, jestli se URI shoduje s routou a k získání parametrů prostě jen využijeme vytvořený regulární výraz:
<?php
function route_match($uri, $route, array $defaults = array())
{
if (preg_match(create_route_regex($route), $uri, $params)) {
return array_merge($defaults, $params);
}
return false;
}
Nechápu, že to nikdo nikde nevyužívá (alespoň jsem neviděl, že by to někdo využíval). Když se to třebas obalí nějakou třídou, bude použití ještě o fous hezčí. Prostě se na začátku nadefinují routy, pak se rozkáže match() a uvidíme, co z toho vyleze :o) No, není to jednoduché?
UPDATE 6. 9. 2008
Tak jsem už našel, kde se routování na stejném (přinejmenším alespoň v tom základu) principu používá – v Nette. Tenhle framework člověka napřestane udivovat :o)
Komentáře
Přidat komentář