Overview

Packages

  • PHP
  • Router

Classes

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