Add a basic framework for stackable partial matching.

8.0.x
Larry Garfield 2012-06-10 19:30:58 -05:00 committed by effulgentsia
parent 52fd27522b
commit b0f90a1046
6 changed files with 278 additions and 8 deletions

View File

@ -0,0 +1,44 @@
<?php
namespace Drupal\Core\Routing;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouteCollection;
/**
* This class filters routes based on their HTTP Method.
*/
class HttpMethodMatcher implements PartialMatcherInterface {
protected $routes;
public function __construct(RouteCollection $routes) {
$this->routes = $routes;
}
/**
* 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 matchByRequest(Request $request) {
$method = $request->getMethod();
$collection = new RouteCollection();
foreach ($this->routes->all() as $name => $route) {
$allowed_methods = $route->getRequirement('_method');
if ($allowed_methods === NULL || in_array($method, explode('|', strtoupper($allowed_methods)))) {
$collection->add($name, $route);
}
}
return $collection;
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Drupal\Core\Routing;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
/**
* A NestedMatcher allows for multiple-stage resolution of a route.
*/
interface NestedMatcherInterface extends UrlMatcherInterface {
/**
* Adds a partial matcher to the matching plan.
*
* Partial matchers will be run in the order in which they are added.
*
* @param PartialMatcherInterface $matcher
* A partial
*
* @return NestedMatcherInterface
* The current matcher.
*/
public function addPartialMatcher(PartialMatcherInterface $matcher);
/**
* Sets the final matcher for the matching plan.
*
* @param UrlMatcherInterface $final
* The matcher that will be called last to ensure only a single route is
* found.
*
* @return NestedMatcherInterface
* The current matcher.
*/
public function setFinalMatcher(UrlMatcherInterface $final);
}

View File

@ -0,0 +1,22 @@
<?php
namespace Drupal\Core\Routing;
use Symfony\Component\HttpFoundation\Request;
/**
* A PartialMatcher works like a UrlMatcher, but will return multiple candidate routes.
*/
interface PartialMatcherInterface {
/**
* 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 matchByRequest(Request $request);
}

View File

@ -0,0 +1,100 @@
<?php
/**
* @file
* Definition of Drupal\Core\Routing\UrlMatcher.
*/
namespace Drupal\Core\Routing;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
use Symfony\Component\Routing\RequestContext;
/**
* UrlMatcher matches URL based on a set of routes.
*/
class UrlMatcher implements UrlMatcherInterface {
/**
* The request context for this matcher.
*
* @var Symfony\Component\Routing\RequestContext
*/
protected $context;
/**
* The request object for this matcher.
*
* @var Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* Constructor.
*/
public function __construct() {
// We will not actually use this object, but it's needed to conform to
// the interface.
$this->context = new RequestContext();
}
/**
* Sets the request context.
*
* This method is just to satisfy the interface, and is largely vestigial.
* The request context object does not contain the information we need, so
* we will use the original request object.
*
* @param Symfony\Component\Routing\RequestContext $context
* The context.
*
* @api
*/
public function setContext(RequestContext $context) {
$this->context = $context;
}
/**
* Gets the request context.
*
* This method is just to satisfy the interface, and is largely vestigial.
* The request context object does not contain the information we need, so
* we will use the original request object.
*
* @return Symfony\Component\Routing\RequestContext
* The context.
*/
public function getContext() {
return $this->context;
}
/**
* Sets the request object to use.
*
* This is used by the RouterListener to make additional request attributes
* available.
*
* @param Symfony\Component\HttpFoundation\Request $request
* The request object.
*/
public function setRequest(Request $request) {
$this->request = $request;
}
/**
* Gets the request object.
*
* @return Symfony\Component\HttpFoundation\Request $request
* The request object.
*/
public function getRequest() {
return $this->request;
}
public function match($pathinfo) {
}
}

View File

@ -64,12 +64,12 @@ class UrlMatcherDumper implements MatcherDumperInterface {
*
* Available options:
*
* * class: The class name
* * route_set: The route grouping that is being dumped. All existing
* routes with this route set will be deleted on dump.
* * base_class: The base class name
*
* @param array $options An array of options
*
* @return string A PHP class representing the matcher class
* @param $options array
* $options An array of options
*/
public function dump(array $options = array()) {
$options += array(
@ -88,8 +88,6 @@ class UrlMatcherDumper implements MatcherDumperInterface {
'route',
));
foreach ($this->routes as $name => $route) {
$compiled = $route->compile();
$values = array(
@ -115,13 +113,14 @@ class UrlMatcherDumper implements MatcherDumperInterface {
$insert->execute();
// Transaction ends here.
}
/**
* Gets the routes to match.
*
* @return RouteCollection A RouteCollection instance
* @return RouteCollection
* A RouteCollection instance representing all routes currently in the
* dumper.
*/
public function getRoutes() {
return $this->routes;

View File

@ -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\HttpMethodMatcher;
/**
* Basic tests for the UrlMatcherDumper.
*/
class PartialMatcherTest extends UnitTestBase {
public static function getInfo() {
return array(
'name' => 'Partial matcher HTTP Method tests',
'description' => 'Confirm that the Http Method partial matcher is functioning properly.',
'group' => 'Routing',
);
}
function setUp() {
parent::setUp();
}
/**
* Confirms that the HttpMethod matcher matches properly.
*/
function testFilterRoutes() {
$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);
$matcher = new HttpMethodMatcher($collection, 'GET');
$routes = $matcher->matchByRequest(Request::create('path/one', 'GET'));
$this->assertEqual(count($routes->all()), 4, t('The correct number of routes was found.'));
$this->assertNotNull($routes->get('route_a'), t('The first matching route was found.'));
$this->assertNull($routes->get('route_b'), t('The non-matching route was not found.'));
$this->assertNotNull($routes->get('route_c'), t('The second matching route was found.'));
$this->assertNotNull($routes->get('route_d'), t('The all-matching route was found.'));
$this->assertNotNull($routes->get('route_e'), t('The multi-matching route was found.'));
}
}