[FrameworkBundle][HttpKernel] Provide intuitive error message when a controller fails because it's not registered as a service
This commit is contained in:
parent
59e63805a3
commit
fbfc623b72
@ -23,5 +23,10 @@
|
||||
<argument type="service" id="debug.argument_resolver.inner" />
|
||||
<argument type="service" id="debug.stopwatch" />
|
||||
</service>
|
||||
|
||||
<service id="argument_resolver.not_tagged_controller" class="Symfony\Component\HttpKernel\Controller\ArgumentResolver\NotTaggedControllerValueResolver">
|
||||
<tag name="controller.argument_value_resolver" priority="-200" />
|
||||
<argument />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -0,0 +1,81 @@
|
||||
<?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\Controller\ArgumentResolver;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
|
||||
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
|
||||
|
||||
/**
|
||||
* Provides an intuitive error message when controller fails because it is not registered as a service.
|
||||
*
|
||||
* @author Simeon Kolev <simeon.kolev9@gmail.com>
|
||||
*/
|
||||
final class NotTaggedControllerValueResolver implements ArgumentValueResolverInterface
|
||||
{
|
||||
private $container;
|
||||
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supports(Request $request, ArgumentMetadata $argument)
|
||||
{
|
||||
$controller = $request->attributes->get('_controller');
|
||||
|
||||
if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) {
|
||||
$controller = $controller[0].'::'.$controller[1];
|
||||
} elseif (!\is_string($controller) || '' === $controller) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ('\\' === $controller[0]) {
|
||||
$controller = ltrim($controller, '\\');
|
||||
}
|
||||
|
||||
if (!$this->container->has($controller) && false !== $i = strrpos($controller, ':')) {
|
||||
$controller = substr($controller, 0, $i).strtolower(substr($controller, $i));
|
||||
}
|
||||
|
||||
return false === $this->container->has($controller);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resolve(Request $request, ArgumentMetadata $argument)
|
||||
{
|
||||
if (\is_array($controller = $request->attributes->get('_controller'))) {
|
||||
$controller = $controller[0].'::'.$controller[1];
|
||||
}
|
||||
|
||||
if ('\\' === $controller[0]) {
|
||||
$controller = ltrim($controller, '\\');
|
||||
}
|
||||
|
||||
if (!$this->container->has($controller)) {
|
||||
$i = strrpos($controller, ':');
|
||||
$controller = substr($controller, 0, $i).strtolower(substr($controller, $i));
|
||||
}
|
||||
|
||||
$what = sprintf('argument $%s of "%s()"', $argument->getName(), $controller);
|
||||
$message = sprintf('Could not resolve %s, maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?', $what);
|
||||
|
||||
throw new RuntimeException($message);
|
||||
}
|
||||
}
|
@ -34,17 +34,19 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface
|
||||
private $resolverServiceId;
|
||||
private $controllerTag;
|
||||
private $controllerLocator;
|
||||
private $notTaggedControllerResolverServiceId;
|
||||
|
||||
public function __construct(string $resolverServiceId = 'argument_resolver.service', string $controllerTag = 'controller.service_arguments', string $controllerLocator = 'argument_resolver.controller_locator')
|
||||
public function __construct(string $resolverServiceId = 'argument_resolver.service', string $controllerTag = 'controller.service_arguments', string $controllerLocator = 'argument_resolver.controller_locator', string $notTaggedControllerResolverServiceId = 'argument_resolver.not_tagged_controller')
|
||||
{
|
||||
$this->resolverServiceId = $resolverServiceId;
|
||||
$this->controllerTag = $controllerTag;
|
||||
$this->controllerLocator = $controllerLocator;
|
||||
$this->notTaggedControllerResolverServiceId = $notTaggedControllerResolverServiceId;
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (false === $container->hasDefinition($this->resolverServiceId)) {
|
||||
if (false === $container->hasDefinition($this->resolverServiceId) && false === $container->hasDefinition($this->notTaggedControllerResolverServiceId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -181,8 +183,17 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface
|
||||
}
|
||||
}
|
||||
|
||||
$container->getDefinition($this->resolverServiceId)
|
||||
->replaceArgument(0, $controllerLocatorRef = ServiceLocatorTagPass::register($container, $controllers));
|
||||
$controllerLocatorRef = ServiceLocatorTagPass::register($container, $controllers);
|
||||
|
||||
if ($container->hasDefinition($this->resolverServiceId)) {
|
||||
$container->getDefinition($this->resolverServiceId)
|
||||
->replaceArgument(0, $controllerLocatorRef);
|
||||
}
|
||||
|
||||
if ($container->hasDefinition($this->notTaggedControllerResolverServiceId)) {
|
||||
$container->getDefinition($this->notTaggedControllerResolverServiceId)
|
||||
->replaceArgument(0, $controllerLocatorRef);
|
||||
}
|
||||
|
||||
$container->setAlias($this->controllerLocator, (string) $controllerLocatorRef);
|
||||
}
|
||||
|
@ -0,0 +1,117 @@
|
||||
<?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\NotTaggedControllerValueResolver;
|
||||
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
|
||||
|
||||
class NotTaggedControllerValueResolverTest extends TestCase
|
||||
{
|
||||
public function testDoSupportWhenControllerDoNotExists()
|
||||
{
|
||||
$resolver = new NotTaggedControllerValueResolver(new ServiceLocator([]));
|
||||
$argument = new ArgumentMetadata('dummy', \stdClass::class, false, false, null);
|
||||
$request = $this->requestWithAttributes(['_controller' => 'my_controller']);
|
||||
|
||||
$this->assertTrue($resolver->supports($request, $argument));
|
||||
}
|
||||
|
||||
public function testDoNotSupportWhenControllerExists()
|
||||
{
|
||||
$resolver = new NotTaggedControllerValueResolver(new ServiceLocator([
|
||||
'App\\Controller\\Mine::method' => function () {
|
||||
return new ServiceLocator([
|
||||
'dummy' => function () {
|
||||
return new \stdClass();
|
||||
},
|
||||
]);
|
||||
},
|
||||
]));
|
||||
$argument = new ArgumentMetadata('dummy', \stdClass::class, false, false, null);
|
||||
$request = $this->requestWithAttributes(['_controller' => 'App\\Controller\\Mine::method']);
|
||||
|
||||
$this->assertFalse($resolver->supports($request, $argument));
|
||||
}
|
||||
|
||||
public function testDoNotSupportEmptyController()
|
||||
{
|
||||
$resolver = new NotTaggedControllerValueResolver(new ServiceLocator([]));
|
||||
$argument = new ArgumentMetadata('dummy', \stdClass::class, false, false, null);
|
||||
$request = $this->requestWithAttributes(['_controller' => '']);
|
||||
$this->assertFalse($resolver->supports($request, $argument));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
|
||||
* @expectedExceptionMessage Could not resolve argument $dummy of "App\Controller\Mine::method()", maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?
|
||||
*/
|
||||
public function testController()
|
||||
{
|
||||
$resolver = new NotTaggedControllerValueResolver(new ServiceLocator([]));
|
||||
$argument = new ArgumentMetadata('dummy', \stdClass::class, false, false, null);
|
||||
$request = $this->requestWithAttributes(['_controller' => 'App\\Controller\\Mine::method']);
|
||||
$this->assertTrue($resolver->supports($request, $argument));
|
||||
$resolver->resolve($request, $argument);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
|
||||
* @expectedExceptionMessage Could not resolve argument $dummy of "App\Controller\Mine::method()", maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?
|
||||
*/
|
||||
public function testControllerWithATrailingBackSlash()
|
||||
{
|
||||
$resolver = new NotTaggedControllerValueResolver(new ServiceLocator([]));
|
||||
$argument = new ArgumentMetadata('dummy', \stdClass::class, false, false, null);
|
||||
$request = $this->requestWithAttributes(['_controller' => '\\App\\Controller\\Mine::method']);
|
||||
$this->assertTrue($resolver->supports($request, $argument));
|
||||
$resolver->resolve($request, $argument);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
|
||||
* @expectedExceptionMessage Could not resolve argument $dummy of "App\Controller\Mine::method()", maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?
|
||||
*/
|
||||
public function testControllerWithMethodNameStartUppercase()
|
||||
{
|
||||
$resolver = new NotTaggedControllerValueResolver(new ServiceLocator([]));
|
||||
$argument = new ArgumentMetadata('dummy', \stdClass::class, false, false, null);
|
||||
$request = $this->requestWithAttributes(['_controller' => 'App\\Controller\\Mine::Method']);
|
||||
$this->assertTrue($resolver->supports($request, $argument));
|
||||
$resolver->resolve($request, $argument);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
|
||||
* @expectedExceptionMessage Could not resolve argument $dummy of "App\Controller\Mine::method()", maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?
|
||||
*/
|
||||
public function testControllerNameIsAnArray()
|
||||
{
|
||||
$resolver = new NotTaggedControllerValueResolver(new ServiceLocator([]));
|
||||
$argument = new ArgumentMetadata('dummy', \stdClass::class, false, false, null);
|
||||
$request = $this->requestWithAttributes(['_controller' => ['App\\Controller\\Mine', 'method']]);
|
||||
$this->assertTrue($resolver->supports($request, $argument));
|
||||
$resolver->resolve($request, $argument);
|
||||
}
|
||||
|
||||
private function requestWithAttributes(array $attributes)
|
||||
{
|
||||
$request = Request::create('/');
|
||||
foreach ($attributes as $name => $value) {
|
||||
$request->attributes->set($name, $value);
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
@ -376,6 +376,19 @@ class RegisterControllerArgumentLocatorsPassTest extends TestCase
|
||||
$this->assertInstanceOf(ServiceClosureArgument::class, $locator['someArg']);
|
||||
$this->assertEquals(new Reference('parent'), $locator['someArg']->getValues()[0]);
|
||||
}
|
||||
|
||||
public function testNotTaggedControllerServiceReceivesLocatorArgument()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$resolver = $container->register('argument_resolver.not_tagged_controller')->addArgument([]);
|
||||
|
||||
$pass = new RegisterControllerArgumentLocatorsPass();
|
||||
$pass->process($container);
|
||||
|
||||
$locatorArgument = $container->getDefinition('argument_resolver.not_tagged_controller')->getArgument(0);
|
||||
|
||||
$this->assertInstanceOf(Reference::class, $locatorArgument);
|
||||
}
|
||||
}
|
||||
|
||||
class RegisterTestController
|
||||
|
Reference in New Issue
Block a user