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