Skip to content
Snippets Groups Projects
Commit 7bf7eb83 authored by Dries Buytaert's avatar Dries Buytaert
Browse files

Issue #1801570 by effulgentsia, klausi, Crell: DX: Replace hook_route_info()...

Issue #1801570 by effulgentsia, klausi, Crell: DX: Replace hook_route_info() with YAML files and RouteBuildEvent.
parent 75b5ee12
No related branches found
No related tags found
2 merge requests!7452Issue #1797438. HTML5 validation is preventing form submit and not fully...,!789Issue #3210310: Adjust Database API to remove deprecated Drupal 9 code in Drupal 10
Showing
with 273 additions and 87 deletions
......@@ -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';
}
......@@ -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.
*
* @return \Symfony\Component\Routing\RouteCollection
* The route collection that contains the new dynamic route.
*/
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.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment