Add a skeleton for a Path matcher.
The PathMatcher matches against the database table structure generated by the MatcherDumper. As of this commit the lookup is not yet implemented. It's still in testing.8.0.x
parent
db11de09c8
commit
e9a95aa1fb
|
@ -23,6 +23,13 @@ class CompiledRoute {
|
|||
*/
|
||||
protected $patternOutline;
|
||||
|
||||
/**
|
||||
* The number of parts in the path of this route.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $numParts;
|
||||
|
||||
/**
|
||||
* The Route object of which this object is the compiled version.
|
||||
*
|
||||
|
@ -45,11 +52,14 @@ class CompiledRoute {
|
|||
* The fitness of the route.
|
||||
* @param string $fit
|
||||
* The pattern outline for this route.
|
||||
* @param int $num_parts
|
||||
* The number of parts in the path.
|
||||
*/
|
||||
public function __construct(Route $route, $fit, $pattern_outline) {
|
||||
public function __construct(Route $route, $fit, $pattern_outline, $num_parts) {
|
||||
$this->route = $route;
|
||||
$this->fit = $fit;
|
||||
$this->patternOutline = $pattern_outline;
|
||||
$this->numParts = $num_parts;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -64,6 +74,19 @@ class CompiledRoute {
|
|||
return $this->fit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of parts in this route's path.
|
||||
*
|
||||
* The string "foo/bar/baz" has 3 parts, regardless of how many of them are
|
||||
* placeholders.
|
||||
*
|
||||
* @return int
|
||||
* The number of parts in the path.
|
||||
*/
|
||||
public function getNumParts() {
|
||||
return $this->numParts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pattern outline of this route.
|
||||
*
|
||||
|
|
|
@ -85,6 +85,7 @@ class MatcherDumper implements MatcherDumperInterface {
|
|||
'fit',
|
||||
'pattern',
|
||||
'pattern_outline',
|
||||
'number_parts',
|
||||
'route',
|
||||
));
|
||||
|
||||
|
@ -96,6 +97,7 @@ class MatcherDumper implements MatcherDumperInterface {
|
|||
'fit' => $compiled->getFit(),
|
||||
'pattern' => $compiled->getPattern(),
|
||||
'pattern_outline' => $compiled->getPatternOutline(),
|
||||
'number_parts' => $compiled->getNumParts(),
|
||||
'route' => serialize($route),
|
||||
);
|
||||
$insert->values($values);
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Routing;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
|
||||
/**
|
||||
* Description of PathMatcher
|
||||
*
|
||||
* @author crell
|
||||
*/
|
||||
class PathMatcher implements InitialMatcherInterface {
|
||||
/**
|
||||
* The database connection from which to read route information.
|
||||
*
|
||||
* @var Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The name of the SQL table from which to read the routes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tableName;
|
||||
|
||||
public function __construct(Connection $connection, $table = 'router') {
|
||||
$this->connection = $connection;
|
||||
|
||||
$this->tableName = $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches a request against multiple routes.
|
||||
*
|
||||
* @param Request $request
|
||||
* A Request object against which to match.
|
||||
*
|
||||
* @return RouteCollection
|
||||
* A RouteCollection of matched routes.
|
||||
*/
|
||||
public function matchRequestPartial(Request $request) {
|
||||
|
||||
$path = $request->getPathInfo();
|
||||
|
||||
$parts = array_slide(explode('/', $path), 0, MatcherDumper::MAX_PARTS);
|
||||
|
||||
$number_parts = count($parts);
|
||||
|
||||
$ancestors = $this->getCandidateOutlines($parts);
|
||||
|
||||
// @todo We want to allow matching more than one result because there could
|
||||
// be more than one result with the same path. But how do we do that and
|
||||
// limit by fit?
|
||||
$routes = $this->connection
|
||||
->select($this->tableName, 'r')
|
||||
->fields('r', array('name', 'route'))
|
||||
->condition('pattern_outline', $ancestors, 'IN')
|
||||
->condition('number_parts', $number_parts)
|
||||
->execute()
|
||||
->fetchAllKeyed();
|
||||
|
||||
$collection = new RouteCollection();
|
||||
foreach ($routes as $name => $route) {
|
||||
$collection->add($name, $route);
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of path pattern outlines that could match the path parts.
|
||||
*
|
||||
* @param array $parts
|
||||
* The parts of the path for which we want candidates.
|
||||
* @return array
|
||||
* An array of outlines that could match the specified path parts.
|
||||
*/
|
||||
public function getCandidateOutlines(array $parts) {
|
||||
|
||||
$number_parts = count($parts);
|
||||
$length = $number_parts - 1;
|
||||
$end = (1 << $number_parts) - 1;
|
||||
$candidates = array();
|
||||
|
||||
$start = pow($number_parts-1, 2);
|
||||
|
||||
// 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, $start);
|
||||
|
||||
foreach ($masks as $i) {
|
||||
$current = '';
|
||||
for ($j = $length; $j >= 0; $j--) {
|
||||
// Check the bit on the $j offset.
|
||||
if ($i & (1 << $j)) {
|
||||
// Bit one means the original value.
|
||||
$current .= $parts[$length - $j];
|
||||
}
|
||||
else {
|
||||
// Bit zero means means wildcard.
|
||||
$current .= '%';
|
||||
}
|
||||
// Unless we are at offset 0, add a slash.
|
||||
if ($j) {
|
||||
$current .= '/';
|
||||
}
|
||||
}
|
||||
$candidates[] = $current;
|
||||
}
|
||||
|
||||
return $candidates;
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,9 @@ class RouteCompiler implements RouteCompilerInterface {
|
|||
|
||||
$pattern_outline = $this->getPatternOutline($route->getPattern());
|
||||
|
||||
return new CompiledRoute($route, $fit, $pattern_outline);
|
||||
$num_parts = count(explode('/', $pattern_outline));
|
||||
|
||||
return new CompiledRoute($route, $fit, $pattern_outline, $num_parts);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,14 @@ use Drupal\Core\Routing\MatcherDumper;
|
|||
* Basic tests for the UrlMatcherDumper.
|
||||
*/
|
||||
class MatcherDumperTest extends UnitTestBase {
|
||||
|
||||
/**
|
||||
* A collection of shared fixture data for tests.
|
||||
*
|
||||
* @var RoutingFixtures
|
||||
*/
|
||||
protected $fixtures;
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Dumper tests',
|
||||
|
@ -26,6 +34,12 @@ class MatcherDumperTest extends UnitTestBase {
|
|||
);
|
||||
}
|
||||
|
||||
function __construct($test_id = NULL) {
|
||||
parent::__construct($test_id);
|
||||
|
||||
$this->fixtures = new RoutingFixtures();
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
}
|
||||
|
@ -132,80 +146,13 @@ class MatcherDumperTest extends UnitTestBase {
|
|||
* Creates a test database table just for unit testing purposes.
|
||||
*/
|
||||
protected function prepareTables($connection) {
|
||||
|
||||
$tables['test_routes'] = array(
|
||||
'description' => 'Maps paths to various callbacks (access, page and title)',
|
||||
'fields' => array(
|
||||
'name' => array(
|
||||
'description' => 'Primary Key: Machine name of this route',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'pattern' => array(
|
||||
'description' => 'The path pattern for this URI',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'pattern_outline' => array(
|
||||
'description' => 'The pattern',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'route_set' => array(
|
||||
'description' => 'The route set grouping to which a route belongs.',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'access_callback' => array(
|
||||
'description' => 'The callback which determines the access to this router path. Defaults to user_access.',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'access_arguments' => array(
|
||||
'description' => 'A serialized array of arguments for the access callback.',
|
||||
'type' => 'blob',
|
||||
'not null' => FALSE,
|
||||
),
|
||||
'fit' => array(
|
||||
'description' => 'A numeric representation of how specific the path is.',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
),
|
||||
'number_parts' => array(
|
||||
'description' => 'Number of parts in this router path.',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'size' => 'small',
|
||||
),
|
||||
'route' => array(
|
||||
'description' => 'A serialized Route object',
|
||||
'type' => 'text',
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'fit' => array('fit'),
|
||||
'pattern_outline' => array('pattern_outline'),
|
||||
'route_set' => array('route_set'),
|
||||
),
|
||||
'primary key' => array('name'),
|
||||
);
|
||||
|
||||
|
||||
$tables = $this->fixtures->routingTableDefinition();
|
||||
$schema = $connection->schema();
|
||||
$schema->dropTable('test_routes');
|
||||
$schema->createTable('test_routes', $tables['test_routes']);
|
||||
|
||||
foreach ($tables as $name => $table) {
|
||||
$schema->dropTable($name);
|
||||
$schema->createTable($name, $table);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\system\Tests\Routing\PartialMatcherTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Routing;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
use Drupal\simpletest\UnitTestBase;
|
||||
use Drupal\Core\Routing\PathMatcher;
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
/**
|
||||
* Basic tests for the UrlMatcherDumper.
|
||||
*/
|
||||
class PathMatcherTest extends UnitTestBase {
|
||||
|
||||
/**
|
||||
* A collection of shared fixture data for tests.
|
||||
*
|
||||
* @var RoutingFixtures
|
||||
*/
|
||||
protected $fixtures;
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Path matcher tests',
|
||||
'description' => 'Confirm that the path matching library is working correctly.',
|
||||
'group' => 'Routing',
|
||||
);
|
||||
}
|
||||
|
||||
function __construct($test_id = NULL) {
|
||||
parent::__construct($test_id);
|
||||
|
||||
$this->fixtures = new RoutingFixtures();
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that the correct candidate outlines are generated.
|
||||
*/
|
||||
public function testCandidateOutlines() {
|
||||
|
||||
$connection = Database::getConnection();
|
||||
$matcher = new PathMatcher($connection);
|
||||
|
||||
$parts = array('node', '5', 'edit');
|
||||
|
||||
$candidates = $matcher->getCandidateOutlines($parts);
|
||||
|
||||
//debug($candidates);
|
||||
|
||||
$candidates = array_flip($candidates);
|
||||
|
||||
$this->assertTrue(count($candidates) == 4, t('Correct number of candidates found'));
|
||||
$this->assertTrue(array_key_exists('node/5/edit', $candidates), t('First candidate found.'));
|
||||
$this->assertTrue(array_key_exists('node/5/%', $candidates), t('Second candidate found.'));
|
||||
$this->assertTrue(array_key_exists('node/%/edit', $candidates), t('Third candidate found.'));
|
||||
$this->assertTrue(array_key_exists('node/%/%', $candidates), t('Fourth candidate found.'));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\system\Tests\Routing;
|
||||
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Utility methods to generate sample data, database configuration, etc.
|
||||
*/
|
||||
class RoutingFixtures {
|
||||
|
||||
/**
|
||||
* Returns a standard set of routes for testing.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\RouteCollection
|
||||
*/
|
||||
public function sampleRouteCollection() {
|
||||
$collection = new RouteCollection();
|
||||
|
||||
$route = new Route('path/one');
|
||||
$route->setRequirement('_method', 'GET');
|
||||
$collection->add('route_a', $route);
|
||||
|
||||
$route = new Route('path/one');
|
||||
$route->setRequirement('_method', 'PUT');
|
||||
$collection->add('route_b', $route);
|
||||
|
||||
$route = new Route('path/two');
|
||||
$route->setRequirement('_method', 'GET');
|
||||
$collection->add('route_c', $route);
|
||||
|
||||
$route = new Route('path/three');
|
||||
$collection->add('route_d', $route);
|
||||
|
||||
$route = new Route('path/two');
|
||||
$route->setRequirement('_method', 'GET|HEAD');
|
||||
$collection->add('route_e', $route);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function routingTableDefinition() {
|
||||
|
||||
$tables['test_routes'] = array(
|
||||
'description' => 'Maps paths to various callbacks (access, page and title)',
|
||||
'fields' => array(
|
||||
'name' => array(
|
||||
'description' => 'Primary Key: Machine name of this route',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'pattern' => array(
|
||||
'description' => 'The path pattern for this URI',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'pattern_outline' => array(
|
||||
'description' => 'The pattern',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'route_set' => array(
|
||||
'description' => 'The route set grouping to which a route belongs.',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'access_callback' => array(
|
||||
'description' => 'The callback which determines the access to this router path. Defaults to user_access.',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'access_arguments' => array(
|
||||
'description' => 'A serialized array of arguments for the access callback.',
|
||||
'type' => 'blob',
|
||||
'not null' => FALSE,
|
||||
),
|
||||
'fit' => array(
|
||||
'description' => 'A numeric representation of how specific the path is.',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
),
|
||||
'number_parts' => array(
|
||||
'description' => 'Number of parts in this router path.',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'size' => 'small',
|
||||
),
|
||||
'route' => array(
|
||||
'description' => 'A serialized Route object',
|
||||
'type' => 'text',
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'fit' => array('fit'),
|
||||
'pattern_outline' => array('pattern_outline'),
|
||||
'route_set' => array('route_set'),
|
||||
),
|
||||
'primary key' => array('name'),
|
||||
);
|
||||
|
||||
return $tables;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue