Created
April 7, 2011 18:23
-
-
Save pavelkucera/908373 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* This file is part of the Nette Framework (http://nette.org) | |
* | |
* Copyright (c) 2004, 2011 David Grudl (http://davidgrudl.com) | |
* | |
* For the full copyright and license information, please view | |
* the file license.txt that was distributed with this source code. | |
*/ | |
namespace Nette\Application; | |
use Nette, | |
Nette\String; | |
/** | |
* The bidirectional route is responsible for mapping | |
* HTTP request to a PresenterRoute object for dispatch and vice-versa. | |
* | |
* @author David Grudl | |
*/ | |
class Route extends Nette\Object implements IRouter | |
{ | |
const PRESENTER_KEY = 'presenter'; | |
const MODULE_KEY = 'module'; | |
/** flag */ | |
const CASE_SENSITIVE = 256; | |
/** @internal uri type */ | |
const HOST = 1, | |
PATH = 2, | |
RELATIVE = 3; | |
/** key used in {@link Route::$styles} or metadata {@link Route::__construct} */ | |
const VALUE = 'value'; | |
const PATTERN = 'pattern'; | |
const FILTER_IN = 'filterIn'; | |
const FILTER_OUT = 'filterOut'; | |
const FILTER_TABLE = 'filterTable'; | |
/** @internal fixity types - how to handle default value? {@link Route::$metadata} */ | |
const OPTIONAL = 0, | |
PATH_OPTIONAL = 1, | |
CONSTANT = 2; | |
/** @var bool */ | |
public static $defaultFlags = 0; | |
/** @var array */ | |
public static $styles = array( | |
'#' => array( // default style for path parameters | |
self::PATTERN => '[^/]+', | |
self::FILTER_IN => 'rawurldecode', | |
self::FILTER_OUT => 'rawurlencode', | |
), | |
'?#' => array( // default style for query parameters | |
), | |
'module' => array( | |
self::PATTERN => '[a-z][a-z0-9.-]*', | |
self::FILTER_IN => array(__CLASS__, 'path2presenter'), | |
self::FILTER_OUT => array(__CLASS__, 'presenter2path'), | |
), | |
'presenter' => array( | |
self::PATTERN => '[a-z][a-z0-9.-]*', | |
self::FILTER_IN => array(__CLASS__, 'path2presenter'), | |
self::FILTER_OUT => array(__CLASS__, 'presenter2path'), | |
), | |
'action' => array( | |
self::PATTERN => '[a-z][a-z0-9-]*', | |
self::FILTER_IN => array(__CLASS__, 'path2action'), | |
self::FILTER_OUT => array(__CLASS__, 'action2path'), | |
), | |
'?module' => array( | |
), | |
'?presenter' => array( | |
), | |
'?action' => array( | |
), | |
); | |
/** @var string */ | |
private $mask; | |
/** @var array */ | |
private $sequence; | |
/** @var string regular expression pattern */ | |
private $re; | |
/** @var array of [value & fixity, filterIn, filterOut] */ | |
private $metadata = array(); | |
/** @var array */ | |
private $xlat; | |
/** @var int HOST, PATH, RELATIVE */ | |
private $type; | |
/** @var int */ | |
private $flags; | |
/** | |
* @param string URL mask, e.g. '<presenter>/<action>/<id \d{1,3}>' | |
* @param array|string default values or metadata | |
* @param int flags | |
*/ | |
public function __construct($mask, $metadata = array(), $flags = 0) | |
{ | |
if (is_string($metadata)) { | |
$a = strrpos($metadata, ':'); | |
if (!$a) { | |
throw new \InvalidArgumentException("Second argument must be array or string in format Presenter:action, '$metadata' given."); | |
} | |
$metadata = array( | |
self::PRESENTER_KEY => substr($metadata, 0, $a), | |
'action' => $a === strlen($metadata) - 1 ? 'default' : substr($metadata, $a + 1), | |
); | |
} | |
$this->flags = $flags | self::$defaultFlags; | |
$this->setMask($mask, $metadata); | |
} | |
/** | |
* Maps HTTP request to a PresenterRequest object. | |
* @param Nette\Web\IHttpRequest | |
* @return PresenterRequest|NULL | |
*/ | |
public function match(Nette\Web\IHttpRequest $httpRequest) | |
{ | |
// combine with precedence: mask (params in URL-path), fixity, query, (post,) defaults | |
// 1) URL MASK | |
$uri = $httpRequest->getUri(); | |
if ($this->type === self::HOST) { | |
$path = '//' . $uri->getHost() . $uri->getPath(); | |
} elseif ($this->type === self::RELATIVE) { | |
$basePath = $uri->getBasePath(); | |
if (strncmp($uri->getPath(), $basePath, strlen($basePath)) !== 0) { | |
return NULL; | |
} | |
$path = (string) substr($uri->getPath(), strlen($basePath)); | |
} else { | |
$path = $uri->getPath(); | |
} | |
if ($path !== '') { | |
$path = rtrim($path, '/') . '/'; | |
} | |
if (!$matches = String::match($path, $this->re)) { | |
// stop, not matched | |
return NULL; | |
} | |
// deletes numeric keys, restore '-' chars | |
$params = array(); | |
foreach ($matches as $k => $v) { | |
if (is_string($k) && $v !== '') { | |
$params[str_replace('___', '-', $k)] = $v; // trick | |
} | |
} | |
// 2) CONSTANT FIXITY | |
foreach ($this->metadata as $name => $meta) { | |
if (isset($params[$name])) { | |
//$params[$name] = $this->flags & self::CASE_SENSITIVE === 0 ? strtolower($params[$name]) : */$params[$name]; // strtolower damages UTF-8 | |
} elseif (isset($meta['fixity']) && $meta['fixity'] !== self::OPTIONAL) { | |
$params[$name] = NULL; // cannot be overwriten in 3) and detected by isset() in 4) | |
} | |
} | |
// 3) QUERY | |
if ($this->xlat) { | |
$params += self::renameKeys($httpRequest->getQuery(), array_flip($this->xlat)); | |
} else { | |
$params += $httpRequest->getQuery(); | |
} | |
// 4) APPLY FILTERS & FIXITY | |
foreach ($this->metadata as $name => $meta) { | |
if (isset($params[$name])) { | |
if (!is_scalar($params[$name])) { | |
} elseif (isset($meta[self::FILTER_TABLE][$params[$name]])) { // applyies filterTable only to scalar parameters | |
$params[$name] = $meta[self::FILTER_TABLE][$params[$name]]; | |
} elseif (isset($meta[self::FILTER_IN])) { // applyies filterIn only to scalar parameters | |
$params[$name] = call_user_func($meta[self::FILTER_IN], (string) $params[$name]); | |
if ($params[$name] === NULL && !isset($meta['fixity'])) { | |
return NULL; // rejected by filter | |
} | |
} | |
} elseif (isset($meta['fixity'])) { | |
$params[$name] = $meta[self::VALUE]; | |
} | |
} | |
// 5) BUILD PresenterRequest | |
if (!isset($params[self::PRESENTER_KEY])) { | |
throw new \InvalidStateException('Missing presenter in route definition.'); | |
} | |
if (isset($this->metadata[self::MODULE_KEY])) { | |
if (!isset($params[self::MODULE_KEY])) { | |
throw new \InvalidStateException('Missing module in route definition.'); | |
} | |
$presenter = $params[self::MODULE_KEY] . ':' . $params[self::PRESENTER_KEY]; | |
unset($params[self::MODULE_KEY], $params[self::PRESENTER_KEY]); | |
} else { | |
$presenter = $params[self::PRESENTER_KEY]; | |
unset($params[self::PRESENTER_KEY]); | |
} | |
return new PresenterRequest( | |
$presenter, | |
$httpRequest->getMethod(), | |
$params, | |
$httpRequest->getPost(), | |
$httpRequest->getFiles(), | |
array(PresenterRequest::SECURED => $httpRequest->isSecured()) | |
); | |
} | |
/** | |
* Processes route metadata to presenter request params | |
* @param array | |
* @param array | |
* @return array | |
*/ | |
protected function processMetadata(array $metadata, array $params) | |
{ | |
foreach ($metadata as $name => $meta) { | |
if (!isset($params[$name])) continue; // retains NULL values | |
if (isset($meta['fixity'])) { | |
if (is_scalar($params[$name]) && strcasecmp($params[$name], $meta[self::VALUE]) === 0) { | |
// remove default values; NULL values are retain | |
unset($params[$name]); | |
continue; | |
} elseif ($meta['fixity'] === self::CONSTANT) { | |
return NULL; // missing or wrong parameter '$name' | |
} | |
} | |
if (!is_scalar($params[$name])) { | |
} elseif (isset($meta['filterTable2'][$params[$name]])) { | |
$params[$name] = $meta['filterTable2'][$params[$name]]; | |
} elseif (isset($meta[self::FILTER_OUT])) { | |
$params[$name] = call_user_func($meta[self::FILTER_OUT], $params[$name]); | |
} | |
if (isset($meta[self::PATTERN]) && !preg_match($meta[self::PATTERN], rawurldecode($params[$name]))) { | |
return NULL; // pattern not match | |
} | |
} | |
return $params; | |
} | |
/** | |
* @param array | |
* @param array | |
* @param Nette\WebUri | |
* @return string | |
*/ | |
protected function buildUri(array $metadata, array $params, Nette\Web\Uri $refUri) | |
{ | |
// compositing path | |
$sequence = $this->sequence; | |
$brackets = array(); | |
$required = 0; | |
$uri = ''; | |
$i = count($sequence) - 1; | |
do { | |
$uri = $sequence[$i] . $uri; | |
if ($i === 0) break; | |
$i--; | |
$name = $sequence[$i]; $i--; // parameter name | |
if ($name === ']') { // opening optional part | |
$brackets[] = $uri; | |
} elseif ($name[0] === '[') { // closing optional part | |
$tmp = array_pop($brackets); | |
if ($required < count($brackets) + 1) { // is this level optional? | |
if ($name !== '[!') { // and not "required"-optional | |
$uri = $tmp; | |
} | |
} else { | |
$required = count($brackets); | |
} | |
} elseif ($name[0] === '?') { // "foo" parameter | |
continue; | |
} elseif (isset($params[$name]) && $params[$name] != '') { // intentionally == | |
$required = count($brackets); // make this level required | |
$uri = $params[$name] . $uri; | |
unset($params[$name]); | |
} elseif (isset($metadata[$name]['fixity'])) { // has default value? | |
$uri = $metadata[$name]['defOut'] . $uri; | |
} else { | |
return NULL; // missing parameter '$name' | |
} | |
} while (TRUE); | |
// build query string | |
if ($this->xlat) { | |
$params = self::renameKeys($params, $this->xlat); | |
} | |
$sep = ini_get('arg_separator.input'); | |
$query = http_build_query($params, '', $sep ? $sep[0] : '&'); | |
if ($query != '') $uri .= '?' . $query; // intentionally == | |
// absolutize path | |
if ($this->type === self::RELATIVE) { | |
$uri = '//' . $refUri->getAuthority() . $refUri->getBasePath() . $uri; | |
} elseif ($this->type === self::PATH) { | |
$uri = '//' . $refUri->getAuthority() . $uri; | |
} | |
if (strpos($uri, '//', 2) !== FALSE) { | |
return NULL; // TODO: implement counterpart in match() ? | |
} | |
$uri = ($this->flags & self::SECURED ? 'https:' : 'http:') . $uri; | |
return $uri; | |
} | |
/** | |
* Constructs absolute URL from PresenterRequest object. | |
* @param PresenterRequest | |
* @param Nette\Web\Uri | |
* @return string|NULL | |
*/ | |
public function constructUrl(PresenterRequest $appRequest, Nette\Web\Uri $refUri) | |
{ | |
if ($this->isOneWay()) { | |
return NULL; | |
} | |
$params = $appRequest->getParams(); | |
$metadata = $this->metadata; | |
$presenter = $appRequest->getPresenterName(); | |
$params[self::PRESENTER_KEY] = $presenter; | |
if (isset($metadata[self::MODULE_KEY])) { // try split into module and [submodule:]presenter parts | |
$module = $metadata[self::MODULE_KEY]; | |
if (isset($module['fixity']) && strncasecmp($presenter, $module[self::VALUE] . ':', strlen($module[self::VALUE]) + 1) === 0) { | |
$a = strlen($module[self::VALUE]); | |
} else { | |
$a = strrpos($presenter, ':'); | |
} | |
if ($a === FALSE) { | |
$params[self::MODULE_KEY] = ''; | |
} else { | |
$params[self::MODULE_KEY] = substr($presenter, 0, $a); | |
$params[self::PRESENTER_KEY] = substr($presenter, $a + 1); | |
} | |
} | |
$params = $this->processMetadata($metadata, $params); | |
if (!$params) { | |
return NULL; | |
} | |
return $this->buildUri($metadata, $params, $refUri); | |
} | |
/** | |
* Parse mask and array of default values; initializes object. | |
* @param string | |
* @param array | |
* @return void | |
*/ | |
private function setMask($mask, array $metadata) | |
{ | |
$this->mask = $mask; | |
// detect '//host/path' vs. '/abs. path' vs. 'relative path' | |
if (substr($mask, 0, 2) === '//') { | |
$this->type = self::HOST; | |
} elseif (substr($mask, 0, 1) === '/') { | |
$this->type = self::PATH; | |
} else { | |
$this->type = self::RELATIVE; | |
} | |
foreach ($metadata as $name => $meta) { | |
if (!is_array($meta)) { | |
$metadata[$name] = array(self::VALUE => $meta, 'fixity' => self::CONSTANT); | |
} elseif (array_key_exists(self::VALUE, $meta)) { | |
$metadata[$name]['fixity'] = self::CONSTANT; | |
} | |
} | |
// PARSE MASK | |
// <parameter-name[=default] [pattern] [#class]> or [ or ] or ?... | |
$parts = String::split($mask, '/<([^>#= ]+)(=[^># ]*)? *([^>#]*)(#?[^>\[\]]*)>|(\[!?|\]|\s*\?.*)/'); | |
$this->xlat = array(); | |
$i = count($parts) - 1; | |
// PARSE QUERY PART OF MASK | |
if (isset($parts[$i - 1]) && substr(ltrim($parts[$i - 1]), 0, 1) === '?') { | |
// name=<parameter-name [pattern][#class]> | |
$matches = String::matchAll($parts[$i - 1], '/(?:([a-zA-Z0-9_.-]+)=)?<([^># ]+) *([^>#]*)(#?[^>]*)>/'); | |
foreach ($matches as $match) { | |
list(, $param, $name, $pattern, $class) = $match; // $pattern is not used | |
if ($class !== '') { | |
if (!isset(self::$styles[$class])) { | |
throw new \InvalidStateException("Parameter '$name' has '$class' flag, but Route::\$styles['$class'] is not set."); | |
} | |
$meta = self::$styles[$class]; | |
} elseif (isset(self::$styles['?' . $name])) { | |
$meta = self::$styles['?' . $name]; | |
} else { | |
$meta = self::$styles['?#']; | |
} | |
if (isset($metadata[$name])) { | |
$meta = $metadata[$name] + $meta; | |
} | |
if (array_key_exists(self::VALUE, $meta)) { | |
$meta['fixity'] = self::OPTIONAL; | |
} | |
unset($meta['pattern']); | |
$meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? NULL : array_flip($meta[self::FILTER_TABLE]); | |
$metadata[$name] = $meta; | |
if ($param !== '') { | |
$this->xlat[$name] = $param; | |
} | |
} | |
$i -= 6; | |
} | |
// PARSE PATH PART OF MASK | |
$brackets = 0; // optional level | |
$re = ''; | |
$sequence = array(); | |
$autoOptional = array(0, 0); // strlen($re), count($sequence) | |
do { | |
array_unshift($sequence, $parts[$i]); | |
$re = preg_quote($parts[$i], '#') . $re; | |
if ($i === 0) break; | |
$i--; | |
$part = $parts[$i]; // [ or ] | |
if ($part === '[' || $part === ']' || $part === '[!') { | |
$brackets += $part[0] === '[' ? -1 : 1; | |
if ($brackets < 0) { | |
throw new \InvalidArgumentException("Unexpected '$part' in mask '$mask'."); | |
} | |
array_unshift($sequence, $part); | |
$re = ($part[0] === '[' ? '(?:' : ')?') . $re; | |
$i -= 5; | |
continue; | |
} | |
$class = $parts[$i]; $i--; // validation class | |
$pattern = trim($parts[$i]); $i--; // validation condition (as regexp) | |
$default = $parts[$i]; $i--; // default value | |
$name = $parts[$i]; $i--; // parameter name | |
array_unshift($sequence, $name); | |
if ($name[0] === '?') { // "foo" parameter | |
$re = '(?:' . preg_quote(substr($name, 1), '#') . '|' . $pattern . ')' . $re; | |
$sequence[1] = substr($name, 1) . $sequence[1]; | |
continue; | |
} | |
// check name (limitation by regexp) | |
if (preg_match('#[^a-z0-9_-]#i', $name)) { | |
throw new \InvalidArgumentException("Parameter name must be alphanumeric string due to limitations of PCRE, '$name' given."); | |
} | |
// pattern, condition & metadata | |
if ($class !== '') { | |
if (!isset(self::$styles[$class])) { | |
throw new \InvalidStateException("Parameter '$name' has '$class' flag, but Route::\$styles['$class'] is not set."); | |
} | |
$meta = self::$styles[$class]; | |
} elseif (isset(self::$styles[$name])) { | |
$meta = self::$styles[$name]; | |
} else { | |
$meta = self::$styles['#']; | |
} | |
if (isset($metadata[$name])) { | |
$meta = $metadata[$name] + $meta; | |
} | |
if ($pattern == '' && isset($meta[self::PATTERN])) { | |
$pattern = $meta[self::PATTERN]; | |
} | |
if ($default !== '') { | |
$meta[self::VALUE] = (string) substr($default, 1); | |
$meta['fixity'] = self::PATH_OPTIONAL; | |
} | |
$meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? NULL : array_flip($meta[self::FILTER_TABLE]); | |
if (array_key_exists(self::VALUE, $meta)) { | |
if (isset($meta['filterTable2'][$meta[self::VALUE]])) { | |
$meta['defOut'] = $meta['filterTable2'][$meta[self::VALUE]]; | |
} elseif (isset($meta[self::FILTER_OUT])) { | |
$meta['defOut'] = call_user_func($meta[self::FILTER_OUT], $meta[self::VALUE]); | |
} else { | |
$meta['defOut'] = $meta[self::VALUE]; | |
} | |
} | |
$meta[self::PATTERN] = "#(?:$pattern)$#A" . ($this->flags & self::CASE_SENSITIVE ? '' : 'iu'); | |
// include in expression | |
$re = '(?P<' . str_replace('-', '___', $name) . '>' . $pattern . ')' . $re; // str_replace is dirty trick to enable '-' in parameter name | |
if ($brackets) { // is in brackets? | |
if (!isset($meta[self::VALUE])) { | |
$meta[self::VALUE] = $meta['defOut'] = NULL; | |
} | |
$meta['fixity'] = self::PATH_OPTIONAL; | |
} elseif (isset($meta['fixity'])) { // auto-optional | |
$re = '(?:' . substr_replace($re, ')?', strlen($re) - $autoOptional[0], 0); | |
array_splice($sequence, count($sequence) - $autoOptional[1], 0, array(']', '')); | |
array_unshift($sequence, '[', ''); | |
$meta['fixity'] = self::PATH_OPTIONAL; | |
} else { | |
$autoOptional = array(strlen($re), count($sequence)); | |
} | |
$metadata[$name] = $meta; | |
} while (TRUE); | |
if ($brackets) { | |
throw new \InvalidArgumentException("Missing closing ']' in mask '$mask'."); | |
} | |
$this->re = '#' . $re . '/?$#A' . ($this->flags & self::CASE_SENSITIVE ? '' : 'iu'); | |
$this->metadata = $metadata; | |
$this->sequence = $sequence; | |
} | |
/** | |
* Returns mask. | |
* @return string | |
*/ | |
public function getMask() | |
{ | |
return $this->mask; | |
} | |
/** | |
* Returns metadata. | |
* @return array | |
*/ | |
public function getMetadata() | |
{ | |
return $this->metadata; | |
} | |
/** | |
* Is route one-way? | |
* @return string | |
*/ | |
public function isOneWay() | |
{ | |
return $this->flags & self::ONE_WAY; | |
} | |
/** | |
* Returns default values. | |
* @return array | |
*/ | |
public function getDefaults() | |
{ | |
$defaults = array(); | |
foreach ($this->metadata as $name => $meta) { | |
if (isset($meta['fixity'])) { | |
$defaults[$name] = $meta[self::VALUE]; | |
} | |
} | |
return $defaults; | |
} | |
/********************* Utilities ****************d*g**/ | |
/** | |
* Proprietary cache aim. | |
* @return string|FALSE | |
*/ | |
public function getTargetPresenter() | |
{ | |
if ($this->flags & self::ONE_WAY) { | |
return FALSE; | |
} | |
$m = $this->metadata; | |
$module = ''; | |
if (isset($m[self::MODULE_KEY])) { | |
if (isset($m[self::MODULE_KEY]['fixity']) && $m[self::MODULE_KEY]['fixity'] === self::CONSTANT) { | |
$module = $m[self::MODULE_KEY][self::VALUE] . ':'; | |
} else { | |
return NULL; | |
} | |
} | |
if (isset($m[self::PRESENTER_KEY]['fixity']) && $m[self::PRESENTER_KEY]['fixity'] === self::CONSTANT) { | |
return $module . $m[self::PRESENTER_KEY][self::VALUE]; | |
} | |
return NULL; | |
} | |
/** | |
* Rename keys in array. | |
* @param array | |
* @param array | |
* @return array | |
*/ | |
private static function renameKeys($arr, $xlat) | |
{ | |
if (empty($xlat)) return $arr; | |
$res = array(); | |
$occupied = array_flip($xlat); | |
foreach ($arr as $k => $v) { | |
if (isset($xlat[$k])) { | |
$res[$xlat[$k]] = $v; | |
} elseif (!isset($occupied[$k])) { | |
$res[$k] = $v; | |
} | |
} | |
return $res; | |
} | |
/********************* Inflectors ****************d*g**/ | |
/** | |
* camelCaseAction name -> dash-separated. | |
* @param string | |
* @return string | |
*/ | |
private static function action2path($s) | |
{ | |
$s = preg_replace('#(.)(?=[A-Z])#', '$1-', $s); | |
$s = strtolower($s); | |
$s = rawurlencode($s); | |
return $s; | |
} | |
/** | |
* dash-separated -> camelCaseAction name. | |
* @param string | |
* @return string | |
*/ | |
private static function path2action($s) | |
{ | |
$s = strtolower($s); | |
$s = preg_replace('#-(?=[a-z])#', ' ', $s); | |
$s = substr(ucwords('x' . $s), 1); | |
//$s = lcfirst(ucwords($s)); | |
$s = str_replace(' ', '', $s); | |
return $s; | |
} | |
/** | |
* PascalCase:Presenter name -> dash-and-dot-separated. | |
* @param string | |
* @return string | |
*/ | |
private static function presenter2path($s) | |
{ | |
$s = strtr($s, ':', '.'); | |
$s = preg_replace('#([^.])(?=[A-Z])#', '$1-', $s); | |
$s = strtolower($s); | |
$s = rawurlencode($s); | |
return $s; | |
} | |
/** | |
* dash-and-dot-separated -> PascalCase:Presenter name. | |
* @param string | |
* @return string | |
*/ | |
private static function path2presenter($s) | |
{ | |
$s = strtolower($s); | |
$s = preg_replace('#([.-])(?=[a-z])#', '$1 ', $s); | |
$s = ucwords($s); | |
$s = str_replace('. ', ':', $s); | |
$s = str_replace('- ', '', $s); | |
return $s; | |
} | |
/********************* Route::$styles manipulator ****************d*g**/ | |
/** | |
* Creates new style. | |
* @param string style name (#style, urlParameter, ?queryParameter) | |
* @param string optional parent style name | |
* @return void | |
*/ | |
public static function addStyle($style, $parent = '#') | |
{ | |
if (isset(self::$styles[$style])) { | |
throw new \InvalidArgumentException("Style '$style' already exists."); | |
} | |
if ($parent !== NULL) { | |
if (!isset(self::$styles[$parent])) { | |
throw new \InvalidArgumentException("Parent style '$parent' doesn't exist."); | |
} | |
self::$styles[$style] = self::$styles[$parent]; | |
} else { | |
self::$styles[$style] = array(); | |
} | |
} | |
/** | |
* Changes style property value. | |
* @param string style name (#style, urlParameter, ?queryParameter) | |
* @param string property name (Route::PATTERN, Route::FILTER_IN, Route::FILTER_OUT, Route::FILTER_TABLE) | |
* @param mixed property value | |
* @return void | |
*/ | |
public static function setStyleProperty($style, $key, $value) | |
{ | |
if (!isset(self::$styles[$style])) { | |
throw new \InvalidArgumentException("Style '$style' doesn't exist."); | |
} | |
self::$styles[$style][$key] = $value; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment