diff --git a/core/lib/Drupal/Core/Access/AccessCheckInterface.php b/core/lib/Drupal/Core/Access/AccessCheckInterface.php new file mode 100644 index 0000000..a675f41 --- /dev/null +++ b/core/lib/Drupal/Core/Access/AccessCheckInterface.php @@ -0,0 +1,40 @@ +checkIds[] = $service_id; + } + + /** + * For each route, saves a list of applicable access checks to the route. + * + * @param RouteCollection $routes + * A collection of routes to apply checks to. + */ + public function setChecks(RouteCollection $routes) { + foreach ($routes as $route) { + $checks = $this->applies($route); + if (!empty($checks)) { + $route->setOption('_access_checks', $checks); + } + } + } + + /** + * Determine which registered access checks apply to a route. + * + * @param Symfony\Component\Routing\Route $route + * The route to get list of access checks for. + * + * @return array + * An array of service ids for the access checks that apply to passed + * route. + */ + protected function applies(Route $route) { + $checks = array(); + + foreach ($this->checkIds as $service_id) { + if (empty($this->checks[$service_id])) { + $this->loadCheck($service_id); + } + + if ($this->checks[$service_id]->applies($route)) { + $checks[] = $service_id; + } + } + + return $checks; + } + + /** + * Checks a route against applicable access check services. + * + * Determines whether the route is accessible or not. + * + * @param Symfony\Component\Routing\Route $route + * The route to check access to. + * + * @throws Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + * If any access check denies access or none explicitly approve. + */ + public function check(Route $route) { + $access = FALSE; + $checks = $route->getOption('_access_checks') ?: array(); + + // No checks == deny by default. + foreach ($checks as $service_id) { + if (empty($this->checks[$service_id])) { + $this->loadCheck($service_id); + } + + $access = $this->checks[$service_id]->access($route); + if ($access === FALSE) { + // A check has denied access, no need to continue checking. + break; + } + } + + // Access has been denied or not explicily approved. + if (!$access) { + throw new AccessDeniedHttpException(); + } + } + + /** + * Lazy-loads access check services. + * + * @param string $service_id + * The service id of the access check service to load. + */ + protected function loadCheck($service_id) { + if (!in_array($service_id, $this->checkIds)) { + throw new \InvalidArgumentException(sprintf('No check has been registered for %s', $service_id)); + } + + $this->checks[$service_id] = $this->container->get($service_id); + } + +} diff --git a/core/lib/Drupal/Core/Access/DefaultAccessCheck.php b/core/lib/Drupal/Core/Access/DefaultAccessCheck.php new file mode 100644 index 0000000..7904656 --- /dev/null +++ b/core/lib/Drupal/Core/Access/DefaultAccessCheck.php @@ -0,0 +1,31 @@ +getRequirement('_access'); + } + + /** + * Implements Drupal\Core\Access\AccessCheckInterface::access(). + */ + public function access(Route $route) { + return $route->getRequirement('_access'); + } +} diff --git a/core/lib/Drupal/Core/Access/PermissionAccessCheck.php b/core/lib/Drupal/Core/Access/PermissionAccessCheck.php new file mode 100644 index 0000000..f89a423 --- /dev/null +++ b/core/lib/Drupal/Core/Access/PermissionAccessCheck.php @@ -0,0 +1,34 @@ +hasDefault('_permission'); + } + + /** + * Implements Drupal\Core\Access\AccessCheckInterface::access(). + */ + public function access(Route $route) { + $permission = $route->getDefault('_permission'); + // @todo Replace user_access() with a correctly injected and session-using + // alternative. + // If user_access() fails, return NULL to give other checks a chance. + return user_access($permission) ? TRUE : NULL; + } +} diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php index bbc4e2e..d4c0b94 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -8,6 +8,7 @@ namespace Drupal\Core; use Drupal\Core\DependencyInjection\Compiler\RegisterKernelListenersPass; +use Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass; use Drupal\Core\DependencyInjection\Compiler\RegisterMatchersPass; use Drupal\Core\DependencyInjection\Compiler\RegisterNestedMatchersPass; use Drupal\Core\DependencyInjection\Compiler\RegisterSerializationClassesPass; @@ -72,7 +73,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') @@ -103,8 +106,17 @@ public function build(ContainerBuilder $container) { $container->register('view_subscriber', 'Drupal\Core\EventSubscriber\ViewSubscriber') ->addArgument(new Reference('content_negotiation')) ->addTag('event_subscriber'); + $container->register('legacy_access_subscriber', 'Drupal\Core\EventSubscriber\LegacyAccessSubscriber') + ->addTag('event_subscriber'); + $container->register('access_manager', 'Drupal\Core\Access\AccessManager') + ->addMethodCall('setContainer', array(new Reference('service_container'))); $container->register('access_subscriber', 'Drupal\Core\EventSubscriber\AccessSubscriber') + ->addArgument(new Reference('access_manager')) ->addTag('event_subscriber'); + $container->register('access_check.default', 'Drupal\Core\Access\DefaultAccessCheck') + ->addTag('access_check'); + $container->register('access_check.permission', 'Drupal\Core\Access\PermissionAccessCheck') + ->addTag('access_check'); $container->register('maintenance_mode_subscriber', 'Drupal\Core\EventSubscriber\MaintenanceModeSubscriber') ->addTag('event_subscriber'); $container->register('path_subscriber', 'Drupal\Core\EventSubscriber\PathSubscriber') @@ -138,6 +150,7 @@ public function build(ContainerBuilder $container) { $container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING); // Add a compiler pass for adding Normalizers and Encoders to Serializer. $container->addCompilerPass(new RegisterSerializationClassesPass()); + $container->addCompilerPass(new RegisterAccessChecksPass()); } } diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php new file mode 100644 index 0000000..a69e35a --- /dev/null +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php @@ -0,0 +1,29 @@ +hasDefinition('access_manager')) { + return; + } + $access_manager = $container->getDefinition('access_manager'); + foreach ($container->findTaggedServiceIds('access_check') as $id => $attributes) { + $access_manager->addMethodCall('AddCheckService', array($id)); + } + } +} diff --git a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php index 4f1dc75..d97f392 100644 --- a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php @@ -11,6 +11,9 @@ use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Drupal\Core\Routing\RoutingEvents; +use Drupal\Core\Access\AccessManager; +use Drupal\Core\Routing\RouteBuildEvent; /** * Access subscriber for controller requests. @@ -18,21 +21,41 @@ class AccessSubscriber implements EventSubscriberInterface { /** - * Verifies that the current user can access the requested path. + * Constructs a new AccessCheckManager. * - * @todo This is a total hack to keep our current access system working. It - * should be replaced with something robust and injected at some point. + * @param AccessCheckManager $access_check_manager + * The access check manager that will be responsible for applying + * AccessCheckers against routes. + */ + public function __construct(AccessManager $access_manager) { + $this->accessManager = $access_manager; + } + + /** + * Verifies that the current user can access the requested path. * * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event * The Event to process. */ public function onKernelRequestAccessCheck(GetResponseEvent $event) { + $request = $event->getRequest(); + if (!$request->attributes->has('_route')) { + // If no Route is available it is likely a static resource and access is + // handled elsewhere. + return; + } - $router_item = $event->getRequest()->attributes->get('drupal_menu_item'); + $this->accessManager->check($request->attributes->get('_route')); + } - if (isset($router_item['access']) && !$router_item['access']) { - throw new AccessDeniedHttpException(); - } + /** + * Apply access checks to routes. + * + * @param Drupal\Core\Routing\RouteBuildEvent $event + * The event to process. + */ + public function onRoutingRouteAlterSetAccessCheck(RouteBuildEvent $event) { + $this->accessManager->setChecks($event->getRouteCollection()); } /** @@ -43,6 +66,8 @@ public function onKernelRequestAccessCheck(GetResponseEvent $event) { */ static function getSubscribedEvents() { $events[KernelEvents::REQUEST][] = array('onKernelRequestAccessCheck', 30); + // Setting very low priority to ensure access checks are run after alters. + $events[RoutingEvents::ALTER][] = array('onRoutingRouteAlterSetAccessCheck', 0); return $events; } diff --git a/core/lib/Drupal/Core/EventSubscriber/LegacyAccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/LegacyAccessSubscriber.php new file mode 100644 index 0000000..707de62 --- /dev/null +++ b/core/lib/Drupal/Core/EventSubscriber/LegacyAccessSubscriber.php @@ -0,0 +1,49 @@ +getRequest()->attributes->get('drupal_menu_item'); + + if (isset($router_item['access']) && !$router_item['access']) { + throw new AccessDeniedHttpException(); + } + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::REQUEST][] = array('onKernelRequestAccessCheck', 30); + + return $events; + } +} diff --git a/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php b/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php index 45d0888..cc1adde 100644 --- a/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php +++ b/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php @@ -53,7 +53,8 @@ public function matchRequest(Request $request) { preg_match($compiled->getRegex(), $path, $matches); - return array_merge($this->mergeDefaults($matches, $route->getDefaults()), array('_route' => $name)); + $route->setOption('_name', $name); + return array_merge($this->mergeDefaults($matches, $route->getDefaults()), array('_route' => $route)); } } 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..fc12ee8 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. @@ -33,16 +38,26 @@ 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. * * @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 \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 0000000..3ca6ef6 --- /dev/null +++ b/core/lib/Drupal/Core/Routing/RoutingEvents.php @@ -0,0 +1,38 @@ +setCollection($collection); $attributes = $matcher->matchRequest($request); - $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.'); + $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.'); $this->assertEqual($attributes['_controller'], 'foo', 'The correct controller was found.'); } @@ -82,7 +82,7 @@ public function testFinalMatcherPattern() { $matcher->setCollection($collection); $attributes = $matcher->matchRequest($request); - $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.'); + $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.'); $this->assertEqual($attributes['_controller'], 'foo', 'The correct controller was found.'); $this->assertEqual($attributes['value'], 'narf', 'Required placeholder value found.'); } @@ -105,7 +105,7 @@ public function testFinalMatcherPatternDefalts() { $matcher->setCollection($collection); $attributes = $matcher->matchRequest($request); - $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.'); + $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.'); $this->assertEqual($attributes['_controller'], 'foo', 'The correct controller was found.'); $this->assertEqual($attributes['value'], 'poink', 'Optional placeholder value used default.'); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php index c98da2e..8055743 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php @@ -44,7 +44,7 @@ function __construct($test_id = NULL) { $this->fixtures = new RoutingFixtures(); } - + /** * Confirms that the HttpMethod matcher matches properly. */ @@ -78,7 +78,7 @@ public function testNestedMatcher() { $attributes = $matcher->matchRequest($request); - $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.'); + $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.'); } /** diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php index 444785c..de29538 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php @@ -60,6 +60,6 @@ public function testNestedMatcher() { $attributes = $matcher->matchRequest($request); - $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.'); + $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.'); } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/RouterPermissionTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/RouterPermissionTest.php new file mode 100644 index 0000000..8964d66 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Routing/RouterPermissionTest.php @@ -0,0 +1,63 @@ + 'Router Permission tests', + 'description' => 'Function Tests for the routing permission system.', + 'group' => 'Routing', + ); + } + + /** + * Confirms that the router can get to a controller. + */ + public function testPermissionAccessDenied() { + + $this->drupalGet('router_test/test5'); + $this->assertResponse(403, 'Access denied for a route where we don\'t have a permission'); + } + + /** + * Confirms that a router path defaults to access denied. + * + * Unspecified access controls on a route result in an access denied response. + */ + public function testDefaultAccessDenied() { + + $this->drupalGet('router_test/test6'); + $this->assertResponse(403, 'Access denied by default if no access specified'); + } + + /** + * Confirms that our default controller logic works properly. + */ + public function testPermissionAccessPassed() { + + $user = $this->drupalCreateUser(array('access test5')); + + $this->drupalGet('router_test/test5'); + $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 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/lib/Drupal/router_test/TestControllers.php b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php index fa92fd8..2afb9b3 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,12 @@ public function test4($value) { return $value; } + public function test5() { + return new Response('test5'); + } + + public function test6() { + return new Response('test6'); + } + } 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..bbaedd0 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,13 @@ '\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; +function router_test_permission() { + return array( + 'access test5' => array( + 'title' => t('Access test5 route'), + 'description' => t('Test permission only.'), + ), + ); } 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..23971bc --- /dev/null +++ b/core/modules/system/tests/modules/router_test/router_test.routing.yml @@ -0,0 +1,40 @@ +router_test_1: + pattern: '/router_test/test1' + defaults: + _controller: '\Drupal\router_test\TestControllers::test1' + requirements: + _access: 'TRUE' + +router_test_2: + pattern: '/router_test/test2' + defaults: + _content: '\Drupal\router_test\TestControllers::test2' + requirements: + _access: 'TRUE' + +router_test_3: + pattern: '/router_test/test3/{value}' + defaults: + _content: '\Drupal\router_test\TestControllers::test3' + requirements: + _access: 'TRUE' + +router_test_4: + pattern: '/router_test/test4/{value}' + defaults: + _content: '\Drupal\router_test\TestControllers::test4' + value: 'narf' + requirements: + _access: 'TRUE' + +router_test_5: + pattern: 'router_test/test5' + defaults: + _controller: '\Drupal\router_test\TestControllers::test5' + requirements: + _permission: 'access test5' + +router_test_6: + pattern: 'router_test/test6' + defaults: + _controller: '\Drupal\router_test\TestControllers::test6' diff --git a/core/update.php b/core/update.php index 968e8f4..98296bb 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.