[DependencyInjection] adds emulation of "exception-on-invalid-reference" behavior

This pass requires that all of references are valid at the end of
the compilation process.
This commit is contained in:
Johannes Schmitt 2011-04-15 00:11:50 +02:00
parent db90e0ab8d
commit 6d7a9d752d
8 changed files with 137 additions and 5 deletions

View File

@ -20,6 +20,11 @@ PR11 to PR12
<bundle>MyBundle</bundle>
* 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

@ -123,10 +123,10 @@ class TraceableEventDispatcher extends ContainerAwareEventDispatcher implements
/**
* Returns information about the listener
*
*
* @param object $listener The listener
* @param string $eventName The event name
*
*
* @return array Informations about the listener
*/
private function getListenerInfo($listener, $eventName)

View File

@ -0,0 +1,46 @@
<?php
namespace Symfony\Component\DependencyInjection\Compiler;
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->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 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,42 @@
<?php
namespace Symfony\Tests\Component\DependencyInjection\Compiler;
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);
}
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));
}