Making the RegisterControllerArgumentLocatorsPass throw an exception on a bad type-hint
This commit is contained in:
parent
ae9bda1ffc
commit
22b905226d
@ -110,9 +110,12 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($methods as list($r, $parameters)) {
|
foreach ($methods as list($r, $parameters)) {
|
||||||
|
/** @var \ReflectionMethod $r */
|
||||||
|
|
||||||
// create a per-method map of argument-names to service/type-references
|
// create a per-method map of argument-names to service/type-references
|
||||||
$args = array();
|
$args = array();
|
||||||
foreach ($parameters as $p) {
|
foreach ($parameters as $p) {
|
||||||
|
/** @var \ReflectionParameter $p */
|
||||||
$type = $target = ProxyHelper::getTypeHint($r, $p, true);
|
$type = $target = ProxyHelper::getTypeHint($r, $p, true);
|
||||||
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
|
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
|
||||||
|
|
||||||
@ -129,6 +132,17 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($type && !$p->isOptional() && !$p->allowsNull() && !class_exists($type) && !interface_exists($type, false)) {
|
||||||
|
$message = sprintf('Cannot determine controller argument for "%s::%s()": the $%s argument is type-hinted with the non-existent class or interface: "%s".', $class, $r->name, $p->name, $type);
|
||||||
|
|
||||||
|
// see if the type-hint lives in the same namespace as the controller
|
||||||
|
if (0 === strncmp($type, $class, strrpos($class, '\\'))) {
|
||||||
|
$message .= ' Did you forget to add a use statement?';
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidArgumentException($message);
|
||||||
|
}
|
||||||
|
|
||||||
$args[$p->name] = $type ? new TypedReference($target, $type, $r->class, $invalidBehavior) : new Reference($target, $invalidBehavior);
|
$args[$p->name] = $type ? new TypedReference($target, $type, $r->class, $invalidBehavior) : new Reference($target, $invalidBehavior);
|
||||||
}
|
}
|
||||||
// register the maps as a per-method service-locators
|
// register the maps as a per-method service-locators
|
||||||
|
@ -204,6 +204,68 @@ class RegisterControllerArgumentLocatorsPassTest extends TestCase
|
|||||||
$locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0);
|
$locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0);
|
||||||
$this->assertSame(array('foo:fooAction'), array_keys($locator));
|
$this->assertSame(array('foo:fooAction'), array_keys($locator));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
|
||||||
|
* @expectedExceptionMessage Cannot determine controller argument for "Symfony\Component\HttpKernel\Tests\DependencyInjection\NonExistentClassController::fooAction()": the $nonExistent argument is type-hinted with the non-existent class or interface: "Symfony\Component\HttpKernel\Tests\DependencyInjection\NonExistentClass". Did you forget to add a use statement?
|
||||||
|
*/
|
||||||
|
public function testExceptionOnNonExistentTypeHint()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->register('argument_resolver.service')->addArgument(array());
|
||||||
|
|
||||||
|
$container->register('foo', NonExistentClassController::class)
|
||||||
|
->addTag('controller.service_arguments');
|
||||||
|
|
||||||
|
$pass = new RegisterControllerArgumentLocatorsPass();
|
||||||
|
$pass->process($container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
|
||||||
|
* @expectedExceptionMessage Cannot determine controller argument for "Symfony\Component\HttpKernel\Tests\DependencyInjection\NonExistentClassDifferentNamespaceController::fooAction()": the $nonExistent argument is type-hinted with the non-existent class or interface: "Acme\NonExistentClass".
|
||||||
|
*/
|
||||||
|
public function testExceptionOnNonExistentTypeHintDifferentNamespace()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->register('argument_resolver.service')->addArgument(array());
|
||||||
|
|
||||||
|
$container->register('foo', NonExistentClassDifferentNamespaceController::class)
|
||||||
|
->addTag('controller.service_arguments');
|
||||||
|
|
||||||
|
$pass = new RegisterControllerArgumentLocatorsPass();
|
||||||
|
$pass->process($container);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNoExceptionOnNonExistentTypeHintOptionalArg()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$resolver = $container->register('argument_resolver.service')->addArgument(array());
|
||||||
|
|
||||||
|
$container->register('foo', NonExistentClassOptionalController::class)
|
||||||
|
->addTag('controller.service_arguments');
|
||||||
|
|
||||||
|
$pass = new RegisterControllerArgumentLocatorsPass();
|
||||||
|
$pass->process($container);
|
||||||
|
|
||||||
|
$locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0);
|
||||||
|
$this->assertSame(array('foo:barAction', 'foo:fooAction'), array_keys($locator));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testArgumentWithNoTypeHintIsOk()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$resolver = $container->register('argument_resolver.service')->addArgument(array());
|
||||||
|
|
||||||
|
$container->register('foo', ArgumentWithoutTypeController::class)
|
||||||
|
->addTag('controller.service_arguments');
|
||||||
|
|
||||||
|
$pass = new RegisterControllerArgumentLocatorsPass();
|
||||||
|
$pass->process($container);
|
||||||
|
|
||||||
|
$locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0);
|
||||||
|
$this->assertEmpty(array_keys($locator));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RegisterTestController
|
class RegisterTestController
|
||||||
@ -233,3 +295,35 @@ class ContainerAwareRegisterTestController implements ContainerAwareInterface
|
|||||||
class ControllerDummy
|
class ControllerDummy
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NonExistentClassController
|
||||||
|
{
|
||||||
|
public function fooAction(NonExistentClass $nonExistent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NonExistentClassDifferentNamespaceController
|
||||||
|
{
|
||||||
|
public function fooAction(\Acme\NonExistentClass $nonExistent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NonExistentClassOptionalController
|
||||||
|
{
|
||||||
|
public function fooAction(NonExistentClass $nonExistent = null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function barAction(NonExistentClass $nonExistent = null, $bar)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArgumentWithoutTypeController
|
||||||
|
{
|
||||||
|
public function fooAction($someArg)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -120,7 +120,7 @@ class RemoveEmptyControllerArgumentLocatorsPassTest extends TestCase
|
|||||||
|
|
||||||
class RemoveTestController1
|
class RemoveTestController1
|
||||||
{
|
{
|
||||||
public function fooAction(\stdClass $bar, NotFound $baz)
|
public function fooAction(\stdClass $bar, ClassNotInContainer $baz)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,7 +131,7 @@ class RemoveTestController2
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function fooAction(NotFound $bar)
|
public function fooAction(ClassNotInContainer $bar)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,3 +142,7 @@ class InvokableRegisterTestController
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ClassNotInContainer
|
||||||
|
{
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user