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;
+  }
+
+}