2011-01-26 23:14:31 +00:00
< ? php
namespace Symfony\Component\DependencyInjection\Compiler ;
2011-03-13 18:56:13 +00:00
use Symfony\Component\DependencyInjection\Exception\ScopeWideningException ;
use Symfony\Component\DependencyInjection\Exception\ScopeCrossingException ;
2011-01-26 23:14:31 +00:00
use Symfony\Component\DependencyInjection\Definition ;
use Symfony\Component\DependencyInjection\ContainerInterface ;
use Symfony\Component\DependencyInjection\Reference ;
use Symfony\Component\DependencyInjection\ContainerBuilder ;
/**
* Checks the validity of references
*
* The following checks are performed by this pass :
* - target definitions are not abstract
* - target definitions are of equal or wider scope
* - target definitions are in the same scope hierarchy
*
* @ author Johannes M . Schmitt < schmittjoh @ gmail . com >
*/
class CheckReferenceValidityPass implements CompilerPassInterface
{
2011-03-11 13:50:46 +00:00
private $container ;
private $currentId ;
private $currentDefinition ;
private $currentScope ;
private $currentScopeAncestors ;
private $currentScopeChildren ;
2011-01-26 23:14:31 +00:00
2011-02-13 18:06:41 +00:00
/**
* Processes the ContainerBuilder to validate References .
*
2011-03-04 14:26:00 +00:00
* @ param ContainerBuilder $container
2011-02-13 18:06:41 +00:00
*/
2011-01-26 23:14:31 +00:00
public function process ( ContainerBuilder $container )
{
$this -> container = $container ;
$children = $this -> container -> getScopeChildren ();
$ancestors = array ();
$scopes = $this -> container -> getScopes ();
foreach ( $scopes as $name => $parent ) {
$ancestors [ $name ] = array ( $parent );
while ( isset ( $scopes [ $parent ])) {
$ancestors [ $name ][] = $parent = $scopes [ $parent ];
}
}
foreach ( $container -> getDefinitions () as $id => $definition ) {
if ( $definition -> isSynthetic () || $definition -> isAbstract ()) {
continue ;
}
$this -> currentId = $id ;
$this -> currentDefinition = $definition ;
$this -> currentScope = $scope = $definition -> getScope ();
if ( ContainerInterface :: SCOPE_CONTAINER === $scope ) {
$this -> currentScopeChildren = array_keys ( $scopes );
$this -> currentScopeAncestors = array ();
} else if ( ContainerInterface :: SCOPE_PROTOTYPE !== $scope ) {
$this -> currentScopeChildren = $children [ $scope ];
$this -> currentScopeAncestors = $ancestors [ $scope ];
}
$this -> validateReferences ( $definition -> getArguments ());
$this -> validateReferences ( $definition -> getMethodCalls ());
2011-03-04 14:26:00 +00:00
$this -> validateReferences ( $definition -> getProperties ());
2011-01-26 23:14:31 +00:00
}
}
2011-02-13 18:06:41 +00:00
/**
* Validates an array of References .
*
* @ param array $arguments An array of Reference objects
* @ throws \RuntimeException when there is a reference to an abstract definition .
*/
2011-03-11 13:50:46 +00:00
private function validateReferences ( array $arguments )
2011-01-26 23:14:31 +00:00
{
foreach ( $arguments as $argument ) {
if ( is_array ( $argument )) {
$this -> validateReferences ( $argument );
} elseif ( $argument instanceof Reference ) {
$targetDefinition = $this -> getDefinition (( string ) $argument );
if ( null !== $targetDefinition && $targetDefinition -> isAbstract ()) {
throw new \RuntimeException ( sprintf (
'The definition "%s" has a reference to an abstract definition "%s". '
. 'Abstract definitions cannot be the target of references.' ,
$this -> currentId ,
$argument
));
}
$this -> validateScope ( $argument , $targetDefinition );
}
}
}
2011-02-13 18:06:41 +00:00
/**
* Validates the scope of a single Reference .
*
2011-03-04 14:26:00 +00:00
* @ param Reference $reference
* @ param Definition $definition
2011-02-13 18:06:41 +00:00
* @ throws \RuntimeException when there is an issue with the Reference scope
*/
2011-03-11 13:50:46 +00:00
private function validateScope ( Reference $reference , Definition $definition = null )
2011-01-26 23:14:31 +00:00
{
if ( ContainerInterface :: SCOPE_PROTOTYPE === $this -> currentScope ) {
return ;
}
if ( ! $reference -> isStrict ()) {
return ;
}
if ( null === $definition ) {
return ;
}
if ( $this -> currentScope === $scope = $definition -> getScope ()) {
return ;
}
$id = ( string ) $reference ;
if ( in_array ( $scope , $this -> currentScopeChildren , true )) {
2011-03-13 18:56:13 +00:00
$ex = new ScopeWideningException ( sprintf (
2011-01-26 23:14:31 +00:00
'Scope Widening Injection detected: The definition "%s" references the service "%s" which belongs to a narrower scope. '
. 'Generally, it is safer to either move "%s" to scope "%s" or alternatively rely on the provider pattern by injecting the container itself, and requesting the service "%s" each time it is needed. '
. 'In rare, special cases however that might not be necessary, then you can set the reference to strict=false to get rid of this error.' ,
$this -> currentId ,
$id ,
$this -> currentId ,
$scope ,
$id
));
2011-03-13 18:56:13 +00:00
$ex -> setServiceId ( $this -> currentId );
throw $ex ;
2011-01-26 23:14:31 +00:00
}
if ( ! in_array ( $scope , $this -> currentScopeAncestors , true )) {
2011-03-13 18:56:13 +00:00
$ex = new ScopeCrossingException ( sprintf (
'Scope Crossing Injection detected: The definition "%s" references the service "%s" which belongs to another scope hierarchy. '
2011-01-26 23:14:31 +00:00
. 'This service might not be available consistently. Generally, it is safer to either move the definition "%s" to scope "%s", or '
. 'declare "%s" as a child scope of "%s". If you can be sure that the other scope is always active, you can set the reference to strict=false to get rid of this error.' ,
$this -> currentId ,
$id ,
$this -> currentId ,
$scope ,
$this -> currentScope ,
$scope
));
2011-03-13 18:56:13 +00:00
$ex -> setServiceId ( $this -> currentId );
throw $ex ;
2011-01-26 23:14:31 +00:00
}
}
2011-02-13 18:06:41 +00:00
/**
* Returns the Definition given an id .
*
* @ param string $id Definition identifier
* @ return Definition
*/
2011-03-11 13:50:46 +00:00
private function getDefinition ( $id )
2011-01-26 23:14:31 +00:00
{
if ( ! $this -> container -> hasDefinition ( $id )) {
return null ;
}
return $this -> container -> getDefinition ( $id );
}
}