DynamicRouter.php 12 KB

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