DynamicRouter.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. <?php
  2. /*
  3. * This file is part of the Symfony CMF package.
  4. *
  5. * (c) 2011-2014 Symfony CMF
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Cmf\Component\Routing;
  11. use Symfony\Component\HttpFoundation\Request;
  12. use Symfony\Component\Routing\RequestContext;
  13. use Symfony\Component\Routing\RouteCollection;
  14. use Symfony\Component\Routing\RouterInterface;
  15. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  16. use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
  17. use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
  18. use Symfony\Component\Routing\RequestContextAwareInterface;
  19. use Symfony\Component\Routing\Exception\RouteNotFoundException;
  20. use Symfony\Component\Routing\Exception\ResourceNotFoundException;
  21. use Symfony\Component\Routing\Exception\MethodNotAllowedException;
  22. use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
  23. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  24. use Symfony\Cmf\Component\Routing\Event\Events;
  25. use Symfony\Cmf\Component\Routing\Event\RouterMatchEvent;
  26. /**
  27. * A flexible router accepting matcher and generator through injection and
  28. * using the RouteEnhancer concept to generate additional data on the routes.
  29. *
  30. * @author Larry Garfield
  31. * @author David Buchmann
  32. */
  33. class DynamicRouter implements RouterInterface, RequestMatcherInterface, ChainedRouterInterface
  34. {
  35. /**
  36. * @var RequestMatcherInterface|UrlMatcherInterface
  37. */
  38. protected $matcher;
  39. /**
  40. * @var UrlGeneratorInterface
  41. */
  42. protected $generator;
  43. /**
  44. * @var EventDispatcherInterface
  45. */
  46. protected $eventDispatcher;
  47. /**
  48. * @var RouteEnhancerInterface[]
  49. */
  50. protected $enhancers = array();
  51. /**
  52. * Cached sorted list of enhancers
  53. *
  54. * @var RouteEnhancerInterface[]
  55. */
  56. protected $sortedEnhancers = array();
  57. /**
  58. * The regexp pattern that needs to be matched before a dynamic lookup is made
  59. *
  60. * @var string
  61. */
  62. protected $uriFilterRegexp;
  63. /**
  64. * @var RequestContext
  65. */
  66. protected $context;
  67. /**
  68. * @var RouteCollection
  69. */
  70. private $routeCollection;
  71. /**
  72. * @param RequestContext $context
  73. * @param RequestMatcherInterface|UrlMatcherInterface $matcher
  74. * @param UrlGeneratorInterface $generator
  75. * @param string $uriFilterRegexp
  76. * @param EventDispatcherInterface|null $eventDispatcher
  77. * @param RouteProviderInterface $provider
  78. */
  79. public function __construct(RequestContext $context,
  80. $matcher,
  81. UrlGeneratorInterface $generator,
  82. $uriFilterRegexp = '',
  83. EventDispatcherInterface $eventDispatcher = null,
  84. RouteProviderInterface $provider = null
  85. ) {
  86. $this->context = $context;
  87. if (! $matcher instanceof RequestMatcherInterface && ! $matcher instanceof UrlMatcherInterface) {
  88. throw new \InvalidArgumentException('Matcher must implement either Symfony\Component\Routing\Matcher\RequestMatcherInterface or Symfony\Component\Routing\Matcher\UrlMatcherInterface');
  89. }
  90. $this->matcher = $matcher;
  91. $this->generator = $generator;
  92. $this->eventDispatcher = $eventDispatcher;
  93. $this->uriFilterRegexp = $uriFilterRegexp;
  94. $this->provider = $provider;
  95. $this->generator->setContext($context);
  96. }
  97. /**
  98. * {@inheritDoc}
  99. */
  100. public function getRouteCollection()
  101. {
  102. if (!$this->routeCollection instanceof RouteCollection) {
  103. $this->routeCollection = $this->provider
  104. ? new LazyRouteCollection($this->provider) : new RouteCollection();
  105. }
  106. return $this->routeCollection;
  107. }
  108. /**
  109. * @return RequestMatcherInterface|UrlMatcherInterface
  110. */
  111. public function getMatcher()
  112. {
  113. /* we may not set the context in DynamicRouter::setContext as this
  114. * would lead to symfony cache warmup problems.
  115. * a request matcher does not need the request context separately as it
  116. * can get it from the request.
  117. */
  118. if ($this->matcher instanceof RequestContextAwareInterface) {
  119. $this->matcher->setContext($this->getContext());
  120. }
  121. return $this->matcher;
  122. }
  123. /**
  124. * @return UrlGeneratorInterface
  125. */
  126. public function getGenerator()
  127. {
  128. $this->generator->setContext($this->getContext());
  129. return $this->generator;
  130. }
  131. /**
  132. * Generates a URL from the given parameters.
  133. *
  134. * If the generator is not able to generate the url, it must throw the
  135. * RouteNotFoundException as documented below.
  136. *
  137. * @param string $name The name of the route
  138. * @param mixed $parameters An array of parameters
  139. * @param Boolean $absolute Whether to generate an absolute URL
  140. *
  141. * @return string The generated URL
  142. *
  143. * @throws RouteNotFoundException if route doesn't exist
  144. *
  145. * @api
  146. */
  147. public function generate($name, $parameters = array(), $absolute = false)
  148. {
  149. return $this->getGenerator()->generate($name, $parameters, $absolute);
  150. }
  151. /**
  152. * Delegate to our generator
  153. *
  154. * {@inheritDoc}
  155. */
  156. public function supports($name)
  157. {
  158. if ($this->generator instanceof VersatileGeneratorInterface) {
  159. return $this->generator->supports($name);
  160. }
  161. return is_string($name);
  162. }
  163. /**
  164. * Tries to match a URL path with a set of routes.
  165. *
  166. * If the matcher can not find information, it must throw one of the
  167. * exceptions documented below.
  168. *
  169. * @param string $pathinfo The path info to be parsed (raw format, i.e. not
  170. * urldecoded)
  171. *
  172. * @return array An array of parameters
  173. *
  174. * @throws ResourceNotFoundException If the resource could not be found
  175. * @throws MethodNotAllowedException If the resource was found but the
  176. * request method is not allowed
  177. *
  178. * @deprecated Use matchRequest exclusively to avoid problems. This method will be removed in version 2.0
  179. * @api
  180. */
  181. public function match($pathinfo)
  182. {
  183. $request = Request::create($pathinfo);
  184. if ($this->eventDispatcher) {
  185. $event = new RouterMatchEvent();
  186. $this->eventDispatcher->dispatch(Events::PRE_DYNAMIC_MATCH, $event);
  187. }
  188. if (! empty($this->uriFilterRegexp) && ! preg_match($this->uriFilterRegexp, $pathinfo)) {
  189. throw new ResourceNotFoundException("$pathinfo does not match the '{$this->uriFilterRegexp}' pattern");
  190. }
  191. $matcher = $this->getMatcher();
  192. if (! $matcher instanceof UrlMatcherInterface) {
  193. throw new \InvalidArgumentException('Wrong matcher type, you need to call matchRequest');
  194. }
  195. $defaults = $matcher->match($pathinfo);
  196. return $this->applyRouteEnhancers($defaults, $request);
  197. }
  198. /**
  199. * Tries to match a request with a set of routes and returns the array of
  200. * information for that route.
  201. *
  202. * If the matcher can not find information, it must throw one of the
  203. * exceptions documented below.
  204. *
  205. * @param Request $request The request to match
  206. *
  207. * @return array An array of parameters
  208. *
  209. * @throws ResourceNotFoundException If no matching resource could be found
  210. * @throws MethodNotAllowedException If a matching resource was found but
  211. * the request method is not allowed
  212. */
  213. public function matchRequest(Request $request)
  214. {
  215. if ($this->eventDispatcher) {
  216. $event = new RouterMatchEvent($request);
  217. $this->eventDispatcher->dispatch(Events::PRE_DYNAMIC_MATCH_REQUEST, $event);
  218. }
  219. if (! empty($this->uriFilterRegexp)
  220. && ! preg_match($this->uriFilterRegexp, $request->getPathInfo())
  221. ) {
  222. throw new ResourceNotFoundException("{$request->getPathInfo()} does not match the '{$this->uriFilterRegexp}' pattern");
  223. }
  224. $matcher = $this->getMatcher();
  225. if ($matcher instanceof UrlMatcherInterface) {
  226. $defaults = $matcher->match($request->getPathInfo());
  227. } else {
  228. $defaults = $matcher->matchRequest($request);
  229. }
  230. return $this->applyRouteEnhancers($defaults, $request);
  231. }
  232. /**
  233. * Apply the route enhancers to the defaults, according to priorities
  234. *
  235. * @param array $defaults
  236. * @param Request $request
  237. *
  238. * @return array
  239. */
  240. protected function applyRouteEnhancers($defaults, Request $request)
  241. {
  242. foreach ($this->getRouteEnhancers() as $enhancer) {
  243. $defaults = $enhancer->enhance($defaults, $request);
  244. }
  245. return $defaults;
  246. }
  247. /**
  248. * Add route enhancers to the router to let them generate information on
  249. * matched routes.
  250. *
  251. * The order of the enhancers is determined by the priority, the higher the
  252. * value, the earlier the enhancer is run.
  253. *
  254. * @param RouteEnhancerInterface $enhancer
  255. * @param int $priority
  256. */
  257. public function addRouteEnhancer(RouteEnhancerInterface $enhancer, $priority = 0)
  258. {
  259. if (empty($this->enhancers[$priority])) {
  260. $this->enhancers[$priority] = array();
  261. }
  262. $this->enhancers[$priority][] = $enhancer;
  263. $this->sortedEnhancers = array();
  264. return $this;
  265. }
  266. /**
  267. * Sorts the enhancers and flattens them.
  268. *
  269. * @return RouteEnhancerInterface[] the enhancers ordered by priority
  270. */
  271. public function getRouteEnhancers()
  272. {
  273. if (empty($this->sortedEnhancers)) {
  274. $this->sortedEnhancers = $this->sortRouteEnhancers();
  275. }
  276. return $this->sortedEnhancers;
  277. }
  278. /**
  279. * Sort enhancers by priority.
  280. *
  281. * The highest priority number is the highest priority (reverse sorting).
  282. *
  283. * @return RouteEnhancerInterface[] the sorted enhancers
  284. */
  285. protected function sortRouteEnhancers()
  286. {
  287. $sortedEnhancers = array();
  288. krsort($this->enhancers);
  289. foreach ($this->enhancers as $enhancers) {
  290. $sortedEnhancers = array_merge($sortedEnhancers, $enhancers);
  291. }
  292. return $sortedEnhancers;
  293. }
  294. /**
  295. * Sets the request context.
  296. *
  297. * @param RequestContext $context The context
  298. *
  299. * @api
  300. */
  301. public function setContext(RequestContext $context)
  302. {
  303. $this->context = $context;
  304. }
  305. /**
  306. * Gets the request context.
  307. *
  308. * @return RequestContext The context
  309. *
  310. * @api
  311. */
  312. public function getContext()
  313. {
  314. return $this->context;
  315. }
  316. /**
  317. * {@inheritDoc}
  318. *
  319. * Forwards to the generator.
  320. */
  321. public function getRouteDebugMessage($name, array $parameters = array())
  322. {
  323. if ($this->generator instanceof VersatileGeneratorInterface) {
  324. return $this->generator->getRouteDebugMessage($name, $parameters);
  325. }
  326. return "Route '$name' not found";
  327. }
  328. }