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