Version 1
[yaffs-website] / vendor / symfony-cmf / routing / DynamicRouter.php
1 <?php
2
3 /*
4  * This file is part of the Symfony CMF package.
5  *
6  * (c) 2011-2015 Symfony CMF
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Symfony\Cmf\Component\Routing;
13
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;
31
32 /**
33  * A flexible router accepting matcher and generator through injection and
34  * using the RouteEnhancer concept to generate additional data on the routes.
35  *
36  * @author Larry Garfield
37  * @author David Buchmann
38  */
39 class DynamicRouter implements RouterInterface, RequestMatcherInterface, ChainedRouterInterface
40 {
41     /**
42      * @var RequestMatcherInterface|UrlMatcherInterface
43      */
44     protected $matcher;
45
46     /**
47      * @var UrlGeneratorInterface
48      */
49     protected $generator;
50
51     /**
52      * @var EventDispatcherInterface
53      */
54     protected $eventDispatcher;
55
56     /**
57      * @var RouteEnhancerInterface[]
58      */
59     protected $enhancers = array();
60
61     /**
62      * Cached sorted list of enhancers.
63      *
64      * @var RouteEnhancerInterface[]
65      */
66     protected $sortedEnhancers = array();
67
68     /**
69      * The regexp pattern that needs to be matched before a dynamic lookup is
70      * made.
71      *
72      * @var string
73      */
74     protected $uriFilterRegexp;
75
76     /**
77      * @var RequestContext
78      */
79     protected $context;
80
81     /**
82      * @var RouteCollection
83      */
84     private $routeCollection;
85
86     /**
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
93      */
94     public function __construct(RequestContext $context,
95                                 $matcher,
96                                 UrlGeneratorInterface $generator,
97                                 $uriFilterRegexp = '',
98                                 EventDispatcherInterface $eventDispatcher = null,
99                                 RouteProviderInterface $provider = null
100     ) {
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');
104         }
105         $this->matcher = $matcher;
106         $this->generator = $generator;
107         $this->eventDispatcher = $eventDispatcher;
108         $this->uriFilterRegexp = $uriFilterRegexp;
109         $this->provider = $provider;
110
111         $this->generator->setContext($context);
112     }
113
114     /**
115      * {@inheritdoc}
116      */
117     public function getRouteCollection()
118     {
119         if (!$this->routeCollection instanceof RouteCollection) {
120             $this->routeCollection = $this->provider
121                 ? new LazyRouteCollection($this->provider) : new RouteCollection();
122         }
123
124         return $this->routeCollection;
125     }
126
127     /**
128      * @return RequestMatcherInterface|UrlMatcherInterface
129      */
130     public function getMatcher()
131     {
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.
136          */
137         if ($this->matcher instanceof RequestContextAwareInterface) {
138             $this->matcher->setContext($this->getContext());
139         }
140
141         return $this->matcher;
142     }
143
144     /**
145      * @return UrlGeneratorInterface
146      */
147     public function getGenerator()
148     {
149         $this->generator->setContext($this->getContext());
150
151         return $this->generator;
152     }
153
154     /**
155      * Generates a URL from the given parameters.
156      *
157      * If the generator is not able to generate the url, it must throw the
158      * RouteNotFoundException as documented below.
159      *
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)
163      *
164      * @return string The generated URL
165      *
166      * @throws RouteNotFoundException if route doesn't exist
167      *
168      * @api
169      */
170     public function generate($name, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
171     {
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();
178         }
179
180         return $this->getGenerator()->generate($name, $parameters, $referenceType);
181     }
182
183     /**
184      * Delegate to our generator.
185      *
186      * {@inheritdoc}
187      */
188     public function supports($name)
189     {
190         if ($this->generator instanceof VersatileGeneratorInterface) {
191             return $this->generator->supports($name);
192         }
193
194         return is_string($name);
195     }
196
197     /**
198      * Tries to match a URL path with a set of routes.
199      *
200      * If the matcher can not find information, it must throw one of the
201      * exceptions documented below.
202      *
203      * @param string $pathinfo The path info to be parsed (raw format, i.e. not
204      *                         urldecoded)
205      *
206      * @return array An array of parameters
207      *
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
211      *
212      * @deprecated Use matchRequest exclusively to avoid problems. This method will be removed in version 2.0
213      *
214      * @api
215      */
216     public function match($pathinfo)
217     {
218         @trigger_error(__METHOD__.'() is deprecated since version 1.3 and will be removed in 2.0. Use matchRequest() instead.', E_USER_DEPRECATED);
219
220         $request = Request::create($pathinfo);
221         if ($this->eventDispatcher) {
222             $event = new RouterMatchEvent();
223             $this->eventDispatcher->dispatch(Events::PRE_DYNAMIC_MATCH, $event);
224         }
225
226         if (!empty($this->uriFilterRegexp) && !preg_match($this->uriFilterRegexp, $pathinfo)) {
227             throw new ResourceNotFoundException("$pathinfo does not match the '{$this->uriFilterRegexp}' pattern");
228         }
229
230         $matcher = $this->getMatcher();
231         if (!$matcher instanceof UrlMatcherInterface) {
232             throw new \InvalidArgumentException('Wrong matcher type, you need to call matchRequest');
233         }
234
235         $defaults = $matcher->match($pathinfo);
236
237         return $this->applyRouteEnhancers($defaults, $request);
238     }
239
240     /**
241      * Tries to match a request with a set of routes and returns the array of
242      * information for that route.
243      *
244      * If the matcher can not find information, it must throw one of the
245      * exceptions documented below.
246      *
247      * @param Request $request The request to match
248      *
249      * @return array An array of parameters
250      *
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
254      */
255     public function matchRequest(Request $request)
256     {
257         if ($this->eventDispatcher) {
258             $event = new RouterMatchEvent($request);
259             $this->eventDispatcher->dispatch(Events::PRE_DYNAMIC_MATCH_REQUEST, $event);
260         }
261
262         if (!empty($this->uriFilterRegexp)
263             && !preg_match($this->uriFilterRegexp, $request->getPathInfo())
264         ) {
265             throw new ResourceNotFoundException("{$request->getPathInfo()} does not match the '{$this->uriFilterRegexp}' pattern");
266         }
267
268         $matcher = $this->getMatcher();
269         if ($matcher instanceof UrlMatcherInterface) {
270             $defaults = $matcher->match($request->getPathInfo());
271         } else {
272             $defaults = $matcher->matchRequest($request);
273         }
274
275         return $this->applyRouteEnhancers($defaults, $request);
276     }
277
278     /**
279      * Apply the route enhancers to the defaults, according to priorities.
280      *
281      * @param array   $defaults
282      * @param Request $request
283      *
284      * @return array
285      */
286     protected function applyRouteEnhancers($defaults, Request $request)
287     {
288         foreach ($this->getRouteEnhancers() as $enhancer) {
289             $defaults = $enhancer->enhance($defaults, $request);
290         }
291
292         return $defaults;
293     }
294
295     /**
296      * Add route enhancers to the router to let them generate information on
297      * matched routes.
298      *
299      * The order of the enhancers is determined by the priority, the higher the
300      * value, the earlier the enhancer is run.
301      *
302      * @param RouteEnhancerInterface $enhancer
303      * @param int                    $priority
304      */
305     public function addRouteEnhancer(RouteEnhancerInterface $enhancer, $priority = 0)
306     {
307         if (empty($this->enhancers[$priority])) {
308             $this->enhancers[$priority] = array();
309         }
310
311         $this->enhancers[$priority][] = $enhancer;
312         $this->sortedEnhancers = array();
313
314         return $this;
315     }
316
317     /**
318      * Sorts the enhancers and flattens them.
319      *
320      * @return RouteEnhancerInterface[] the enhancers ordered by priority
321      */
322     public function getRouteEnhancers()
323     {
324         if (empty($this->sortedEnhancers)) {
325             $this->sortedEnhancers = $this->sortRouteEnhancers();
326         }
327
328         return $this->sortedEnhancers;
329     }
330
331     /**
332      * Sort enhancers by priority.
333      *
334      * The highest priority number is the highest priority (reverse sorting).
335      *
336      * @return RouteEnhancerInterface[] the sorted enhancers
337      */
338     protected function sortRouteEnhancers()
339     {
340         $sortedEnhancers = array();
341         krsort($this->enhancers);
342
343         foreach ($this->enhancers as $enhancers) {
344             $sortedEnhancers = array_merge($sortedEnhancers, $enhancers);
345         }
346
347         return $sortedEnhancers;
348     }
349
350     /**
351      * Sets the request context.
352      *
353      * @param RequestContext $context The context
354      *
355      * @api
356      */
357     public function setContext(RequestContext $context)
358     {
359         $this->context = $context;
360     }
361
362     /**
363      * Gets the request context.
364      *
365      * @return RequestContext The context
366      *
367      * @api
368      */
369     public function getContext()
370     {
371         return $this->context;
372     }
373
374     /**
375      * {@inheritdoc}
376      *
377      * Forwards to the generator.
378      */
379     public function getRouteDebugMessage($name, array $parameters = array())
380     {
381         if ($this->generator instanceof VersatileGeneratorInterface) {
382             return $this->generator->getRouteDebugMessage($name, $parameters);
383         }
384
385         return "Route '$name' not found";
386     }
387 }