diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php index bbc4e2e..d9b19e9 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -11,6 +11,7 @@ use Drupal\Core\DependencyInjection\Compiler\RegisterMatchersPass; use Drupal\Core\DependencyInjection\Compiler\RegisterNestedMatchersPass; use Drupal\Core\DependencyInjection\Compiler\RegisterSerializationClassesPass; +use Drupal\Core\DependencyInjection\Compiler\RegisterRouteLoadersPass; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; @@ -72,7 +73,14 @@ 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')) + // Placeholder argument replaced with services tagged 'route_loader' by + // the RegisterRouteLoadersPass compiler pass. + ->addArgument(array()) + ->addArgument(new Reference('dispatcher')); + $container->register('router.loader.yaml', 'Drupal\Core\Routing\YamlRouteLoader') + ->addTag('route_loader'); + $container->addCompilerPass(new RegisterRouteLoadersPass()); $container->register('matcher', 'Drupal\Core\Routing\ChainMatcher'); $container->register('legacy_url_matcher', 'Drupal\Core\LegacyUrlMatcher') diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteLoadersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteLoadersPass.php new file mode 100644 index 0000000..338e538 --- /dev/null +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteLoadersPass.php @@ -0,0 +1,65 @@ +getDefinition('router.builder'); + + // Retrieve registered route loaders from the container. + foreach ($container->findTaggedServiceIds('route_loader') as $id => $attributes) { + $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0; + $loaders[$priority][] = new Reference($id); + } + + // Add the registered loaders to the route builder as the 3rd argument. + if (!empty($loaders)) { + $definition->replaceArgument(2, $this->sort($loaders)); + } + } + + /** + * Sorts by priority. + * + * Order services from highest priority number to lowest (reverse sorting). + * + * @param array $services + * A nested array keyed on priority number. For each priority number, the + * value is an array of Symfony\Component\DependencyInjection\Reference + * objects, each a reference to a route loader service. + * + * @return array + * A flattened array of Reference objects from $services, ordered from high + * to low priority. + */ + protected function sort($services) { + $sorted = array(); + krsort($services); + + // Flatten the array. + foreach ($services as $a) { + $sorted = array_merge($sorted, $a); + } + + return $sorted; + } +} diff --git a/core/lib/Drupal/Core/Routing/RouteBuildEvent.php b/core/lib/Drupal/Core/Routing/RouteBuildEvent.php new file mode 100644 index 0000000..017b83a --- /dev/null +++ b/core/lib/Drupal/Core/Routing/RouteBuildEvent.php @@ -0,0 +1,54 @@ +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 3a27767..873b262 100644 --- a/core/lib/Drupal/Core/Routing/RouteBuilder.php +++ b/core/lib/Drupal/Core/Routing/RouteBuilder.php @@ -7,8 +7,11 @@ namespace Drupal\Core\Routing; -use Drupal\Core\Lock\LockBackendInterface; use Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Routing\RouteCollection; + +use Drupal\Core\Lock\LockBackendInterface; /** * Managing class for rebuilding the router table. @@ -33,16 +36,39 @@ class RouteBuilder { protected $lock; /** + * An array of \Drupal\Core\Routing\RouteLoaderInterface objects. + * + * During route rebuilding, each loader is invoked for each enabled Drupal + * module. + * + * @var array $loaders + */ + protected $loaders; + + /** + * The event dispatcher to notify of routes. + * + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + protected $dispatcher; + + /** * Construcs the RouteBuilder using the passed MatcherDumperInterface. * * @param \Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface $dumper * The matcher dumper used to store the route information. * @param \Drupal\Core\Lock\LockBackendInterface $lock * The lock backend. + * @param array $loaders + * An array of \Drupal\Core\Routing\RouteLoaderInterface objects. + * @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, array $loaders, EventDispatcherInterface $dispatcher) { $this->dumper = $dumper; $this->lock = $lock; + $this->loaders = $loaders; + $this->dispatcher = $dispatcher; } /** @@ -59,13 +85,18 @@ public function rebuild() { // 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(); + foreach ($this->loaders as $loader) { + $collection->addCollection($loader->getRouteCollection($module)); + } + $this->dispatcher->dispatch('routing.alter', new RouteBuildEvent($collection, $module)); + $this->dumper->addRoutes($collection); $this->dumper->dump(array('route_set' => $module)); } + $this->lock->release('router_rebuild'); } diff --git a/core/lib/Drupal/Core/Routing/RouteLoaderInterface.php b/core/lib/Drupal/Core/Routing/RouteLoaderInterface.php new file mode 100644 index 0000000..1ab2b42 --- /dev/null +++ b/core/lib/Drupal/Core/Routing/RouteLoaderInterface.php @@ -0,0 +1,25 @@ +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); + } + } + } + return $collection; + } + +} diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index e4c4ce7..a5a5821 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -566,51 +566,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. * * This hook enables modules to register paths in order to define how URL 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 4da939d..b3d9bbc 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 @@ '\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 0000000..a49dd80 --- /dev/null +++ b/core/modules/system/tests/modules/router_test/router_test.routing.yml @@ -0,0 +1,20 @@ +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' diff --git a/core/update.php b/core/update.php index 968e8f4..1ec66c4 100644 --- a/core/update.php +++ b/core/update.php @@ -459,7 +459,9 @@ 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(array()) + ->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.