diff --git a/core/lib/Drupal/Core/Controller/ControllerResolver.php b/core/lib/Drupal/Core/Controller/ControllerResolver.php index f165018..d5193e7 100644 --- a/core/lib/Drupal/Core/Controller/ControllerResolver.php +++ b/core/lib/Drupal/Core/Controller/ControllerResolver.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Controller; +use Drupal\Core\Routing\RouteMatch; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ControllerResolver as BaseControllerResolver; @@ -136,7 +137,35 @@ protected function createController($controller) { * {@inheritdoc} */ protected function doGetArguments(Request $request, $controller, array $parameters) { - $arguments = parent::doGetArguments($request, $controller, $parameters); + $attributes = $request->attributes->all(); + $arguments = array(); + foreach ($parameters as $param) { + if (array_key_exists($param->name, $attributes)) { + $arguments[] = $attributes[$param->name]; + } + elseif ($param->getClass() && $param->getClass()->isInstance($request)) { + $arguments[] = $request; + } + elseif ($param->getClass() && ($param->getClass()->name == 'Drupal\Core\Routing\RouteMatchInterface' || is_subclass_of($param->getClass()->name, 'Drupal\Core\Routing\RouteMatchInterface'))) { + $arguments[] = RouteMatch::createFromRequest($request); + } + elseif ($param->isDefaultValueAvailable()) { + $arguments[] = $param->getDefaultValue(); + } + else { + if (is_array($controller)) { + $repr = sprintf('%s::%s()', get_class($controller[0]), $controller[1]); + } + elseif (is_object($controller)) { + $repr = get_class($controller); + } + else { + $repr = $controller; + } + + throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $repr, $param->name)); + } + } // The parameter converter overrides the raw request attributes with the // upcasted objects. However, it keeps a backup copy of the original, raw diff --git a/core/lib/Drupal/Core/Controller/DialogController.php b/core/lib/Drupal/Core/Controller/DialogController.php index 5ea3f49..575b41a 100644 --- a/core/lib/Drupal/Core/Controller/DialogController.php +++ b/core/lib/Drupal/Core/Controller/DialogController.php @@ -10,6 +10,8 @@ use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Ajax\OpenDialogCommand; use Drupal\Core\Page\HtmlPage; +use Drupal\Core\Routing\RouteMatch; +use Drupal\Core\Routing\RouteMatchInterface; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -57,8 +59,8 @@ public function __construct(ControllerResolverInterface $controller_resolver, Ti * @return \Drupal\Core\Ajax\AjaxResponse * AjaxResponse to return the content wrapper in a modal dialog. */ - public function modal(Request $request, $_content) { - return $this->dialog($request, $_content, TRUE); + public function modal(Request $request, RouteMatchInterface $route_match, $_content) { + return $this->dialog($request, $route_match, $_content, TRUE); } /** @@ -66,6 +68,8 @@ public function modal(Request $request, $_content) { * * @param \Symfony\Component\HttpFoundation\Request $request * The request object. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The route match. * @param mixed $_content * A controller definition string, or a callable object/closure. * @param bool $modal @@ -74,7 +78,7 @@ public function modal(Request $request, $_content) { * @return \Drupal\Core\Ajax\AjaxResponse * AjaxResponse to return the content wrapper in a dialog. */ - public function dialog(Request $request, $_content, $modal = FALSE) { + public function dialog(Request $request, RouteMatchInterface $route_match, $_content, $modal = FALSE) { $page_content = $this->getContentResult($request, $_content); // Allow controllers to return a HtmlPage or a Response object directly. @@ -94,7 +98,7 @@ public function dialog(Request $request, $_content, $modal = FALSE) { $content = drupal_render($page_content); drupal_process_attached($page_content); - $title = isset($page_content['#title']) ? $page_content['#title'] : $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)); + $title = isset($page_content['#title']) ? $page_content['#title'] : $this->titleResolver->getTitle($request, $route_match->getRouteObject()); $response = new AjaxResponse(); // Fetch any modal options passed in from data-dialog-options. $options = $request->request->get('dialogOptions', array()); @@ -117,7 +121,7 @@ public function dialog(Request $request, $_content, $modal = FALSE) { } else { // Generate a target based on the route id. - $route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME); + $route_name = $route_match->getRouteName(); $target = '#' . drupal_html_id("drupal-dialog-$route_name"); } } diff --git a/core/modules/config_translation/src/Controller/ConfigTranslationController.php b/core/modules/config_translation/src/Controller/ConfigTranslationController.php index d6d9e34..646d3b8 100644 --- a/core/modules/config_translation/src/Controller/ConfigTranslationController.php +++ b/core/modules/config_translation/src/Controller/ConfigTranslationController.php @@ -13,6 +13,7 @@ use Drupal\Core\Language\Language; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\PathProcessor\InboundPathProcessorInterface; +use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; @@ -109,13 +110,15 @@ public static function create(ContainerInterface $container) { * * @param \Symfony\Component\HttpFoundation\Request $request * Page request object. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The route match. * @param string $plugin_id * The plugin ID of the mapper. * * @return array * Page render array. */ - public function itemPage(Request $request, $plugin_id) { + public function itemPage(Request $request, RouteMatchInterface $route_match, $plugin_id) { /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */ $mapper = $this->configMapperManager->createInstance($plugin_id); $mapper->populateFromRequest($request); @@ -164,7 +167,7 @@ public function itemPage(Request $request, $plugin_id) { // Check access for the path/route for editing, so we can decide to // include a link to edit or not. - $edit_access = $this->accessManager->checkNamedRoute($mapper->getBaseRouteName(), $request->attributes->get('_raw_variables')->all(), $this->account); + $edit_access = $this->accessManager->checkNamedRoute($mapper->getBaseRouteName(), $route_match->getRawParameters()->all(), $this->account); // Build list of operations. $operations = array(); diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php index d917e41..505337c 100644 --- a/core/modules/rest/src/RequestHandler.php +++ b/core/modules/rest/src/RequestHandler.php @@ -7,6 +7,7 @@ namespace Drupal\rest; +use Drupal\Core\Routing\RouteMatchInterface; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerAwareTrait; @@ -27,15 +28,17 @@ class RequestHandler implements ContainerAwareInterface { /** * Handles a web API request. * - * @param Symfony\Component\HttpFoundation\Request $request + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The route match. + * @param \Symfony\Component\HttpFoundation\Request $request * The HTTP request object. * * @return \Symfony\Component\HttpFoundation\Response * The response object. */ - public function handle(Request $request) { + public function handle(RouteMatchInterface $route_match, Request $request) { - $plugin = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)->getDefault('_plugin'); + $plugin = $route_match->getRouteObject()->getDefault('_plugin'); $method = strtolower($request->getMethod()); $resource = $this->container @@ -87,7 +90,7 @@ public function handle(Request $request) { // All REST routes are restricted to exactly one format, so instead of // parsing it out of the Accept headers again, we can simply retrieve the // format requirement. If there is no format associated, just pick JSON. - $format = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)->getRequirement('_format') ?: 'json'; + $format = $route_match->getRouteObject()->getRequirement('_format') ?: 'json'; try { $response = call_user_func_array(array($resource, $method), array_merge($parameters, array($unserialized, $request))); } diff --git a/core/modules/user/src/Controller/UserController.php b/core/modules/user/src/Controller/UserController.php index 7ce404d..739a365 100644 --- a/core/modules/user/src/Controller/UserController.php +++ b/core/modules/user/src/Controller/UserController.php @@ -139,7 +139,7 @@ public function resetPass($uid, $timestamp, $hash) { * Returns either a redirect to the user page or the render * array of the login form. */ - public function userPage(Request $request) { + public function userPage() { $user = $this->currentUser(); if ($user->id()) { $response = $this->redirect('entity.user.canonical', array('user' => $user->id())); diff --git a/core/modules/views/src/Routing/ViewPageController.php b/core/modules/views/src/Routing/ViewPageController.php index 1898b03..2eec5b5 100644 --- a/core/modules/views/src/Routing/ViewPageController.php +++ b/core/modules/views/src/Routing/ViewPageController.php @@ -10,6 +10,7 @@ use Drupal\Component\Utility\String; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Routing\RouteMatchInterface; use Drupal\views\ViewExecutableFactory; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -59,12 +60,19 @@ public static function create(ContainerInterface $container) { } /** - * Handles a response for a view. + * Handler a response for a given view and display. + * + * @param string $view_id + * The ID of the view + * @param string $display_id + * The ID of the display. + * @param \Symfony\Component\HttpFoundation\Request $request + * The request. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The route match. + * @return null|void */ - public function handle(Request $request) { - $view_id = $request->attributes->get('view_id'); - $display_id = $request->attributes->get('display_id'); - + public function handle($view_id, $display_id, Request $request, RouteMatchInterface $route_match) { $entity = $this->storage->load($view_id); if (empty($entity)) { throw new NotFoundHttpException(String::format('Page controller for view %id requested, but view was not found.', array('%id' => $view_id))); @@ -75,7 +83,7 @@ public function handle(Request $request) { $view->initHandlers(); $args = array(); - $map = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)->getOption('_view_argument_map', array()); + $map = $route_match->getRouteObject()->getOption('_view_argument_map', array()); $arguments_length = count($view->argument); for ($argument_index = 0; $argument_index < $arguments_length; $argument_index++) { // Allow parameters be pulled from the request. @@ -85,18 +93,11 @@ public function handle(Request $request) { $attribute = 'arg_' . $argument_index; if (isset($map[$attribute])) { $attribute = $map[$attribute]; - - // First try to get from the original values then on the not converted - // ones. - if ($request->attributes->has('_raw_variables')) { - $arg = $request->attributes->get('_raw_variables')->get($attribute); - } - else { - $arg = $request->attributes->get($attribute); - } + } + if ($arg = $route_match->getRawParameter($attribute)) { } else { - $arg = $request->attributes->get($attribute); + $arg = $route_match->getParameter($attribute); } if (isset($arg)) { diff --git a/core/modules/views/tests/src/Unit/Routing/ViewPageControllerTest.php b/core/modules/views/tests/src/Unit/Routing/ViewPageControllerTest.php index 33630cd..421f23a 100644 --- a/core/modules/views/tests/src/Unit/Routing/ViewPageControllerTest.php +++ b/core/modules/views/tests/src/Unit/Routing/ViewPageControllerTest.php @@ -7,6 +7,7 @@ namespace Drupal\Tests\views\Unit\Routing; +use Drupal\Core\Routing\RouteMatch; use Drupal\Tests\UnitTestCase; use Drupal\views\Routing\ViewPageController; use Symfony\Cmf\Component\Routing\RouteObjectInterface; @@ -85,9 +86,10 @@ public function testPageController() { $request = new Request(); $request->attributes->set('view_id', 'test_page_view'); $request->attributes->set('display_id', 'default'); - $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('')); + $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('/test', ['view_id' => 'test_page_view', 'display_id' => 'default'])); + $route_match = RouteMatch::createFromRequest($request); - $output = $this->pageController->handle($request); + $output = $this->pageController->handle($route_match->getParameter('view_id'), $route_match->getParameter('display_id'), $request, $route_match); $this->assertInternalType('array', $output); $this->assertEquals(array('#markup' => 'example output'), $output); } @@ -132,9 +134,10 @@ public function testHandleWithArgumentsWithoutOverridden() { $request->attributes->set('display_id', 'page_1'); // Add the argument to the request. $request->attributes->set('arg_0', 'test-argument'); - $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('')); + $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('/test/{arg_0}', ['view_id' => 'test_page_view', 'display_id' => 'default'])); + $route_match = RouteMatch::createFromRequest($request); - $this->pageController->handle($request); + $this->pageController->handle($route_match->getParameter('view_id'), $route_match->getParameter('display_id'), $request, $route_match); } /** @@ -179,11 +182,12 @@ public function testHandleWithArgumentsOnOveriddenRoute() { $request->attributes->set('display_id', 'page_1'); // Add the argument to the request. $request->attributes->set('parameter', 'test-argument'); - $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('', array(), array(), array('_view_argument_map' => array( + $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('/test/{parameter}', ['view_id' => 'test_page_view', 'display_id' => 'default'], [], ['_view_argument_map' => [ 'arg_0' => 'parameter', - )))); + ]])); + $route_match = RouteMatch::createFromRequest($request); - $this->pageController->handle($request); + $this->pageController->handle($route_match->getParameter('view_id'), $route_match->getParameter('display_id'), $request, $route_match); } /** @@ -231,12 +235,12 @@ public function testHandleWithArgumentsOnOveriddenRouteWithUpcasting() { $request->attributes->set('test_entity', $this->getMock('Drupal\Core\Entity\EntityInterface')); $raw_variables = new ParameterBag(array('test_entity' => 'example_id')); $request->attributes->set('_raw_variables', $raw_variables); - - $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('', array(), array(), array('_view_argument_map' => array( + $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('/test/{test_entity}', ['view_id' => 'test_page_view', 'display_id' => 'default'], [], ['_view_argument_map' => [ 'arg_0' => 'test_entity', - )))); + ]])); + $route_match = RouteMatch::createFromRequest($request); - $this->pageController->handle($request); + $this->pageController->handle($route_match->getParameter('view_id'), $route_match->getParameter('display_id'), $request, $route_match); } /** @@ -251,7 +255,9 @@ public function testHandleWithNotExistingView() { $request = new Request(); $request->attributes->set('view_id', $random_view_id); $request->attributes->set('display_id', 'default'); - $this->pageController->handle($request); + $route_match = RouteMatch::createFromRequest($request); + + $this->pageController->handle($route_match->getParameter('view_id'), $route_match->getParameter('display_id'), $request, $route_match); } } diff --git a/core/tests/Drupal/Tests/Core/Controller/ControllerResolverTest.php b/core/tests/Drupal/Tests/Core/Controller/ControllerResolverTest.php index d245f1c..91491c8 100644 --- a/core/tests/Drupal/Tests/Core/Controller/ControllerResolverTest.php +++ b/core/tests/Drupal/Tests/Core/Controller/ControllerResolverTest.php @@ -11,6 +11,8 @@ use Drupal\Core\DependencyInjection\ClassResolver; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Routing\RouteMatch; +use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Tests\UnitTestCase; use Symfony\Component\DependencyInjection\ContainerAware; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -59,7 +61,7 @@ protected function setUp() { * @see \Drupal\Core\Controller\ControllerResolver::doGetArguments() */ public function testGetArguments() { - $controller = function(EntityInterface $entity, $user) { + $controller = function(EntityInterface $entity, $user, RouteMatchInterface $route_match) { }; $mock_entity = $this->getMockBuilder('Drupal\Core\Entity\Entity') ->disableOriginalConstructor() @@ -74,6 +76,7 @@ public function testGetArguments() { $this->assertEquals($mock_entity, $arguments[0], 'Type hinted variables should use upcasted values.'); $this->assertEquals(1, $arguments[1], 'Not type hinted variables should use not upcasted values.'); + $this->assertEquals(RouteMatch::createFromRequest($request), $arguments[2], 'Ensure that the route match object is passed along as well'); } /** @@ -203,12 +206,30 @@ protected function assertCallableController($controller, $class, $output) { $this->assertSame($output, call_user_func($controller)); } + /** + * Tests getArguments with a route match and a request. + * + * @covers ::getArguments + * @covers ::doGetArguments + */ + public function testGetArgumentsWithRouteMatchAndRequest() { + $request = Request::create('/test'); + $mock_controller = new MockController(); + $arguments = $this->controllerResolver->getArguments($request, [$mock_controller, 'getControllerWithRequestAndRouteMatch']); + $this->assertEquals([RouteMatch::createFromRequest($request), $request], $arguments); + } + } class MockController { public function getResult() { return 'This is a regular controller.'; } + + public function getControllerWithRequestAndRouteMatch(RouteMatchInterface $route_match, Request $request) { + return 'this is another example controller'; + } + } class MockContainerInjection implements ContainerInjectionInterface { protected $result;