diff --git a/core/core.services.yml b/core/core.services.yml index 43c61040aa39fd76b12897872576cdf25ed2c53a..51ce4c4ca2714837fe82006565d014ed9beb33d6 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -284,7 +284,7 @@ services: - [setRequest, ['@?request=']] router.route_provider: class: Drupal\Core\Routing\RouteProvider - arguments: ['@database', '@router.builder'] + arguments: ['@database', '@router.builder', '@state'] tags: - { name: event_subscriber } router.route_preloader: @@ -334,7 +334,7 @@ services: arguments: ['@database'] router.dumper: class: Drupal\Core\Routing\MatcherDumper - arguments: ['@database'] + arguments: ['@database', '@state'] router.builder: class: Drupal\Core\Routing\RouteBuilder arguments: ['@router.dumper', '@lock', '@event_dispatcher', '@module_handler', '@controller_resolver', '@state'] diff --git a/core/lib/Drupal/Core/Routing/MatcherDumper.php b/core/lib/Drupal/Core/Routing/MatcherDumper.php index 889b6918e617029ce779f54da407cbded847779b..aeed49ca6c6c96c891e8ceada96812239795a2ef 100644 --- a/core/lib/Drupal/Core/Routing/MatcherDumper.php +++ b/core/lib/Drupal/Core/Routing/MatcherDumper.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Routing; +use Drupal\Core\KeyValueStore\StateInterface; use Symfony\Component\Routing\RouteCollection; use Drupal\Core\Database\Connection; @@ -30,6 +31,13 @@ class MatcherDumper implements MatcherDumperInterface { */ protected $routes; + /** + * The state. + * + * @var \Drupal\Core\KeyValueStore\StateInterface + */ + protected $state; + /** * The name of the SQL table to which to dump the routes. * @@ -43,11 +51,14 @@ class MatcherDumper implements MatcherDumperInterface { * @param \Drupal\Core\Database\Connection $connection * The database connection which will be used to store the route * information. + * @param \Drupal\Core\KeyValueStore\StateInterface $state + * The state. * @param string $table * (optional) The table to store the route info in. Defaults to 'router'. */ - public function __construct(Connection $connection, $table = 'router') { + public function __construct(Connection $connection, StateInterface $state, $table = 'router') { $this->connection = $connection; + $this->state = $state; $this->tableName = $table; } @@ -79,6 +90,7 @@ public function dump(array $options = array()) { $options += array( 'provider' => '', ); + // If there are no new routes, just delete any previously existing of this // provider. if (empty($this->routes) || !count($this->routes)) { @@ -88,6 +100,8 @@ public function dump(array $options = array()) { } // Convert all of the routes into database records. else { + // Accumulate the menu masks on top of any we found before. + $masks = array_flip($this->state->get('routing.menu_masks.' . $this->tableName, array())); $insert = $this->connection->insert($this->tableName)->fields(array( 'name', 'provider', @@ -101,6 +115,11 @@ public function dump(array $options = array()) { foreach ($this->routes as $name => $route) { $route->setOption('compiler_class', '\Drupal\Core\Routing\RouteCompiler'); $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 + // patterns that exist so that we can minimize the the number of path + // patterns we need to check in the RouteProvider. + $masks[$compiled->getFit()] = 1; $names[] = $name; $values = array( 'name' => $name, @@ -136,6 +155,10 @@ public function dump(array $options = array()) { watchdog_exception('Routing', $e); throw $e; } + // Sort the masks so they are in order of descending fit. + $masks = array_keys($masks); + rsort($masks); + $this->state->set('routing.menu_masks.' . $this->tableName, $masks); } // The dumper is reused for multiple providers, so reset the queued routes. $this->routes = NULL; diff --git a/core/lib/Drupal/Core/Routing/RouteCompiler.php b/core/lib/Drupal/Core/Routing/RouteCompiler.php index 966f9d48d1eaa8a8c62991749549b6c7f5daaa16..6ae58e18f083ccec9323ecfb501126bf71266376 100644 --- a/core/lib/Drupal/Core/Routing/RouteCompiler.php +++ b/core/lib/Drupal/Core/Routing/RouteCompiler.php @@ -93,7 +93,10 @@ public static function getFit($path) { // 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 the number of path + // patterns we need to check in the RouteProvider. $fit = 0; foreach ($parts as $k => $part) { if (strpos($part, '{') === FALSE) { diff --git a/core/lib/Drupal/Core/Routing/RouteProvider.php b/core/lib/Drupal/Core/Routing/RouteProvider.php index 20b0ec138706e110810e52610a113fbb64bcb278..3dfe65e8f273d60372cbdae86d6155fa6f2e0f9d 100644 --- a/core/lib/Drupal/Core/Routing/RouteProvider.php +++ b/core/lib/Drupal/Core/Routing/RouteProvider.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Routing; use Drupal\Component\Utility\String; +use Drupal\Core\KeyValueStore\StateInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Exception\RouteNotFoundException; @@ -42,6 +43,13 @@ class RouteProvider implements RouteProviderInterface, EventSubscriberInterface */ protected $routeBuilder; + /** + * The state. + * + * @var \Drupal\Core\KeyValueStore\StateInterface + */ + protected $state; + /** * A cache of already-loaded routes, keyed by route name. * @@ -56,12 +64,15 @@ class RouteProvider implements RouteProviderInterface, EventSubscriberInterface * A database connection object. * @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder * The route builder. + * @param \Drupal\Core\KeyValueStore\StateInterface $state + * The state. * @param string $table * The table in the database to use for matching. */ - public function __construct(Connection $connection, RouteBuilderInterface $route_builder, $table = 'router') { + public function __construct(Connection $connection, RouteBuilderInterface $route_builder, StateInterface $state, $table = 'router') { $this->connection = $connection; $this->routeBuilder = $route_builder; + $this->state = $state; $this->tableName = $table; } @@ -115,10 +126,6 @@ public function getRouteCollectionForRequest(Request $request) { $collection = $this->getRoutesByPath($path); } - if (!$collection->count()) { - throw new ResourceNotFoundException(String::format("The route for '@path' could not be found", array('@path' => $path))); - } - return $collection; } @@ -202,7 +209,24 @@ public function getCandidateOutlines(array $parts) { // The highest possible mask is a 1 bit for every part of the path. We will // check every value down from there to generate a possible outline. - $masks = range($end, 0); + if ($number_parts == 1) { + $masks = array(1); + } + elseif ($number_parts <= 3) { + // Optimization - don't query the state system for short paths. This also + // insulates against the state entry for masks going missing for common + // user-facing paths since we generate all values without checking state. + $masks = range($end, 1); + } + elseif ($number_parts <= 0) { + // No path can match, short-circuit the process. + $masks = array(); + } + else { + // Get the actual patterns that exist out of state. + $masks = (array) $this->state->get('routing.menu_masks.' . $this->tableName, array()); + } + // Only examine patterns that actually exist as router items (the masks). foreach ($masks as $i) { @@ -260,14 +284,18 @@ protected function getRoutesByPath($path) { return $value !== NULL && $value !== ''; })); + $collection = new RouteCollection(); + $ancestors = $this->getCandidateOutlines($parts); + if (empty($ancestors)) { + return $collection; + } $routes = $this->connection->query("SELECT name, route FROM {" . $this->connection->escapeTable($this->tableName) . "} WHERE pattern_outline IN (:patterns) ORDER BY fit DESC, name ASC", array( ':patterns' => $ancestors, )) ->fetchAllKeyed(); - $collection = new RouteCollection(); foreach ($routes as $name => $route) { $route = unserialize($route); if (preg_match($route->compile()->getRegex(), $path, $matches)) { diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/MatcherDumperTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/MatcherDumperTest.php index 8514b3f5b8ca2accfe79efaac266121566383f28..1b2fd8f1194fb9cd7b3c6033ed61391f5f388823 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Routing/MatcherDumperTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Routing/MatcherDumperTest.php @@ -2,11 +2,13 @@ /** * @file - * Definition of Drupal\system\Tests\Routing\UrlMatcherDumperTest. + * Contains \Drupal\system\Tests\Routing\MatcherDumperTest. */ namespace Drupal\system\Tests\Routing; +use Drupal\Core\KeyValueStore\KeyValueMemoryFactory; +use Drupal\Core\KeyValueStore\State; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -27,6 +29,13 @@ class MatcherDumperTest extends UnitTestBase { */ protected $fixtures; + /** + * The state. + * + * @var \Drupal\Core\KeyValueStore\StateInterface + */ + protected $state; + public static function getInfo() { return array( 'name' => 'Dumper tests', @@ -39,6 +48,7 @@ function __construct($test_id = NULL) { parent::__construct($test_id); $this->fixtures = new RoutingFixtures(); + $this->state = new State(new KeyValueMemoryFactory()); } function setUp() { @@ -50,7 +60,7 @@ function setUp() { */ function testCreate() { $connection = Database::getConnection(); - $dumper= new MatcherDumper($connection); + $dumper= new MatcherDumper($connection, $this->state); $class_name = 'Drupal\Core\Routing\MatcherDumper'; $this->assertTrue($dumper instanceof $class_name, 'Dumper created successfully'); @@ -61,7 +71,7 @@ function testCreate() { */ function testAddRoutes() { $connection = Database::getConnection(); - $dumper= new MatcherDumper($connection); + $dumper= new MatcherDumper($connection, $this->state); $route = new Route('test'); $collection = new RouteCollection(); @@ -82,7 +92,7 @@ function testAddRoutes() { */ function testAddAdditionalRoutes() { $connection = Database::getConnection(); - $dumper= new MatcherDumper($connection); + $dumper= new MatcherDumper($connection, $this->state); $route = new Route('test'); $collection = new RouteCollection(); @@ -118,7 +128,7 @@ function testAddAdditionalRoutes() { */ public function testDump() { $connection = Database::getConnection(); - $dumper= new MatcherDumper($connection, 'test_routes'); + $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); $route = new Route('/test/{my}/path'); $route->setOption('compiler_class', 'Drupal\Core\Routing\RouteCompiler'); @@ -142,12 +152,41 @@ public function testDump() { $this->assertTrue($loaded_route instanceof Route, 'Route object retrieved successfully.'); } + /** + * Tests the determination of the masks generation. + */ + public function testMenuMasksGeneration() { + $connection = Database::getConnection(); + $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); + + $collection = new RouteCollection(); + $collection->add('test_route_1', new Route('/test-length-3/{my}/path')); + $collection->add('test_route_2', new Route('/test-length-3/hello/path')); + $collection->add('test_route_3', new Route('/test-length-5/{my}/path/marvin/magrathea')); + $collection->add('test_route_4', new Route('/test-length-7/{my}/path/marvin/magrathea/earth/ursa-minor')); + + $dumper->addRoutes($collection); + + $this->fixtures->createTables($connection); + + $dumper->dump(array('provider' => 'test')); + // Using binary for readability, we expect a 0 at any wildcard slug. They + // should be ordered from longest to shortest. + $expected = array( + bindec('1011111'), + bindec('10111'), + bindec('111'), + bindec('101'), + ); + $this->assertEqual($this->state->get('routing.menu_masks.test_routes'), $expected); + } + /** * Tests that changing the provider of a route updates the dumped value. */ public function testDumpRouteProviderRename() { $connection = Database::getConnection(); - $dumper = new MatcherDumper($connection, 'test_routes'); + $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); $this->fixtures->createTables($connection); $route = new Route('/test'); diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/RouteProviderTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/RouteProviderTest.php index b1437978c7ade60374eaaa525e02a06ce1887fc1..05d1131221430302a0454238c0ffd86cc527e3ef 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Routing/RouteProviderTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Routing/RouteProviderTest.php @@ -7,6 +7,8 @@ namespace Drupal\system\Tests\Routing; +use Drupal\Core\KeyValueStore\KeyValueMemoryFactory; +use Drupal\Core\KeyValueStore\State; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Exception\RouteNotFoundException; use Symfony\Component\Routing\Route; @@ -39,6 +41,13 @@ class RouteProviderTest extends UnitTestBase { */ protected $routeBuilder; + /** + * The state. + * + * @var \Drupal\Core\KeyValueStore\StateInterface + */ + protected $state; + public static function getInfo() { return array( 'name' => 'Route Provider tests', @@ -47,11 +56,10 @@ public static function getInfo() { ); } - function __construct($test_id = NULL) { - parent::__construct($test_id); - + public function setUp() { $this->fixtures = new RoutingFixtures(); $this->routeBuilder = new NullRouteBuilder(); + $this->state = new State(new KeyValueMemoryFactory()); } public function tearDown() { @@ -66,7 +74,7 @@ public function tearDown() { public function testCandidateOutlines() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder); + $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); $parts = array('node', '5', 'edit'); @@ -74,7 +82,7 @@ public function testCandidateOutlines() { $candidates = array_flip($candidates); - $this->assertTrue(count($candidates) == 8, 'Correct number of candidates found'); + $this->assertTrue(count($candidates) == 7, 'Correct number of candidates found'); $this->assertTrue(array_key_exists('/node/5/edit', $candidates), 'First candidate found.'); $this->assertTrue(array_key_exists('/node/5/%', $candidates), 'Second candidate found.'); $this->assertTrue(array_key_exists('/node/%/edit', $candidates), 'Third candidate found.'); @@ -82,7 +90,6 @@ public function testCandidateOutlines() { $this->assertTrue(array_key_exists('/node/5', $candidates), 'Fifth candidate found.'); $this->assertTrue(array_key_exists('/node/%', $candidates), 'Sixth candidate found.'); $this->assertTrue(array_key_exists('/node', $candidates), 'Seventh candidate found.'); - $this->assertTrue(array_key_exists('/', $candidates), 'Eighth candidate found.'); } /** @@ -90,11 +97,11 @@ public function testCandidateOutlines() { */ function testExactPathMatch() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); $this->fixtures->createTables($connection); - $dumper = new MatcherDumper($connection, 'test_routes'); + $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); $dumper->addRoutes($this->fixtures->sampleRouteCollection()); $dumper->dump(); @@ -114,11 +121,11 @@ function testExactPathMatch() { */ function testOutlinePathMatch() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); $this->fixtures->createTables($connection); - $dumper = new MatcherDumper($connection, 'test_routes'); + $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); $dumper->addRoutes($this->fixtures->complexRouteCollection()); $dumper->dump(); @@ -143,11 +150,11 @@ function testOutlinePathMatch() { */ function testOutlinePathMatchTrailingSlash() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); $this->fixtures->createTables($connection); - $dumper = new MatcherDumper($connection, 'test_routes'); + $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); $dumper->addRoutes($this->fixtures->complexRouteCollection()); $dumper->dump(); @@ -172,7 +179,7 @@ function testOutlinePathMatchTrailingSlash() { */ function testOutlinePathMatchDefaults() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); $this->fixtures->createTables($connection); @@ -181,7 +188,7 @@ function testOutlinePathMatchDefaults() { 'value' => 'poink', ))); - $dumper = new MatcherDumper($connection, 'test_routes'); + $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); $dumper->addRoutes($collection); $dumper->dump(); @@ -210,7 +217,7 @@ function testOutlinePathMatchDefaults() { */ function testOutlinePathMatchDefaultsCollision() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); $this->fixtures->createTables($connection); @@ -220,7 +227,7 @@ function testOutlinePathMatchDefaultsCollision() { ))); $collection->add('narf', new Route('/some/path/here')); - $dumper = new MatcherDumper($connection, 'test_routes'); + $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); $dumper->addRoutes($collection); $dumper->dump(); @@ -249,7 +256,7 @@ function testOutlinePathMatchDefaultsCollision() { */ function testOutlinePathMatchDefaultsCollision2() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); $this->fixtures->createTables($connection); @@ -260,7 +267,7 @@ function testOutlinePathMatchDefaultsCollision2() { $collection->add('narf', new Route('/some/path/here')); $collection->add('eep', new Route('/something/completely/different')); - $dumper = new MatcherDumper($connection, 'test_routes'); + $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); $dumper->addRoutes($collection); $dumper->dump(); @@ -288,14 +295,14 @@ function testOutlinePathMatchDefaultsCollision2() { */ public function testOutlinePathMatchZero() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); $this->fixtures->createTables($connection); $collection = new RouteCollection(); $collection->add('poink', new Route('/some/path/{value}')); - $dumper = new MatcherDumper($connection, 'test_routes'); + $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); $dumper->addRoutes($collection); $dumper->dump(); @@ -323,11 +330,11 @@ public function testOutlinePathMatchZero() { */ function testOutlinePathNoMatch() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); $this->fixtures->createTables($connection); - $dumper = new MatcherDumper($connection, 'test_routes'); + $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); $dumper->addRoutes($this->fixtures->complexRouteCollection()); $dumper->dump(); @@ -335,16 +342,12 @@ function testOutlinePathNoMatch() { $request = Request::create($path, 'GET'); - try { - $routes = $provider->getRoutesByPattern($path); - $this->assertFalse(count($routes), 'No path found with this pattern.'); - $provider->getRouteCollectionForRequest($request); - $this->fail(t('No exception was thrown.')); - } - catch (\Exception $e) { - $this->assertTrue($e instanceof ResourceNotFoundException, 'The correct exception was thrown.'); - } + $routes = $provider->getRoutesByPattern($path); + $this->assertFalse(count($routes), 'No path found with this pattern.'); + + $collection = $provider->getRouteCollectionForRequest($request); + $this->assertTrue(count($collection) == 0, 'Empty route collection found with this pattern.'); } /** @@ -352,11 +355,11 @@ function testOutlinePathNoMatch() { */ function testSystemPathMatch() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); $this->fixtures->createTables($connection); - $dumper = new MatcherDumper($connection, 'test_routes'); + $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); $dumper->addRoutes($this->fixtures->sampleRouteCollection()); $dumper->dump(); @@ -377,11 +380,11 @@ function testSystemPathMatch() { */ protected function testRouteByName() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); $this->fixtures->createTables($connection); - $dumper = new MatcherDumper($connection, 'test_routes'); + $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); $dumper->addRoutes($this->fixtures->sampleRouteCollection()); $dumper->dump(); @@ -412,21 +415,57 @@ protected function testRouteByName() { */ public function testGetRoutesByPatternWithLongPatterns() { $connection = Database::getConnection(); - $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes'); + $provider = new RouteProvider($connection, $this->routeBuilder, $this->state, 'test_routes'); $this->fixtures->createTables($connection); - - $dumper = new MatcherDumper($connection, 'test_routes'); + // This pattern has only 3 parts, so we will get candidates, but no routes, + // even though we have not dumped the routes yet. + $shortest = '/test/1/test2'; + $result = $provider->getRoutesByPattern($shortest); + $this->assertEqual($result->count(), 0); + $candidates = $provider->getCandidateOutlines(explode('/', trim($shortest, '/'))); + $this->assertEqual(count($candidates), 7); + // A longer patten is not found and returns no candidates + $path_to_test = '/test/1/test2/2/test3/3/4/5/6/test4'; + $result = $provider->getRoutesByPattern($path_to_test); + $this->assertEqual($result->count(), 0); + $candidates = $provider->getCandidateOutlines(explode('/', trim($path_to_test, '/'))); + $this->assertEqual(count($candidates), 0); + + // Add a matching route and dump it. + $dumper = new MatcherDumper($connection, $this->state, 'test_routes'); $collection = new RouteCollection(); $collection->add('long_pattern', new Route('/test/{v1}/test2/{v2}/test3/{v3}/{v4}/{v5}/{v6}/test4')); $dumper->addRoutes($collection); $dumper->dump(); - $result = $provider->getRoutesByPattern('/test/1/test2/2/test3/3/4/5/6/test4'); + $result = $provider->getRoutesByPattern($path_to_test); $this->assertEqual($result->count(), 1); // We can't compare the values of the routes directly, nor use // spl_object_hash() because they are separate instances. $this->assertEqual(serialize($result->get('long_pattern')), serialize($collection->get('long_pattern')), 'The right route was found.'); + // We now have a single candidate outline. + $candidates = $provider->getCandidateOutlines(explode('/', trim($path_to_test, '/'))); + $this->assertEqual(count($candidates), 1); + // Longer and shorter patterns are not found. Both are longer than 3, so + // we should not have any candidates either. The fact that we do not + // get any candidates for a longer path is a security feature. + $longer = '/test/1/test2/2/test3/3/4/5/6/test4/trailing/more/parts'; + $result = $provider->getRoutesByPattern($longer); + $this->assertEqual($result->count(), 0); + $candidates = $provider->getCandidateOutlines(explode('/', trim($longer, '/'))); + $this->assertEqual(count($candidates), 1); + $shorter = '/test/1/test2/2/test3'; + $result = $provider->getRoutesByPattern($shorter); + $this->assertEqual($result->count(), 0); + $candidates = $provider->getCandidateOutlines(explode('/', trim($shorter, '/'))); + $this->assertEqual(count($candidates), 0); + // This pattern has only 3 parts, so we will get candidates, but no routes. + // This result is unchanged by running the dumper. + $result = $provider->getRoutesByPattern($shortest); + $this->assertEqual($result->count(), 0); + $candidates = $provider->getCandidateOutlines(explode('/', trim($shortest, '/'))); + $this->assertEqual(count($candidates), 7); } } diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 21bfb283dfc63000cda5d26ef915d9eae69b8e1b..ca5d12806f59bff08653c6e944506c47120a8ed2 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -812,7 +812,8 @@ function system_schema() { ), 'route' => array( 'description' => 'A serialized Route object', - 'type' => 'text', + 'type' => 'blob', + 'size' => 'big', ), 'number_parts' => array( 'description' => 'Number of parts in this router path.',