PageRenderTime 38ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/Nette/Application/Routers/Route.php

https://github.com/DocX/nette
PHP | 779 lines | 491 code | 158 blank | 130 comment | 96 complexity | 11c29dc155a30f889349977311c3b119 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * Nette Framework
  4. *
  5. * Copyright (c) 2004, 2009 David Grudl (http://davidgrudl.com)
  6. *
  7. * This source file is subject to the "Nette license" that is bundled
  8. * with this package in the file license.txt.
  9. *
  10. * For more information please see http://nettephp.com
  11. *
  12. * @copyright Copyright (c) 2004, 2009 David Grudl
  13. * @license http://nettephp.com/license Nette license
  14. * @link http://nettephp.com
  15. * @category Nette
  16. * @package Nette\Application
  17. */
  18. /*namespace Nette\Application;*/
  19. require_once dirname(__FILE__) . '/../../Object.php';
  20. require_once dirname(__FILE__) . '/../../Application/IRouter.php';
  21. /**
  22. * The bidirectional route is responsible for mapping
  23. * HTTP request to a PresenterRoute object for dispatch and vice-versa.
  24. *
  25. * @author David Grudl
  26. * @copyright Copyright (c) 2004, 2009 David Grudl
  27. * @package Nette\Application
  28. */
  29. class Route extends /*Nette\*/Object implements IRouter
  30. {
  31. const PRESENTER_KEY = 'presenter';
  32. const MODULE_KEY = 'module';
  33. /** flag */
  34. const CASE_SENSITIVE = 256;
  35. const FULL_META = 128;
  36. /**#@+ uri type */
  37. const HOST = 1;
  38. const PATH = 2;
  39. const RELATIVE = 3;
  40. /**#@-*/
  41. /**#@+ key used in {@link Route::$styles} or metadata {@link Route::__construct} */
  42. const VALUE = 'value';
  43. const PATTERN = 'pattern';
  44. const FILTER_IN = 'filterIn';
  45. const FILTER_OUT = 'filterOut';
  46. const FILTER_TABLE = 'filterTable';
  47. /**#@-*/
  48. /**#@+ @internal fixity types - how to handle default value? {@link Route::$metadata} */
  49. const OPTIONAL = 0;
  50. const PATH_OPTIONAL = 1;
  51. const CONSTANT = 2;
  52. /**#@-*/
  53. /** @var bool */
  54. public static $defaultFlags = 0;
  55. /** @var array */
  56. public static $styles = array(
  57. '#' => array( // default style for path parameters
  58. self::PATTERN => '[^/]+',
  59. self::FILTER_IN => 'rawurldecode',
  60. self::FILTER_OUT => 'rawurlencode',
  61. ),
  62. '?#' => array( // default style for query parameters
  63. ),
  64. 'module' => array(
  65. self::PATTERN => '[a-z][a-z0-9.-]*',
  66. self::FILTER_IN => array(__CLASS__, 'path2presenter'),
  67. self::FILTER_OUT => array(__CLASS__, 'presenter2path'),
  68. ),
  69. 'presenter' => array(
  70. self::PATTERN => '[a-z][a-z0-9.-]*',
  71. self::FILTER_IN => array(__CLASS__, 'path2presenter'),
  72. self::FILTER_OUT => array(__CLASS__, 'presenter2path'),
  73. ),
  74. 'action' => array(
  75. self::PATTERN => '[a-z][a-z0-9-]*',
  76. self::FILTER_IN => array(__CLASS__, 'path2action'),
  77. self::FILTER_OUT => array(__CLASS__, 'action2path'),
  78. ),
  79. '?module' => array(
  80. ),
  81. '?presenter' => array(
  82. ),
  83. '?action' => array(
  84. ),
  85. );
  86. /** @var string */
  87. private $mask;
  88. /** @var array */
  89. private $sequence;
  90. /** @var string regular expression pattern */
  91. private $re;
  92. /** @var array of [value & fixity, filterIn, filterOut] */
  93. protected $metadata = array();
  94. /** @var array */
  95. protected $xlat;
  96. /** @var int HOST, PATH, RELATIVE */
  97. protected $type;
  98. /** @var int */
  99. protected $flags;
  100. /**
  101. * @param string URL mask, e.g. '<presenter>/<action>/<id \d{1,3}>'
  102. * @param array default values or metadata
  103. * @param int flags
  104. */
  105. public function __construct($mask, array $metadata = array(), $flags = 0)
  106. {
  107. $this->flags = $flags | self::$defaultFlags;
  108. if (!($this->flags & self::FULL_META)) {
  109. foreach ($metadata as $name => $def) {
  110. $metadata[$name] = array(self::VALUE => $def);
  111. }
  112. }
  113. $this->setMask($mask, $metadata);
  114. }
  115. /**
  116. * Maps HTTP request to a PresenterRequest object.
  117. * @param Nette\Web\IHttpRequest
  118. * @return PresenterRequest|NULL
  119. */
  120. public function match(/*Nette\Web\*/IHttpRequest $httpRequest)
  121. {
  122. // combine with precedence: mask (params in URL-path), fixity, query, (post,) defaults
  123. // 1) URL MASK
  124. $uri = $httpRequest->getUri();
  125. if ($this->type === self::HOST) {
  126. $path = '//' . $uri->getHost() . $uri->getPath();
  127. } elseif ($this->type === self::RELATIVE) {
  128. $basePath = $uri->getBasePath();
  129. if (strncmp($uri->getPath(), $basePath, strlen($basePath)) !== 0) {
  130. return NULL;
  131. }
  132. $path = (string) substr($uri->getPath(), strlen($basePath));
  133. } else {
  134. $path = $uri->getPath();
  135. }
  136. if ($path !== '') {
  137. $path = rtrim($path, '/') . '/';
  138. }
  139. if (!preg_match($this->re, $path, $matches)) {
  140. // stop, not matched
  141. return NULL;
  142. }
  143. // deletes numeric keys, restore '-' chars
  144. $params = array();
  145. foreach ($matches as $k => $v) {
  146. if (is_string($k) && $v !== '') {
  147. $params[str_replace('___', '-', $k)] = $v; // trick
  148. }
  149. }
  150. // 2) CONSTANT FIXITY
  151. foreach ($this->metadata as $name => $meta) {
  152. if (isset($params[$name])) {
  153. //$params[$name] = $this->flags & self::CASE_SENSITIVE === 0 ? strtolower($params[$name]) : */$params[$name]; // strtolower damages UTF-8
  154. } elseif (isset($meta['fixity']) && $meta['fixity'] !== self::OPTIONAL) {
  155. $params[$name] = NULL; // cannot be overwriten in 3) and detected by isset() in 4)
  156. }
  157. }
  158. // 3) QUERY
  159. if ($this->xlat) {
  160. $params += self::renameKeys($httpRequest->getQuery(), array_flip($this->xlat));
  161. } else {
  162. $params += $httpRequest->getQuery();
  163. }
  164. // 4) APPLY FILTERS & FIXITY
  165. foreach ($this->metadata as $name => $meta) {
  166. if (isset($params[$name])) {
  167. if (!is_scalar($params[$name])) {
  168. } elseif (isset($meta[self::FILTER_TABLE][$params[$name]])) { // applyies filterTable only to scalar parameters
  169. $params[$name] = $meta[self::FILTER_TABLE][$params[$name]];
  170. } elseif (isset($meta[self::FILTER_IN])) { // applyies filterIn only to scalar parameters
  171. $params[$name] = call_user_func($meta[self::FILTER_IN], (string) $params[$name]);
  172. if ($params[$name] === NULL && !isset($meta['fixity'])) {
  173. return NULL; // rejected by filter
  174. }
  175. }
  176. } elseif (isset($meta['fixity'])) {
  177. $params[$name] = $meta[self::VALUE];
  178. }
  179. }
  180. // 5) BUILD PresenterRequest
  181. if (!isset($params[self::PRESENTER_KEY])) {
  182. throw new /*\*/InvalidStateException('Missing presenter in route definition.');
  183. }
  184. if (isset($this->metadata[self::MODULE_KEY])) {
  185. if (!isset($params[self::MODULE_KEY])) {
  186. throw new /*\*/InvalidStateException('Missing module in route definition.');
  187. }
  188. $presenter = $params[self::MODULE_KEY] . ':' . $params[self::PRESENTER_KEY];
  189. unset($params[self::MODULE_KEY], $params[self::PRESENTER_KEY]);
  190. } else {
  191. $presenter = $params[self::PRESENTER_KEY];
  192. unset($params[self::PRESENTER_KEY]);
  193. }
  194. return new PresenterRequest(
  195. $presenter,
  196. $httpRequest->getMethod(),
  197. $params,
  198. $httpRequest->getPost(),
  199. $httpRequest->getFiles(),
  200. array(PresenterRequest::SECURED => $httpRequest->isSecured())
  201. );
  202. }
  203. /**
  204. * Constructs absolute URL from PresenterRequest object.
  205. * @param Nette\Web\IHttpRequest
  206. * @param PresenterRequest
  207. * @return string|NULL
  208. */
  209. public function constructUrl(PresenterRequest $appRequest, /*Nette\Web\*/IHttpRequest $httpRequest)
  210. {
  211. if ($this->flags & self::ONE_WAY) {
  212. return NULL;
  213. }
  214. $params = $appRequest->getParams();
  215. $metadata = $this->metadata;
  216. $presenter = $appRequest->getPresenterName();
  217. if (isset($metadata[self::MODULE_KEY])) {
  218. if (isset($metadata[self::MODULE_KEY]['fixity'])) {
  219. $a = strlen($metadata[self::MODULE_KEY][self::VALUE]);
  220. if (substr($presenter, $a, 1) !== ':') {
  221. return NULL; // module not match
  222. }
  223. } else {
  224. $a = strrpos($presenter, ':');
  225. }
  226. $params[self::MODULE_KEY] = substr($presenter, 0, $a);
  227. $params[self::PRESENTER_KEY] = substr($presenter, $a + 1);
  228. } else {
  229. $params[self::PRESENTER_KEY] = $presenter;
  230. }
  231. foreach ($metadata as $name => $meta) {
  232. if (!isset($params[$name])) continue; // retains NULL values
  233. if (isset($meta['fixity'])) {
  234. if (is_scalar($params[$name]) && strcasecmp($params[$name], $meta[self::VALUE]) === 0) {
  235. // remove default values; NULL values are retain
  236. unset($params[$name]);
  237. continue;
  238. } elseif ($meta['fixity'] === self::CONSTANT) {
  239. return NULL; // missing or wrong parameter '$name'
  240. }
  241. }
  242. if (!is_scalar($params[$name])) {
  243. } elseif (isset($meta['filterTable2'][$params[$name]])) {
  244. $params[$name] = $meta['filterTable2'][$params[$name]];
  245. } elseif (isset($meta[self::FILTER_OUT])) {
  246. $params[$name] = call_user_func($meta[self::FILTER_OUT], $params[$name]);
  247. }
  248. if (isset($meta[self::PATTERN]) && !preg_match($meta[self::PATTERN], $params[$name])) {
  249. return NULL; // pattern not match
  250. }
  251. }
  252. // compositing path
  253. $sequence = $this->sequence;
  254. $brackets = array();
  255. $required = NULL; // NULL for auto-optional
  256. $uri = '';
  257. $i = count($sequence) - 1;
  258. do {
  259. $uri = $sequence[$i] . $uri;
  260. if ($i === 0) break;
  261. $i--;
  262. $name = $sequence[$i]; $i--; // parameter name
  263. if ($name === '}') { // opening optional part
  264. $brackets[] = $uri;
  265. } elseif ($name[0] === '{') { // closing optional part
  266. $tmp = array_pop($brackets);
  267. if ($required < count($brackets) + 1) { // is this level optional?
  268. if ($name !== '{!') { // and not "required"-optional
  269. $uri = $tmp;
  270. }
  271. } else {
  272. $required = count($brackets);
  273. }
  274. } elseif ($name[0] === '?') { // "foo" parameter
  275. continue;
  276. } elseif (isset($params[$name]) && $params[$name] != '') { // intentionally ==
  277. $required = count($brackets); // make this level required
  278. $uri = $params[$name] . $uri;
  279. unset($params[$name]);
  280. } elseif (isset($metadata[$name]['fixity'])) { // has default value?
  281. if ($required === NULL && !$brackets) { // auto-optional
  282. $uri = '';
  283. } else {
  284. $uri = $metadata[$name]['defOut'] . $uri;
  285. }
  286. } else {
  287. return NULL; // missing parameter '$name'
  288. }
  289. } while (TRUE);
  290. // build query string
  291. if ($this->xlat) {
  292. $params = self::renameKeys($params, $this->xlat);
  293. }
  294. $sep = ini_get('arg_separator.input');
  295. $query = http_build_query($params, '', $sep ? $sep[0] : '&');
  296. if ($query != '') $uri .= '?' . $query; // intentionally ==
  297. // absolutize path
  298. if ($this->type === self::RELATIVE) {
  299. $uri = '//' . $httpRequest->getUri()->getAuthority() . $httpRequest->getUri()->getBasePath() . $uri;
  300. } elseif ($this->type === self::PATH) {
  301. $uri = '//' . $httpRequest->getUri()->getAuthority() . $uri;
  302. }
  303. if (strpos($uri, '//', 2) !== FALSE) {
  304. return NULL; // TODO: implement counterpart in match() ?
  305. }
  306. $uri = ($this->flags & self::SECURED ? 'https:' : 'http:') . $uri;
  307. return $uri;
  308. }
  309. /**
  310. * Parse mask and array of default values; initializes object.
  311. * @param string
  312. * @param array
  313. * @return void
  314. */
  315. private function setMask($mask, array $metadata)
  316. {
  317. $this->mask = $mask;
  318. // detect '//host/path' vs. '/abs. path' vs. 'relative path'
  319. if (substr($mask, 0, 2) === '//') {
  320. $this->type = self::HOST;
  321. } elseif (substr($mask, 0, 1) === '/') {
  322. $this->type = self::PATH;
  323. } else {
  324. $this->type = self::RELATIVE;
  325. }
  326. foreach ($metadata as $name => $meta) {
  327. if (array_key_exists(self::VALUE, $meta)) {
  328. $metadata[$name]['fixity'] = self::CONSTANT;
  329. }
  330. }
  331. // 1) PARSE QUERY PART OF MASK
  332. $this->xlat = array();
  333. $pos = strpos($mask, ' ? ');
  334. if ($pos !== FALSE) {
  335. preg_match_all(
  336. '/(?:([a-zA-Z0-9_.-]+)=)?<([^># ]+) *([^>#]*)(#?[^>]*)>/', // name=<parameter-name [pattern][#class]>
  337. substr($mask, $pos + 1),
  338. $matches,
  339. PREG_SET_ORDER
  340. );
  341. $mask = rtrim(substr($mask, 0, $pos));
  342. foreach ($matches as $match) {
  343. list(, $param, $name, $pattern, $class) = $match; // $pattern is unsed
  344. if ($class !== '') {
  345. if (!isset(self::$styles[$class])) {
  346. throw new /*\*/InvalidStateException("Parameter '$name' has '$class' flag, but Route::\$styles['$class'] is not set.");
  347. }
  348. $meta = self::$styles[$class];
  349. } elseif (isset(self::$styles['?' . $name])) {
  350. $meta = self::$styles['?' . $name];
  351. } else {
  352. $meta = self::$styles['?#'];
  353. }
  354. if (isset($metadata[$name])) {
  355. $meta = $metadata[$name] + $meta;
  356. }
  357. if (array_key_exists(self::VALUE, $meta)) {
  358. $meta['fixity'] = self::OPTIONAL;
  359. }
  360. unset($meta['pattern']);
  361. $meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? NULL : array_flip($meta[self::FILTER_TABLE]);
  362. $metadata[$name] = $meta;
  363. if ($param !== '') {
  364. $this->xlat[$name] = $param;
  365. }
  366. }
  367. }
  368. // 2) PARSE URI-PATH PART OF MASK
  369. $parts = preg_split(
  370. '/<([^># ]+) *([^>#]*)(#?[^>{}]*)>|(\{!?|\})/', // <parameter-name [pattern] [#class]> or { or }
  371. $mask,
  372. -1,
  373. PREG_SPLIT_DELIM_CAPTURE
  374. );
  375. $brackets = 0; // optional level
  376. $autoOptional = TRUE;
  377. $sequence = array();
  378. $i = count($parts) - 1;
  379. $re = '';
  380. do {
  381. array_unshift($sequence, $parts[$i]);
  382. $re = preg_quote($parts[$i], '#') . $re;
  383. if ($i === 0) break;
  384. $i--;
  385. $bracket = $parts[$i]; // { or }
  386. if ($bracket === '{' || $bracket === '}' || $bracket === '{!') {
  387. $brackets += $bracket[0] === '{' ? -1 : 1;
  388. if ($brackets < 0) {
  389. throw new /*\*/InvalidArgumentException("Unexpected '$bracket' in mask '$mask'.");
  390. }
  391. array_unshift($sequence, $bracket);
  392. $re = ($bracket[0] === '{' ? '(?:' : ')?') . $re;
  393. $i -= 4;
  394. continue;
  395. }
  396. $class = $parts[$i]; $i--; // validation class
  397. $pattern = trim($parts[$i]); $i--; // validation condition (as regexp)
  398. $name = $parts[$i]; $i--; // parameter name
  399. array_unshift($sequence, $name);
  400. if ($name[0] === '?') { // "foo" parameter
  401. $re = '(?:' . preg_quote(substr($name, 1), '#') . '|' . $pattern . ')' . $re;
  402. $sequence[1] = substr($name, 1) . $sequence[1];
  403. continue;
  404. }
  405. // check name (limitation by regexp)
  406. if (preg_match('#[^a-z0-9_-]#i', $name)) {
  407. throw new /*\*/InvalidArgumentException("Parameter name must be alphanumeric string due to limitations of PCRE, '$name' given.");
  408. }
  409. // pattern, condition & metadata
  410. if ($class !== '') {
  411. if (!isset(self::$styles[$class])) {
  412. throw new /*\*/InvalidStateException("Parameter '$name' has '$class' flag, but Route::\$styles['$class'] is not set.");
  413. }
  414. $meta = self::$styles[$class];
  415. } elseif (isset(self::$styles[$name])) {
  416. $meta = self::$styles[$name];
  417. } else {
  418. $meta = self::$styles['#'];
  419. }
  420. if (isset($metadata[$name])) {
  421. $meta = $metadata[$name] + $meta;
  422. }
  423. if ($pattern == '' && isset($meta[self::PATTERN])) {
  424. $pattern = $meta[self::PATTERN];
  425. }
  426. $meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? NULL : array_flip($meta[self::FILTER_TABLE]);
  427. if (isset($meta[self::VALUE])) {
  428. if (isset($meta['filterTable2'][$meta[self::VALUE]])) {
  429. $meta['defOut'] = $meta['filterTable2'][$meta[self::VALUE]];
  430. } elseif (isset($meta[self::FILTER_OUT])) {
  431. $meta['defOut'] = call_user_func($meta[self::FILTER_OUT], $meta[self::VALUE]);
  432. } else {
  433. $meta['defOut'] = $meta[self::VALUE];
  434. }
  435. }
  436. $meta[self::PATTERN] = "#(?:$pattern)$#A" . ($this->flags & self::CASE_SENSITIVE ? '' : 'i');
  437. // include in expression
  438. $re = '(?P<' . str_replace('-', '___', $name) . '>' . $pattern . ')' . $re; // str_replace is dirty trick to enable '-' in parameter name
  439. if ($brackets) { // is in brackets?
  440. if (!isset($meta[self::VALUE])) {
  441. $meta[self::VALUE] = $meta['defOut'] = NULL;
  442. }
  443. $meta['fixity'] = self::PATH_OPTIONAL;
  444. } elseif (isset($meta['fixity'])) { // auto-optional
  445. if (!$autoOptional) {
  446. throw new /*\*/InvalidArgumentException("Parameter '$name' must not be optional because parameters standing on the right side are not optional.");
  447. }
  448. $re = '(?:' . $re . ')?';
  449. $meta['fixity'] = self::PATH_OPTIONAL;
  450. } else {
  451. $autoOptional = FALSE;
  452. }
  453. $metadata[$name] = $meta;
  454. } while (TRUE);
  455. if ($brackets) {
  456. throw new /*\*/InvalidArgumentException("Missing closing '}' in mask '$mask'.");
  457. }
  458. $this->re = '#' . $re . '/?$#A' . ($this->flags & self::CASE_SENSITIVE ? '' : 'i');
  459. $this->metadata = $metadata;
  460. $this->sequence = $sequence;
  461. }
  462. /**
  463. * Returns mask.
  464. * @return string
  465. */
  466. public function getMask()
  467. {
  468. return $this->mask;
  469. }
  470. /**
  471. * Returns default values.
  472. * @return array
  473. */
  474. public function getDefaults()
  475. {
  476. $defaults = array();
  477. foreach ($this->metadata as $name => $meta) {
  478. if (isset($meta['fixity'])) {
  479. $defaults[$name] = $meta[self::VALUE];
  480. }
  481. }
  482. return $defaults;
  483. }
  484. /********************* Utilities ****************d*g**/
  485. /**
  486. * Proprietary cache aim.
  487. * @return string|FALSE
  488. */
  489. public function getTargetPresenter()
  490. {
  491. if ($this->flags & self::ONE_WAY) {
  492. return FALSE;
  493. }
  494. $m = $this->metadata;
  495. $module = '';
  496. if (isset($m[self::MODULE_KEY])) {
  497. if (isset($m[self::MODULE_KEY]['fixity']) && $m[self::MODULE_KEY]['fixity'] === self::CONSTANT) {
  498. $module = $m[self::MODULE_KEY][self::VALUE] . ':';
  499. } else {
  500. return NULL;
  501. }
  502. }
  503. if (isset($m[self::PRESENTER_KEY]['fixity']) && $m[self::PRESENTER_KEY]['fixity'] === self::CONSTANT) {
  504. return $module . $m[self::PRESENTER_KEY][self::VALUE];
  505. }
  506. return NULL;
  507. }
  508. /**
  509. * Rename keys in array.
  510. * @param array
  511. * @param array
  512. * @return array
  513. */
  514. private static function renameKeys($arr, $xlat)
  515. {
  516. if (empty($xlat)) return $arr;
  517. $res = array();
  518. $occupied = array_flip($xlat);
  519. foreach ($arr as $k => $v) {
  520. if (isset($xlat[$k])) {
  521. $res[$xlat[$k]] = $v;
  522. } elseif (!isset($occupied[$k])) {
  523. $res[$k] = $v;
  524. }
  525. }
  526. return $res;
  527. }
  528. /********************* Inflectors ****************d*g**/
  529. /**
  530. * camelCaseAction name -> dash-separated.
  531. * @param string
  532. * @return string
  533. */
  534. private static function action2path($s)
  535. {
  536. $s = preg_replace('#(.)(?=[A-Z])#', '$1-', $s);
  537. $s = strtolower($s);
  538. $s = rawurlencode($s);
  539. return $s;
  540. }
  541. /**
  542. * dash-separated -> camelCaseAction name.
  543. * @param string
  544. * @return string
  545. */
  546. private static function path2action($s)
  547. {
  548. $s = strtolower($s);
  549. $s = preg_replace('#-(?=[a-z])#', ' ', $s);
  550. $s = substr(ucwords('x' . $s), 1);
  551. //$s = lcfirst(ucwords($s));
  552. $s = str_replace(' ', '', $s);
  553. return $s;
  554. }
  555. /**
  556. * PascalCase:Presenter name -> dash-and-dot-separated.
  557. * @param string
  558. * @return string
  559. */
  560. private static function presenter2path($s)
  561. {
  562. $s = strtr($s, ':', '.');
  563. $s = preg_replace('#([^.])(?=[A-Z])#', '$1-', $s);
  564. $s = strtolower($s);
  565. $s = rawurlencode($s);
  566. return $s;
  567. }
  568. /**
  569. * dash-and-dot-separated -> PascalCase:Presenter name.
  570. * @param string
  571. * @return string
  572. */
  573. private static function path2presenter($s)
  574. {
  575. $s = strtolower($s);
  576. $s = preg_replace('#([.-])(?=[a-z])#', '$1 ', $s);
  577. $s = ucwords($s);
  578. $s = str_replace('. ', ':', $s);
  579. $s = str_replace('- ', '', $s);
  580. return $s;
  581. }
  582. /********************* Route::$styles manipulator ****************d*g**/
  583. /**
  584. * Creates new style.
  585. * @param string style name (#style, urlParameter, ?queryParameter)
  586. * @param string optional parent style name
  587. * @param void
  588. */
  589. public static function addStyle($style, $parent = '#')
  590. {
  591. if (isset(self::$styles[$style])) {
  592. throw new /*\*/InvalidArgumentException("Style '$style' already exists.");
  593. }
  594. if ($parent !== NULL) {
  595. if (!isset(self::$styles[$parent])) {
  596. throw new /*\*/InvalidArgumentException("Parent style '$parent' doesn't exist.");
  597. }
  598. self::$styles[$style] = self::$styles[$parent];
  599. } else {
  600. self::$styles[$style] = array();
  601. }
  602. }
  603. /**
  604. * Changes style property value.
  605. * @param string style name (#style, urlParameter, ?queryParameter)
  606. * @param string property name (Route::PATTERN, Route::FILTER_IN, Route::FILTER_OUT, Route::FILTER_TABLE)
  607. * @param mixed property value
  608. * @param void
  609. */
  610. public static function setStyleProperty($style, $key, $value)
  611. {
  612. if (!isset(self::$styles[$style])) {
  613. throw new /*\*/InvalidArgumentException("Style '$style' doesn't exist.");
  614. }
  615. self::$styles[$style][$key] = $value;
  616. }
  617. }