Commit c2605893 authored by Dries's avatar Dries

Issue #1833440 by scor, Crell, klausi: Implement partial matcher based on...

Issue #1833440 by scor, Crell, klausi: Implement partial matcher based on content negotiation MIME type.
parent 79b823ac
......@@ -108,6 +108,8 @@ public function build(ContainerBuilder $container) {
->addTag('nested_matcher', array('method' => 'setInitialMatcher'));
$container->register('http_method_matcher', 'Drupal\Core\Routing\HttpMethodMatcher')
->addTag('nested_matcher', array('method' => 'addPartialMatcher'));
$container->register('mime_type_matcher', 'Drupal\Core\Routing\MimeTypeMatcher')
->addTag('nested_matcher', array('method' => 'addPartialMatcher'));
$container->register('first_entry_final_matcher', 'Drupal\Core\Routing\FirstEntryFinalMatcher')
->addTag('nested_matcher', array('method' => 'setFinalMatcher'));
......
<?php
/**
* @file
* Contains Drupal\Core\Routing\MimeTypeMatcher.
*/
namespace Drupal\Core\Routing;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
/**
* This class filters routes based on the media type in HTTP Accept headers.
*/
class MimeTypeMatcher extends PartialMatcher {
/**
* Matches a request against multiple routes.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* A Request object against which to match.
*
* @return \Symfony\Component\Routing\RouteCollection
* A RouteCollection of matched routes.
*/
public function matchRequestPartial(Request $request) {
// Generates a list of Symfony formats matching the acceptable MIME types.
// @todo replace by proper content negotiation library.
$acceptable_mime_types = $request->getAcceptableContentTypes();
$acceptable_formats = array_map(array($request, 'getFormat'), $acceptable_mime_types);
$collection = new RouteCollection();
foreach ($this->routes->all() as $name => $route) {
// _format could be a |-delimited list of supported formats.
$supported_formats = array_filter(explode('|', $route->getRequirement('_format')));
// The route partially matches if it doesn't care about format, if it
// explicitly allows any format, or if one of its allowed formats is
// in the request's list of acceptable formats.
if (empty($supported_formats) || in_array('*/*', $acceptable_mime_types) || array_intersect($acceptable_formats, $supported_formats)) {
$collection->add($name, $route);
}
}
if (!count($collection)) {
// @todo update with UnsupportedMediaTypeException when available
// @see http://drupal.org/node/1831074
throw new RouteNotFoundException();
}
return $collection;
}
}
<?php
/**
* @file
* Contains Drupal\system\Tests\Routing\MimeTypeMatcherTest.
*/
namespace Drupal\system\Tests\Routing;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Drupal\simpletest\UnitTestBase;
use Drupal\Core\Routing\MimeTypeMatcher;
use Drupal\Core\Routing\NestedMatcher;
use Drupal\Core\Routing\FirstEntryFinalMatcher;
use Exception;
/**
* Basic tests for the MimeTypeMatcher class.
*/
class MimeTypeMatcherTest extends UnitTestBase {
/**
* A collection of shared fixture data for tests.
*
* @var RoutingFixtures
*/
protected $fixtures;
public static function getInfo() {
return array(
'name' => 'Partial matcher MIME types tests',
'description' => 'Confirm that the mime types partial matcher is functioning properly.',
'group' => 'Routing',
);
}
function __construct($test_id = NULL) {
parent::__construct($test_id);
$this->fixtures = new RoutingFixtures();
}
/**
* Confirms that the MimeType matcher matches properly.
*/
public function testFilterRoutes() {
$matcher = new MimeTypeMatcher();
$matcher->setCollection($this->fixtures->sampleRouteCollection());
// Tests basic JSON request.
$request = Request::create('path/two', 'GET');
$request->headers->set('Accept', 'application/json, text/xml;q=0.9');
$routes = $matcher->matchRequestPartial($request);
$this->assertEqual(count($routes->all()), 4, 'The correct number of routes was found.');
$this->assertNotNull($routes->get('route_c'), 'The json route was found.');
$this->assertNull($routes->get('route_e'), 'The html route was not found.');
// Tests JSON request with alternative JSON MIME type Accept header.
$request = Request::create('path/two', 'GET');
$request->headers->set('Accept', 'application/x-json, text/xml;q=0.9');
$routes = $matcher->matchRequestPartial($request);
$this->assertEqual(count($routes->all()), 4, 'The correct number of routes was found.');
$this->assertNotNull($routes->get('route_c'), 'The json route was found.');
$this->assertNull($routes->get('route_e'), 'The html route was not found.');
// Tests basic HTML request.
$request = Request::create('path/two', 'GET');
$request->headers->set('Accept', 'text/html, text/xml;q=0.9');
$routes = $matcher->matchRequestPartial($request);
$this->assertEqual(count($routes->all()), 4, 'The correct number of routes was found.');
$this->assertNull($routes->get('route_c'), 'The json route was not found.');
$this->assertNotNull($routes->get('route_e'), 'The html route was found.');
}
/**
* Confirms we can nest multiple partial matchers.
*/
public function testNestedMatcher() {
$matcher = new NestedMatcher();
$matcher->setInitialMatcher(new MockPathMatcher($this->fixtures->sampleRouteCollection()));
$matcher->addPartialMatcher(new MimeTypeMatcher());
$matcher->setFinalMatcher(new FirstEntryFinalMatcher());
$request = Request::create('/path/two', 'GET');
$request->headers->set('Accept', 'text/html, text/xml;q=0.9');
$attributes = $matcher->matchRequest($request);
$this->assertEqual($attributes['_route'], 'route_e', 'The correct matching route was found.');
}
/**
* Confirms that the MimeTypeMatcher matcher throws an exception for no-route.
*/
public function testNoRouteFound() {
$matcher = new MimeTypeMatcher();
// Remove the sample routes that would match any method.
$routes = $this->fixtures->sampleRouteCollection();
$routes->remove('route_a');
$routes->remove('route_b');
$routes->remove('route_c');
$routes->remove('route_d');
$matcher->setCollection($routes);
try {
$request = Request::create('path/two', 'GET');
$request->headers->set('Accept', 'application/json, text/xml;q=0.9');
$routes = $matcher->matchRequestPartial($request);
$this->fail(t('No exception was thrown.'));
}
catch (Exception $e) {
$this->assertTrue($e instanceof RouteNotFoundException, 'The correct exception was thrown.');
}
}
}
......@@ -61,6 +61,7 @@ public function sampleRouteCollection() {
$route = new Route('path/two');
$route->setRequirement('_method', 'GET');
$route->setRequirement('_format', 'json');
$collection->add('route_c', $route);
$route = new Route('path/three');
......@@ -68,6 +69,7 @@ public function sampleRouteCollection() {
$route = new Route('path/two');
$route->setRequirement('_method', 'GET|HEAD');
$route->setRequirement('_format', 'html');
$collection->add('route_e', $route);
return $collection;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment