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:
Fabien Potencier 2011-04-21 22:44:20 +02:00
commit 8b2b8e16dc
7 changed files with 164 additions and 3 deletions

View File

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

View File

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

View File

@ -66,6 +66,7 @@ class PassConfig
new AnalyzeServiceReferencesPass(),
new RemoveUnusedDefinitionsPass(),
)),
new CheckExceptionOnInvalidReferenceBehaviorPass(),
);
}

View File

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

View File

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

View File

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

View File

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