123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- <?php
- /*
- * This file is part of the symfony package.
- * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- /**
- * sfPatternRouting class controls the generation and parsing of URLs.
- *
- * It parses and generates URLs by delegating the work to an array of sfRoute objects.
- *
- * @package symfony
- * @subpackage routing
- * @author Fabien Potencier <fabien.potencier@symfony-project.com>
- * @version SVN: $Id: sfPatternRouting.class.php 13288 2008-11-23 21:35:40Z fabien $
- */
- class sfPatternRouting extends sfRouting
- {
- protected
- $currentRouteName = null,
- $currentInternalUri = array(),
- $routes = array(),
- $cacheData = array(),
- $cacheChanged = false;
- /**
- * Initializes this Routing.
- *
- * Available options:
- *
- * * suffix: The default suffix
- * * variable_prefixes: An array of characters that starts a variable name (: by default)
- * * segment_separators: An array of allowed characters for segment separators (/ and . by default)
- * * variable_regex: A regex that match a valid variable name ([\w\d_]+ by default)
- * * generate_shortest_url: Whether to generate the shortest URL possible (true by default)
- * * extra_parameters_as_query_string: Whether to generate extra parameters as a query string
- *
- * @see sfRouting
- */
- public function initialize(sfEventDispatcher $dispatcher, sfCache $cache = null, $options = array())
- {
- $options = array_merge(array(
- 'variable_prefixes' => array(':'),
- 'segment_separators' => array('/', '.'),
- 'variable_regex' => '[\w\d_]+',
- 'load_configuration' => false,
- 'suffix' => '',
- 'generate_shortest_url' => true,
- 'extra_parameters_as_query_string' => true,
- ), $options);
- // for BC
- if ('.' == $options['suffix'])
- {
- $options['suffix'] = '';
- }
- parent::initialize($dispatcher, $cache, $options);
- if (!is_null($this->cache) && $cacheData = $this->cache->get('symfony.routing.data'))
- {
- $this->cacheData = unserialize($cacheData);
- }
- }
- /**
- * @see sfRouting
- */
- public function loadConfiguration()
- {
- if (!is_null($this->cache) && $routes = $this->cache->get('symfony.routing.configuration'))
- {
- $this->routes = unserialize($routes);
- }
- else
- {
- if ($this->options['load_configuration'] && $config = sfContext::getInstance()->getConfigCache()->checkConfig('config/routing.yml', true))
- {
- $this->setRoutes(include($config));
- }
- parent::loadConfiguration();
- if (!is_null($this->cache))
- {
- $this->cache->set('symfony.routing.configuration', serialize($this->routes));
- }
- }
- }
- /**
- * @see sfRouting
- */
- public function getCurrentInternalUri($withRouteName = false)
- {
- return is_null($this->currentRouteName) ? null : $this->currentInternalUri[$withRouteName ? 0 : 1];
- }
- /**
- * Gets the current route name.
- *
- * @return string The route name
- */
- public function getCurrentRouteName()
- {
- return $this->currentRouteName;
- }
- /**
- * @see sfRouting
- */
- public function getRoutes()
- {
- return $this->routes;
- }
- /**
- * @see sfRouting
- */
- public function setRoutes($routes)
- {
- foreach ($routes as $name => $route)
- {
- $this->connect($name, $route);
- }
- return $this->routes;
- }
- /**
- * @see sfRouting
- */
- public function hasRoutes()
- {
- return count($this->routes) ? true : false;
- }
- /**
- * @see sfRouting
- */
- public function clearRoutes()
- {
- if ($this->options['logging'])
- {
- $this->dispatcher->notify(new sfEvent($this, 'application.log', array('Clear all current routes')));
- }
- $this->routes = array();
- }
- /**
- * Returns true if the route name given is defined.
- *
- * @param string $name The route name
- *
- * @return boolean
- */
- public function hasRouteName($name)
- {
- return isset($this->routes[$name]) ? true : false;
- }
- /**
- * Adds a new route at the beginning of the current list of routes.
- *
- * @see connect
- */
- public function prependRoute($name, $route)
- {
- $routes = $this->routes;
- $this->routes = array();
- $newroutes = $this->connect($name, $route);
- $this->routes = array_merge($newroutes, $routes);
- return $this->routes;
- }
- /**
- * Adds a new route.
- *
- * Alias for the connect method.
- *
- * @see connect
- */
- public function appendRoute($name, $route)
- {
- return $this->connect($name, $route);
- }
- /**
- * Adds a new route before a given one in the current list of routes.
- *
- * @see connect
- */
- public function insertRouteBefore($pivot, $name, $route)
- {
- if (!isset($this->routes[$pivot]))
- {
- throw new sfConfigurationException(sprintf('Unable to insert route "%s" before inexistent route "%s".', $name, $pivot));
- }
- $routes = $this->routes;
- $this->routes = array();
- $newroutes = array();
- foreach ($routes as $key => $value)
- {
- if ($key == $pivot)
- {
- $newroutes = array_merge($newroutes, $this->connect($name, $route));
- }
- $newroutes[$key] = $value;
- }
- return $this->routes = $newroutes;
- }
- /**
- * Adds a new route at the end of the current list of routes.
- *
- * A route string is a string with 2 special constructions:
- * - :string: :string denotes a named paramater (available later as $request->getParameter('string'))
- * - *: * match an indefinite number of parameters in a route
- *
- * Here is a very common rule in a symfony project:
- *
- * <code>
- * $r->connect('default', new sfRoute('/:module/:action/*'));
- * </code>
- *
- * @param string $name The route name
- * @param sfRoute $route A sfRoute instance
- *
- * @return array current routes
- */
- public function connect($name, $route)
- {
- $routes = $route instanceof sfRouteCollection ? $route : array($name => $route);
- foreach (self::flattenRoutes($routes) as $name => $route)
- {
- $this->routes[$name] = $route;
- $this->configureRoute($route);
- if ($this->options['logging'])
- {
- $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Connect %s "%s" (%s)', get_class($route), $name, $route->getPattern()))));
- }
- }
- return $this->routes;
- }
- public function configureRoute(sfRoute $route)
- {
- $route->setDefaultParameters($this->defaultParameters);
- $route->setDefaultOptions($this->options);
- }
- /**
- * Sets a default parameter.
- *
- * @param string $key The key
- * @param string $value The value
- */
- public function setDefaultParameter($key, $value)
- {
- parent::setDefaultParameter($key, $value);
- foreach ($this->routes as $route)
- {
- $route->setDefaultParameters($this->defaultParameters);
- }
- }
- /**
- * Sets the default parameters for URL generation.
- *
- * @param array $parameters An array of default parameters
- */
- public function setDefaultParameters($parameters)
- {
- parent::setDefaultParameters($parameters);
- foreach ($this->routes as $route)
- {
- $route->setDefaultParameters($this->defaultParameters);
- }
- }
- /**
- * @see sfRouting
- */
- public function generate($name, $params = array(), $absolute = false)
- {
- // fetch from cache
- if (!is_null($this->cache))
- {
- $cacheKey = 'generate_'.$name.'_'.md5(serialize(array_merge($this->defaultParameters, $params))).'_'.md5(serialize($this->options['context']));
- if (isset($this->cacheData[$cacheKey]))
- {
- return $this->fixGeneratedUrl($this->cacheData[$cacheKey], $absolute);
- }
- }
- if ($name)
- {
- // named route
- if (!isset($this->routes[$name]))
- {
- throw new sfConfigurationException(sprintf('The route "%s" does not exist.', $name));
- }
- $route = $this->routes[$name];
- }
- else
- {
- // find a matching route
- if (false === $route = $this->getRouteThatMatchesParameters($params, $this->options['context']))
- {
- throw new sfConfigurationException(sprintf('Unable to find a matching route to generate url for params "%s".', is_object($params) ? 'Object('.get_class($params).')' : str_replace("\n", '', var_export($params, true))));
- }
- }
- $url = $route->generate($params, $this->options['context'], $absolute);
- // store in cache
- if (!is_null($this->cache))
- {
- $this->cacheChanged = true;
- $this->cacheData[$cacheKey] = $url;
- }
- return $this->fixGeneratedUrl($url, $absolute);
- }
- /**
- * @see sfRouting
- */
- public function parse($url)
- {
- if (false === $info = $this->findRoute($url))
- {
- return false;
- }
- if ($this->options['logging'])
- {
- $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Match route "%s" (%s) for %s with parameters %s', $info['name'], $info['pattern'], $url, str_replace("\n", '', var_export($info['parameters'], true))))));
- }
- // store the current internal URI
- $this->updateCurrentInternalUri($info['name'], $info['parameters']);
- $route = $this->routes[$info['name']];
- $route->bind($this->options['context'], $info['parameters']);
- $info['parameters']['_sf_route'] = $route;
- return $info['parameters'];
- }
- protected function updateCurrentInternalUri($name, array $parameters)
- {
- // store the route name
- $this->currentRouteName = $name;
- $internalUri = array('@'.$this->currentRouteName, $parameters['module'].'/'.$parameters['action']);
- unset($parameters['module'], $parameters['action']);
- $params = array();
- foreach ($parameters as $key => $value)
- {
- $params[] = $key.'='.$value;
- }
- // sort to guaranty unicity
- sort($params);
- $params = $params ? '?'.implode('&', $params) : '';
- $this->currentInternalUri = array($internalUri[0].$params, $internalUri[1].$params);
- }
- /**
- * Finds a matching route for given URL.
- *
- * Returns false if no route matches.
- *
- * Returned array contains:
- *
- * - name: name or alias of the route that matched
- * - route: the actual matching route object
- * - parameters: array containing key value pairs of the request parameters including defaults
- *
- * @param string $url URL to be parsed
- *
- * @return array|false An array with routing information or false if no route matched
- */
- public function findRoute($url)
- {
- $url = $this->normalizeUrl($url);
- // fetch from cache
- if (!is_null($this->cache))
- {
- $cacheKey = 'parse_'.$url.'_'.md5(serialize($this->options['context']));
- if (isset($this->cacheData[$cacheKey]))
- {
- return $this->cacheData[$cacheKey];
- }
- }
- $info = $this->getRouteThatMatchesUrl($url);
- // store in cache
- if (!is_null($this->cache))
- {
- $this->cacheChanged = true;
- $this->cacheData[$cacheKey] = $info;
- }
- return $info;
- }
- static public function flattenRoutes($routes)
- {
- $flattenRoutes = array();
- foreach ($routes as $name => $route)
- {
- if ($route instanceof sfRouteCollection)
- {
- $flattenRoutes = array_merge($flattenRoutes, self::flattenRoutes($route));
- }
- else
- {
- $flattenRoutes[$name] = $route;
- }
- }
- return $flattenRoutes;
- }
- protected function getRouteThatMatchesUrl($url)
- {
- foreach ($this->routes as $name => $route)
- {
- if (false === $parameters = $route->matchesUrl($url, $this->options['context']))
- {
- continue;
- }
- return array('name' => $name, 'pattern' => $route->getPattern(), 'parameters' => $parameters);
- }
- return false;
- }
- protected function getRouteThatMatchesParameters($parameters)
- {
- foreach ($this->routes as $name => $route)
- {
- if ($route->matchesParameters($parameters, $this->options['context']))
- {
- return $route;
- }
- }
- return false;
- }
- protected function normalizeUrl($url)
- {
- // an URL should start with a '/', mod_rewrite doesn't respect that, but no-mod_rewrite version does.
- if ('/' != $url[0])
- {
- $url = '/'.$url;
- }
- // we remove the query string
- if (false !== $pos = strpos($url, '?'))
- {
- $url = substr($url, 0, $pos);
- }
- // remove multiple /
- $url = preg_replace('#/+#', '/', $url);
- return $url;
- }
- /**
- * @see sfRouting
- */
- public function shutdown()
- {
- if (!is_null($this->cache) && $this->cacheChanged)
- {
- $this->cacheChanged = false;
- $this->cache->set('symfony.routing.data', serialize($this->cacheData));
- }
- }
- }
|