Issue #2031487 by fubhy, dawehner, pwolanin: When replacing the upcasted values in the request attributes array, retain the original raw value too.

8.0.x
Nathaniel Catchpole 2013-07-27 15:35:36 +01:00
parent d3c5919d17
commit 58afd192b8
4 changed files with 113 additions and 7 deletions

View File

@ -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;
}
}

View File

@ -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')) {

View File

@ -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()}'.";
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Controller\ControllerResolverTest.
*/
namespace Drupal\Tests\Core\Controller;
use Drupal\Core\Controller\ControllerResolver;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Tests\UnitTestCase;
use Drupal\user\UserInterface;
use Guzzle\Http\Message\Request;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
/**
* Tests that the Drupal-extended ControllerResolver is functioning properly.
*
* @see \Drupal\Core\Controller\ControllerResolver
*/
class ControllerResolverTest extends UnitTestCase {
/**
* The tested controller resolver.
*
* @var \Drupal\Core\Controller\ControllerResolver
*/
public $controllerResolver;
public static function getInfo() {
return array(
'name' => '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.');
}
}