Commit 536a61b8 authored by webchick's avatar webchick

Issue #1801570 by effulgentsia, klausi, Crell, disasm: DX: Replace...

Issue #1801570 by effulgentsia, klausi, Crell, disasm: DX: Replace hook_route_info() with YAML files and RouteBuildEvent.
parent f9ce687f
......@@ -72,7 +72,9 @@ public function build(ContainerBuilder $container) {
->addArgument(new Reference('database'));
$container->register('router.builder', 'Drupal\Core\Routing\RouteBuilder')
->addArgument(new Reference('router.dumper'))
->addArgument(new Reference('lock'));
->addArgument(new Reference('lock'))
->addArgument(new Reference('dispatcher'));
$container->register('matcher', 'Drupal\Core\Routing\ChainMatcher');
$container->register('legacy_url_matcher', 'Drupal\Core\LegacyUrlMatcher')
......
<?php
/**
* @file
* Definition of Drupal\Core\Routing\RouteBuildEvent.
*/
namespace Drupal\Core\Routing;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Routing\RouteCollection;
/**
* Represents route building information as event.
*/
class RouteBuildEvent extends Event {
/**
* The route collection.
*
* @var \Symfony\Component\Routing\RouteCollection
*/
protected $routeCollection;
/**
* The module name that provides the route.
*
* @var string
*/
protected $module;
/**
* Constructs a RouteBuildEvent object.
*/
public function __construct(RouteCollection $route_collection, $module) {
$this->routeCollection = $route_collection;
$this->module = $module;
}
/**
* Gets the route collection.
*/
public function getRouteCollection() {
return $this->routeCollection;
}
/**
* Gets the module that provides the route.
*/
public function getModule() {
return $this->module;
}
}
......@@ -7,8 +7,13 @@
namespace Drupal\Core\Routing;
use Drupal\Core\Lock\LockBackendInterface;
use Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use Drupal\Core\Lock\LockBackendInterface;
/**
* Managing class for rebuilding the router table.
......@@ -32,6 +37,13 @@ class RouteBuilder {
*/
protected $lock;
/**
* The event dispatcher to notify of routes.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $dispatcher;
/**
* Construcs the RouteBuilder using the passed MatcherDumperInterface.
*
......@@ -39,10 +51,13 @@ class RouteBuilder {
* The matcher dumper used to store the route information.
* @param \Drupal\Core\Lock\LockBackendInterface $lock
* The lock backend.
* @param \Symfony\Component\EventDispatcherEventDispatcherInterface
* The event dispatcher to notify of routes.
*/
public function __construct(MatcherDumperInterface $dumper, LockBackendInterface $lock) {
public function __construct(MatcherDumperInterface $dumper, LockBackendInterface $lock, EventDispatcherInterface $dispatcher) {
$this->dumper = $dumper;
$this->lock = $lock;
$this->dispatcher = $dispatcher;
}
/**
......@@ -57,15 +72,38 @@ public function rebuild() {
return;
}
$parser = new Parser();
// We need to manually call each module so that we can know which module
// a given item came from.
foreach (module_implements('route_info') as $module) {
$routes = call_user_func($module . '_route_info');
drupal_alter('router_info', $routes, $module);
$this->dumper->addRoutes($routes);
// @todo Use an injected Extension service rather than module_list():
// http://drupal.org/node/1331486.
foreach (module_list() as $module) {
$collection = new RouteCollection();
$routing_file = DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . '/' . $module . '.routing.yml';
if (file_exists($routing_file)) {
$routes = $parser->parse(file_get_contents($routing_file));
if (!empty($routes)) {
foreach ($routes as $name => $route_info) {
$defaults = isset($route_info['defaults']) ? $route_info['defaults'] : array();
$requirements = isset($route_info['requirements']) ? $route_info['requirements'] : array();
$route = new Route($route_info['pattern'], $defaults, $requirements);
$collection->add($name, $route);
}
}
}
$this->dispatcher->dispatch(RoutingEvents::ALTER, new RouteBuildEvent($collection, $module));
$this->dumper->addRoutes($collection);
$this->dumper->dump(array('route_set' => $module));
}
// Now allow modules to register additional, dynamic routes.
$collection = new RouteCollection();
$this->dispatcher->dispatch(RoutingEvents::DYNAMIC, new RouteBuildEvent($collection, 'dynamic_routes'));
$this->dispatcher->dispatch(RoutingEvents::ALTER, new RouteBuildEvent($collection, 'dynamic_routes'));
$this->dumper->addRoutes($collection);
$this->dumper->dump(array('route_set' => 'dynamic_routes'));
$this->lock->release('router_rebuild');
}
......
<?php
/**
* @file
* Definition of \Drupal\Core\Routing\RoutingEvents.
*/
namespace Drupal\Core\Routing;
/**
* Contains all events thrown in the core routing component.
*/
final class RoutingEvents {
/**
* The ALTER event is fired on a route collection to allow changes to routes.
*
* This event is used to process new routes before they get saved.
*
* @see \Drupal\Core\Routing\RouteBuildEvent
*
* @var string
*/
const ALTER = 'routing.route_alter';
/**
* The DYNAMIC event is fired to allow modules to register additional routes.
*
* Most routes are static, an should be defined as such. Dynamic routes are
* only those whose existence changes depending on the state of the system
* at runtime, depending on configuration.
*
* @see \Drupal\Core\Routing\RouteBuildEvent
*
* @var string
*/
const DYNAMIC = 'routing.route_dynamic';
}
<?php
/**
* Definition of \Drupal\rest\EventSubscriber\RouteSubscriber.
*/
namespace Drupal\rest\EventSubscriber;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Core\Routing\RoutingEvents;
use Drupal\rest\Plugin\Type\ResourcePluginManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Subscriber for REST-style routes.
*/
class RouteSubscriber implements EventSubscriberInterface {
/**
* The plugin manager for REST plugins.
*
* @var \Drupal\rest\Plugin\Type\ResourcePluginManager
*/
protected $manager;
/**
* The Drupal configuration factory.
*
* @var \Drupal\Core\Config\ConfigFactory
*/
protected $config;
/**
* Constructs a RouteSubscriber object.
*
* @param \Drupal\rest\Plugin\Type\ResourcePluginManager $manager
* The resource plugin manager.
* @param \Drupal\Core\Config\ConfigFactory $config
* The configuration factory holding resource settings.
*/
public function __construct(ResourcePluginManager $manager, ConfigFactory $config) {
$this->manager = $manager;
$this->config = $config;
}
/**
* Adds routes to enabled REST resources.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The route building event.
*/
public function dynamicRoutes(RouteBuildEvent $event) {
$collection = $event->getRouteCollection();
$resources = $this->config->get('rest')->load()->get('resources');
if ($resources && $enabled = array_intersect_key($this->manager->getDefinitions(), $resources)) {
foreach ($enabled as $key => $resource) {
$plugin = $this->manager->getInstance(array('id' => $key));
// @todo Switch to ->addCollection() once http://drupal.org/node/1819018 is resolved.
foreach ($plugin->routes() as $name => $route) {
$collection->add("rest.$name", $route);
}
}
}
}
/**
* Implements EventSubscriberInterface::getSubscribedEvents().
*/
static function getSubscribedEvents() {
$events[RoutingEvents::DYNAMIC] = 'dynamicRoutes';
return $events;
}
}
......@@ -8,6 +8,7 @@
namespace Drupal\rest;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
......@@ -22,5 +23,10 @@ public function build(ContainerBuilder $container) {
// Register the resource manager class with the dependency injection
// container.
$container->register('plugin.manager.rest', 'Drupal\rest\Plugin\Type\ResourcePluginManager');
$container->register('rest.route_subscriber', 'Drupal\rest\EventSubscriber\RouteSubscriber')
->addArgument(new Reference('plugin.manager.rest'))
->addArgument(new Reference('config.factory'))
->addTag('event_subscriber');
}
}
......@@ -51,13 +51,9 @@ public function setUp() {
public function testWatchdog() {
// Write a log message to the DB.
watchdog('rest_test', 'Test message');
// Get ID of the written message.
$result = db_select('watchdog', 'w')
->condition('type', 'rest_test')
->fields('w', array('wid'))
->execute()
->fetchCol();
$id = $result[0];
// Get the ID of the written message.
$id = db_query_range("SELECT wid FROM {watchdog} WHERE type = :type ORDER BY wid DESC", 0, 1, array(':type' => 'rest_test'))
->fetchField();
// Create a user account that has the required permissions to read
// the watchdog resource via the web API.
......
......@@ -5,9 +5,6 @@
* RESTful web services module.
*/
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Implements hook_menu().
*
......@@ -26,32 +23,6 @@ function rest_menu() {
return $items;
}
/**
* Implements hook_route_info().
*/
function rest_route_info() {
$collection = new RouteCollection();
// This hook is called during module enabling where the manager has not been
// registered as service yet.
if (drupal_container()->has('plugin.manager.rest')) {
$manager = drupal_container()->get('plugin.manager.rest');
$resources = config('rest')->get('resources');
if ($resources && $enabled = array_intersect_key($manager->getDefinitions(), $resources)) {
foreach ($enabled as $key => $resource) {
$plugin = $manager->getInstance(array('id' => $key));
// @todo Switch to ->addCollection() once http://drupal.org/node/1819018 is resolved.
foreach ($plugin->routes() as $name => $route) {
$collection->add("rest.$name", $route);
}
}
}
}
return $collection;
}
/**
* Implements hook_permission().
*/
......
......@@ -86,4 +86,20 @@ public function testControllerPlaceholdersDefaultValues() {
$this->assertNoPattern('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
}
/**
* Checks that dynamically defined and altered routes work correctly.
*
* @see \Drupal\router_test\RouteSubscriber
*/
public function testDynamicRoutes() {
// Test the dynamically added route.
$this->drupalGet('router_test/test5');
$this->assertResponse(200);
$this->assertRaw('test5', 'The correct string was returned because the route was successful.');
// Test the altered route.
$this->drupalGet('router_test/test6');
$this->assertResponse(200);
$this->assertRaw('test5', 'The correct string was returned because the route was successful.');
}
}
......@@ -565,51 +565,6 @@ function hook_menu_get_item_alter(&$router_item, $path, $original_map) {
}
}
/**
* Defines routes in the new router system.
*
* A route is a Symfony Route object. See the Symfony documentation for more
* details on the available options. Of specific note:
* - _controller: This is the PHP callable that will handle a request matching
* the route.
* - _content: This is the PHP callable that will handle the body of a request
* matching this route. A default controller will provide the page
* rendering around it.
*
* Typically you will only specify one or the other of those properties.
*
* @deprecated
* This mechanism for registering routes is temporary. It will be replaced
* by a more robust mechanism in the near future. It is documented here
* only for completeness.
*/
function hook_route_info() {
$collection = new RouteCollection();
$route = new Route('router_test/test1', array(
'_controller' => '\Drupal\router_test\TestControllers::test1'
));
$collection->add('router_test_1', $route);
$route = new Route('router_test/test2', array(
'_content' => '\Drupal\router_test\TestControllers::test2'
));
$collection->add('router_test_2', $route);
$route = new Route('router_test/test3/{value}', array(
'_content' => '\Drupal\router_test\TestControllers::test3'
));
$collection->add('router_test_3', $route);
$route = new Route('router_test/test4/{value}', array(
'_content' => '\Drupal\router_test\TestControllers::test4',
'value' => 'narf',
));
$collection->add('router_test_4', $route);
return $collection;
}
/**
* Define menu items and page callbacks.
*
......
<?php
/**
* Definition of \Drupal\router_test\RouteTestSubscriber.
*/
namespace Drupal\router_test;
use \Drupal\Core\Routing\RouteBuildEvent;
use \Drupal\Core\Routing\RoutingEvents;
use \Symfony\Component\EventDispatcher\EventSubscriberInterface;
use \Symfony\Component\Routing\Route;
/**
* Listens to the dynamic route event and add a test route.
*/
class RouteTestSubscriber implements EventSubscriberInterface {
/**
* Implements EventSubscriberInterface::getSubscribedEvents().
*/
static function getSubscribedEvents() {
$events[RoutingEvents::DYNAMIC] = 'dynamicRoutes';
$events[RoutingEvents::ALTER] = 'alterRoutes';
return $events;
}
/**
* Adds a dynamic test route.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The route building event.
*/
public function dynamicRoutes(RouteBuildEvent $event) {
$collection = $event->getRouteCollection();
$route = new Route('/router_test/test5', array(
'_content' => '\Drupal\router_test\TestControllers::test5'
));
$collection->add('router_test_5', $route);
}
/**
* Alters an existing test route.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The route building event.
*
* @return \Symfony\Component\Routing\RouteCollection
* The altered route collection.
*/
public function alterRoutes(RouteBuildEvent $event) {
if ($event->getModule() == 'router_test') {
$collection = $event->getRouteCollection();
$route = $collection->get('router_test_6');
// Change controller method from test1 to test5.
$route->setDefault('_controller', '\Drupal\router_test\TestControllers::test5');
}
}
}
<?php
/**
* @file
* Definition of \Drupal\router_test\RouterTestBundle.
*/
namespace Drupal\router_test;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* Registers a dynamic route provider.
*/
class RouterTestBundle extends Bundle {
/**
* Overrides Symfony\Component\HttpKernel\Bundle\Bundle::build().
*/
public function build(ContainerBuilder $container) {
$container->register('router_test.subscriber', 'Drupal\router_test\RouteTestSubscriber')->addTag('event_subscriber');
}
}
......@@ -30,4 +30,8 @@ public function test4($value) {
return $value;
}
public function test5() {
return "test5";
}
}
<?php
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Implements hook_router_info().
*/
function router_test_route_info() {
$collection = new RouteCollection();
$route = new Route('router_test/test1', array(
'_controller' => '\Drupal\router_test\TestControllers::test1'
));
$collection->add('router_test_1', $route);
$route = new Route('router_test/test2', array(
'_content' => '\Drupal\router_test\TestControllers::test2'
));
$collection->add('router_test_2', $route);
$route = new Route('router_test/test3/{value}', array(
'_content' => '\Drupal\router_test\TestControllers::test3'
));
$collection->add('router_test_3', $route);
$route = new Route('router_test/test4/{value}', array(
'_content' => '\Drupal\router_test\TestControllers::test4',
'value' => 'narf',
));
$collection->add('router_test_4', $route);
return $collection;
}
router_test_1:
pattern: '/router_test/test1'
defaults:
_controller: '\Drupal\router_test\TestControllers::test1'
router_test_2:
pattern: '/router_test/test2'
defaults:
_content: '\Drupal\router_test\TestControllers::test2'
router_test_3:
pattern: '/router_test/test3/{value}'
defaults:
_content: '\Drupal\router_test\TestControllers::test3'
router_test_4:
pattern: '/router_test/test4/{value}'
defaults:
_content: '\Drupal\router_test\TestControllers::test4'
value: 'narf'
router_test_6:
pattern: '/router_test/test6'
defaults:
_controller: '\Drupal\router_test\TestControllers::test1'
......@@ -459,7 +459,8 @@ function update_check_requirements($skip_warnings = FALSE) {
->addArgument(new Reference('database'));
$container->register('router.builder', 'Drupal\Core\Routing\RouteBuilder')
->addArgument(new Reference('router.dumper'))
->addArgument(new Reference('lock'));
->addArgument(new Reference('lock'))
->addArgument(new Reference('dispatcher'));
// Turn error reporting back on. From now on, only fatal errors (which are
// not passed through the error handler) will cause a message to be printed.
......
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