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 ;
2016-03-12 20:10:13 +00:00
use Symfony\Component\DependencyInjection\Config\AutowireServiceResource ;
2015-08-24 02:36:41 +01:00
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
{
2016-10-05 21:46:29 +01:00
/**
* @ var ContainerBuilder
*/
2015-08-24 02:36:41 +01:00
private $container ;
private $reflectionClasses = array ();
private $definedTypes = array ();
private $types ;
2016-02-22 00:27:26 +00:00
private $ambiguousServiceTypes = array ();
2015-08-24 02:36:41 +01:00
/**
* { @ inheritdoc }
*/
public function process ( ContainerBuilder $container )
{
2016-04-20 13:06:08 +01:00
$throwingAutoloader = function ( $class ) { throw new \ReflectionException ( sprintf ( 'Class %s does not exist' , $class )); };
spl_autoload_register ( $throwingAutoloader );
try {
$this -> container = $container ;
foreach ( $container -> getDefinitions () as $id => $definition ) {
2016-10-05 21:46:29 +01:00
if ( $autowiredMethods = $definition -> getAutowiredMethods ()) {
$this -> completeDefinition ( $id , $definition , $autowiredMethods );
2016-04-20 13:06:08 +01:00
}
2015-08-24 02:36:41 +01:00
}
2016-08-15 10:28:45 +01:00
} finally {
spl_autoload_unregister ( $throwingAutoloader );
2016-04-20 13:06:08 +01:00
2016-08-15 10:28:45 +01:00
// Free memory and remove circular reference to container
$this -> container = null ;
$this -> reflectionClasses = array ();
$this -> definedTypes = array ();
$this -> types = null ;
$this -> ambiguousServiceTypes = array ();
2016-04-20 13:06:08 +01:00
}
2015-08-24 02:36:41 +01:00
}
2016-03-12 20:10:13 +00:00
/**
* Creates a resource to help know if this service has changed .
*
* @ param \ReflectionClass $reflectionClass
*
* @ return AutowireServiceResource
*/
public static function createResourceForClass ( \ReflectionClass $reflectionClass )
{
$metadata = array ();
if ( $constructor = $reflectionClass -> getConstructor ()) {
$metadata [ '__construct' ] = self :: getResourceMetadataForMethod ( $constructor );
}
2016-10-05 21:46:29 +01:00
foreach ( $reflectionClass -> getMethods ( \ReflectionMethod :: IS_PUBLIC ) as $reflectionMethod ) {
if ( ! $reflectionMethod -> isStatic ()) {
$metadata [ $reflectionMethod -> name ] = self :: getResourceMetadataForMethod ( $reflectionMethod );
}
2016-03-12 20:10:13 +00:00
}
return new AutowireServiceResource ( $reflectionClass -> name , $reflectionClass -> getFileName (), $metadata );
}
2015-08-24 02:36:41 +01:00
/**
* Wires the given definition .
*
* @ param string $id
* @ param Definition $definition
2016-10-05 21:46:29 +01:00
* @ param string [] $autowiredMethods
2015-08-24 02:36:41 +01:00
*
* @ throws RuntimeException
*/
2016-10-05 21:46:29 +01:00
private function completeDefinition ( $id , Definition $definition , array $autowiredMethods )
2015-08-24 02:36:41 +01:00
{
if ( ! $reflectionClass = $this -> getReflectionClass ( $id , $definition )) {
return ;
}
2016-03-12 20:10:13 +00:00
if ( $this -> container -> isTrackingResources ()) {
$this -> container -> addResource ( static :: createResourceForClass ( $reflectionClass ));
}
2015-08-24 02:36:41 +01:00
2016-10-05 21:46:29 +01:00
$methodsCalled = array ();
foreach ( $definition -> getMethodCalls () as $methodCall ) {
$methodsCalled [ $methodCall [ 0 ]] = true ;
2016-01-30 09:38:42 +00:00
}
2016-10-05 21:46:29 +01:00
foreach ( $this -> getMethodsToAutowire ( $id , $reflectionClass , $autowiredMethods ) as $reflectionMethod ) {
if ( ! isset ( $methodsCalled [ $reflectionMethod -> name ])) {
$this -> autowireMethod ( $id , $definition , $reflectionMethod );
}
}
}
/**
* Gets the list of methods to autowire .
*
* @ param string $id
* @ param \ReflectionClass $reflectionClass
2017-01-16 15:14:49 +00:00
* @ param string [] $configuredAutowiredMethods
2016-10-05 21:46:29 +01:00
*
* @ return \ReflectionMethod []
*/
2017-01-16 15:14:49 +00:00
private function getMethodsToAutowire ( $id , \ReflectionClass $reflectionClass , array $configuredAutowiredMethods )
2016-10-05 21:46:29 +01:00
{
$found = array ();
$regexList = array ();
2017-01-16 15:14:49 +00:00
// Always try to autowire the constructor
if ( in_array ( '__construct' , $configuredAutowiredMethods , true )) {
$autowiredMethods = $configuredAutowiredMethods ;
} else {
$autowiredMethods = array_merge ( array ( '__construct' ), $configuredAutowiredMethods );
}
2016-10-05 21:46:29 +01:00
foreach ( $autowiredMethods as $pattern ) {
$regexList [] = '/^' . str_replace ( '\*' , '.*' , preg_quote ( $pattern , '/' )) . '$/i' ;
}
foreach ( $reflectionClass -> getMethods ( \ReflectionMethod :: IS_PUBLIC ) as $reflectionMethod ) {
if ( $reflectionMethod -> isStatic ()) {
continue ;
}
foreach ( $regexList as $k => $regex ) {
if ( preg_match ( $regex , $reflectionMethod -> name )) {
$found [] = $autowiredMethods [ $k ];
yield $reflectionMethod ;
continue 2 ;
}
}
}
2017-01-16 15:14:49 +00:00
if ( $notFound = array_diff ( $configuredAutowiredMethods , $found )) {
2016-10-05 21:46:29 +01:00
$compiler = $this -> container -> getCompiler ();
$compiler -> addLogMessage ( $compiler -> getLoggingFormatter () -> formatUnusedAutowiringPatterns ( $this , $id , $notFound ));
}
}
/**
* Autowires the constructor or a setter .
*
* @ param string $id
* @ param Definition $definition
* @ param \ReflectionMethod $reflectionMethod
*
* @ throws RuntimeException
*/
private function autowireMethod ( $id , Definition $definition , \ReflectionMethod $reflectionMethod )
{
if ( $isConstructor = $reflectionMethod -> isConstructor ()) {
$arguments = $definition -> getArguments ();
} else {
$arguments = array ();
}
$addMethodCall = false ; // Whether the method should be added to the definition as a call or as arguments
foreach ( $reflectionMethod -> getParameters () as $index => $parameter ) {
2016-02-29 00:33:45 +00:00
if ( array_key_exists ( $index , $arguments ) && '' !== $arguments [ $index ]) {
2015-08-24 02:36:41 +01:00
continue ;
}
try {
if ( ! $typeHint = $parameter -> getClass ()) {
2016-02-22 01:17:09 +00:00
// no default value? Then fail
if ( ! $parameter -> isOptional ()) {
2016-10-05 21:46:29 +01:00
if ( $isConstructor ) {
throw new RuntimeException ( sprintf ( 'Unable to autowire argument index %d ($%s) for the service "%s". If this is an object, give it a type-hint. Otherwise, specify this argument\'s value explicitly.' , $index , $parameter -> name , $id ));
}
return ;
2016-02-22 01:17:09 +00:00
}
// specifically pass the default value
$arguments [ $index ] = $parameter -> getDefaultValue ();
2015-08-24 02:36:41 +01:00
continue ;
}
if ( null === $this -> types ) {
$this -> populateAvailableTypes ();
}
if ( isset ( $this -> types [ $typeHint -> name ])) {
$value = new Reference ( $this -> types [ $typeHint -> name ]);
2016-10-05 21:46:29 +01:00
$addMethodCall = true ;
2015-08-24 02:36:41 +01:00
} else {
try {
$value = $this -> createAutowiredDefinition ( $typeHint , $id );
2016-10-05 21:46:29 +01:00
$addMethodCall = true ;
2015-08-24 02:36:41 +01:00
} catch ( RuntimeException $e ) {
2015-10-13 17:59:07 +01:00
if ( $parameter -> allowsNull ()) {
$value = null ;
} elseif ( $parameter -> isDefaultValueAvailable ()) {
$value = $parameter -> getDefaultValue ();
} else {
2016-10-05 21:46:29 +01:00
// The exception code is set to 1 if the exception must be thrown even if it's a setter
if ( 1 === $e -> getCode () || $isConstructor ) {
throw $e ;
}
return ;
2015-08-24 02:36:41 +01:00
}
}
}
2016-04-20 13:06:08 +01:00
} catch ( \ReflectionException $e ) {
2015-08-24 02:36:41 +01:00
// Typehint against a non-existing class
if ( ! $parameter -> isDefaultValueAvailable ()) {
2016-10-05 21:46:29 +01:00
if ( $isConstructor ) {
throw new RuntimeException ( sprintf ( 'Cannot autowire argument %s for %s because the type-hinted class does not exist (%s).' , $index + 1 , $definition -> getClass (), $e -> getMessage ()), 0 , $e );
}
return ;
2015-08-24 02:36:41 +01:00
}
$value = $parameter -> getDefaultValue ();
}
2016-02-21 23:51:49 +00:00
$arguments [ $index ] = $value ;
2015-08-24 02:36:41 +01:00
}
2016-02-21 23:51:49 +00:00
// it's possible index 1 was set, then index 0, then 2, etc
// make sure that we re-order so they're injected as expected
ksort ( $arguments );
2016-10-05 21:46:29 +01:00
if ( $isConstructor ) {
$definition -> setArguments ( $arguments );
} elseif ( $addMethodCall ) {
$definition -> addMethodCall ( $reflectionMethod -> name , $arguments );
}
2015-08-24 02:36:41 +01:00
}
/**
* 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 ;
}
2016-02-23 09:13:51 +00:00
if ( ! $reflectionClass = $this -> getReflectionClass ( $id , $definition )) {
2015-11-03 19:42:49 +00:00
return ;
}
2016-02-23 09:13:51 +00:00
foreach ( $reflectionClass -> getInterfaces () as $reflectionInterface ) {
$this -> set ( $reflectionInterface -> name , $id );
2015-08-24 02:36:41 +01:00
}
2016-02-23 09:13:51 +00:00
do {
$this -> set ( $reflectionClass -> name , $id );
} while ( $reflectionClass = $reflectionClass -> getParentClass ());
2015-08-24 02:36:41 +01:00
}
/**
* Associates a type and a service id if applicable .
*
* @ param string $type
* @ param string $id
*/
private function set ( $type , $id )
{
2016-02-22 00:27:26 +00:00
if ( isset ( $this -> definedTypes [ $type ])) {
2015-08-24 02:36:41 +01:00
return ;
}
2016-03-06 22:03:14 +00:00
// is this already a type/class that is known to match multiple services?
if ( isset ( $this -> ambiguousServiceTypes [ $type ])) {
$this -> addServiceToAmbiguousType ( $id , $type );
return ;
}
2016-02-22 00:27:26 +00:00
// check to make sure the type doesn't match multiple services
2015-08-24 02:36:41 +01:00
if ( isset ( $this -> types [ $type ])) {
if ( $this -> types [ $type ] === $id ) {
return ;
}
2016-02-22 00:27:26 +00:00
// keep an array of all services matching this type
2016-03-06 22:03:14 +00:00
$this -> addServiceToAmbiguousType ( $id , $type );
2016-02-22 00:27:26 +00:00
2015-08-24 02:36:41 +01:00
unset ( $this -> types [ $type ]);
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 )
{
2016-02-22 00:27:26 +00:00
if ( isset ( $this -> ambiguousServiceTypes [ $typeHint -> name ])) {
$classOrInterface = $typeHint -> isInterface () ? 'interface' : 'class' ;
$matchingServices = implode ( ', ' , $this -> ambiguousServiceTypes [ $typeHint -> name ]);
2016-10-05 21:46:29 +01:00
throw new RuntimeException ( sprintf ( 'Unable to autowire argument of type "%s" for the service "%s". Multiple services exist for this %s (%s).' , $typeHint -> name , $id , $classOrInterface , $matchingServices ), 1 );
2016-02-22 00:27:26 +00:00
}
if ( ! $typeHint -> isInstantiable ()) {
$classOrInterface = $typeHint -> isInterface () ? 'interface' : 'class' ;
2016-05-02 16:12:26 +01:00
throw new RuntimeException ( sprintf ( 'Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.' , $typeHint -> name , $id , $classOrInterface ));
2015-08-24 02:36:41 +01:00
}
$argumentId = sprintf ( 'autowired.%s' , $typeHint -> name );
$argumentDefinition = $this -> container -> register ( $argumentId , $typeHint -> name );
$argumentDefinition -> setPublic ( false );
$this -> populateAvailableType ( $argumentId , $argumentDefinition );
2016-05-02 16:12:26 +01:00
try {
2016-10-05 21:46:29 +01:00
$this -> completeDefinition ( $argumentId , $argumentDefinition , array ( '__construct' ));
2016-05-02 16:12:26 +01:00
} catch ( RuntimeException $e ) {
2016-05-02 16:12:26 +01:00
$classOrInterface = $typeHint -> isInterface () ? 'interface' : 'class' ;
$message = sprintf ( 'Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.' , $typeHint -> name , $id , $classOrInterface );
throw new RuntimeException ( $message , 0 , $e );
}
2015-08-24 02:36:41 +01:00
return new Reference ( $argumentId );
}
/**
* Retrieves the reflection class associated with the given service .
*
* @ param string $id
* @ param Definition $definition
*
2016-04-20 07:44:03 +01:00
* @ return \ReflectionClass | false
2015-08-24 02:36:41 +01:00
*/
private function getReflectionClass ( $id , Definition $definition )
{
if ( isset ( $this -> reflectionClasses [ $id ])) {
return $this -> reflectionClasses [ $id ];
}
2016-02-23 09:13:51 +00:00
// Cannot use reflection if the class isn't set
2015-08-24 02:36:41 +01:00
if ( ! $class = $definition -> getClass ()) {
2016-04-20 07:44:03 +01:00
return false ;
2015-08-24 02:36:41 +01:00
}
$class = $this -> container -> getParameterBag () -> resolveValue ( $class );
try {
2016-04-20 07:44:03 +01:00
$reflector = new \ReflectionClass ( $class );
2016-04-20 13:06:08 +01:00
} catch ( \ReflectionException $e ) {
2016-04-20 07:44:03 +01:00
$reflector = false ;
2015-08-24 02:36:41 +01:00
}
2016-04-20 07:44:03 +01:00
return $this -> reflectionClasses [ $id ] = $reflector ;
2015-08-24 02:36:41 +01:00
}
2016-03-06 22:03:14 +00:00
private function addServiceToAmbiguousType ( $id , $type )
{
// keep an array of all services matching this type
if ( ! isset ( $this -> ambiguousServiceTypes [ $type ])) {
$this -> ambiguousServiceTypes [ $type ] = array (
$this -> types [ $type ],
);
}
$this -> ambiguousServiceTypes [ $type ][] = $id ;
}
2016-03-12 20:10:13 +00:00
2016-04-03 08:41:26 +01:00
private static function getResourceMetadataForMethod ( \ReflectionMethod $method )
2016-03-12 20:10:13 +00:00
{
$methodArgumentsMetadata = array ();
foreach ( $method -> getParameters () as $parameter ) {
try {
$class = $parameter -> getClass ();
} catch ( \ReflectionException $e ) {
// type-hint is against a non-existent class
$class = false ;
}
$methodArgumentsMetadata [] = array (
'class' => $class ,
'isOptional' => $parameter -> isOptional (),
'defaultValue' => $parameter -> isOptional () ? $parameter -> getDefaultValue () : null ,
);
}
return $methodArgumentsMetadata ;
}
2015-08-24 02:36:41 +01:00
}