Mercurial > hg > isophonics-drupal-site
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/lib/Drupal/Core/Routing/RouteCompiler.php Wed Nov 29 16:09:58 2017 +0000 @@ -0,0 +1,141 @@ +<?php + +namespace Drupal\Core\Routing; + +use Symfony\Component\Routing\RouteCompilerInterface; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCompiler as SymfonyRouteCompiler; + +/** + * Compiler to generate derived information from a Route necessary for matching. + */ +class RouteCompiler extends SymfonyRouteCompiler implements RouteCompilerInterface { + + /** + * Utility constant to use for regular expressions against the path. + */ + const REGEX_DELIMITER = '#'; + + /** + * Compiles the current route instance. + * + * Because so much of the parent class is private, we need to call the parent + * class's compile() method and then dissect its return value to build our + * new compiled object. If upstream gets refactored so we can subclass more + * easily then this may not be necessary. + * + * @param \Symfony\Component\Routing\Route $route + * A Route instance. + * + * @return \Drupal\Core\Routing\CompiledRoute + * A CompiledRoute instance. + */ + public static function compile(Route $route) { + + $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 (strpos($part, '{') === FALSE) { + $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; + } + +}