4 * This file is part of the Symfony CMF package.
6 * (c) 2011-2015 Symfony CMF
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Cmf\Component\Routing;
14 use Symfony\Component\HttpFoundation\Request;
15 use Symfony\Component\Routing\RequestContext;
16 use Symfony\Component\Routing\Route;
17 use Symfony\Component\Routing\RouteCollection;
18 use Symfony\Component\Routing\RouterInterface;
19 use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
20 use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
21 use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
22 use Symfony\Component\Routing\RequestContextAwareInterface;
23 use Symfony\Component\Routing\Exception\RouteNotFoundException;
24 use Symfony\Component\Routing\Exception\ResourceNotFoundException;
25 use Symfony\Component\Routing\Exception\MethodNotAllowedException;
26 use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
27 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
28 use Symfony\Cmf\Component\Routing\Event\Events;
29 use Symfony\Cmf\Component\Routing\Event\RouterMatchEvent;
30 use Symfony\Cmf\Component\Routing\Event\RouterGenerateEvent;
33 * A flexible router accepting matcher and generator through injection and
34 * using the RouteEnhancer concept to generate additional data on the routes.
36 * @author Larry Garfield
37 * @author David Buchmann
39 class DynamicRouter implements RouterInterface, RequestMatcherInterface, ChainedRouterInterface
42 * @var RequestMatcherInterface|UrlMatcherInterface
47 * @var UrlGeneratorInterface
52 * @var EventDispatcherInterface
54 protected $eventDispatcher;
57 * @var RouteEnhancerInterface[]
59 protected $enhancers = array();
62 * Cached sorted list of enhancers.
64 * @var RouteEnhancerInterface[]
66 protected $sortedEnhancers = array();
69 * The regexp pattern that needs to be matched before a dynamic lookup is
74 protected $uriFilterRegexp;
82 * @var RouteCollection
84 private $routeCollection;
87 * @param RequestContext $context
88 * @param RequestMatcherInterface|UrlMatcherInterface $matcher
89 * @param UrlGeneratorInterface $generator
90 * @param string $uriFilterRegexp
91 * @param EventDispatcherInterface|null $eventDispatcher
92 * @param RouteProviderInterface $provider
94 public function __construct(RequestContext $context,
96 UrlGeneratorInterface $generator,
97 $uriFilterRegexp = '',
98 EventDispatcherInterface $eventDispatcher = null,
99 RouteProviderInterface $provider = null
101 $this->context = $context;
102 if (!$matcher instanceof RequestMatcherInterface && !$matcher instanceof UrlMatcherInterface) {
103 throw new \InvalidArgumentException('Matcher must implement either Symfony\Component\Routing\Matcher\RequestMatcherInterface or Symfony\Component\Routing\Matcher\UrlMatcherInterface');
105 $this->matcher = $matcher;
106 $this->generator = $generator;
107 $this->eventDispatcher = $eventDispatcher;
108 $this->uriFilterRegexp = $uriFilterRegexp;
109 $this->provider = $provider;
111 $this->generator->setContext($context);
117 public function getRouteCollection()
119 if (!$this->routeCollection instanceof RouteCollection) {
120 $this->routeCollection = $this->provider
121 ? new LazyRouteCollection($this->provider) : new RouteCollection();
124 return $this->routeCollection;
128 * @return RequestMatcherInterface|UrlMatcherInterface
130 public function getMatcher()
132 /* we may not set the context in DynamicRouter::setContext as this
133 * would lead to symfony cache warmup problems.
134 * a request matcher does not need the request context separately as it
135 * can get it from the request.
137 if ($this->matcher instanceof RequestContextAwareInterface) {
138 $this->matcher->setContext($this->getContext());
141 return $this->matcher;
145 * @return UrlGeneratorInterface
147 public function getGenerator()
149 $this->generator->setContext($this->getContext());
151 return $this->generator;
155 * Generates a URL from the given parameters.
157 * If the generator is not able to generate the url, it must throw the
158 * RouteNotFoundException as documented below.
160 * @param string|Route $name The name of the route or the Route instance
161 * @param mixed $parameters An array of parameters
162 * @param bool|string $referenceType The type of reference to be generated (one of the constants in UrlGeneratorInterface)
164 * @return string The generated URL
166 * @throws RouteNotFoundException if route doesn't exist
170 public function generate($name, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
172 if ($this->eventDispatcher) {
173 $event = new RouterGenerateEvent($name, $parameters, $referenceType);
174 $this->eventDispatcher->dispatch(Events::PRE_DYNAMIC_GENERATE, $event);
175 $name = $event->getRoute();
176 $parameters = $event->getParameters();
177 $referenceType = $event->getReferenceType();
180 return $this->getGenerator()->generate($name, $parameters, $referenceType);
184 * Delegate to our generator.
188 public function supports($name)
190 if ($this->generator instanceof VersatileGeneratorInterface) {
191 return $this->generator->supports($name);
194 return is_string($name);
198 * Tries to match a URL path with a set of routes.
200 * If the matcher can not find information, it must throw one of the
201 * exceptions documented below.
203 * @param string $pathinfo The path info to be parsed (raw format, i.e. not
206 * @return array An array of parameters
208 * @throws ResourceNotFoundException If the resource could not be found
209 * @throws MethodNotAllowedException If the resource was found but the
210 * request method is not allowed
212 * @deprecated Use matchRequest exclusively to avoid problems. This method will be removed in version 2.0
216 public function match($pathinfo)
218 @trigger_error(__METHOD__.'() is deprecated since version 1.3 and will be removed in 2.0. Use matchRequest() instead.', E_USER_DEPRECATED);
220 $request = Request::create($pathinfo);
221 if ($this->eventDispatcher) {
222 $event = new RouterMatchEvent();
223 $this->eventDispatcher->dispatch(Events::PRE_DYNAMIC_MATCH, $event);
226 if (!empty($this->uriFilterRegexp) && !preg_match($this->uriFilterRegexp, $pathinfo)) {
227 throw new ResourceNotFoundException("$pathinfo does not match the '{$this->uriFilterRegexp}' pattern");
230 $matcher = $this->getMatcher();
231 if (!$matcher instanceof UrlMatcherInterface) {
232 throw new \InvalidArgumentException('Wrong matcher type, you need to call matchRequest');
235 $defaults = $matcher->match($pathinfo);
237 return $this->applyRouteEnhancers($defaults, $request);
241 * Tries to match a request with a set of routes and returns the array of
242 * information for that route.
244 * If the matcher can not find information, it must throw one of the
245 * exceptions documented below.
247 * @param Request $request The request to match
249 * @return array An array of parameters
251 * @throws ResourceNotFoundException If no matching resource could be found
252 * @throws MethodNotAllowedException If a matching resource was found but
253 * the request method is not allowed
255 public function matchRequest(Request $request)
257 if ($this->eventDispatcher) {
258 $event = new RouterMatchEvent($request);
259 $this->eventDispatcher->dispatch(Events::PRE_DYNAMIC_MATCH_REQUEST, $event);
262 if (!empty($this->uriFilterRegexp)
263 && !preg_match($this->uriFilterRegexp, $request->getPathInfo())
265 throw new ResourceNotFoundException("{$request->getPathInfo()} does not match the '{$this->uriFilterRegexp}' pattern");
268 $matcher = $this->getMatcher();
269 if ($matcher instanceof UrlMatcherInterface) {
270 $defaults = $matcher->match($request->getPathInfo());
272 $defaults = $matcher->matchRequest($request);
275 return $this->applyRouteEnhancers($defaults, $request);
279 * Apply the route enhancers to the defaults, according to priorities.
281 * @param array $defaults
282 * @param Request $request
286 protected function applyRouteEnhancers($defaults, Request $request)
288 foreach ($this->getRouteEnhancers() as $enhancer) {
289 $defaults = $enhancer->enhance($defaults, $request);
296 * Add route enhancers to the router to let them generate information on
299 * The order of the enhancers is determined by the priority, the higher the
300 * value, the earlier the enhancer is run.
302 * @param RouteEnhancerInterface $enhancer
303 * @param int $priority
305 public function addRouteEnhancer(RouteEnhancerInterface $enhancer, $priority = 0)
307 if (empty($this->enhancers[$priority])) {
308 $this->enhancers[$priority] = array();
311 $this->enhancers[$priority][] = $enhancer;
312 $this->sortedEnhancers = array();
318 * Sorts the enhancers and flattens them.
320 * @return RouteEnhancerInterface[] the enhancers ordered by priority
322 public function getRouteEnhancers()
324 if (empty($this->sortedEnhancers)) {
325 $this->sortedEnhancers = $this->sortRouteEnhancers();
328 return $this->sortedEnhancers;
332 * Sort enhancers by priority.
334 * The highest priority number is the highest priority (reverse sorting).
336 * @return RouteEnhancerInterface[] the sorted enhancers
338 protected function sortRouteEnhancers()
340 $sortedEnhancers = array();
341 krsort($this->enhancers);
343 foreach ($this->enhancers as $enhancers) {
344 $sortedEnhancers = array_merge($sortedEnhancers, $enhancers);
347 return $sortedEnhancers;
351 * Sets the request context.
353 * @param RequestContext $context The context
357 public function setContext(RequestContext $context)
359 $this->context = $context;
363 * Gets the request context.
365 * @return RequestContext The context
369 public function getContext()
371 return $this->context;
377 * Forwards to the generator.
379 public function getRouteDebugMessage($name, array $parameters = array())
381 if ($this->generator instanceof VersatileGeneratorInterface) {
382 return $this->generator->getRouteDebugMessage($name, $parameters);
385 return "Route '$name' not found";