Mercurial > hg > isophonics-drupal-site
diff vendor/symfony/dependency-injection/Compiler/AutowirePass.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 1fec387a4317 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vendor/symfony/dependency-injection/Compiler/AutowirePass.php Wed Nov 29 16:09:58 2017 +0000 @@ -0,0 +1,392 @@ +<?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\Config\AutowireServiceResource; +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 $ambiguousServiceTypes = array(); + private $autowired = array(); + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $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) { + if ($definition->isAutowired()) { + $this->completeDefinition($id, $definition); + } + } + } 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->autowired = array(); + } + } + + /** + * 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); + } + + foreach (self::getSetters($reflectionClass) as $reflectionMethod) { + $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 + */ + private function completeDefinition($id, Definition $definition) + { + if ($definition->getFactory()) { + throw new RuntimeException(sprintf('Service "%s" can use either autowiring or a factory, not both.', $id)); + } + + if (!$reflectionClass = $this->getReflectionClass($id, $definition)) { + return; + } + + if ($this->container->isTrackingResources()) { + $this->container->addResource(static::createResourceForClass($reflectionClass)); + } + + if (!$constructor = $reflectionClass->getConstructor()) { + return; + } + $parameters = $constructor->getParameters(); + if (method_exists('ReflectionMethod', 'isVariadic') && $constructor->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(); + + continue; + } + + if (isset($this->autowired[$typeHint->name])) { + $arguments[$index] = $this->autowired[$typeHint->name] ? new Reference($this->autowired[$typeHint->name]) : null; + 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) { + 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 + + 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); + } + + $value = $parameter->getDefaultValue(); + } + + $arguments[$index] = $value; + } + + if ($parameters && !isset($arguments[++$index])) { + while (0 <= --$index) { + $parameter = $parameters[$index]; + if (!$parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== $arguments[$index]) { + break; + } + unset($arguments[$index]); + } + } + + // 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); + } + + /** + * 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) + { + // Never use abstract services + if ($definition->isAbstract()) { + return; + } + + foreach ($definition->getAutowiringTypes() as $type) { + $this->definedTypes[$type] = true; + $this->types[$type] = $id; + unset($this->ambiguousServiceTypes[$type]); + } + + if (!$reflectionClass = $this->getReflectionClass($id, $definition)) { + return; + } + + foreach ($reflectionClass->getInterfaces() as $reflectionInterface) { + $this->set($reflectionInterface->name, $id); + } + + do { + $this->set($reflectionClass->name, $id); + } while ($reflectionClass = $reflectionClass->getParentClass()); + } + + /** + * 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])) { + return; + } + + // is this already a type/class that is known to match multiple services? + if (isset($this->ambiguousServiceTypes[$type])) { + $this->ambiguousServiceTypes[$type][] = $id; + + return; + } + + // 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; + } + + // 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->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 + * + * @throws RuntimeException + */ + private function createAutowiredDefinition(\ReflectionClass $typeHint, $id) + { + 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)); + } + + $this->autowired[$typeHint->name] = $argumentId = sprintf('autowired.%s', $typeHint->name); + + $argumentDefinition = $this->container->register($argumentId, $typeHint->name); + $argumentDefinition->setPublic(false); + + 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); + } + + return new Reference($argumentId); + } + + /** + * Retrieves the reflection class associated with the given service. + * + * @param string $id + * @param Definition $definition + * + * @return \ReflectionClass|false + */ + private function getReflectionClass($id, Definition $definition) + { + if (isset($this->reflectionClasses[$id])) { + return $this->reflectionClasses[$id]; + } + + // Cannot use reflection if the class isn't set + if (!$class = $definition->getClass()) { + return false; + } + + $class = $this->container->getParameterBag()->resolveValue($class); + + 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); + }); + } + + $e = null; + + try { + $reflector = new \ReflectionClass($class); + } catch (\Exception $e) { + } catch (\Throwable $e) { + } + + if ($deprecated) { + restore_error_handler(); + } + + if (null !== $e) { + if (!$e instanceof \ReflectionException) { + throw $e; + } + $reflector = false; + } + + return $this->reflectionClasses[$id] = $reflector; + } + + /** + * @param \ReflectionClass $reflectionClass + * + * @return \ReflectionMethod[] + */ + 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(); + 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, + ); + } + + return $methodArgumentsMetadata; + } +}