diff --git a/core/lib/Drupal/Core/Routing/CompiledRoute.php b/core/lib/Drupal/Core/Routing/CompiledRoute.php index 7354e1225a460621c2f2bc661e8fb5f19b702506..05eede7e0e483ae183dfe44b6a60f84f3961d6d1 100644 --- a/core/lib/Drupal/Core/Routing/CompiledRoute.php +++ b/core/lib/Drupal/Core/Routing/CompiledRoute.php @@ -7,7 +7,6 @@ namespace Drupal\Core\Routing; -use Symfony\Component\Routing\Route; use Symfony\Component\Routing\CompiledRoute as SymfonyCompiledRoute; /** @@ -36,13 +35,6 @@ class CompiledRoute extends SymfonyCompiledRoute { */ protected $numParts; - /** - * The Route object of which this object is the compiled version. - * - * @var \Symfony\Component\Routing\Route - */ - protected $route; - /** * Constructs a new compiled route object. * @@ -51,11 +43,9 @@ class CompiledRoute extends SymfonyCompiledRoute { * problem. The parent Symfony class does the same, as well, making it * difficult to override differently. * - * @param \Symfony\Component\Routing\Route $route - * A original Route instance. * @param int $fit * The fitness of the route. - * @param string $fit + * @param string $pattern_outline * The pattern outline for this route. * @param int $num_parts * The number of parts in the path. @@ -76,10 +66,9 @@ class CompiledRoute extends SymfonyCompiledRoute { * @param array $variables * An array of variables (variables defined in the path and in the host patterns) */ - public function __construct(Route $route, $fit, $pattern_outline, $num_parts, $staticPrefix, $regex, array $tokens, array $pathVariables, $hostRegex = null, array $hostTokens = array(), array $hostVariables = array(), array $variables = array()) { + public function __construct($fit, $pattern_outline, $num_parts, $staticPrefix, $regex, array $tokens, array $pathVariables, $hostRegex = null, array $hostTokens = array(), array $hostVariables = array(), array $variables = array()) { parent::__construct($staticPrefix, $regex, $tokens, $pathVariables, $hostRegex, $hostTokens, $hostVariables, $variables); - $this->route = $route; $this->fit = $fit; $this->patternOutline = $pattern_outline; $this->numParts = $num_parts; @@ -123,26 +112,6 @@ public function getPatternOutline() { return $this->patternOutline; } - /** - * Returns the Route instance. - * - * @return Route - * A Route instance. - */ - public function getRoute() { - return $this->route; - } - - /** - * Returns the path. - * - * @return string - * The path. - */ - public function getPath() { - return $this->route->getPath(); - } - /** * Returns the options. * @@ -177,6 +146,8 @@ public function getRequirements() { * {@inheritdoc} */ public function serialize() { + // Calling the parent method is safer than trying to optimize out the extra + // function calls. $data = unserialize(parent::serialize()); $data['fit'] = $this->fit; $data['patternOutline'] = $this->patternOutline; @@ -188,8 +159,7 @@ public function serialize() { /** * {@inheritdoc} */ - public function unserialize($serialized) - { + public function unserialize($serialized) { parent::unserialize($serialized); $data = unserialize($serialized); diff --git a/core/lib/Drupal/Core/Routing/MatcherDumper.php b/core/lib/Drupal/Core/Routing/MatcherDumper.php index 0b409467bcbb171b61ae1a86fba6c6ef7eb9ba50..43e3219c2a579d72b088d7aace07c3031f3ea3c9 100644 --- a/core/lib/Drupal/Core/Routing/MatcherDumper.php +++ b/core/lib/Drupal/Core/Routing/MatcherDumper.php @@ -114,6 +114,7 @@ public function dump(array $options = array()) { foreach ($routes as $name => $route) { /** @var \Symfony\Component\Routing\Route $route */ $route->setOption('compiler_class', '\Drupal\Core\Routing\RouteCompiler'); + /** @var \Drupal\Core\Routing\CompiledRoute $compiled */ $compiled = $route->compile(); // 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 @@ -124,7 +125,7 @@ public function dump(array $options = array()) { $values = array( 'name' => $name, 'fit' => $compiled->getFit(), - 'path' => $compiled->getPath(), + 'path' => $route->getPath(), 'pattern_outline' => $compiled->getPatternOutline(), 'number_parts' => $compiled->getNumParts(), 'route' => serialize($route), diff --git a/core/lib/Drupal/Core/Routing/PreloadableRouteProviderInterface.php b/core/lib/Drupal/Core/Routing/PreloadableRouteProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..fc490df2c06c4c5927c0629dfdd638b60e609276 --- /dev/null +++ b/core/lib/Drupal/Core/Routing/PreloadableRouteProviderInterface.php @@ -0,0 +1,27 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\Routing\PreloadableRouteProviderInterface. + */ + +namespace Drupal\Core\Routing; + +/** + * Extends the router provider interface to pre-load routes. + */ +interface PreloadableRouteProviderInterface extends RouteProviderInterface { + + /** + * Pre-load routes by their names using the provided list of names. + * + * This method exists in order to allow performance optimizations. It allows + * pre-loading serialized routes that may latter be retrieved using + * ::getRoutesByName() + * + * @param string[] $names + * Array of route names to load. + */ + public function preLoadRoutes($names); + +} diff --git a/core/lib/Drupal/Core/Routing/RouteCompiler.php b/core/lib/Drupal/Core/Routing/RouteCompiler.php index 9e6de2c4d9fa99e246f01ea03c8561ae8c9ecb34..5c129cc34bd4a3026d5ec7bfcc3c9a6391f46163 100644 --- a/core/lib/Drupal/Core/Routing/RouteCompiler.php +++ b/core/lib/Drupal/Core/Routing/RouteCompiler.php @@ -46,7 +46,6 @@ public static function compile(Route $route) { $num_parts = count(explode('/', trim($pattern_outline, '/'))); return new CompiledRoute( - $route, $fit, $pattern_outline, $num_parts, diff --git a/core/lib/Drupal/Core/Routing/RoutePreloader.php b/core/lib/Drupal/Core/Routing/RoutePreloader.php index 1f223bfd48608753b32dbc716e699b74322008cc..23a09aa33f21ce6ab71070ac9eb4722404f62a71 100644 --- a/core/lib/Drupal/Core/Routing/RoutePreloader.php +++ b/core/lib/Drupal/Core/Routing/RoutePreloader.php @@ -25,7 +25,7 @@ class RoutePreloader implements EventSubscriberInterface { /** * The route provider. * - * @var \Drupal\Core\Routing\RouteProviderInterface + * @var \Drupal\Core\Routing\RouteProviderInterface|\Drupal\Core\Routing\PreloadableRouteProviderInterface */ protected $routeProvider; @@ -63,18 +63,12 @@ public function __construct(RouteProviderInterface $route_provider, StateInterfa * The event to process. */ public function onRequest(KernelEvent $event) { - // Just preload on normal HTML pages, as they will display menu links. - if ($event->getRequest()->getRequestFormat() == 'html') { - $this->loadNonAdminRoutes(); - } - } - - /** - * Load all the non-admin routes at once. - */ - protected function loadNonAdminRoutes() { - if ($routes = $this->state->get('routing.non_admin_routes', array())) { - $this->routeProvider->getRoutesByNames($routes); + // Only preload on normal HTML pages, as they will display menu links. + if ($this->routeProvider instanceof PreloadableRouteProviderInterface && $event->getRequest()->getRequestFormat() == 'html') { + if ($routes = $this->state->get('routing.non_admin_routes', [])) { + // Preload all the non-admin routes at once. + $this->routeProvider->preLoadRoutes($routes); + } } } diff --git a/core/lib/Drupal/Core/Routing/RouteProvider.php b/core/lib/Drupal/Core/Routing/RouteProvider.php index 4d389f1e9839f01c33908c2e3bf25eebb37c2d9a..8f98802cbb7e288eb6339ef25346dea04c507438 100644 --- a/core/lib/Drupal/Core/Routing/RouteProvider.php +++ b/core/lib/Drupal/Core/Routing/RouteProvider.php @@ -22,7 +22,7 @@ /** * A Route Provider front-end for all Drupal-stored routes. */ -class RouteProvider implements RouteProviderInterface, PagedRouteProviderInterface, EventSubscriberInterface { +class RouteProvider implements PreloadableRouteProviderInterface, PagedRouteProviderInterface, EventSubscriberInterface { /** * The database connection from which to read route information. @@ -48,10 +48,17 @@ class RouteProvider implements RouteProviderInterface, PagedRouteProviderInterfa /** * A cache of already-loaded routes, keyed by route name. * - * @var array + * @var \Symfony\Component\Routing\Route[] */ protected $routes = array(); + /** + * A cache of already-loaded serialized routes, keyed by route name. + * + * @var string[] + */ + protected $serializedRoutes = []; + /** * The current path. * @@ -131,34 +138,32 @@ public function getRouteByName($name) { } /** - * Find many routes by their names using the provided list of names. - * - * Note that this method may not throw an exception if some of the routes - * are not found. It will just return the list of those routes it found. - * - * This method exists in order to allow performance optimizations. The - * simple implementation could be to just repeatedly call - * $this->getRouteByName(). - * - * @param array $names - * The list of names to retrieve. - * - * @return \Symfony\Component\Routing\Route[] - * Iterable thing with the keys the names of the $names argument. + * {@inheritdoc} */ - public function getRoutesByNames($names) { - + public function preLoadRoutes($names) { if (empty($names)) { throw new \InvalidArgumentException('You must specify the route names to load'); } - $routes_to_load = array_diff($names, array_keys($this->routes)); + $routes_to_load = array_diff($names, array_keys($this->routes), array_keys($this->serializedRoutes)); if ($routes_to_load) { $result = $this->connection->query('SELECT name, route FROM {' . $this->connection->escapeTable($this->tableName) . '} WHERE name IN ( :names[] )', array(':names[]' => $routes_to_load)); $routes = $result->fetchAllKeyed(); + $this->serializedRoutes += $routes; + } + } + + /** + * {@inheritdoc} + */ + public function getRoutesByNames($names) { + $this->preLoadRoutes($names); - foreach ($routes as $name => $route) { - $this->routes[$name] = unserialize($route); + foreach ($names as $name) { + // The specified route name might not exist or might be serialized. + if (!isset($this->routes[$name]) && isset($this->serializedRoutes[$name])) { + $this->routes[$name] = unserialize($this->serializedRoutes[$name]); + unset($this->serializedRoutes[$name]); } } @@ -291,6 +296,7 @@ public function getAllRoutes() { */ public function reset() { $this->routes = array(); + $this->serializedRoutes = array(); } /** diff --git a/core/modules/system/src/Tests/Routing/MockRouteProvider.php b/core/modules/system/src/Tests/Routing/MockRouteProvider.php index dce1b27ffc5def2c9bd12c4e7f68b81e0cd97177..eba563fa12f036537428a03d771ac861948d1d6f 100644 --- a/core/modules/system/src/Tests/Routing/MockRouteProvider.php +++ b/core/modules/system/src/Tests/Routing/MockRouteProvider.php @@ -56,6 +56,13 @@ public function getRouteByName($name) { return reset($routes); } + /** + * {@inheritdoc} + */ + public function preLoadRoutes($names) { + // Nothing to do. + } + /** * Implements \Symfony\Cmf\Component\Routing\RouteProviderInterface::getRoutesByName(). */ diff --git a/core/tests/Drupal/Tests/Core/Routing/RouteCompilerTest.php b/core/tests/Drupal/Tests/Core/Routing/RouteCompilerTest.php index 4d1977c499562b1bbe8bf9ccfbc1c85ac6453030..a64cd696462e740964c93f31149c7a1f30d8f322 100644 --- a/core/tests/Drupal/Tests/Core/Routing/RouteCompilerTest.php +++ b/core/tests/Drupal/Tests/Core/Routing/RouteCompilerTest.php @@ -62,7 +62,6 @@ public function testCompilation() { $route->setOption('compiler_class', 'Drupal\Core\Routing\RouteCompiler'); $compiled = $route->compile(); - $this->assertEquals($route, $compiled->getRoute(), 'Compiled route has the incorrect route object.'); $this->assertEquals($compiled->getFit(), 5 /* That's 101 binary*/, 'The fit was incorrect.'); $this->assertEquals($compiled->getPatternOutline(), '/test/%/more', 'The pattern outline was not correct.'); } @@ -79,7 +78,6 @@ public function testCompilationDefaultValue() { $route->setOption('compiler_class', 'Drupal\Core\Routing\RouteCompiler'); $compiled = $route->compile(); - $this->assertEquals($route, $compiled->getRoute(), 'Compiled route has an incorrect route object.'); $this->assertEquals($compiled->getFit(), 5 /* That's 101 binary*/, 'The fit was not correct.'); $this->assertEquals($compiled->getPatternOutline(), '/test/%/more', 'The pattern outline was not correct.'); } diff --git a/core/tests/Drupal/Tests/Core/Routing/RoutePreloaderTest.php b/core/tests/Drupal/Tests/Core/Routing/RoutePreloaderTest.php index 5692e6a1df71e57d1f8671ce1ebd7122bcfbbebf..189aa92fb927e246b636b117963474472d0626a7 100644 --- a/core/tests/Drupal/Tests/Core/Routing/RoutePreloaderTest.php +++ b/core/tests/Drupal/Tests/Core/Routing/RoutePreloaderTest.php @@ -45,7 +45,7 @@ class RoutePreloaderTest extends UnitTestCase { * {@inheritdoc} */ protected function setUp() { - $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); + $this->routeProvider = $this->getMock('Drupal\Core\Routing\PreloadableRouteProviderInterface'); $this->state = $this->getMock('\Drupal\Core\State\StateInterface'); $this->preloader = new RoutePreloader($this->routeProvider, $this->state); } @@ -153,8 +153,8 @@ public function testOnRequestOnHtml() { ->will($this->returnValue($request)); $this->routeProvider->expects($this->once()) - ->method('getRoutesByNames') - ->with(array('test2')); + ->method('preLoadRoutes') + ->with(['test2']); $this->state->expects($this->once()) ->method('get') ->with('routing.non_admin_routes')