2015-08-24 02:36:41 +01:00
< ? 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\DependencyInjection\Compiler ;
use Symfony\Component\DependencyInjection\ContainerBuilder ;
use Symfony\Component\DependencyInjection\Definition ;
use Symfony\Component\DependencyInjection\Exception\RuntimeException ;
use Symfony\Component\DependencyInjection\Reference ;
/**
* Guesses constructor arguments of services definitions and try to instantiate services if necessary .
*
* @ author Kévin Dunglas < dunglas @ gmail . com >
*/
class AutowirePass implements CompilerPassInterface
{
private $container ;
private $reflectionClasses = array ();
private $definedTypes = array ();
private $types ;
private $notGuessableTypes = array ();
/**
* { @ inheritdoc }
*/
public function process ( ContainerBuilder $container )
{
$this -> container = $container ;
foreach ( $container -> getDefinitions () as $id => $definition ) {
if ( $definition -> isAutowired ()) {
$this -> completeDefinition ( $id , $definition );
}
}
// Free memory and remove circular reference to container
$this -> container = null ;
$this -> reflectionClasses = array ();
$this -> definedTypes = array ();
$this -> types = null ;
$this -> notGuessableTypes = array ();
}
/**
* Wires the given definition .
*
* @ param string $id
* @ param Definition $definition
*
* @ throws RuntimeException
*/
private function completeDefinition ( $id , Definition $definition )
{
if ( ! $reflectionClass = $this -> getReflectionClass ( $id , $definition )) {
return ;
}
$this -> container -> addClassResource ( $reflectionClass );
if ( ! $constructor = $reflectionClass -> getConstructor ()) {
return ;
}
$arguments = $definition -> getArguments ();
foreach ( $constructor -> getParameters () as $index => $parameter ) {
$argumentExists = array_key_exists ( $index , $arguments );
if ( $argumentExists && '' !== $arguments [ $index ]) {
continue ;
}
try {
if ( ! $typeHint = $parameter -> getClass ()) {
continue ;
}
if ( null === $this -> types ) {
$this -> populateAvailableTypes ();
}
if ( isset ( $this -> types [ $typeHint -> name ])) {
$value = new Reference ( $this -> types [ $typeHint -> name ]);
} else {
try {
$value = $this -> createAutowiredDefinition ( $typeHint , $id );
} catch ( RuntimeException $e ) {
2015-10-13 17:59:07 +01:00
if ( $parameter -> allowsNull ()) {
$value = null ;
} elseif ( $parameter -> isDefaultValueAvailable ()) {
$value = $parameter -> getDefaultValue ();
} else {
2015-08-24 02:36:41 +01:00
throw $e ;
}
}
}
} catch ( \ReflectionException $reflectionException ) {
// Typehint against a non-existing class
if ( ! $parameter -> isDefaultValueAvailable ()) {
2015-11-04 18:04:11 +00:00
throw new RuntimeException ( sprintf ( 'Cannot autowire argument %s for %s because the type-hinted class does not exist (%s).' , $index + 1 , $definition -> getClass (), $reflectionException -> getMessage ()), 0 , $reflectionException );
2015-08-24 02:36:41 +01:00
}
$value = $parameter -> getDefaultValue ();
}
if ( $argumentExists ) {
$definition -> replaceArgument ( $index , $value );
} else {
$definition -> addArgument ( $value );
}
}
}
/**
* Populates the list of available types .
*/
private function populateAvailableTypes ()
{
$this -> types = array ();
foreach ( $this -> container -> getDefinitions () as $id => $definition ) {
$this -> populateAvailableType ( $id , $definition );
}
}
/**
* Populates the list of available types for a given definition .
*
* @ param string $id
* @ param Definition $definition
*/
private function populateAvailableType ( $id , Definition $definition )
{
2015-11-04 00:40:26 +00:00
// Never use abstract services
if ( $definition -> isAbstract ()) {
return ;
}
2015-08-24 02:36:41 +01:00
foreach ( $definition -> getAutowiringTypes () as $type ) {
$this -> definedTypes [ $type ] = true ;
$this -> types [ $type ] = $id ;
}
2015-11-04 00:40:26 +00:00
// Cannot use reflection if the class isn't set
2015-11-03 19:42:49 +00:00
if ( ! $definition -> getClass ()) {
return ;
}
2015-08-24 02:36:41 +01:00
if ( $reflectionClass = $this -> getReflectionClass ( $id , $definition )) {
$this -> extractInterfaces ( $id , $reflectionClass );
$this -> extractAncestors ( $id , $reflectionClass );
}
}
/**
* Extracts the list of all interfaces implemented by a class .
*
* @ param string $id
* @ param \ReflectionClass $reflectionClass
*/
private function extractInterfaces ( $id , \ReflectionClass $reflectionClass )
{
foreach ( $reflectionClass -> getInterfaces () as $interfaceName => $reflectionInterface ) {
$this -> set ( $interfaceName , $id );
$this -> extractInterfaces ( $id , $reflectionInterface );
}
}
/**
* Extracts all inherited types of a class .
*
* @ param string $id
* @ param \ReflectionClass $reflectionClass
*/
private function extractAncestors ( $id , \ReflectionClass $reflectionClass )
{
$this -> set ( $reflectionClass -> name , $id );
if ( $reflectionParentClass = $reflectionClass -> getParentClass ()) {
$this -> extractAncestors ( $id , $reflectionParentClass );
}
}
/**
* Associates a type and a service id if applicable .
*
* @ param string $type
* @ param string $id
*/
private function set ( $type , $id )
{
if ( isset ( $this -> definedTypes [ $type ]) || isset ( $this -> notGuessableTypes [ $type ])) {
return ;
}
if ( isset ( $this -> types [ $type ])) {
if ( $this -> types [ $type ] === $id ) {
return ;
}
unset ( $this -> types [ $type ]);
$this -> notGuessableTypes [ $type ] = true ;
return ;
}
$this -> types [ $type ] = $id ;
}
/**
* Registers a definition for the type if possible or throws an exception .
*
* @ param \ReflectionClass $typeHint
* @ param string $id
*
* @ return Reference A reference to the registered definition
*
* @ throws RuntimeException
*/
private function createAutowiredDefinition ( \ReflectionClass $typeHint , $id )
{
if ( ! $typeHint -> isInstantiable ()) {
throw new RuntimeException ( sprintf ( 'Unable to autowire argument of type "%s" for the service "%s".' , $typeHint -> name , $id ));
}
$argumentId = sprintf ( 'autowired.%s' , $typeHint -> name );
$argumentDefinition = $this -> container -> register ( $argumentId , $typeHint -> name );
$argumentDefinition -> setPublic ( false );
$this -> populateAvailableType ( $argumentId , $argumentDefinition );
$this -> completeDefinition ( $argumentId , $argumentDefinition );
return new Reference ( $argumentId );
}
/**
* Retrieves the reflection class associated with the given service .
*
* @ param string $id
* @ param Definition $definition
*
* @ return \ReflectionClass | null
*/
private function getReflectionClass ( $id , Definition $definition )
{
if ( isset ( $this -> reflectionClasses [ $id ])) {
return $this -> reflectionClasses [ $id ];
}
if ( ! $class = $definition -> getClass ()) {
return ;
}
$class = $this -> container -> getParameterBag () -> resolveValue ( $class );
try {
return $this -> reflectionClasses [ $id ] = new \ReflectionClass ( $class );
} catch ( \ReflectionException $reflectionException ) {
// return null
}
}
}