Merge remote branch 'schmittjoh/referenceValidation'
* schmittjoh/referenceValidation: [DependencyInjection] also check references of inlined services [DependencyInjection] adds emulation of "exception-on-invalid-reference" behavior
This commit is contained in:
commit
8b2b8e16dc
@ -63,6 +63,11 @@ PR11 to PR12
|
||||
arbitrary accounts when the SwitchUserListener was activated. Configurations
|
||||
which do not use the SwitchUserListener are not affected.
|
||||
|
||||
* The Dependency Injection Container now strongly validates the references of
|
||||
all your services at the end of its compilation process. If you have invalid
|
||||
references this will result in a compile-time exception instead of a run-time
|
||||
exception (the previous behavior).
|
||||
|
||||
PR10 to PR11
|
||||
------------
|
||||
|
||||
|
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Exception\NonExistentServiceException;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Checks that all references are pointing to a valid service.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class CheckExceptionOnInvalidReferenceBehaviorPass implements CompilerPassInterface
|
||||
{
|
||||
private $container;
|
||||
private $sourceId;
|
||||
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
|
||||
foreach ($container->getDefinitions() as $id => $definition) {
|
||||
$this->sourceId = $id;
|
||||
$this->processDefinition($definition);
|
||||
}
|
||||
}
|
||||
|
||||
private function processDefinition(Definition $definition)
|
||||
{
|
||||
$this->processReferences($definition->getArguments());
|
||||
$this->processReferences($definition->getMethodCalls());
|
||||
$this->processReferences($definition->getProperties());
|
||||
}
|
||||
|
||||
private function processReferences(array $arguments)
|
||||
{
|
||||
foreach ($arguments as $argument) {
|
||||
if (is_array($argument)) {
|
||||
$this->processReferences($argument);
|
||||
} else if ($argument instanceof Definition) {
|
||||
$this->processDefinition($argument);
|
||||
} else if ($argument instanceof Reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $argument->getInvalidBehavior()) {
|
||||
$destId = (string) $argument;
|
||||
|
||||
if (!$this->container->has($destId)) {
|
||||
throw new NonExistentServiceException($destId, $this->sourceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -66,6 +66,7 @@ class PassConfig
|
||||
new AnalyzeServiceReferencesPass(),
|
||||
new RemoveUnusedDefinitionsPass(),
|
||||
)),
|
||||
new CheckExceptionOnInvalidReferenceBehaviorPass(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Exception\NonExistentServiceException;
|
||||
use Symfony\Component\DependencyInjection\Exception\CircularReferenceException;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
|
||||
@ -237,7 +238,7 @@ class Container implements ContainerInterface
|
||||
}
|
||||
|
||||
if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) {
|
||||
throw new \InvalidArgumentException(sprintf('The service "%s" does not exist.', $id));
|
||||
throw new NonExistentServiceException($id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown when a non-existent service is requested.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class NonExistentServiceException extends InvalidArgumentException
|
||||
{
|
||||
private $id;
|
||||
private $sourceId;
|
||||
|
||||
public function __construct($id, $sourceId = null)
|
||||
{
|
||||
if (null === $sourceId) {
|
||||
$msg = sprintf('You have requested a non-existent service "%s".', $id);
|
||||
} else {
|
||||
$msg = sprintf('The service "%s" has a dependency on a non-existent service "%s".', $sourceId, $id);
|
||||
}
|
||||
|
||||
parent::__construct($msg);
|
||||
|
||||
$this->id = $id;
|
||||
$this->sourceId = $sourceId;
|
||||
}
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getSourceId()
|
||||
{
|
||||
return $this->sourceId;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Tests\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CheckExceptionOnInvalidReferenceBehaviorPass;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
class CheckExceptionOnInvalidReferenceBehaviorPassTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testProcess()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
||||
$container
|
||||
->register('a', '\stdClass')
|
||||
->addArgument(new Reference('b'))
|
||||
;
|
||||
$container->register('b', '\stdClass');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\DependencyInjection\Exception\NonExistentServiceException
|
||||
*/
|
||||
public function testProcessThrowsExceptionOnInvalidReference()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
||||
$container
|
||||
->register('a', '\stdClass')
|
||||
->addArgument(new Reference('b'))
|
||||
;
|
||||
|
||||
$this->process($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\DependencyInjection\Exception\NonExistentServiceException
|
||||
*/
|
||||
public function testProcessThrowsExceptionOnInvalidReferenceFromInlinedDefinition()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
||||
$def = new Definition();
|
||||
$def->addArgument(new Reference('b'));
|
||||
|
||||
$container
|
||||
->register('a', '\stdClass')
|
||||
->addArgument($def)
|
||||
;
|
||||
|
||||
$this->process($container);
|
||||
}
|
||||
|
||||
private function process(ContainerBuilder $container)
|
||||
{
|
||||
$pass = new CheckExceptionOnInvalidReferenceBehaviorPass();
|
||||
$pass->process($container);
|
||||
}
|
||||
}
|
@ -161,8 +161,7 @@ class ContainerTest extends \PHPUnit_Framework_TestCase
|
||||
$sc->get('');
|
||||
$this->fail('->get() throws a \InvalidArgumentException exception if the service is empty');
|
||||
} catch (\Exception $e) {
|
||||
$this->assertInstanceOf('\InvalidArgumentException', $e, '->get() throws a \InvalidArgumentException exception if the service is empty');
|
||||
$this->assertEquals('The service "" does not exist.', $e->getMessage(), '->get() throws a \InvalidArgumentException exception if the service is empty');
|
||||
$this->assertInstanceOf('Symfony\Component\DependencyInjection\Exception\NonExistentServiceException', $e, '->get() throws a NonExistentServiceException exception if the service is empty');
|
||||
}
|
||||
$this->assertNull($sc->get('', ContainerInterface::NULL_ON_INVALID_REFERENCE));
|
||||
}
|
||||
|
Reference in New Issue
Block a user