Version 1
[yaffs-website] / web / core / lib / Drupal / Core / Routing / RouteCompiler.php
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 }