From 7bf7eb8343834a5dffefb7b02ed54980dac038e7 Mon Sep 17 00:00:00 2001 From: Dries <dries@buytaert.net> Date: Wed, 14 Nov 2012 13:51:52 -0500 Subject: [PATCH] Issue #1801570 by effulgentsia, klausi, Crell: DX: Replace hook_route_info() with YAML files and RouteBuildEvent. --- core/lib/Drupal/Core/CoreBundle.php | 4 +- .../Drupal/Core/Routing/RouteBuildEvent.php | 54 ++++++++++++++++ core/lib/Drupal/Core/Routing/RouteBuilder.php | 52 +++++++++++++--- .../lib/Drupal/Core/Routing/RoutingEvents.php | 38 ++++++++++++ .../system/Tests/Routing/RouterTest.php | 16 +++++ core/modules/system/system.api.php | 45 -------------- .../router_test/RouteTestSubscriber.php | 62 +++++++++++++++++++ .../Drupal/router_test/RouterTestBundle.php | 24 +++++++ .../Drupal/router_test/TestControllers.php | 4 ++ .../modules/router_test/router_test.module | 33 ---------- .../router_test/router_test.routing.yml | 25 ++++++++ core/update.php | 3 +- 12 files changed, 273 insertions(+), 87 deletions(-) create mode 100644 core/lib/Drupal/Core/Routing/RouteBuildEvent.php create mode 100644 core/lib/Drupal/Core/Routing/RoutingEvents.php create mode 100644 core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouteTestSubscriber.php create mode 100644 core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouterTestBundle.php create mode 100644 core/modules/system/tests/modules/router_test/router_test.routing.yml diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php index bbc4e2e3dfeb..622ce3d818f7 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -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') diff --git a/core/lib/Drupal/Core/Routing/RouteBuildEvent.php b/core/lib/Drupal/Core/Routing/RouteBuildEvent.php new file mode 100644 index 000000000000..017b83a83316 --- /dev/null +++ b/core/lib/Drupal/Core/Routing/RouteBuildEvent.php @@ -0,0 +1,54 @@ +<?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; + } + +} diff --git a/core/lib/Drupal/Core/Routing/RouteBuilder.php b/core/lib/Drupal/Core/Routing/RouteBuilder.php index 3a27767c5683..fc12ee8f7685 100644 --- a/core/lib/Drupal/Core/Routing/RouteBuilder.php +++ b/core/lib/Drupal/Core/Routing/RouteBuilder.php @@ -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'); } diff --git a/core/lib/Drupal/Core/Routing/RoutingEvents.php b/core/lib/Drupal/Core/Routing/RoutingEvents.php new file mode 100644 index 000000000000..3ca6ef6e9fa2 --- /dev/null +++ b/core/lib/Drupal/Core/Routing/RoutingEvents.php @@ -0,0 +1,38 @@ +<?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'; +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/RouterTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/RouterTest.php index 413737516ac3..e4611354fa9f 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Routing/RouterTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Routing/RouterTest.php @@ -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.'); + } } diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index d10d3e072a53..e9c086086e73 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -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. * diff --git a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouteTestSubscriber.php b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouteTestSubscriber.php new file mode 100644 index 000000000000..f0b9bd7c3b35 --- /dev/null +++ b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouteTestSubscriber.php @@ -0,0 +1,62 @@ +<?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'); + } + } +} diff --git a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouterTestBundle.php b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouterTestBundle.php new file mode 100644 index 000000000000..f2e123b3d4c2 --- /dev/null +++ b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/RouterTestBundle.php @@ -0,0 +1,24 @@ +<?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'); + } +} diff --git a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php index fa92fd89b3e5..bcf18b7b0fdd 100644 --- a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php +++ b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php @@ -30,4 +30,8 @@ public function test4($value) { return $value; } + public function test5() { + return "test5"; + } + } diff --git a/core/modules/system/tests/modules/router_test/router_test.module b/core/modules/system/tests/modules/router_test/router_test.module index 4da939d70592..b3d9bbc7f371 100644 --- a/core/modules/system/tests/modules/router_test/router_test.module +++ b/core/modules/system/tests/modules/router_test/router_test.module @@ -1,34 +1 @@ <?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; -} diff --git a/core/modules/system/tests/modules/router_test/router_test.routing.yml b/core/modules/system/tests/modules/router_test/router_test.routing.yml new file mode 100644 index 000000000000..cc177d38b37b --- /dev/null +++ b/core/modules/system/tests/modules/router_test/router_test.routing.yml @@ -0,0 +1,25 @@ +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' diff --git a/core/update.php b/core/update.php index 968e8f4e4cf6..98296bbaf81f 100644 --- a/core/update.php +++ b/core/update.php @@ -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. -- GitLab