[ Webhosting ProFiTux.cz ] [ www.battleground.cz ] [ Onanie ] [ mp3 zdarma ] [ Jura Impressa F50 ] [ registr dlužníků ] [ Sex po telefonu Tě udělá ] [ NákupMánia.cz ] [ Online hry zdarma ] [ Bunda ]
[ Sex po telefonu 100% živě ] [ Horoskop na rok 2010 ] [ Totální MASAKR ] [ Sex po telefonu 100% živě ] [ Zahrada-Trávníky.cz ] [ Tepláky ] [ Sex po telefonu 100% živě ] [ Andělské karty ] [ Exkluzivní porcované čaje ]

Routujeme... obráceně — jakubův notes – programování a vejšplechty

Píšu o:

Je sice hezké, když se nám podaří z adresy získat pole parametrů, ale ještě hezčí je, pokud dokážeme z pole parametrů získat zpátky adresu. Tento článek navazuje na a zároveň rozšiřuje ten předchozí, proto si doporučuji nejdříve přečíst „první část“.

Používat na generování adres router oproti přímému výpisu má jednu nespornou výhodu – se změnou rout se změní zároveň i všechny adresy v aplikaci, což je prostě paráda v porovnání s tím, když by se adresy musely měnit růčo – ono by to bylo poznat i u těch malých aplikací, navíc, komu by se zo chtělo dělat? :o)

Takže princip fungování rout zůstává stejný z předchozího článku, ale jelikož při generování adresy je potřeba mít k dispozici všechny routy, rozhodl jsem se, že bude lepší zapouzdřit funkčnost do nějaké třídy – v našem případě Router. Bude obsahovat pouze jednu vlastnost – privátní pole rout routes. Dále pak tři veřejné metody – addRoute() k přidání nové routy, match() k získání pole parametrů z adresy a uri() k získání adresy z pole parametrů – a ještě privátní parseRoute(), jejíž účel je doufám jasný :o)

addRoute() a match() jsou nudné.

public function addRoute($route, array $defaults = array())
{
    $strf = '';
    $regex = '~^';
    $map = array();

    foreach ($this->parseRoute($route) as $x => $part) {
        if ($x % 2 == 0) {
            $strf .= $part;
            $regex .= preg_quote($part, '~');
        } else {
            list($name, ) = explode("\037", $part);
            $strf .= '%s';
            $regex .= '(?<' . $name . '>' .
                (empty($part_regex) ? '[A-Za-z0-9-_\\.]+' : $part_regex) .
                ')';

            $map[] = $name;
        }
    }

    $regex .= '$~';
    $from_route = array();
    if (count($map) > 0) {
        $from_route = array_combine(
            array_values($map), 
            array_fill(0, count($map), null)
        );
    }

    $this->routes[] = array(
        'regex' => $regex,
        'strf' => $strf,
        'map' => array_flip($map),
        'params' => array_merge($defaults, $from_route)
    );
}

addRoute() prostě jen naparsuje routu, vytvoří regulár a „zpáteční“ formátovací řetězec pro funkci vsprintf(), která je pak používána k získávání adresy v uri(). Bohužel, ale tady je potřeba již použít mapu kvůli tomu, aby mohly být parametry klíč-hodnota předávány uri() v libovolném pořadí. Netěší mě to, ale je to tak. Další možností by bylo si uschovat naparsovanou routu, ale to už se mi ta mapa zdá elegantnější :o)

public function match($uri)
{
    foreach ($this->routes as $route) {
        if (preg_match($route['regex'], $uri, $params)) {
            return array_merge($route['params'], $params);
        }
    }

    return null;
}

match() je to vlastně route_match() z předchozího článku, akorátež používá již udělané reguláry z addRoute() a prochází všechny routy jednu po druhé. Je tzv. „first-fit“, nebo-li najde první vyhovující routu. Ale teď již k tomu hlavnímu – k uri().

public function uri(array $params)
{
    foreach ($this->routes as $route) {
        $r_keys = array_keys($route['params']);
        sort($r_keys);
        $p_keys = array_keys($params);
        sort($p_keys);

        if (!(count(array_diff($r_keys, $p_keys)) < 1 &&
            count(array_diff($p_keys, $r_keys)) < 1 &&
            array_reduce(array_diff($route['params'], $params), 
            create_function('$a, $b', 'return ($a === null && $b === null) ? null : true;'
            )) === null)) {
            continue;
        }

        $vsparams = array();
        foreach ($params as $k => $v) {
            if (isset($route['map'][$k])) {
                $vsparams[$route['map'][$k]] = $v;
            }
        }

        ksort($vsparams);
        $url = vsprintf($route['strf'], $vsparams);

        if (preg_match($route['regex'], $url)) {
            return $url;
        }
    }

    return null;
}

