setOption('utf8', TRUE); $symfony_compiled = parent::compile($route); // The Drupal-specific compiled information. $stripped_path = static::getPathWithoutDefaults($route); $fit = static::getFit($stripped_path); $pattern_outline = static::getPatternOutline($stripped_path); // We count the number of parts including any optional trailing parts. This // allows the RouteProvider to filter candidate routes more efficiently. $num_parts = count(explode('/', trim($route->getPath(), '/'))); return new CompiledRoute( $fit, $pattern_outline, $num_parts, // The following parameters are what Symfony uses in // \Symfony\Component\Routing\Matcher\UrlMatcher::matchCollection(). // Set the static prefix to an empty string since it is redundant to // the matching in \Drupal\Core\Routing\RouteProvider::getRoutesByPath() // and by skipping it we more easily make the routing case-insensitive. '', $symfony_compiled->getRegex(), $symfony_compiled->getTokens(), $symfony_compiled->getPathVariables(), $symfony_compiled->getHostRegex(), $symfony_compiled->getHostTokens(), $symfony_compiled->getHostVariables(), $symfony_compiled->getVariables() ); } /** * Returns the pattern outline. * * The pattern outline is the path pattern but normalized so that all * placeholders are the string '%'. * * @param string $path * The path for which we want the normalized outline. * * @return string * The path pattern outline. */ public static function getPatternOutline($path) { return preg_replace('#\{\w+\}#', '%', $path); } /** * Determines the fitness of the provided path. * * @param string $path * The path whose fitness we want. * * @return int * The fitness of the path, as an integer. */ public static function getFit($path) { $parts = explode('/', trim($path, '/')); $number_parts = count($parts); // We store the highest index of parts here to save some work in the fit // calculation loop. $slashes = $number_parts - 1; // The fit value is a binary number which has 1 at every fixed path // position and 0 where there is a wildcard. We keep track of all such // patterns that exist so that we can minimize the number of path // patterns we need to check in the RouteProvider. $fit = 0; foreach ($parts as $k => $part) { if (!str_contains($part, '{')) { $fit |= 1 << ($slashes - $k); } } return $fit; } /** * Returns the path of the route, without placeholders with a default value. * * When computing the path outline and fit, we want to skip default-value * placeholders. If we didn't, the path would never match. Note that this * only works for placeholders at the end of the path. Infix placeholders * with default values don't make sense anyway, so that should not be a * problem. * * @param \Symfony\Component\Routing\Route $route * The route to have the placeholders removed from. * * @return string * The path string, stripped of placeholders that have default values. */ public static function getPathWithoutDefaults(Route $route) { $path = $route->getPath(); $defaults = $route->getDefaults(); // Remove placeholders with default values from the outline, so that they // will still match. $remove = array_map(function ($a) { return '/{' . $a . '}'; }, array_keys($defaults)); $path = str_replace($remove, '', $path); return $path; } }