comparison core/lib/Drupal/Core/Routing/Router.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 1fec387a4317
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
1 <?php
2
3 namespace Drupal\Core\Routing;
4
5 use Drupal\Core\Path\CurrentPathStack;
6 use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface as BaseRouteEnhancerInterface;
7 use Symfony\Cmf\Component\Routing\LazyRouteCollection;
8 use Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface as BaseRouteFilterInterface;
9 use Symfony\Cmf\Component\Routing\RouteProviderInterface as BaseRouteProviderInterface;
10 use Symfony\Component\HttpFoundation\Request;
11 use Symfony\Component\Routing\Exception\MethodNotAllowedException;
12 use Symfony\Component\Routing\Exception\ResourceNotFoundException;
13 use Symfony\Component\Routing\Generator\UrlGeneratorInterface as BaseUrlGeneratorInterface;
14 use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
15 use Symfony\Component\Routing\RouteCollection;
16 use Symfony\Component\Routing\RouterInterface;
17
18 /**
19 * Router implementation in Drupal.
20 *
21 * A router determines, for an incoming request, the active controller, which is
22 * a callable that creates a response.
23 *
24 * It consists of several steps, of which each are explained in more details
25 * below:
26 * 1. Get a collection of routes which potentially match the current request.
27 * This is done by the route provider. See ::getInitialRouteCollection().
28 * 2. Filter the collection down further more. For example this filters out
29 * routes applying to other formats: See ::applyRouteFilters()
30 * 3. Find the best matching route out of the remaining ones, by applying a
31 * regex. See ::matchCollection().
32 * 4. Enhance the list of route attributes, for example loading entity objects.
33 * See ::applyRouteEnhancers().
34 *
35 * This implementation uses ideas of the following routers:
36 * - \Symfony\Cmf\Component\Routing\DynamicRouter
37 * - \Drupal\Core\Routing\UrlMatcher
38 * - \Symfony\Cmf\Component\Routing\NestedMatcher\NestedMatcher
39 *
40 * @see \Symfony\Cmf\Component\Routing\DynamicRouter
41 * @see \Drupal\Core\Routing\UrlMatcher
42 * @see \Symfony\Cmf\Component\Routing\NestedMatcher\NestedMatcher
43 */
44 class Router extends UrlMatcher implements RequestMatcherInterface, RouterInterface {
45
46 /**
47 * The route provider responsible for the first-pass match.
48 *
49 * @var \Symfony\Cmf\Component\Routing\RouteProviderInterface
50 */
51 protected $routeProvider;
52
53 /**
54 * The list of available enhancers.
55 *
56 * @var \Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface[]
57 */
58 protected $enhancers = [];
59
60 /**
61 * Cached sorted list of enhancers.
62 *
63 * @var \Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface[]
64 */
65 protected $sortedEnhancers;
66
67 /**
68 * The list of available route filters.
69 *
70 * @var \Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface[]
71 */
72 protected $filters = [];
73
74 /**
75 * Cached sorted list route filters.
76 *
77 * @var \Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface[]
78 */
79 protected $sortedFilters;
80
81 /**
82 * The URL generator.
83 *
84 * @var \Symfony\Component\Routing\Generator\UrlGeneratorInterface
85 */
86 protected $urlGenerator;
87
88 /**
89 * Constructs a new Router.
90 *
91 * @param \Symfony\Cmf\Component\Routing\RouteProviderInterface $route_provider
92 * The route provider.
93 * @param \Drupal\Core\Path\CurrentPathStack $current_path
94 * The current path stack.
95 * @param \Symfony\Component\Routing\Generator\UrlGeneratorInterface $url_generator
96 * The URL generator.
97 */
98 public function __construct(BaseRouteProviderInterface $route_provider, CurrentPathStack $current_path, BaseUrlGeneratorInterface $url_generator) {
99 parent::__construct($current_path);
100 $this->routeProvider = $route_provider;
101 $this->urlGenerator = $url_generator;
102 }
103
104 /**
105 * Adds a route enhancer to the list of used route enhancers.
106 *
107 * @param \Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface $route_enhancer
108 * A route enhancer.
109 * @param int $priority
110 * (optional) The priority of the enhancer. Higher number enhancers will be
111 * used first.
112 *
113 * @return $this
114 */
115 public function addRouteEnhancer(BaseRouteEnhancerInterface $route_enhancer, $priority = 0) {
116 $this->enhancers[$priority][] = $route_enhancer;
117 return $this;
118 }
119
120 /**
121 * Adds a route filter to the list of used route filters.
122 *
123 * @param \Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface $route_filter
124 * A route filter.
125 * @param int $priority
126 * (optional) The priority of the filter. Higher number filters will be used
127 * first.
128 *
129 * @return $this
130 */
131 public function addRouteFilter(BaseRouteFilterInterface $route_filter, $priority = 0) {
132 $this->filters[$priority][] = $route_filter;
133
134 return $this;
135 }
136
137 /**
138 * {@inheritdoc}
139 */
140 public function match($pathinfo) {
141 $request = Request::create($pathinfo);
142
143 return $this->matchRequest($request);
144 }
145
146 /**
147 * {@inheritdoc}
148 */
149 public function matchRequest(Request $request) {
150 $collection = $this->getInitialRouteCollection($request);
151 $collection = $this->applyRouteFilters($collection, $request);
152
153 if ($ret = $this->matchCollection(rawurldecode($this->currentPath->getPath($request)), $collection)) {
154 return $this->applyRouteEnhancers($ret, $request);
155 }
156
157 throw 0 < count($this->allow)
158 ? new MethodNotAllowedException(array_unique($this->allow))
159 : new ResourceNotFoundException(sprintf('No routes found for "%s".', $this->currentPath->getPath()));
160 }
161
162 /**
163 * Tries to match a URL with a set of routes.
164 *
165 * @param string $pathinfo
166 * The path info to be parsed
167 * @param \Symfony\Component\Routing\RouteCollection $routes
168 * The set of routes.
169 *
170 * @return array|null
171 * An array of parameters. NULL when there is no match.
172 */
173 protected function matchCollection($pathinfo, RouteCollection $routes) {
174 // Try a case-sensitive match.
175 $match = $this->doMatchCollection($pathinfo, $routes, TRUE);
176 // Try a case-insensitive match.
177 if ($match === NULL && $routes->count() > 0) {
178 $match = $this->doMatchCollection($pathinfo, $routes, FALSE);
179 }
180 return $match;
181 }
182
183 /**
184 * Tries to match a URL with a set of routes.
185 *
186 * This code is very similar to Symfony's UrlMatcher::matchCollection() but it
187 * supports case-insensitive matching. The static prefix optimization is
188 * removed as this duplicates work done by the query in
189 * RouteProvider::getRoutesByPath().
190 *
191 * @param string $pathinfo
192 * The path info to be parsed
193 * @param \Symfony\Component\Routing\RouteCollection $routes
194 * The set of routes.
195 * @param bool $case_sensitive
196 * Determines if the match should be case-sensitive of not.
197 *
198 * @return array|null
199 * An array of parameters. NULL when there is no match.
200 *
201 * @see \Symfony\Component\Routing\Matcher\UrlMatcher::matchCollection()
202 * @see \Drupal\Core\Routing\RouteProvider::getRoutesByPath()
203 */
204 protected function doMatchCollection($pathinfo, RouteCollection $routes, $case_sensitive) {
205 foreach ($routes as $name => $route) {
206 $compiledRoute = $route->compile();
207
208 // Set the regex to use UTF-8.
209 $regex = $compiledRoute->getRegex() . 'u';
210 if (!$case_sensitive) {
211 $regex = $regex . 'i';
212 }
213 if (!preg_match($regex, $pathinfo, $matches)) {
214 continue;
215 }
216
217 $hostMatches = [];
218 if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) {
219 $routes->remove($name);
220 continue;
221 }
222
223 // Check HTTP method requirement.
224 if ($requiredMethods = $route->getMethods()) {
225 // HEAD and GET are equivalent as per RFC.
226 if ('HEAD' === $method = $this->context->getMethod()) {
227 $method = 'GET';
228 }
229
230 if (!in_array($method, $requiredMethods)) {
231 $this->allow = array_merge($this->allow, $requiredMethods);
232 $routes->remove($name);
233 continue;
234 }
235 }
236
237 $status = $this->handleRouteRequirements($pathinfo, $name, $route);
238
239 if (self::ROUTE_MATCH === $status[0]) {
240 return $status[1];
241 }
242
243 if (self::REQUIREMENT_MISMATCH === $status[0]) {
244 $routes->remove($name);
245 continue;
246 }
247
248 return $this->getAttributes($route, $name, array_replace($matches, $hostMatches));
249 }
250 }
251
252 /**
253 * Returns a collection of potential matching routes for a request.
254 *
255 * @param \Symfony\Component\HttpFoundation\Request $request
256 * The current request.
257 *
258 * @return \Symfony\Component\Routing\RouteCollection
259 * The initial fetched route collection.
260 */
261 protected function getInitialRouteCollection(Request $request) {
262 return $this->routeProvider->getRouteCollectionForRequest($request);
263 }
264
265 /**
266 * Apply the route enhancers to the defaults, according to priorities.
267 *
268 * @param array $defaults
269 * The defaults coming from the final matched route.
270 * @param \Symfony\Component\HttpFoundation\Request $request
271 * The request.
272 *
273 * @return array
274 * The request attributes after applying the enhancers. This might consist
275 * raw values from the URL but also upcasted values, like entity objects,
276 * from route enhancers.
277 */
278 protected function applyRouteEnhancers($defaults, Request $request) {
279 foreach ($this->getRouteEnhancers() as $enhancer) {
280 $defaults = $enhancer->enhance($defaults, $request);
281 }
282
283 return $defaults;
284 }
285
286 /**
287 * Sorts the enhancers and flattens them.
288 *
289 * @return \Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface[]
290 * The enhancers ordered by priority.
291 */
292 public function getRouteEnhancers() {
293 if (!isset($this->sortedEnhancers)) {
294 $this->sortedEnhancers = $this->sortRouteEnhancers();
295 }
296
297 return $this->sortedEnhancers;
298 }
299
300 /**
301 * Sort enhancers by priority.
302 *
303 * The highest priority number is the highest priority (reverse sorting).
304 *
305 * @return \Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface[]
306 * The sorted enhancers.
307 */
308 protected function sortRouteEnhancers() {
309 $sortedEnhancers = [];
310 krsort($this->enhancers);
311
312 foreach ($this->enhancers as $enhancers) {
313 $sortedEnhancers = array_merge($sortedEnhancers, $enhancers);
314 }
315
316 return $sortedEnhancers;
317 }
318
319 /**
320 * Applies all route filters to a given route collection.
321 *
322 * This method reduces the sets of routes further down, for example by
323 * checking the HTTP method.
324 *
325 * @param \Symfony\Component\Routing\RouteCollection $collection
326 * The route collection.
327 * @param \Symfony\Component\HttpFoundation\Request $request
328 * The request.
329 *
330 * @return \Symfony\Component\Routing\RouteCollection
331 * The filtered/sorted route collection.
332 */
333 protected function applyRouteFilters(RouteCollection $collection, Request $request) {
334 // Route filters are expected to throw an exception themselves if they
335 // end up filtering the list down to 0.
336 foreach ($this->getRouteFilters() as $filter) {
337 $collection = $filter->filter($collection, $request);
338 }
339
340 return $collection;
341 }
342
343 /**
344 * Sorts the filters and flattens them.
345 *
346 * @return \Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface[]
347 * The filters ordered by priority
348 */
349 public function getRouteFilters() {
350 if (!isset($this->sortedFilters)) {
351 $this->sortedFilters = $this->sortFilters();
352 }
353
354 return $this->sortedFilters;
355 }
356
357 /**
358 * Sort filters by priority.
359 *
360 * The highest priority number is the highest priority (reverse sorting).
361 *
362 * @return \Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface[]
363 * The sorted filters.
364 */
365 protected function sortFilters() {
366 $sortedFilters = [];
367 krsort($this->filters);
368
369 foreach ($this->filters as $filters) {
370 $sortedFilters = array_merge($sortedFilters, $filters);
371 }
372
373 return $sortedFilters;
374 }
375
376 /**
377 * {@inheritdoc}
378 */
379 public function getRouteCollection() {
380 return new LazyRouteCollection($this->routeProvider);
381 }
382
383 /**
384 * {@inheritdoc}
385 */
386 public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH) {
387 @trigger_error('Use the \Drupal\Core\Url object instead', E_USER_DEPRECATED);
388 return $this->urlGenerator->generate($name, $parameters, $referenceType);
389 }
390
391 }