PageRenderTime 50ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/servers/urlcatcher.org/htdocs/lib/vendor/symfony/lib/routing/sfPatternRouting.class.php

https://github.com/bigcalm/urlcatcher
PHP | 537 lines | 297 code | 65 blank | 175 comment | 35 complexity | 24107cfd6be051b26ffff9b7c5410db1 MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the symfony package.
  4. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  5. *
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. */
  9. /**
  10. * sfPatternRouting class controls the generation and parsing of URLs.
  11. *
  12. * It parses and generates URLs by delegating the work to an array of sfRoute objects.
  13. *
  14. * @package symfony
  15. * @subpackage routing
  16. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  17. * @version SVN: $Id: sfPatternRouting.class.php 23180 2009-10-19 07:19:52Z FabianLange $
  18. */
  19. class sfPatternRouting extends sfRouting
  20. {
  21. protected
  22. $currentRouteName = null,
  23. $currentInternalUri = array(),
  24. $routes = array(),
  25. $cacheData = array(),
  26. $cacheChanged = false;
  27. /**
  28. * Initializes this Routing.
  29. *
  30. * Available options:
  31. *
  32. * * suffix: The default suffix
  33. * * variable_prefixes: An array of characters that starts a variable name (: by default)
  34. * * segment_separators: An array of allowed characters for segment separators (/ and . by default)
  35. * * variable_regex: A regex that match a valid variable name ([\w\d_]+ by default)
  36. * * generate_shortest_url: Whether to generate the shortest URL possible (true by default)
  37. * * extra_parameters_as_query_string: Whether to generate extra parameters as a query string
  38. * * lookup_cache_dedicated_keys: Whether to use dedicated keys for parse/generate cache (false by default)
  39. * WARNING: When this option is activated, do not use sfFileCache; use a fast access
  40. * cache backend (like sfAPCCache).
  41. *
  42. * @see sfRouting
  43. */
  44. public function initialize(sfEventDispatcher $dispatcher, sfCache $cache = null, $options = array())
  45. {
  46. $options = array_merge(array(
  47. 'variable_prefixes' => array(':'),
  48. 'segment_separators' => array('/', '.'),
  49. 'variable_regex' => '[\w\d_]+',
  50. 'load_configuration' => false,
  51. 'suffix' => '',
  52. 'generate_shortest_url' => true,
  53. 'extra_parameters_as_query_string' => true,
  54. 'lookup_cache_dedicated_keys' => false,
  55. ), $options);
  56. // for BC
  57. if ('.' == $options['suffix'])
  58. {
  59. $options['suffix'] = '';
  60. }
  61. parent::initialize($dispatcher, $cache, $options);
  62. if (null !== $this->cache && !$options['lookup_cache_dedicated_keys'] && $cacheData = $this->cache->get('symfony.routing.data'))
  63. {
  64. $this->cacheData = unserialize($cacheData);
  65. }
  66. }
  67. /**
  68. * @see sfRouting
  69. */
  70. public function loadConfiguration()
  71. {
  72. if ($this->options['load_configuration'] && $config = sfContext::getInstance()->getConfigCache()->checkConfig('config/routing.yml', true))
  73. {
  74. foreach (include($config) as $name => $route)
  75. {
  76. $this->routes[$name] = $route;
  77. }
  78. }
  79. parent::loadConfiguration();
  80. }
  81. /**
  82. * @see sfRouting
  83. */
  84. public function getCurrentInternalUri($withRouteName = false)
  85. {
  86. return null === $this->currentRouteName ? null : $this->currentInternalUri[$withRouteName ? 0 : 1];
  87. }
  88. /**
  89. * Gets the current route name.
  90. *
  91. * @return string The route name
  92. */
  93. public function getCurrentRouteName()
  94. {
  95. return $this->currentRouteName;
  96. }
  97. /**
  98. * @see sfRouting
  99. */
  100. public function getRoutes()
  101. {
  102. return $this->routes;
  103. }
  104. /**
  105. * @see sfRouting
  106. */
  107. public function setRoutes($routes)
  108. {
  109. foreach ($routes as $name => $route)
  110. {
  111. $this->connect($name, $route);
  112. }
  113. }
  114. /**
  115. * @see sfRouting
  116. */
  117. public function hasRoutes()
  118. {
  119. return count($this->routes) ? true : false;
  120. }
  121. /**
  122. * @see sfRouting
  123. */
  124. public function clearRoutes()
  125. {
  126. if ($this->options['logging'])
  127. {
  128. $this->dispatcher->notify(new sfEvent($this, 'application.log', array('Clear all current routes')));
  129. }
  130. $this->routes = array();
  131. }
  132. /**
  133. * Returns true if the route name given is defined.
  134. *
  135. * @param string $name The route name
  136. *
  137. * @return boolean
  138. */
  139. public function hasRouteName($name)
  140. {
  141. return isset($this->routes[$name]) ? true : false;
  142. }
  143. /**
  144. * Adds a new route at the beginning of the current list of routes.
  145. *
  146. * @see connect
  147. */
  148. public function prependRoute($name, $route)
  149. {
  150. $routes = $this->routes;
  151. $this->routes = array();
  152. $this->connect($name, $route);
  153. $this->routes = array_merge($this->routes, $routes);
  154. }
  155. /**
  156. * Adds a new route.
  157. *
  158. * Alias for the connect method.
  159. *
  160. * @see connect
  161. */
  162. public function appendRoute($name, $route)
  163. {
  164. return $this->connect($name, $route);
  165. }
  166. /**
  167. * Adds a new route before a given one in the current list of routes.
  168. *
  169. * @see connect
  170. */
  171. public function insertRouteBefore($pivot, $name, $route)
  172. {
  173. if (!isset($this->routes[$pivot]))
  174. {
  175. throw new sfConfigurationException(sprintf('Unable to insert route "%s" before inexistent route "%s".', $name, $pivot));
  176. }
  177. $routes = $this->routes;
  178. $this->routes = array();
  179. $newroutes = array();
  180. foreach ($routes as $key => $value)
  181. {
  182. if ($key == $pivot)
  183. {
  184. $this->connect($name, $route);
  185. $newroutes = array_merge($newroutes, $this->routes);
  186. }
  187. $newroutes[$key] = $value;
  188. }
  189. $this->routes = $newroutes;
  190. }
  191. /**
  192. * Adds a new route at the end of the current list of routes.
  193. *
  194. * A route string is a string with 2 special constructions:
  195. * - :string: :string denotes a named paramater (available later as $request->getParameter('string'))
  196. * - *: * match an indefinite number of parameters in a route
  197. *
  198. * Here is a very common rule in a symfony project:
  199. *
  200. * <code>
  201. * $r->connect('default', new sfRoute('/:module/:action/*'));
  202. * </code>
  203. *
  204. * @param string $name The route name
  205. * @param sfRoute $route A sfRoute instance
  206. *
  207. * @return array current routes
  208. */
  209. public function connect($name, $route)
  210. {
  211. $routes = $route instanceof sfRouteCollection ? $route : array($name => $route);
  212. foreach (self::flattenRoutes($routes) as $name => $route)
  213. {
  214. $this->routes[$name] = $route;
  215. $this->configureRoute($route);
  216. if ($this->options['logging'])
  217. {
  218. $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Connect %s "%s" (%s)', get_class($route), $name, $route->getPattern()))));
  219. }
  220. }
  221. }
  222. public function configureRoute(sfRoute $route)
  223. {
  224. $route->setDefaultParameters($this->defaultParameters);
  225. $route->setDefaultOptions($this->options);
  226. }
  227. /**
  228. * Sets a default parameter.
  229. *
  230. * @param string $key The key
  231. * @param string $value The value
  232. */
  233. /*
  234. public function setDefaultParameter($key, $value)
  235. {
  236. parent::setDefaultParameter($key, $value);
  237. foreach ($this->routes as $name => $route)
  238. {
  239. if (is_string($route))
  240. {
  241. $route = $this->loadRoute($name);
  242. }
  243. $route->setDefaultParameters($this->defaultParameters);
  244. }
  245. }
  246. */
  247. /**
  248. * Sets the default parameters for URL generation.
  249. *
  250. * @param array $parameters An array of default parameters
  251. */
  252. /*
  253. public function setDefaultParameters($parameters)
  254. {
  255. parent::setDefaultParameters($parameters);
  256. foreach ($this->routes as $name => $route)
  257. {
  258. if (is_string($route))
  259. {
  260. $route = $this->loadRoute($name);
  261. }
  262. $route->setDefaultParameters($this->defaultParameters);
  263. }
  264. }
  265. */
  266. /**
  267. * @see sfRouting
  268. */
  269. public function generate($name, $params = array(), $absolute = false)
  270. {
  271. // fetch from cache
  272. if (null !== $this->cache)
  273. {
  274. $cacheKey = 'generate_'.$name.'_'.md5(serialize(array_merge($this->defaultParameters, $params))).'_'.md5(serialize($this->options['context']));
  275. if ($this->options['lookup_cache_dedicated_keys'] && $url = $this->cache->get('symfony.routing.data.'.$cacheKey))
  276. {
  277. return $this->fixGeneratedUrl($url, $absolute);
  278. }
  279. elseif (isset($this->cacheData[$cacheKey]))
  280. {
  281. return $this->fixGeneratedUrl($this->cacheData[$cacheKey], $absolute);
  282. }
  283. }
  284. if ($name)
  285. {
  286. // named route
  287. if (!isset($this->routes[$name]))
  288. {
  289. throw new sfConfigurationException(sprintf('The route "%s" does not exist.', $name));
  290. }
  291. $route = $this->routes[$name];
  292. $route->setDefaultParameters($this->defaultParameters);
  293. }
  294. else
  295. {
  296. // find a matching route
  297. if (false === $route = $this->getRouteThatMatchesParameters($params, $this->options['context']))
  298. {
  299. 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))));
  300. }
  301. }
  302. $url = $route->generate($params, $this->options['context'], $absolute);
  303. // store in cache
  304. if (null !== $this->cache)
  305. {
  306. if ($this->options['lookup_cache_dedicated_keys'])
  307. {
  308. $this->cache->set('symfony.routing.data.'.$cacheKey, $url);
  309. }
  310. else
  311. {
  312. $this->cacheChanged = true;
  313. $this->cacheData[$cacheKey] = $url;
  314. }
  315. }
  316. return $this->fixGeneratedUrl($url, $absolute);
  317. }
  318. /**
  319. * @see sfRouting
  320. */
  321. public function parse($url)
  322. {
  323. if (false === $info = $this->findRoute($url))
  324. {
  325. $this->currentRouteName = null;
  326. $this->currentInternalUri = array();
  327. return false;
  328. }
  329. if ($this->options['logging'])
  330. {
  331. $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))))));
  332. }
  333. // store the current internal URI
  334. $this->updateCurrentInternalUri($info['name'], $info['parameters']);
  335. $route = $this->routes[$info['name']];
  336. $route->setDefaultParameters($this->defaultParameters);
  337. $route->bind($this->options['context'], $info['parameters']);
  338. $info['parameters']['_sf_route'] = $route;
  339. return $info['parameters'];
  340. }
  341. protected function updateCurrentInternalUri($name, array $parameters)
  342. {
  343. // store the route name
  344. $this->currentRouteName = $name;
  345. $internalUri = array('@'.$this->currentRouteName, $parameters['module'].'/'.$parameters['action']);
  346. unset($parameters['module'], $parameters['action']);
  347. $params = array();
  348. foreach ($parameters as $key => $value)
  349. {
  350. $params[] = $key.'='.$value;
  351. }
  352. // sort to guaranty unicity
  353. sort($params);
  354. $params = $params ? '?'.implode('&', $params) : '';
  355. $this->currentInternalUri = array($internalUri[0].$params, $internalUri[1].$params);
  356. }
  357. /**
  358. * Finds a matching route for given URL.
  359. *
  360. * Returns false if no route matches.
  361. *
  362. * Returned array contains:
  363. *
  364. * - name: name or alias of the route that matched
  365. * - pattern: the compiled pattern of the route that matched
  366. * - parameters: array containing key value pairs of the request parameters including defaults
  367. *
  368. * @param string $url URL to be parsed
  369. *
  370. * @return array|false An array with routing information or false if no route matched
  371. */
  372. public function findRoute($url)
  373. {
  374. $url = $this->normalizeUrl($url);
  375. // fetch from cache
  376. if (null !== $this->cache)
  377. {
  378. $cacheKey = 'parse_'.$url.'_'.md5(serialize($this->options['context']));
  379. if ($this->options['lookup_cache_dedicated_keys'] && $info = $this->cache->get('symfony.routing.data.'.$cacheKey))
  380. {
  381. return unserialize($info);
  382. }
  383. elseif (isset($this->cacheData[$cacheKey]))
  384. {
  385. return $this->cacheData[$cacheKey];
  386. }
  387. }
  388. $info = $this->getRouteThatMatchesUrl($url);
  389. // store in cache
  390. if (null !== $this->cache)
  391. {
  392. if ($this->options['lookup_cache_dedicated_keys'])
  393. {
  394. $this->cache->set('symfony.routing.data.'.$cacheKey, serialize($info));
  395. }
  396. else
  397. {
  398. $this->cacheChanged = true;
  399. $this->cacheData[$cacheKey] = $info;
  400. }
  401. }
  402. return $info;
  403. }
  404. static public function flattenRoutes($routes)
  405. {
  406. $flattenRoutes = array();
  407. foreach ($routes as $name => $route)
  408. {
  409. if ($route instanceof sfRouteCollection)
  410. {
  411. $flattenRoutes = array_merge($flattenRoutes, self::flattenRoutes($route));
  412. }
  413. else
  414. {
  415. $flattenRoutes[$name] = $route;
  416. }
  417. }
  418. return $flattenRoutes;
  419. }
  420. protected function getRouteThatMatchesUrl($url)
  421. {
  422. foreach ($this->routes as $name => $route)
  423. {
  424. $route->setDefaultParameters($this->defaultParameters);
  425. if (false === $parameters = $route->matchesUrl($url, $this->options['context']))
  426. {
  427. continue;
  428. }
  429. return array('name' => $name, 'pattern' => $route->getPattern(), 'parameters' => $parameters);
  430. }
  431. return false;
  432. }
  433. protected function getRouteThatMatchesParameters($parameters)
  434. {
  435. foreach ($this->routes as $name => $route)
  436. {
  437. $route->setDefaultParameters($this->defaultParameters);
  438. if ($route->matchesParameters($parameters, $this->options['context']))
  439. {
  440. return $route;
  441. }
  442. }
  443. return false;
  444. }
  445. protected function normalizeUrl($url)
  446. {
  447. // an URL should start with a '/', mod_rewrite doesn't respect that, but no-mod_rewrite version does.
  448. if ('/' != substr($url, 0, 1))
  449. {
  450. $url = '/'.$url;
  451. }
  452. // we remove the query string
  453. if (false !== $pos = strpos($url, '?'))
  454. {
  455. $url = substr($url, 0, $pos);
  456. }
  457. // remove multiple /
  458. $url = preg_replace('#/+#', '/', $url);
  459. return $url;
  460. }
  461. /**
  462. * @see sfRouting
  463. */
  464. public function shutdown()
  465. {
  466. if (null !== $this->cache && $this->cacheChanged)
  467. {
  468. $this->cacheChanged = false;
  469. $this->cache->set('symfony.routing.data', serialize($this->cacheData));
  470. }
  471. }
  472. }