Dlouho jsem přemýšlel nad tím, jakou nejlepší strategii pro vytváření adres zvolit. Některé frameworky si routy prostě pojmenují (Zend Framework), u jiných se rozhoduje podle pár parametrů (např. pomocí kombinace presenteru a view v Nette (pokud se nepletu); odhaduju, že v takových Rails to bude fungovat na podobném principu). Jenže tohle není součást žádného frameworku, takže se nelze specializovat jen na určitý okruh parametrů – chce to porovnávat všechny :o)

V addRoute() bylo kromě regulárů a „zpětného“ formátovacího řetězce vytvořeno ještě pole všech parametrů, které ta která routa může mít. U parametrů získávaných z routy byla počáteční hodnota nastavena na null. Musím říct, že jsem si s ověřováním parametrů k vytváření adresy hrál dlouho a pořád si nejsem jistý, jestli to nakonec vždycky udělá to, co se od toho očekává. Ale v podstatě existují tyto pravidla:

  1. Musí být předány všechny parametry, které routa má.

  2. Pokud má routa nastaveny nějaké výchozí parametry, tak se musí shodovat.

Nakonec jsem to vyřešil několika voláními array_diff() :o) Pak už je to jenom o tom, že se musí zmapovat parametry do pole pro vsprintf(). A nakonec ještě poslední ověření, jestli nově vytvořená routa odpovídá reguláru.

Doufám, že někomu tenhle článek pomohl, či alespoň ukázal možnost řešení routování v PHP. Jak je vidět, není to vůbec těžké :o) Pokud by někdo přišel s lepším řešením, nechť se podělí, ať odkazem, či přímo, v komentářích.

Celý zdrojový kód.

Ze stejné skupiny

celá skupina…

Komentáře

S tímto jsem si velmi dlouho hrál, ale nakonec jsem od toho odstoupil. Narazil jsem na situaci, kdy ony routy jsou trochu komplikovanější.
Otázka zní, jak v tvém řešení přejít z jedné routy na druhou?
— napsal(a) Hrach (web, 4. 9. 2008, 22:10:42)
Hrach
> Otázka zní, jak v tvém řešení přejít z jedné routy na druhou?
Nevím, jestli jsem správně pochopil otázku. Takže možná budu odpovídat na něco úplně jiného, ale...
Jelikož vyhodnocování, jestli se použije ta která routa, závisí na předaných parametrech (na klíčích i na hodnotách), pokud ve výsledku zůstanou zachovány stejné parametry (naparsované z routy i defaultní), může se řetězec routy jakkoli měnit. Lepší bude asi příklad.
Řekněme, že máme na začátku nastavenou takovou klasickou routu typu:
$router->addRoute('/:modul/:kontroler/:akce');
Jenže v aplikaci jsme zjistili, že modul stejně vždycky využíváme ten defaultní (s příznačným názvem default), takže v adrese akorát zaclání. Generování odkazů v šabloně vypadá nějak takto:
<a href="<?php echo $router->uri(array('modul' => 'default', 'kontroler' => '...', 'akce' => '...')); ?>">...</a>
Aniž by bylo potřeba jakkoli měnit generování odkazů v šablonách, stačí jenom změnit na začátku routu a všechny odkazy se v aplikaci změní:
$router->addRoute('/:kontroler/:akce', array('modul' => 'default'));
— napsal(a) Jakub Kulhan (web, 4. 9. 2008, 22:44:54)
V Nette to funguje přesně podle těch dvou bodů, které popisuješ (tj. porovnání všech parametrů). Ještě je možné routu označit jako jednosměrnou a z generování ji tak vyřadit - to se týká hlavně rout, které existují z důvodu zpětné kompatibility.
Nicméně hledání vyhovující routy jsem chtěl trošku zoptimalizovat. Protože nejobvykleji lišící se parametry jsou presenter a view, routy se podle nich předkešují. Proto se může zdát, že se vybírá podle těchto parametrů, ale není to tak, z hlediska routeru je parametr jako parametr.
— napsal(a) David Grudl (web, 3. 10. 2008, 21:28:03)

Přidat komentář