X-Git-Url: https://yaffs.net/gitweb/?a=blobdiff_plain;f=vendor%2Fsymfony%2Fdependency-injection%2FCompiler%2FAutowirePass.php;h=803490cecd295d5333a3ee44f1209db7f31ebb0e;hb=af6d1fb995500ae68849458ee10d66abbdcfb252;hp=b2795485531615ff281e0fa7479a4bb5a86e7360;hpb=eba34333e3c89f208d2f72fa91351ad019a71583;p=yaffs-website diff --git a/vendor/symfony/dependency-injection/Compiler/AutowirePass.php b/vendor/symfony/dependency-injection/Compiler/AutowirePass.php index b27954855..803490cec 100644 --- a/vendor/symfony/dependency-injection/Compiler/AutowirePass.php +++ b/vendor/symfony/dependency-injection/Compiler/AutowirePass.php @@ -11,143 +11,249 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Symfony\Component\Config\Resource\ClassExistenceResource; +use Symfony\Component\DependencyInjection\Config\AutowireServiceResource; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; -use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; +use Symfony\Component\DependencyInjection\TypedReference; /** - * Guesses constructor arguments of services definitions and try to instantiate services if necessary. + * Inspects existing service definitions and wires the autowired ones using the type hints of their classes. * * @author Kévin Dunglas + * @author Nicolas Grekas */ -class AutowirePass implements CompilerPassInterface +class AutowirePass extends AbstractRecursivePass { - private $container; - private $reflectionClasses = array(); private $definedTypes = array(); private $types; - private $notGuessableTypes = array(); + private $ambiguousServiceTypes; private $autowired = array(); + private $lastFailure; + private $throwOnAutowiringException; + private $autowiringExceptions = array(); + private $strictMode; + + /** + * @param bool $throwOnAutowireException Errors can be retrieved via Definition::getErrors() + */ + public function __construct($throwOnAutowireException = true) + { + $this->throwOnAutowiringException = $throwOnAutowireException; + } + + /** + * @deprecated since version 3.4, to be removed in 4.0. + * + * @return AutowiringFailedException[] + */ + public function getAutowiringExceptions() + { + @trigger_error('Calling AutowirePass::getAutowiringExceptions() is deprecated since Symfony 3.4 and will be removed in 4.0. Use Definition::getErrors() instead.', E_USER_DEPRECATED); + + return $this->autowiringExceptions; + } /** * {@inheritdoc} */ public function process(ContainerBuilder $container) { - $throwingAutoloader = function ($class) { throw new \ReflectionException(sprintf('Class %s does not exist', $class)); }; - spl_autoload_register($throwingAutoloader); + // clear out any possibly stored exceptions from before + $this->autowiringExceptions = array(); + $this->strictMode = $container->hasParameter('container.autowiring.strict_mode') && $container->getParameter('container.autowiring.strict_mode'); try { - $this->container = $container; - foreach ($container->getDefinitions() as $id => $definition) { - if ($definition->isAutowired()) { - $this->completeDefinition($id, $definition); - } - } - } catch (\Exception $e) { - } catch (\Throwable $e) { + parent::process($container); + } finally { + $this->definedTypes = array(); + $this->types = null; + $this->ambiguousServiceTypes = null; + $this->autowired = array(); } + } - spl_autoload_unregister($throwingAutoloader); + /** + * Creates a resource to help know if this service has changed. + * + * @param \ReflectionClass $reflectionClass + * + * @return AutowireServiceResource + * + * @deprecated since version 3.3, to be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead. + */ + public static function createResourceForClass(\ReflectionClass $reflectionClass) + { + @trigger_error('The '.__METHOD__.'() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead.', E_USER_DEPRECATED); - // Free memory and remove circular reference to container - $this->container = null; - $this->reflectionClasses = array(); - $this->definedTypes = array(); - $this->types = null; - $this->notGuessableTypes = array(); - $this->autowired = array(); + $metadata = array(); - if (isset($e)) { - throw $e; + foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) { + if (!$reflectionMethod->isStatic()) { + $metadata[$reflectionMethod->name] = self::getResourceMetadataForMethod($reflectionMethod); + } } + + return new AutowireServiceResource($reflectionClass->name, $reflectionClass->getFileName(), $metadata); } /** - * Wires the given definition. - * - * @param string $id - * @param Definition $definition - * - * @throws RuntimeException + * {@inheritdoc} */ - private function completeDefinition($id, Definition $definition) + protected function processValue($value, $isRoot = false) + { + try { + return $this->doProcessValue($value, $isRoot); + } catch (AutowiringFailedException $e) { + if ($this->throwOnAutowiringException) { + throw $e; + } + + $this->autowiringExceptions[] = $e; + $this->container->getDefinition($this->currentId)->addError($e->getMessage()); + + return parent::processValue($value, $isRoot); + } + } + + private function doProcessValue($value, $isRoot = false) { - if ($definition->getFactory() || $definition->getFactoryClass(false) || $definition->getFactoryService(false)) { - throw new RuntimeException(sprintf('Service "%s" can use either autowiring or a factory, not both.', $id)); + if ($value instanceof TypedReference) { + if ($ref = $this->getAutowiredReference($value, $value->getRequiringClass() ? sprintf('for "%s" in "%s"', $value->getType(), $value->getRequiringClass()) : '')) { + return $ref; + } + $this->container->log($this, $this->createTypeNotFoundMessage($value, 'it')); } + $value = parent::processValue($value, $isRoot); - if (!$reflectionClass = $this->getReflectionClass($id, $definition)) { - return; + if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) { + return $value; } + if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) { + $this->container->log($this, sprintf('Skipping service "%s": Class or interface "%s" cannot be loaded.', $this->currentId, $value->getClass())); - $this->container->addClassResource($reflectionClass); + return $value; + } - if (!$constructor = $reflectionClass->getConstructor()) { - return; + $methodCalls = $value->getMethodCalls(); + + try { + $constructor = $this->getConstructor($value, false); + } catch (RuntimeException $e) { + throw new AutowiringFailedException($this->currentId, $e->getMessage(), 0, $e); } - $parameters = $constructor->getParameters(); - if (method_exists('ReflectionMethod', 'isVariadic') && $constructor->isVariadic()) { + + if ($constructor) { + array_unshift($methodCalls, array($constructor, $value->getArguments())); + } + + $methodCalls = $this->autowireCalls($reflectionClass, $methodCalls); + + if ($constructor) { + list(, $arguments) = array_shift($methodCalls); + + if ($arguments !== $value->getArguments()) { + $value->setArguments($arguments); + } + } + + if ($methodCalls !== $value->getMethodCalls()) { + $value->setMethodCalls($methodCalls); + } + + return $value; + } + + /** + * @param \ReflectionClass $reflectionClass + * @param array $methodCalls + * + * @return array + */ + private function autowireCalls(\ReflectionClass $reflectionClass, array $methodCalls) + { + foreach ($methodCalls as $i => $call) { + list($method, $arguments) = $call; + + if ($method instanceof \ReflectionFunctionAbstract) { + $reflectionMethod = $method; + } else { + $reflectionMethod = $this->getReflectionMethod(new Definition($reflectionClass->name), $method); + } + + $arguments = $this->autowireMethod($reflectionMethod, $arguments); + + if ($arguments !== $call[1]) { + $methodCalls[$i][1] = $arguments; + } + } + + return $methodCalls; + } + + /** + * Autowires the constructor or a method. + * + * @param \ReflectionFunctionAbstract $reflectionMethod + * @param array $arguments + * + * @return array The autowired arguments + * + * @throws AutowiringFailedException + */ + private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments) + { + $class = $reflectionMethod instanceof \ReflectionMethod ? $reflectionMethod->class : $this->currentId; + $method = $reflectionMethod->name; + $parameters = $reflectionMethod->getParameters(); + if (method_exists('ReflectionMethod', 'isVariadic') && $reflectionMethod->isVariadic()) { array_pop($parameters); } - $arguments = $definition->getArguments(); foreach ($parameters as $index => $parameter) { if (array_key_exists($index, $arguments) && '' !== $arguments[$index]) { continue; } - try { - if (!$typeHint = $parameter->getClass()) { - if (isset($arguments[$index])) { - continue; - } - - // no default value? Then fail - if (!$parameter->isOptional()) { - 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)); - } - - // specifically pass the default value - $arguments[$index] = $parameter->getDefaultValue(); + $type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, true); + if (!$type) { + if (isset($arguments[$index])) { continue; } - if (isset($this->autowired[$typeHint->name])) { - $arguments[$index] = $this->autowired[$typeHint->name] ? new Reference($this->autowired[$typeHint->name]) : null; - continue; - } + // no default value? Then fail + if (!$parameter->isDefaultValueAvailable()) { + // For core classes, isDefaultValueAvailable() can + // be false when isOptional() returns true. If the + // argument *is* optional, allow it to be missing + if ($parameter->isOptional()) { + continue; + } + $type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, false); + $type = $type ? sprintf('is type-hinted "%s"', $type) : 'has no type-hint'; - if (null === $this->types) { - $this->populateAvailableTypes(); + throw new AutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s()" %s, you should configure its value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method, $type)); } - if (isset($this->types[$typeHint->name]) && !isset($this->notGuessableTypes[$typeHint->name])) { - $value = new Reference($this->types[$typeHint->name]); - } else { - try { - $value = $this->createAutowiredDefinition($typeHint, $id); - } catch (RuntimeException $e) { - if ($parameter->isDefaultValueAvailable()) { - $value = $parameter->getDefaultValue(); - } elseif ($parameter->allowsNull()) { - $value = null; - } else { - throw $e; - } - $this->autowired[$typeHint->name] = false; - } - } - } catch (\ReflectionException $e) { - // Typehint against a non-existing class + // specifically pass the default value + $arguments[$index] = $parameter->getDefaultValue(); - if (!$parameter->isDefaultValueAvailable()) { - 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); - } + continue; + } + + if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, !$parameter->isOptional() ? $class : ''), 'for '.sprintf('argument "$%s" of method "%s()"', $parameter->name, $class.'::'.$method))) { + $failureMessage = $this->createTypeNotFoundMessage($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method)); - $value = $parameter->getDefaultValue(); + if ($parameter->isDefaultValueAvailable()) { + $value = $parameter->getDefaultValue(); + } elseif (!$parameter->allowsNull()) { + throw new AutowiringFailedException($this->currentId, $failureMessage); + } + $this->container->log($this, $failureMessage); } $arguments[$index] = $value; @@ -166,18 +272,68 @@ class AutowirePass implements CompilerPassInterface // 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); - $definition->setArguments($arguments); + + return $arguments; + } + + /** + * @return TypedReference|null A reference to the service matching the given type, if any + */ + private function getAutowiredReference(TypedReference $reference, $deprecationMessage) + { + $this->lastFailure = null; + $type = $reference->getType(); + + if ($type !== $this->container->normalizeId($reference) || ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract())) { + return $reference; + } + + if (null === $this->types) { + $this->populateAvailableTypes($this->strictMode); + } + + if (isset($this->definedTypes[$type])) { + return new TypedReference($this->types[$type], $type); + } + + if (!$this->strictMode && isset($this->types[$type])) { + $message = 'Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won\'t be supported in version 4.0.'; + if ($aliasSuggestion = $this->getAliasesSuggestionForType($type = $reference->getType(), $deprecationMessage)) { + $message .= ' '.$aliasSuggestion; + } else { + $message .= sprintf(' You should %s the "%s" service to "%s" instead.', isset($this->types[$this->types[$type]]) ? 'alias' : 'rename (or alias)', $this->types[$type], $type); + } + + @trigger_error($message, E_USER_DEPRECATED); + + return new TypedReference($this->types[$type], $type); + } + + if (!$reference->canBeAutoregistered() || isset($this->types[$type]) || isset($this->ambiguousServiceTypes[$type])) { + return; + } + + if (isset($this->autowired[$type])) { + return $this->autowired[$type] ? new TypedReference($this->autowired[$type], $type) : null; + } + + if (!$this->strictMode) { + return $this->createAutowiredDefinition($type); + } } /** * Populates the list of available types. */ - private function populateAvailableTypes() + private function populateAvailableTypes($onlyAutowiringTypes = false) { $this->types = array(); + if (!$onlyAutowiringTypes) { + $this->ambiguousServiceTypes = array(); + } foreach ($this->container->getDefinitions() as $id => $definition) { - $this->populateAvailableType($id, $definition); + $this->populateAvailableType($id, $definition, $onlyAutowiringTypes); } } @@ -187,20 +343,24 @@ class AutowirePass implements CompilerPassInterface * @param string $id * @param Definition $definition */ - private function populateAvailableType($id, Definition $definition) + private function populateAvailableType($id, Definition $definition, $onlyAutowiringTypes) { // Never use abstract services if ($definition->isAbstract()) { return; } - foreach ($definition->getAutowiringTypes() as $type) { + foreach ($definition->getAutowiringTypes(false) as $type) { $this->definedTypes[$type] = true; $this->types[$type] = $id; - unset($this->notGuessableTypes[$type]); + unset($this->ambiguousServiceTypes[$type]); } - if (!$reflectionClass = $this->getReflectionClass($id, $definition)) { + if ($onlyAutowiringTypes) { + return; + } + + if (preg_match('/^\d+_[^~]++~[._a-zA-Z\d]{7}$/', $id) || $definition->isDeprecated() || !$reflectionClass = $this->container->getReflectionClass($definition->getClass(), false)) { return; } @@ -225,110 +385,175 @@ class AutowirePass implements CompilerPassInterface return; } - if (!isset($this->types[$type])) { - $this->types[$type] = $id; + // is this already a type/class that is known to match multiple services? + if (isset($this->ambiguousServiceTypes[$type])) { + $this->ambiguousServiceTypes[$type][] = $id; return; } - if ($this->types[$type] === $id) { + // check to make sure the type doesn't match multiple services + if (!isset($this->types[$type]) || $this->types[$type] === $id) { + $this->types[$type] = $id; + return; } - if (!isset($this->notGuessableTypes[$type])) { - $this->notGuessableTypes[$type] = true; - $this->types[$type] = (array) $this->types[$type]; + // keep an array of all services matching this type + if (!isset($this->ambiguousServiceTypes[$type])) { + $this->ambiguousServiceTypes[$type] = array($this->types[$type]); + unset($this->types[$type]); } - - $this->types[$type][] = $id; + $this->ambiguousServiceTypes[$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 + * @param string $type * - * @throws RuntimeException + * @return TypedReference|null A reference to the registered definition */ - private function createAutowiredDefinition(\ReflectionClass $typeHint, $id) + private function createAutowiredDefinition($type) { - if (isset($this->notGuessableTypes[$typeHint->name])) { - $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class'; - $matchingServices = implode(', ', $this->types[$typeHint->name]); - - 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)); - } - - if (!$typeHint->isInstantiable()) { - $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class'; - 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)); + if (!($typeHint = $this->container->getReflectionClass($type, false)) || !$typeHint->isInstantiable()) { + return; } - $this->autowired[$typeHint->name] = $argumentId = sprintf('autowired.%s', $typeHint->name); - - $argumentDefinition = $this->container->register($argumentId, $typeHint->name); + $currentId = $this->currentId; + $this->currentId = $type; + $this->autowired[$type] = $argumentId = sprintf('autowired.%s', $type); + $argumentDefinition = new Definition($type); $argumentDefinition->setPublic(false); + $argumentDefinition->setAutowired(true); try { - $this->completeDefinition($argumentId, $argumentDefinition); - } catch (RuntimeException $e) { - $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); + $originalThrowSetting = $this->throwOnAutowiringException; + $this->throwOnAutowiringException = true; + $this->processValue($argumentDefinition, true); + $this->container->setDefinition($argumentId, $argumentDefinition); + } catch (AutowiringFailedException $e) { + $this->autowired[$type] = false; + $this->lastFailure = $e->getMessage(); + $this->container->log($this, $this->lastFailure); + + return; + } finally { + $this->throwOnAutowiringException = $originalThrowSetting; + $this->currentId = $currentId; } - return new Reference($argumentId); + @trigger_error(sprintf('Relying on service auto-registration for type "%s" is deprecated since Symfony 3.4 and won\'t be supported in 4.0. Create a service named "%s" instead.', $type, $type), E_USER_DEPRECATED); + + $this->container->log($this, sprintf('Type "%s" has been auto-registered for service "%s".', $type, $this->currentId)); + + return new TypedReference($argumentId, $type); } - /** - * Retrieves the reflection class associated with the given service. - * - * @param string $id - * @param Definition $definition - * - * @return \ReflectionClass|false - */ - private function getReflectionClass($id, Definition $definition) + private function createTypeNotFoundMessage(TypedReference $reference, $label) { - if (isset($this->reflectionClasses[$id])) { - return $this->reflectionClasses[$id]; + if (!$r = $this->container->getReflectionClass($type = $reference->getType(), false)) { + // either $type does not exist or a parent class does not exist + try { + $resource = new ClassExistenceResource($type, false); + // isFresh() will explode ONLY if a parent class/trait does not exist + $resource->isFresh(0); + $parentMsg = false; + } catch (\ReflectionException $e) { + $parentMsg = $e->getMessage(); + } + + $message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ? sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found'); + } else { + $alternatives = $this->createTypeAlternatives($reference); + $message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists'; + $message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $alternatives); + + if ($r->isInterface() && !$alternatives) { + $message .= ' Did you create a class that implements this interface?'; + } } - // Cannot use reflection if the class isn't set - if (!$class = $definition->getClass()) { - return false; + $message = sprintf('Cannot autowire service "%s": %s %s', $this->currentId, $label, $message); + + if (null !== $this->lastFailure) { + $message = $this->lastFailure."\n".$message; + $this->lastFailure = null; } - $class = $this->container->getParameterBag()->resolveValue($class); + return $message; + } - if ($deprecated = $definition->isDeprecated()) { - $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) { - return (E_USER_DEPRECATED === $level || !$prevErrorHandler) ? false : $prevErrorHandler($level, $message, $file, $line); - }); + private function createTypeAlternatives(TypedReference $reference) + { + // try suggesting available aliases first + if ($message = $this->getAliasesSuggestionForType($type = $reference->getType())) { + return ' '.$message; + } + if (null === $this->ambiguousServiceTypes) { + $this->populateAvailableTypes(); } - $e = null; + if (isset($this->ambiguousServiceTypes[$type])) { + $message = sprintf('one of these existing services: "%s"', implode('", "', $this->ambiguousServiceTypes[$type])); + } elseif (isset($this->types[$type])) { + $message = sprintf('the existing "%s" service', $this->types[$type]); + } elseif ($reference->getRequiringClass() && !$reference->canBeAutoregistered() && !$this->strictMode) { + return ' It cannot be auto-registered because it is from a different root namespace.'; + } else { + return; + } - try { - $reflector = new \ReflectionClass($class); - } catch (\Exception $e) { - } catch (\Throwable $e) { + return sprintf(' You should maybe alias this %s to %s.', class_exists($type, false) ? 'class' : 'interface', $message); + } + + /** + * @deprecated since version 3.3, to be removed in 4.0. + */ + private static function getResourceMetadataForMethod(\ReflectionMethod $method) + { + $methodArgumentsMetadata = array(); + foreach ($method->getParameters() as $parameter) { + try { + $class = $parameter->getClass(); + } catch (\ReflectionException $e) { + // type-hint is against a non-existent class + $class = false; + } + + $isVariadic = method_exists($parameter, 'isVariadic') && $parameter->isVariadic(); + $methodArgumentsMetadata[] = array( + 'class' => $class, + 'isOptional' => $parameter->isOptional(), + 'defaultValue' => ($parameter->isOptional() && !$isVariadic) ? $parameter->getDefaultValue() : null, + ); } - if ($deprecated) { - restore_error_handler(); + return $methodArgumentsMetadata; + } + + private function getAliasesSuggestionForType($type, $extraContext = null) + { + $aliases = array(); + foreach (class_parents($type) + class_implements($type) as $parent) { + if ($this->container->has($parent) && !$this->container->findDefinition($parent)->isAbstract()) { + $aliases[] = $parent; + } } - if (null !== $e) { - if (!$e instanceof \ReflectionException) { - throw $e; + $extraContext = $extraContext ? ' '.$extraContext : ''; + if (1 < $len = count($aliases)) { + $message = sprintf('Try changing the type-hint%s to one of its parents: ', $extraContext); + for ($i = 0, --$len; $i < $len; ++$i) { + $message .= sprintf('%s "%s", ', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]); } - $reflector = false; + $message .= sprintf('or %s "%s".', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]); + + return $message; } - return $this->reflectionClasses[$id] = $reflector; + if ($aliases) { + return sprintf('Try changing the type-hint%s to "%s" instead.', $extraContext, $aliases[0]); + } } }