Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / vendor / symfony / dependency-injection / Compiler / AutowirePass.php
index ae1f24d31780559b59426bf029d7490411556b90..803490cecd295d5333a3ee44f1209db7f31ebb0e 100644 (file)
 
 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 <dunglas@gmail.com>
+ * @author Nicolas Grekas <p@tchwork.com>
  */
-class AutowirePass implements CompilerPassInterface
+class AutowirePass extends AbstractRecursivePass
 {
-    private $container;
-    private $reflectionClasses = array();
     private $definedTypes = array();
     private $types;
-    private $ambiguousServiceTypes = 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);
-                }
-            }
+            parent::process($container);
         } finally {
-            spl_autoload_unregister($throwingAutoloader);
-
-            // Free memory and remove circular reference to container
-            $this->reflectionClasses = array();
             $this->definedTypes = array();
             $this->types = null;
-            $this->ambiguousServiceTypes = array();
+            $this->ambiguousServiceTypes = null;
             $this->autowired = array();
         }
     }
@@ -64,108 +82,178 @@ class AutowirePass implements CompilerPassInterface
      * @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)
     {
-        $metadata = array();
+        @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);
 
-        if ($constructor = $reflectionClass->getConstructor()) {
-            $metadata['__construct'] = self::getResourceMetadataForMethod($constructor);
-        }
+        $metadata = array();
 
-        foreach (self::getSetters($reflectionClass) as $reflectionMethod) {
-            $metadata[$reflectionMethod->name] = self::getResourceMetadataForMethod($reflectionMethod);
+        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()) {
-            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()));
 
-        if ($this->container->isTrackingResources()) {
-            $this->container->addResource(static::createResourceForClass($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);
+        }
+
+        if ($constructor) {
+            array_unshift($methodCalls, array($constructor, $value->getArguments()));
         }
-        $parameters = $constructor->getParameters();
-        if (method_exists('ReflectionMethod', 'isVariadic') && $constructor->isVariadic()) {
+
+        $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])) {
-                    $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;
@@ -184,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);
         }
     }
 
@@ -205,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->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;
         }
 
@@ -268,106 +410,106 @@ class AutowirePass implements CompilerPassInterface
     /**
      * 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->ambiguousServiceTypes[$typeHint->name])) {
-            $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
-            $matchingServices = implode(', ', $this->ambiguousServiceTypes[$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);
 
-        // Cannot use reflection if the class isn't set
-        if (!$class = $definition->getClass()) {
-            return false;
+            if ($r->isInterface() && !$alternatives) {
+                $message .= ' Did you create a class that implements this interface?';
+            }
         }
 
-        $class = $this->container->getParameterBag()->resolveValue($class);
+        $message = sprintf('Cannot autowire service "%s": %s %s', $this->currentId, $label, $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);
-            });
+        if (null !== $this->lastFailure) {
+            $message = $this->lastFailure."\n".$message;
+            $this->lastFailure = null;
         }
 
-        $e = null;
+        return $message;
+    }
 
-        try {
-            $reflector = new \ReflectionClass($class);
-        } catch (\Exception $e) {
-        } catch (\Throwable $e) {
+    private function createTypeAlternatives(TypedReference $reference)
+    {
+        // try suggesting available aliases first
+        if ($message = $this->getAliasesSuggestionForType($type = $reference->getType())) {
+            return ' '.$message;
         }
-
-        if ($deprecated) {
-            restore_error_handler();
+        if (null === $this->ambiguousServiceTypes) {
+            $this->populateAvailableTypes();
         }
 
-        if (null !== $e) {
-            if (!$e instanceof \ReflectionException) {
-                throw $e;
-            }
-            $reflector = false;
+        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;
         }
 
-        return $this->reflectionClasses[$id] = $reflector;
+        return sprintf(' You should maybe alias this %s to %s.', class_exists($type, false) ? 'class' : 'interface', $message);
     }
 
     /**
-     * @param \ReflectionClass $reflectionClass
-     *
-     * @return \ReflectionMethod[]
+     * @deprecated since version 3.3, to be removed in 4.0.
      */
-    private static function getSetters(\ReflectionClass $reflectionClass)
-    {
-        foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
-            if (!$reflectionMethod->isStatic() && 1 === $reflectionMethod->getNumberOfParameters() && 0 === strpos($reflectionMethod->name, 'set')) {
-                yield $reflectionMethod;
-            }
-        }
-    }
-
     private static function getResourceMetadataForMethod(\ReflectionMethod $method)
     {
         $methodArgumentsMetadata = array();
@@ -389,4 +531,29 @@ class AutowirePass implements CompilerPassInterface
 
         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;
+            }
+        }
+
+        $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]);
+            }
+            $message .= sprintf('or %s "%s".', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]);
+
+            return $message;
+        }
+
+        if ($aliases) {
+            return sprintf('Try changing the type-hint%s to "%s" instead.', $extraContext, $aliases[0]);
+        }
+    }
 }