Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php
- /*
- * Aktuální verzi, popis a příklady použití najdete na adrese: http://projekty.vize.name/router/
- */
- /**
- * Rozšířená verze. Hlavní změny:
- * - zjednodušené generování URL, které se liší jen hodnotou jediného parametru
- * - umožněny nepovinné části routy
- * - možnost vynechání části URL před zpracováním, doplnění prefixu do výstupní adresy
- * - automatické přesměrování z nepoužívaných URL na nové
- * - předá řízení libovolnému callbacku (funkce nebo třída+metoda; nadefinuje se jen přibližně, hodnoty se doplní z URL)
- *
- * @author Jakub Kulhan (původní verze), http://bukaj.netuje.cz/blog/jednoduchy-routing-v-php
- * @author Viktorie Halasů (rozšíření), http://projekty.vize.name/router/
- * @version 1.3
- * @package Router
- */
- class Router implements ArrayAccess {
- /** @var string Pro přesměrování, do tohoto parametru se má zkopírovat hodnota ze staré URL. */
- const COPY_OLD_VALUE = '@@copy';
- /** @var string Určuje, že tato část výstupní adresy se bude dynamicky dosazovat (polotovar). */
- const PREPARE = "\032";
- /** @var string Název klíče v poli $_GET, do kterého se v .htaccess ukládá URL (nepovinné). */
- protected $urlRouteKey = 'route';
- /** @var string Základ URL. */
- protected $baseUrl = '';
- /** @var string Část vstupní URL, která má být odstraněna před hledáním vhodné routy. */
- protected $ignoredUrlPart = '';
- /** @var string Řetězec, který se má přidat před každou výstupní URL. */
- protected $outputUrlPrefix = '';
- /** @var string Aktuální URL (to, co bylo předané metodě parseURL). */
- protected $currentUrl = '';
- /** @var array Parametry z rozebrané URL. */
- protected $params = array();
- /** @var array Výchozí hodnoty parametrů, pokud bude URL prázdná. */
- protected $defaultParams = array();
- /** @var array Parsované routy. */
- protected $routes = array();
- /** @var array Připravené šablony URL. */
- protected $urlTemplates = array();
- /** @var string Maska pro jméno kontroleru / metody / funkce, kterou bude volat metoda delegate. */
- protected $callbackMask = '';
- /** @var string Přeložené jméno callbacku. */
- protected $callback = '';
- /** @var string Callback pro případ chyby (neexistující metoda/třída/funkce). */
- protected $errorCallback = '';
- /**
- * Konstruktor
- * @param array $defaults Výchozí parametry pro celý router.
- */
- public function __construct(array $defaults = array()) {
- $this->setBaseUrl();
- $this->defaultParams = $defaults;
- }
- /**
- * Parsuje a přidá další routu.
- * @param string $route
- * @param array $defaults Pole výchozích hodnot.
- * @param int $flags Modifikátory routy (bitmask). FIXED | CI | REDIR
- * @param array $redir Pokud se přesměrovávají nepoužívané URL na nové (flag REDIR), musí obsahovat buď pole hodnot
- * pro vytvoření nové URL, anebo callback, který toto pole vrací.
- * @see Route::__construct()
- * @return Router (fluent interface)
- *
- * @throws InvalidArgumentException Pokud je nastavený flag REDIR, ale chybí údaj, kam se má přesměrovávat.
- */
- final public function addRoute($route, array $defaults = array(), $flags=0, $redir = null) {
- $class = $flags & Route::SIMPLE ? 'SimpleRoute' : 'Route';
- $this->routes[] = new $class($route, $defaults, $flags, $redir);
- return $this;
- }
- /**
- * Připraví URL jako pojmenovanou šablonu (= jedna hodnota se bude dynamicky doplňovat, ostatní zůstanou stejné).
- * @param string $name Jméno šablony
- * @param array $params Parametry pro vytvoření URL.
- * Variabilní parametr musí mít jako hodnotu konstantu Router::PREPARE. Pokud se pro tuto část routy používá vlastní regulár, je potřeba ke konstantě připojit i řetězec, který tomuto reguláru odpovídá.
- * @param mixed $query_string Query string (volitelně). Buď hotový řetězec, nebo pole [název]=>hodnota.
- * @param string $fragment Kotva.
- * @return Router (fluent interface)
- *
- * @throws InvalidArgumentException Pokud požadované jméno už existuje nebo zadané parametry neodpovídají žádné
- * routě nebo zadané jméno není řetězec.
- * @throws UnexpectedValueException Pokud nebyla určená variabilní část.
- */
- final public function prepareUrlTemplate($tplName, array $params, $queryString = '', $fragment = '') {
- if ($this->templateExists($tplName)) {
- throw new InvalidArgumentException("Router: Pojmenovaná šablona URL '$tplName' již existuje!");
- } elseif (!is_string($tplName)) {
- throw new InvalidArgumentException("Router: Jméno pro šablonu URL je špatného typu (očekáván řetězec).");
- }
- $queryString = $this->createQueryString($queryString);
- /* Najde parametry, které se budou dynamicky doplňovat. */
- $variables = array();
- foreach ($params as $name => $value) {
- /* Pokud nebyla zadaná hodnota pro regulár této části, použije se výchozí */
- if (strpos($value, self::PREPARE) !== false) {
- $value = str_replace(self::PREPARE, '', $value);
- $params[$name] = empty($value) ? Route::DEFAULT_VALUE : $value;
- $variables[$name] = self::PREPARE;
- }
- }
- /* Pokud nebyly zadané proměnné části ani v parametrech, ani v query string, varuje uživatele. */
- if (empty($variables) && strpos($queryString, self::PREPARE) === false) {
- trigger_error("Varování routeru: Šablona URL '$tplName' neobsahuje proměnnou část.", E_USER_WARNING);
- }
- /* Najde vhodnou routu a vytvoří šablonu */
- foreach ($this->routes as $route) {
- if ($route->hasFlag(Route::FIXED)) {
- continue;
- }
- if ($route->createUrlFromParams($params)) {
- if (!empty($variables)) {
- $route->addVariableUrlParts($variables);
- }
- $url = $this->outputUrlPrefix . $route->getLastCreatedUrl();
- if (!empty($queryString)) {
- $url = $this->insertParamsToUrl($url, $queryString);
- }
- if (!empty($fragment) || $fragment === 0) {
- $url .= '#' . $fragment;
- }
- $this->urlTemplates[$tplName] = $url;
- return $this;
- }
- }
- throw new InvalidArgumentException("Router: Zadané parametry neodpovídají žádné routě, URL nelze vytvořit.");
- }
- /**
- * Vytvoří hotovou URL podle pojmenované šablony
- * @param string $tplName Jméno šablony
- * @param string $value Řetězec, který se má doplnit
- * @return string URL
- *
- * @throws InvalidArgumentException Pokud neexistuje polotovar s tímto jménem, nebo zadaná hodnota není řetězec/číslo.
- */
- final public function urlFromTemplate($tplName, $value) {
- if (!is_scalar($value) || (is_object($value) && !method_exists($value, '__toString'))) {
- throw new InvalidArgumentException("Router: Zadaná hodnota je špatného typu (očekáván řetězec nebo objekt s metodou __toString).");
- } elseif (!$this->templateExists($tplName)) {
- throw new InvalidArgumentException("Router: Pojmenovaná URL '$tplName' neexistuje!");
- }
- return str_replace(self::PREPARE, (string) $value, $this->urlTemplates[$tplName]);
- }
- /**
- * Podle zadaných parametrů složí URL.
- * @param array $params Parametry. Musí být uvedeny i výchozí hodnoty (2.param addRoute), nepovinné části routy lze vynechat.
- * @param string|array $queryString Buď hotový řetězec, nebo pole [název]=>hodnota.
- * @param string $fragment Kotva
- * @return string
- */
- final public function url(array $params, $queryString = '', $fragment = '') {
- foreach ($this->routes as $route) {
- if ($route->createUrlFromParams($params)) {
- $url = $this->outputUrlPrefix . $route->getLastCreatedUrl();
- $queryString = $this->createQueryString($queryString);
- if (!empty($queryString)) {
- $url = $this->insertParamsToUrl($url, $queryString);
- }
- if (!empty($fragment) || $fragment === 0) {
- $url .= '#' . $fragment;
- }
- return $url;
- }
- }
- trigger_error("Router: Zadané parametry neodpovídají žádné routě, URL nelze vytvořit.", E_USER_WARNING);
- return '';
- }
- /**
- * Zpracuje dodatečné parametry do URL (query string), neescapuje, otazník nepřidává.
- * Vyhodí parametr, ve kterém je routa z mod_rewrite.
- * @param string|array $params Může být pole parametrů i hotový query string.
- * @return string
- */
- final protected function createQueryString($params) {
- if (!empty($params) && is_string($params)) {
- parse_str(ltrim($params, '?&'), $parsed);
- unset($parsed[$this->urlRouteKey]);
- return (!empty($parsed) ? urldecode(http_build_query($parsed, '', '&')) : '');
- }
- elseif (is_array($params)) {
- unset($params[$this->urlRouteKey]);
- return (!empty($params) ? urldecode(http_build_query($params, '', '&')) : '');
- }
- else
- return '';
- }
- /**
- * Vloží do URL další parametry.
- * @param string $url
- * @param string $queryString
- * @return string
- */
- final protected function insertParamsToUrl($url, $queryString) {
- $queryString = (strpos($url, '?') === false ? '?' : '&') . $queryString;
- if (($pos = strpos($url, '#')) === false) {
- return $url . $queryString;
- } else {
- return substr_replace($url, $queryString, $pos, 0);
- }
- }
- /**
- * Rozebere URL na parametry podle první vyhovující routy a vrátí výsledek. Pokud zadaná URL neodpovídá žádné routě, vyvolá varování a nastaví výchozí hodnoty.
- * @param string $url
- * @return array
- *
- * @throws UnexpectedValueException Pokud je tato routa přesměrovávaná a cíl je špatně zadaný.
- */
- final public function parseUrl($url) {
- $url = rtrim($url, '/\\');
- if (!empty($this->ignoredUrlPart)) {
- $url = str_replace($this->ignoredUrlPart, '', $url);
- }
- $this->currentUrl = $url;
- /* Nezadaná cesta nebo nejvyšší adresář webu - použijí se výchozí hodnoty (viz konstruktor). */
- if (empty($this->currentUrl)) {
- $this->setDefaultParams();
- } else {
- foreach ($this->routes as $route) {
- if ($route->matchUrl($this->currentUrl)) {
- $this->params = $route->getParsedUrl();
- if ($route->isRedirected()) {
- $this->redirectRoute($route);
- } else {
- break;
- }
- }
- }
- if (empty($this->params)) {
- trigger_error("Router: Zadaná URL neodpovídá žádné routě.", E_USER_WARNING);
- $this->setDefaultParams();
- }
- }
- $this->resolveCallbackName();
- return $this->params;
- }
- /**
- * Nastaví výchozí požadavek (podle hodnoty callbacku).
- */
- final protected function setDefaultParams() {
- $this->params = $this->defaultParams;
- }
- /**
- * Nastaví masku pro callback. Tam, kde se má doplnit parametr z URL, napište jeho jméno v ostrých závorkách.
- * Lze zadat jak funkci: "<func>", tak třídu + metodu: "<controller>Controller:<action>". Více hodnot oddělte čárkou.
- * @param string $mask
- * @return Router (fluent interface)
- *
- * @throws InvalidArgumentException Pokud vstup není řetězec.
- */
- final public function setCallbackMask($mask) {
- if (!is_string($mask)) {
- throw new InvalidArgumentException("Router::setCallbackMask() - Vstup je špatného typu (očekáván řetězec).");
- }
- $this->callbackMask = str_replace(' ', '', $mask);
- /* Pokud byla už dřív volaná parseUrl(), doplň hodnoty pro callback */
- if (!empty($this->params)) {
- $this->resolveCallbackName();
- }
- return $this;
- }
- /**
- * Nastaví callback, který se má použít v případě chyby (neexistující třídy/metody apod.) Syntax stejná jako pro setCallbackMask, ale bez doplňovaných hodnot.
- * @param string $callback
- * @return Router (fluent interface)
- *
- * @throws InvalidArgumentException Pokud vstup není řetězec.
- */
- final public function setErrorCallback($callback) {
- if (!is_string($callback)) {
- throw new InvalidArgumentException("Router::setErrorCallback() - Vstup je špatného typu (očekáván řetězec).");
- }
- $this->errorCallback = str_replace(' ', '', $callback);
- return $this;
- }
- /**
- * Vytvoří callback pro metodu delegate (z parametrů z URL podle zadané masky).
- *
- * @throws RuntimeException Pokud jsou v callbacku požadované parametry, které v routě chybí.
- */
- final protected function resolveCallbackName() {
- $tmp = $this->callbackMask;
- if (preg_match_all('~<([^>]+)>~', $tmp, $m, PREG_PATTERN_ORDER)) {
- foreach ($m[1] as $name) {
- if (!isset($this->params[$name])) {
- throw new RuntimeException("Router: V callbacku je požadovaný parametr '$name', ale v parametrech z URL chybí.");
- }
- $tmp = str_replace("<$name>", $this->params[$name], $tmp);
- }
- }
- $this->callback = $tmp;
- }
- /**
- * Vytvoří spustitelné callbacky z řetězce. Pokud se jméno třídy opakuje, použije jen jedinou instanci.
- * @param string $str
- * @return array
- *
- * @throws BadFunctionCallException Pokud callback neexistuje.
- */
- protected function callbackFromString($str) {
- $cb = array();
- $instances = array();
- foreach (explode(',', $str) as $part) {
- $tmp = explode(':', $part);
- /* Volá se třída + metoda */
- if (isset($tmp[1])) {
- list($class, $method) = $tmp;
- if (!class_exists($class)) {
- throw new BadFunctionCallException("Router: Třída '$class' neexistuje.");
- }
- if (!isset($instances[$class])) {
- $instances[$class] = new $class;
- }
- if (!method_exists($instances[$class], $method)) {
- throw new BadFunctionCallException("Router: Metoda '$class::$method' neexistuje.");
- }
- $cb[] = array($instances[$class], $method);
- /* Volá se funkce */
- } else {
- $func = $tmp[0];
- if (!function_exists($func)) {
- throw new BadFunctionCallException("Router: Funkce '$func' neexistuje.");
- }
- /* Funkce dostane všechny parametry z rozebrané URL. */
- $cb[] = $func;
- }
- }
- return $cb;
- }
- /**
- * Předá řízení. Nastavené metody/funkce dostanou parametry z rozebrané URL.
- *
- * @throws BadFunctionCallException Pokud callback neexistuje.
- */
- public function delegate() {
- try {
- foreach ($this->callbackFromString($this->callback) as $cb) {
- call_user_func($cb, $this->params);
- }
- } catch (BadFunctionCallException $e) {
- if (!empty($this->errorCallback)) {
- foreach ($this->callbackFromString($this->errorCallback) as $cb) {
- call_user_func($cb, $this->params);
- }
- } else {
- throw $e;
- }
- }
- }
- /**
- * Přesměruje na URL podle nastavení zadané routy.
- * @param Route $route
- *
- * @throws UnexpectedValueException Pokud něco selže během nastavení cílové URL.
- */
- private function redirectRoute(Route $route) {
- try {
- $redirParams = $route->getRedirParams();
- /* Zkopírování požadovaných hodnot ze současné URL */
- foreach ($redirParams as $name => $value) {
- if ($value === self::COPY_OLD_VALUE) {
- $redirParams[$name] = !empty($this->params[$name]) ? $this->params[$name] : '';
- }
- }
- $newUrl = $this->url($redirParams);
- $this->redirect($newUrl);
- die();
- } catch (Exception $e) {
- throw new UnexpectedValueException("Nelze přesměrovat routu. Popis chyby: ".$e->getMessage());
- }
- }
- /**
- * Přesměruje na zadanou URL.
- * @param string $newUrl
- * @param int $code HTTP kód odpovědi, výchozí hodnota odpovídá stavu "Moved Permanently".
- */
- protected function redirect($newUrl, $code = 301) {
- if (headers_sent()) {
- @ header("refresh:1;url=$newUrl");
- } else {
- @ header("Location: ".$newUrl, true, $code);
- }
- exit;
- }
- /**
- * Zjistí, jestli už existuje šablona URL s tímto jménem.
- * @param string $tplName Název
- * @return bool
- */
- final public function templateExists($tplName) {
- return isset($this->urlTemplates[$tplName]);
- }
- /**
- * Zjistí základní URL aplikace (bez koncového lomítka)
- */
- protected function setBaseUrl() {
- $dirName = dirname($_SERVER['PHP_SELF']);
- $dirName = rtrim($dirName, '/\\');
- $this->baseUrl = "http://$_SERVER[SERVER_NAME]" . $dirName;
- $this->baseUrl = str_replace($this->ignoredUrlPart, '', $this->baseUrl);
- }
- /**
- * Nastaví část, která se má ve všech vstupních URL ignorovat.
- * @param string $part
- * @return Router (fluent interface)
- *
- * @throws InvalidArgumentException Pokud vstup není řetězec.
- */
- final public function setIgnoredUrlPart($part) {
- if (!is_string($part)) {
- throw new InvalidArgumentException("Router: Zadaná část URL, která se má vynechat, je špatného typu (očekáván řetězec).");
- }
- $this->ignoredUrlPart = $part;
- $this->setBaseUrl();
- return $this;
- }
- /**
- * Nastaví prefix pro všechny výstupní URL.
- * @param string $prefix
- * @return Router (fluent interface)
- *
- * @throws InvalidArgumentException Pokud vstup není řetězec.
- */
- final public function setOutputUrlPrefix($prefix) {
- if (!is_string($prefix)) {
- throw new InvalidArgumentException("Router: Prefix pro výstupní URL je špatného typu (očekáván řetězec).");
- }
- $this->outputUrlPrefix = $prefix;
- return $this;
- }
- /**
- * Nastaví název parametru $_GET, do kterého se v .htaccess ukládá požadovaná URL. Výchozí hodnota je "route".
- * @param string $param
- * @return Router (fluent interface)
- *
- * @throws InvalidArgumentException Pokud vstup není řetězec.
- */
- final public function setRouteKey($param) {
- if (!is_string($param)) {
- throw new InvalidArgumentException("Router::setRouteKey() - Zadaná hodnota je špatného typu (očekáván řetězec).");
- }
- $this->urlRouteKey = $param;
- return $this;
- }
- /**
- * Vrací základní adresu aplikace (bez koncového lomítka)
- * @return string
- */
- final public function getBaseUrl() {
- return $this->baseUrl;
- }
- /**
- * Vrací URL aktuálního požadavku (bez koncového lomítka)
- * @return string
- */
- final public function getCurrentUrl() {
- return $this->currentUrl;
- }
- /**
- * Vrátí rozebranou URL.
- * @return array
- */
- final public function getParams() {
- return $this->params;
- }
- /**
- * Getter
- * @param string $propertyName
- * @return mixed
- */
- public function __get($propertyName) {
- $func = 'get'.ucfirst($propertyName);
- if (method_exists($this, $func)) {
- return $this->$func();
- } else {
- trigger_error("Router: Vlastnost '$propertyName' neexistuje, nebo není přístupná.", E_USER_NOTICE);
- }
- }
- /**
- * Setter není povolen.
- * @param string $propertyName
- * @param string $propertyValue
- */
- final public function __set($propertyName, $propertyValue) {
- trigger_error("Router: Vlastnost '$propertyName' neexistuje, nebo není veřejně přístupná.", E_USER_WARNING);
- }
- /* array access: -------------------- */
- /**
- * @see ArrayAccess::offsetSet()
- */
- public function offsetSet($offset, $value) {
- if ($value instanceof Route) {
- $this->routes[] = $value;
- } else {
- throw new InvalidArgumentException("Zadaná hodnota musí být instance třídy Route.");
- }
- }
- /**
- * @see ArrayAccess::offsetExists()
- */
- public function offsetExists($offset) {
- return isset($this->routes[$offset]);
- }
- /**
- * Odstranění rout není povoleno.
- * @see ArrayAccess::offsetUnset()
- */
- public function offsetUnset($offset) {
- throw new LogicException("Odstraňování rout není povoleno.");
- }
- /**
- * @see ArrayAccess::offsetGet()
- */
- public function offsetGet($offset) {
- return isset($this->routes[$offset]) ? $this->routes[$offset] : null;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement