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

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children af1871eacc83
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
1 <?php
2
3 namespace Drupal\Core\Routing;
4
5 use Symfony\Component\Routing\RouteCompilerInterface;
6 use Symfony\Component\Routing\Route;
7 use Symfony\Component\Routing\RouteCompiler as SymfonyRouteCompiler;
8
9 /**
10 * Compiler to generate derived information from a Route necessary for matching.
11 */
12 class RouteCompiler extends SymfonyRouteCompiler implements RouteCompilerInterface {
13
14 /**
15 * Utility constant to use for regular expressions against the path.
16 */
17 const REGEX_DELIMITER = '#';
18
19 /**
20 * Compiles the current route instance.
21 *
22 * Because so much of the parent class is private, we need to call the parent
23 * class's compile() method and then dissect its return value to build our
24 * new compiled object. If upstream gets refactored so we can subclass more
25 * easily then this may not be necessary.
26 *
27 * @param \Symfony\Component\Routing\Route $route
28 * A Route instance.
29 *
30 * @return \Drupal\Core\Routing\CompiledRoute
31 * A CompiledRoute instance.
32 */
33 public static function compile(Route $route) {
34
35 $symfony_compiled = parent::compile($route);
36
37 // The Drupal-specific compiled information.
38 $stripped_path = static::getPathWithoutDefaults($route);
39 $fit = static::getFit($stripped_path);
40 $pattern_outline = static::getPatternOutline($stripped_path);
41 // We count the number of parts including any optional trailing parts. This
42 // allows the RouteProvider to filter candidate routes more efficiently.
43 $num_parts = count(explode('/', trim($route->getPath(), '/')));
44
45 return new CompiledRoute(
46 $fit,
47 $pattern_outline,
48 $num_parts,
49
50 // The following parameters are what Symfony uses in
51 // \Symfony\Component\Routing\Matcher\UrlMatcher::matchCollection().
52
53 // Set the static prefix to an empty string since it is redundant to
54 // the matching in \Drupal\Core\Routing\RouteProvider::getRoutesByPath()
55 // and by skipping it we more easily make the routing case-insensitive.
56 '',
57 $symfony_compiled->getRegex(),
58 $symfony_compiled->getTokens(),
59 $symfony_compiled->getPathVariables(),
60 $symfony_compiled->getHostRegex(),
61 $symfony_compiled->getHostTokens(),
62 $symfony_compiled->getHostVariables(),
63 $symfony_compiled->getVariables()
64 );
65 }
66
67 /**
68 * Returns the pattern outline.
69 *
70 * The pattern outline is the path pattern but normalized so that all
71 * placeholders are the string '%'.
72 *
73 * @param string $path
74 * The path for which we want the normalized outline.
75 *
76 * @return string
77 * The path pattern outline.
78 */
79 public static function getPatternOutline($path) {
80 return preg_replace('#\{\w+\}#', '%', $path);
81 }
82
83 /**
84 * Determines the fitness of the provided path.
85 *
86 * @param string $path
87 * The path whose fitness we want.
88 *
89 * @return int
90 * The fitness of the path, as an integer.
91 */
92 public static function getFit($path) {
93 $parts = explode('/', trim($path, '/'));
94 $number_parts = count($parts);
95 // We store the highest index of parts here to save some work in the fit
96 // calculation loop.
97 $slashes = $number_parts - 1;
98 // The fit value is a binary number which has 1 at every fixed path
99 // position and 0 where there is a wildcard. We keep track of all such
100 // patterns that exist so that we can minimize the number of path
101 // patterns we need to check in the RouteProvider.
102 $fit = 0;
103 foreach ($parts as $k => $part) {
104 if (strpos($part, '{') === FALSE) {
105 $fit |= 1 << ($slashes - $k);
106 }
107 }
108
109 return $fit;
110 }
111
112 /**
113 * Returns the path of the route, without placeholders with a default value.
114 *
115 * When computing the path outline and fit, we want to skip default-value
116 * placeholders. If we didn't, the path would never match. Note that this
117 * only works for placeholders at the end of the path. Infix placeholders
118 * with default values don't make sense anyway, so that should not be a
119 * problem.
120 *
121 * @param \Symfony\Component\Routing\Route $route
122 * The route to have the placeholders removed from.
123 *
124 * @return string
125 * The path string, stripped of placeholders that have default values.
126 */
127 public static function getPathWithoutDefaults(Route $route) {
128 $path = $route->getPath();
129 $defaults = $route->getDefaults();
130
131 // Remove placeholders with default values from the outline, so that they
132 // will still match.
133 $remove = array_map(function ($a) {
134 return '/{' . $a . '}';
135 }, array_keys($defaults));
136 $path = str_replace($remove, '', $path);
137
138 return $path;
139 }
140
141 }