bug #24972 [HttpKernel] Fix service arg resolver for controllers as array callables (sroze, nicolas-grekas)

This PR was merged into the 3.4 branch.

Discussion
----------

[HttpKernel] Fix service arg resolver for controllers as array callables

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #24970
| License       | MIT
| Doc PR        | ø

Replacing #24970 as I can't push tests directly to @nicolas-grekas' PR.

> As spotted today during a Symfony 4 workshop at SymfonyCon Cluj, setting a controller as an array [SomeController::class, 'helloAction'] works, it is defined as a service, BUT the actions don't get the services as arguments. This is fixing it.

Commits
-------

fc3d3bb [HttpKernel] Fix service arg resolver for controllers as array callables
a9e9f36 Add service value resolver tests Prove that the service value resolver will not work with the array notation
This commit is contained in:
Nicolas Grekas 2017-11-16 23:41:27 +02:00
commit a2f8c32bd3
2 changed files with 105 additions and 2 deletions

View File

@ -35,7 +35,13 @@ final class ServiceValueResolver implements ArgumentValueResolverInterface
*/
public function supports(Request $request, ArgumentMetadata $argument)
{
return is_string($controller = $request->attributes->get('_controller')) && $this->container->has($controller) && $this->container->get($controller)->has($argument->getName());
$controller = $request->attributes->get('_controller');
if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) {
$controller = $controller[0].'::'.$controller[1];
}
return \is_string($controller) && $this->container->has($controller) && $this->container->get($controller)->has($argument->getName());
}
/**
@ -43,6 +49,10 @@ final class ServiceValueResolver implements ArgumentValueResolverInterface
*/
public function resolve(Request $request, ArgumentMetadata $argument)
{
yield $this->container->get($request->attributes->get('_controller'))->get($argument->getName());
if (\is_array($controller = $request->attributes->get('_controller'))) {
$controller = $controller[0].'::'.$controller[1];
}
yield $this->container->get($controller)->get($argument->getName());
}
}

View File

@ -0,0 +1,93 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Tests\Controller\ArgumentResolver;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\ServiceValueResolver;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
class ServiceValueResolverTest extends TestCase
{
public function testDoNotSupportWhenControllerDoNotExists()
{
$resolver = new ServiceValueResolver(new ServiceLocator(array()));
$argument = new ArgumentMetadata('dummy', DummyService::class, false, false, null);
$request = $this->requestWithAttributes(array('_controller' => 'my_controller'));
$this->assertFalse($resolver->supports($request, $argument));
}
public function testExistingController()
{
$resolver = new ServiceValueResolver(new ServiceLocator(array(
'App\\Controller\\Mine::method' => function () {
return new ServiceLocator(array(
'dummy' => function () {
return new DummyService();
},
));
},
)));
$request = $this->requestWithAttributes(array('_controller' => 'App\\Controller\\Mine::method'));
$argument = new ArgumentMetadata('dummy', DummyService::class, false, false, null);
$this->assertTrue($resolver->supports($request, $argument));
$this->assertYieldEquals(array(new DummyService()), $resolver->resolve($request, $argument));
}
public function testControllerNameIsAnArray()
{
$resolver = new ServiceValueResolver(new ServiceLocator(array(
'App\\Controller\\Mine::method' => function () {
return new ServiceLocator(array(
'dummy' => function () {
return new DummyService();
},
));
},
)));
$request = $this->requestWithAttributes(array('_controller' => array('App\\Controller\\Mine', 'method')));
$argument = new ArgumentMetadata('dummy', DummyService::class, false, false, null);
$this->assertTrue($resolver->supports($request, $argument));
$this->assertYieldEquals(array(new DummyService()), $resolver->resolve($request, $argument));
}
private function requestWithAttributes(array $attributes)
{
$request = Request::create('/');
foreach ($attributes as $name => $value) {
$request->attributes->set($name, $value);
}
return $request;
}
private function assertYieldEquals(array $expected, \Generator $generator)
{
$args = array();
foreach ($generator as $arg) {
$args[] = $arg;
}
$this->assertEquals($expected, $args);
}
}
class DummyService
{
}