Advertisement
Tori

Route (v.1.3)

Jun 5th, 2012
82
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 12.14 KB | None | 0 0
  1. <?php
  2.  
  3. /*
  4.  * Aktuální verzi, popis a příklady použití najdete na adrese: http://projekty.vize.name/router/
  5.  */
  6.  
  7.        
  8. /**
  9.  * Představuje jednu routu.
  10.  * Zadané reguláry pro jednotlivé části routy implicitně rozlišují velikost písmen.
  11.  *
  12.  * @author Jakub Kulhan (původní verze routeru), http://bukaj.netuje.cz/blog/jednoduchy-routing-v-php
  13.  * @author Viktorie Halasu (rozšíření), http://projekty.vize.name/router/
  14.  * @property-read array $redirParams Pole parametrů pro vytvoření URL, na kterou se tato routa přesměrovává.
  15.  * @property-read array $parsedUrl Pole parametrů z URL, která byla rozebrána podle této routy.
  16.  * @property-read string $lastCreatedUrl Poslední vytvořená URL.
  17.  * @package Router
  18.  * @version 1.3
  19.  */
  20.  
  21. class Route     {
  22.    
  23.     /** @var int Modifikátor. Určuje, že z této routy se nebude dělat polotovar. */
  24.     const FIXED = 1;
  25.    
  26.     /** @var int Modifikátor. Určuje, že tato routa nerozlišuje velká a malá písmena. */
  27.     const CI = 2;
  28.    
  29.     /** @var int Modifikátor. Požadavek odpovídající této routě se má ihned přesměrovat na novou URL. */
  30.     const REDIR = 4;
  31.    
  32.     /** @var int Modifikátor. Zadaná routa je pro URL z parametrů (a má se zpracovat třídou SimpleRoute). */
  33.     const SIMPLE = 8;
  34.    
  35.     /** @var string Výchozí regulár pro jednotlivé části routy (písmena bez diakritiky, číslice, podtržítko, tečka, pomlčka). */
  36.     const DEFAULT_REGEX = '[A-Za-z0-9_.-]+';
  37.    
  38.     /** @var string Hodnota, kterou zachytí výchozí regulár. */
  39.     const DEFAULT_VALUE = 'a';
  40.    
  41.     /** @var string Textová podoba této routy tak, jak byla zadaná. */
  42.     protected $source = '';
  43.    
  44.     /** @var bool Má se tato routa přesměrovávat? */
  45.     protected $isRedirected = false;
  46.    
  47.     /** @var int Modifikátory použité pro tuto routu (bitmask). */
  48.     protected $flags = 0;
  49.    
  50.     /** @var array Parametry z rozebrané routy. */
  51.     protected $params;
  52.  
  53.     /** @var array URL rozebraná podle této routy. */
  54.     protected $parsedUrl = array();
  55.    
  56.     /** @var string Regulár pro tuto routu. */
  57.     protected $regex;
  58.    
  59.    
  60.     /** @var array Přesměrování (pole parametrů pro vytvoření URL.) */
  61.     protected $redirParams;
  62.    
  63.     /** @var callback Callback pro přesměrování. */
  64.     protected $redirCallback;
  65.    
  66.        
  67.     /** @var string Formátovací řetězec pro URL. */
  68.     protected $urlFormat;
  69.    
  70.     /** @var array Parametry, ze kterých byla naposled vytvořena URL. */
  71.     protected $lastUrlParams;
  72.    
  73.     /** @var string Poslední vytvořená URL. */
  74.     protected $lastCreatedUrl = '';
  75.    
  76.    
  77.     /**
  78.      * Parsuje zadání routy.
  79.      * @param string $route
  80.      * @param array $defaults Pole výchozích hodnot.
  81.      * @param int $flags Modifikátory routy (bitmask). FIXED | CI | REDIR
  82.      * @param mixed $redir Pokud se přesměrovávají nepoužívané URL na nové (flag REDIR), musí obsahovat buď pole hodnot
  83.      * pro vytvoření nové URL, anebo callback, který toto pole vrací.
  84.      *  Callback funkce dostane jediný parametr - parsovanou routu (ze starého URL). Pokud je použité pole
  85.      * a některý prvek má hodnotu Router::COPY_OLD_VALUE, zkopíruje se do něj hodnota ze stejnojmenného prvku
  86.      * ve staré URL (např. "controller").
  87.      */
  88.     public function __construct($route, array $defaults = array(), $flags = 0, $redir = null)   {
  89.         $this->source = $route;
  90.         if (is_int($flags)) {
  91.             $this->flags = $flags;
  92.         }
  93.         $this->params = array(
  94.             'order'     => array(), /* pořadí parametrů v routě (podle toho se složí výstupní URL): název => pořadí */
  95.             'fromRoute' => array(), /* jen parametry z routy s doplněnými výchozími hodnotami: název => [value => null|hodnota, optional => true|false] */
  96.             'allValues' => array(), /* parametry z routy i výchozí par. */
  97.         );
  98.         $this->urlFormat = '';
  99.         $this->regex = '~^';
  100.        
  101.         $orderIndex = 0;
  102.         foreach ($this->parse($this->source) as $i => $part) {
  103.             $optional = false;
  104.             if ($i % 2 === 0) {
  105.                 $this->urlFormat .= $part;
  106.                 $this->regex .= preg_quote($part, '~');
  107.                
  108.             } else {
  109.                 list($name, $partRegex) = explode("\037", $part);
  110.                 /* escapování oddělovače, zpětné lomítko je potřeba escapovat dvakrát */
  111.                 $partRegex = preg_replace('#(?<!\\\\)~#', '\\~', $partRegex);
  112.                 $this->urlFormat .= '%s';
  113.                 /* Je tato část routy nepovinná? */
  114.                 if ($name{0} === '?')   {
  115.                     $optional = true;
  116.                     if (substr($this->regex, -1) === '/')   {
  117.                         $this->regex .= '?';
  118.                     }
  119.                     $name = substr($name, 1);
  120.                 }
  121.                
  122.                 /* Pokud není definovaný regulár pro tuto část routy, použije se výchozí */
  123.                 $this->regex .= ($optional ? '(?:' : '') . '(?<' . $name . '>' .
  124.                     (!empty($partRegex) ? $partRegex : self::DEFAULT_REGEX) .
  125.                     ')' . ($optional ? ')?' : '');
  126.                
  127.                 /* Uloží se jednotlivé části routy a jejich vlastnosti. */
  128.                 $this->params['order'][$name] = $orderIndex;
  129.                 $this->params['fromRoute'][$name] = array(
  130.                     'value' => null,
  131.                     'optional' => $optional,
  132.                 );
  133.                 $this->params['allValues'][$name] = null;
  134.             }
  135.             $orderIndex++;
  136.         }
  137.        
  138.         /* PCRE modif.: Unicode vždy, case-insensitive volitelně */
  139.         $this->regex .= '$~u' . ($this->hasFlag(self::CI) ? 'i' : '');
  140.        
  141.         /* Přidají se výchozí hodnoty nebo parametry, pokud byly zadány. */
  142.         if (!empty($defaults))  {
  143.             foreach ($defaults as $key => $val) {
  144.                 if (!isset($this->params['fromRoute'][$key]))   {
  145.                     $this->params['fromRoute'][$key]['value'] = $val;
  146.                     $this->params['fromRoute'][$key]['optional'] = false;
  147.                 }
  148.                 $this->params['allValues'][$key] = $val;
  149.             }
  150.         }
  151.        
  152.         if ($this->hasFlag(self::REDIR))    {
  153.             $this->resolveRedirection($redir);
  154.         }
  155.     }
  156.    
  157.  
  158.     /**
  159.      * Pokusí se rozebrat zadanou URL podle této routy.
  160.      * @param string $url
  161.      * @return bool
  162.      */
  163.     public function matchUrl($url)  {
  164.         if (preg_match($this->regex, $url, $urlParams)) {
  165.             $this->parsedUrl = $this->kmerge($this->params['allValues'], $urlParams);
  166.             return true;
  167.         }
  168.         $this->parsedUrl = array();
  169.         return false;
  170.     }
  171.    
  172.    
  173.     /**
  174.      * Podle této routy zkusí ze zadaných parametrů vytvořit URL. Parametry mohou být v libovolném pořadí, nezávisle na pořadí v routě.
  175.      * @param array $params Parametry. Musí být uvedeny i výchozí hodnoty (2.param konstruktoru). Nepovinné části routy nebo výchozí parametry pro celý router (1.param Router::__construct) lze vynechat.
  176.      * @return bool
  177.      */
  178.     public function createUrlFromParams(array $params)  {
  179.        
  180.         $vsparams = array();
  181.        
  182.         foreach ($this->params['fromRoute'] as $parName => $parProps)   {
  183.            
  184.             /* Pokud je v routě víc povinných částí, než bylo zadáno parametrů, máme špatnou routu. */
  185.             if (empty($params) && !$parProps['optional'])   {
  186.                 return false;
  187.             }
  188.            
  189.             if (isset($params[$parName]))   {
  190.                 /* Výchozí parametry musí mít stejnou hodnotu. */
  191.                 if ($parProps['value'] !== null && $params[$parName] != $parProps['value']) {
  192.                     return false;
  193.                 }                  
  194.                 /* Pokud parametr patří do routy (= není výchozí), použije se. */
  195.                 if (isset($this->params['order'][$parName]))    {
  196.                     $vsparams[$this->params['order'][$parName]] = $params[$parName];
  197.                 }
  198.                 unset($params[$parName]);
  199.             }
  200.             /* Parametr nebyl zadaný, patří do routy, ale je nepovinný: použije se prázdná hodnota. */
  201.             elseif ($parProps['optional'] === false)    {
  202.                 $vsparams[$this->params['order'][$parName]] = '';
  203.             }
  204.         }
  205.  
  206.         /* Pokud zbyly parametry, které nejsou v routě, máme špatnou routu. */
  207.         if (!empty($params))    {
  208.             $this->lastUrlParams = array();
  209.             $this->lastCreatedUrl = '';
  210.             return false;
  211.         }
  212.  
  213.         ksort($vsparams);
  214.        
  215.         /* Pro vsprintf: počet zástupných znaků v this->urlFormat a vložených hodnot se musí shodovat. */
  216.         $diff = count($this->params['order']) - count($vsparams);
  217.         if ($diff > 0)  {
  218.             $vsparams = array_merge($vsparams, array_fill(0, $diff, ''));
  219.         }
  220.  
  221.         /* Ověření hotové URL proti reguláru této routy. */
  222.         $url = rtrim(vsprintf($this->urlFormat, $vsparams), '/');
  223.         if (preg_match($this->regex, $url)) {
  224.             $this->lastUrlParams = $vsparams;
  225.             $this->lastCreatedUrl = $url;
  226.             return true;
  227.         } else {
  228.             $this->lastUrlParams = array();
  229.             $this->lastCreatedUrl = '';
  230.             return false;
  231.         }
  232.     }
  233.    
  234.    
  235.     /**
  236.      * Změní hodnotu některých částí u poslední vytvořené URL a vrátí novou URL (pro šablony URL).
  237.      * @param array $variables Proměnné části.
  238.      *
  239.      * @throws LogicException
  240.      */
  241.     public function addVariableUrlParts(array $variables)   {
  242.         if (empty($this->lastUrlParams))    {
  243.             throw new LogicException("Nelze nastavit proměnné parametry routy. Nejdřív se musí vytvořit URL podle routy.");
  244.         }
  245.         foreach ($variables as $name => $value) {
  246.             if (isset($this->lastUrlParams[$this->params['order'][$name]])) {
  247.                 $this->lastUrlParams[$this->params['order'][$name]] = $value;
  248.             }
  249.         }
  250.         $this->lastCreatedUrl = rtrim(vsprintf($this->urlFormat, $this->lastUrlParams), '/');
  251.     }
  252.            
  253.    
  254.     /**
  255.      * Zjistí, jestli má routa nastaven zadaný modifikátor
  256.      * @param int $flag
  257.      * @return bool
  258.      */
  259.     final public function hasFlag($flag)    {
  260.         return (is_int($flag) && $this->flags & $flag);
  261.     }
  262.    
  263.    
  264.     /**
  265.      * Vrací pole parametrů pro vytvoření URL (u přesměrovávané routy)
  266.      * @return array
  267.      */
  268.     final public function getRedirParams()  {
  269.         if (empty($this->redirParams))  {
  270.             $this->redirParams = $this->invokeRedirCallback();
  271.         }
  272.         return $this->redirParams;
  273.     }
  274.    
  275.    
  276.     /**
  277.      * Vrací rozebranou URL.
  278.      * @return array
  279.      */
  280.     final public function getParsedUrl()    {
  281.         return $this->parsedUrl;
  282.     }
  283.    
  284.    
  285.     /**
  286.      * Vrací naposled vytvořenou URL.
  287.      * @return string
  288.      */
  289.     final public function getLastCreatedUrl()   {
  290.         return $this->lastCreatedUrl;
  291.     }
  292.    
  293.    
  294.     /**
  295.      * Zjistí, jestli se tato routa má přesměrovávat.
  296.      * @return bool
  297.      */
  298.     final public function isRedirected()    {
  299.         return $this->isRedirected;
  300.     }
  301.    
  302.    
  303.     /**
  304.      * Parsuje zadanou routu.
  305.      * @param string $route
  306.      * @return array
  307.      */
  308.     protected function parse($route) {
  309.         return explode("\036", trim(
  310.             preg_replace(
  311.                 '~:(\??[A-Za-z0-9_]+)(?:<(.+?)>)?~',
  312.                 "\036\$1\037\$2\036",
  313.                 $route
  314.             ), "\036")
  315.         );
  316.     }
  317.    
  318.    
  319.     /**
  320.      * Sloučí dvě asoc. pole. Podobné jako array_merge, ale nepřepisuje hodnotu v prvním poli prázdným řetězcem ani NULL.
  321.      * @param array $arr1
  322.      * @param array $arr2
  323.      * @return array
  324.      */
  325.     final protected function kmerge($arr1, $arr2)   {
  326.         foreach ($arr2 as $key => $val) {
  327.             if (!isset($arr1[$key]))    {
  328.                 $arr1[$key] = $val;
  329.             } elseif ($val !== '' && $val !== null) {
  330.                 $arr1[$key] = $val;
  331.             }
  332.         }
  333.         return $arr1;
  334.     }
  335.    
  336.    
  337.     /**
  338.      * Nastaví přesměrování
  339.      * @param mixed $redir (Pole nebo callback).
  340.      *
  341.      * @throws InvalidArgumentException Pokud chybí cíl, nebo je špatného dat.typu
  342.      */
  343.     protected function resolveRedirection($redir = null)    {
  344.         if (empty($redir))  {
  345.             throw new InvalidArgumentException("Routa se má přesměrovávat, ale chybí cíl (4. parametr).");
  346.         }
  347.         if (is_array($redir) && !is_int(key($redir)))   {
  348.             $this->redirParams = $redir;
  349.         } else {
  350.             /* ověření pomocí is_callable() se pro úsporu prostředků provádí až v invokeRedirCallback() */
  351.             $this->redirCallback = $redir;
  352.         }
  353.         $this->isRedirected = true;
  354.     }
  355.    
  356.    
  357.     /**
  358.      * Volá callback pro přesměrovávanou routu a vrací jeho výsledek.
  359.      * @return array
  360.      *
  361.      * @throws RuntimeException Pokud je callback neplatný
  362.      * @throws LogicException
  363.      * @throws UnexpectedValueException Pokud callback nevrací pole.
  364.      */
  365.     protected function invokeRedirCallback()    {
  366.         if (!is_callable($this->redirCallback)) {
  367.             throw new RuntimeException("Routa se má přesměrovávat, ale zadaný callback je neplatný.");
  368.         } elseif (empty($this->parsedUrl))  {
  369.             throw new LogicException("Přesměrování routy callbackem nelze volat dříve, než byla parsovaná URL.");
  370.         }
  371.         $redirParams = call_user_func($this->redirCallback, $this->parsedUrl);
  372.         if (!is_array($redirParams))    {
  373.             throw new UnexpectedValueException("Callback pro přesměrování routy musí vracet pole.");
  374.         }
  375.         return $redirParams;
  376.     }
  377.    
  378.    
  379.     /**
  380.      * Vrací textové zadání této routy.
  381.      * @return string
  382.      */
  383.     public function toSource()  {
  384.         return $this->source;
  385.     }
  386. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement