diff --git a/core/lib/Drupal/Core/Controller/ControllerResolver.php b/core/lib/Drupal/Core/Controller/ControllerResolver.php index 385f1984d23..2fd4ee2a6a1 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 Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ControllerResolver as BaseControllerResolver; use Symfony\Component\HttpKernel\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface; @@ -98,4 +99,27 @@ class ControllerResolver extends BaseControllerResolver { return array($controller, $method); } + /** + * {@inheritdoc} + */ + protected function doGetArguments(Request $request, $controller, array $parameters) { + $arguments = parent::doGetArguments($request, $controller, $parameters); + + // The parameter converter overrides the raw request attributes with the + // upcasted objects. However, it keeps a backup copy of the original, raw + // values in a special request attribute ('_raw_variables'). If a controller + // argument has a type hint, we pass it the upcasted object, otherwise we + // pass it the original, raw value. + if ($request->attributes->has('_raw_variables') && $raw = $request->attributes->get('_raw_variables')->all()) { + foreach ($parameters as $parameter) { + // Use the raw value if a parameter has no typehint. + if (!$parameter->getClass() && isset($raw[$parameter->name])) { + $position = $parameter->getPosition(); + $arguments[$position] = $raw[$parameter->name]; + } + } + } + return $arguments; + } + } diff --git a/core/lib/Drupal/Core/ParamConverter/ParamConverterManager.php b/core/lib/Drupal/Core/ParamConverter/ParamConverterManager.php index 0b494d8b749..f4b8a31e06e 100644 --- a/core/lib/Drupal/Core/ParamConverter/ParamConverterManager.php +++ b/core/lib/Drupal/Core/ParamConverter/ParamConverterManager.php @@ -10,6 +10,7 @@ namespace Drupal\Core\ParamConverter; use Symfony\Component\DependencyInjection\ContainerAware; use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface; use Symfony\Cmf\Component\Routing\RouteObjectInterface; +use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\HttpFoundation\Request; @@ -151,7 +152,11 @@ class ParamConverterManager extends ContainerAware implements RouteEnhancerInter * The modified defaults. */ public function enhance(array $defaults, Request $request) { + // Store a backup of the raw $defaults values corresponding to + // variables in the route path pattern. $route = $defaults[RouteObjectInterface::ROUTE_OBJECT]; + $variables = array_flip($route->compile()->getVariables()); + $defaults['_raw_variables'] = new ParameterBag(array_intersect_key($defaults, $variables)); // Skip this enhancer if there are no parameter definitions. if (!$parameters = $route->getOption('parameters')) { diff --git a/core/modules/system/tests/modules/paramconverter_test/lib/Drupal/paramconverter_test/TestControllers.php b/core/modules/system/tests/modules/paramconverter_test/lib/Drupal/paramconverter_test/TestControllers.php index ca95200fa7c..cd9b578167f 100644 --- a/core/modules/system/tests/modules/paramconverter_test/lib/Drupal/paramconverter_test/TestControllers.php +++ b/core/modules/system/tests/modules/paramconverter_test/lib/Drupal/paramconverter_test/TestControllers.php @@ -8,20 +8,21 @@ namespace Drupal\paramconverter_test; use Drupal\Core\Entity\EntityInterface; +use Drupal\node\NodeInterface; +use Symfony\Component\HttpFoundation\Request; /** * Controller routine for testing the paramconverter. */ class TestControllers { - public function testUserNodeFoo($user, $node, $foo) { - $retval = "user: " . (is_object($user) ? $user->label() : $user); - $retval .= ", node: " . (is_object($node) ? $node->label() : $node); - $retval .= ", foo: " . (is_object($foo) ? $foo->label() : $foo); - return $retval; + public function testUserNodeFoo(EntityInterface $user, NodeInterface $node, Request $request) { + $foo = $request->attributes->get('foo'); + $foo = is_object($foo) ? $foo->label() : $foo; + return "user: {$user->label()}, node: {$node->label()}, foo: $foo"; } - public function testNodeSetParent(EntityInterface $node, EntityInterface $parent) { - return "Setting '{$parent->title}' as parent of '{$node->title}'."; + public function testNodeSetParent(NodeInterface $node, NodeInterface $parent) { + return "Setting '{$parent->label()}' as parent of '{$node->label()}'."; } } diff --git a/core/tests/Drupal/Tests/Core/Controller/ControllerResolverTest.php b/core/tests/Drupal/Tests/Core/Controller/ControllerResolverTest.php new file mode 100644 index 00000000000..2fbfa9ab741 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Controller/ControllerResolverTest.php @@ -0,0 +1,76 @@ + 'Controller Resolver tests', + 'description' => 'Tests that the Drupal-extended ControllerResolver is functioning properly.', + 'group' => 'Routing', + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $container = new ContainerBuilder(); + $this->controllerResolver = new ControllerResolver($container); + } + + /** + * Tests getArguments(). + * + * Ensure that doGetArguments uses converted arguments if available. + * + * @see \Drupal\Core\Controller\ControllerResolver::getArguments() + * @see \Drupal\Core\Controller\ControllerResolver::doGetArguments() + */ + public function testGetArguments() { + $controller = function(EntityInterface $entity, $user) { + }; + $mock_entity = $this->getMockBuilder('Drupal\Core\Entity\Entity') + ->disableOriginalConstructor() + ->getMock(); + $mock_account = $this->getMock('Drupal\Core\Session\AccountInterface'); + $request = new \Symfony\Component\HttpFoundation\Request(array(), array(), array( + 'entity' => $mock_entity, + 'user' => $mock_account, + '_raw_variables' => new ParameterBag(array('entity' => 1, 'user' => 1)), + )); + $arguments = $this->controllerResolver->getArguments($request, $controller); + + $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.'); + } + +}