123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- <?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();
- private $usedTypes = 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);
- }
- }
- foreach ($this->usedTypes as $type => $id) {
- if (isset($this->usedTypes[$type]) && isset($this->notGuessableTypes[$type])) {
- $classOrInterface = class_exists($type) ? 'class' : 'interface';
- $matchingServices = implode(', ', $this->types[$type]);
- throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Multiple services exist for this %s (%s).', $type, $id, $classOrInterface, $matchingServices));
- }
- }
- } catch (\Exception $e) {
- } catch (\Throwable $e) {
- }
- spl_autoload_unregister($throwingAutoloader);
- // Free memory and remove circular reference to container
- $this->container = null;
- $this->reflectionClasses = array();
- $this->definedTypes = array();
- $this->types = null;
- $this->notGuessableTypes = array();
- $this->usedTypes = array();
- if (isset($e)) {
- throw $e;
- }
- }
- /**
- * 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) {
- if (array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
- continue;
- }
- try {
- if (!$typeHint = $parameter->getClass()) {
- // 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));
- }
- if (!array_key_exists($index, $arguments)) {
- // specifically pass the default value
- $arguments[$index] = $parameter->getDefaultValue();
- }
- continue;
- }
- if (null === $this->types) {
- $this->populateAvailableTypes();
- }
- if (isset($this->types[$typeHint->name]) && !isset($this->notGuessableTypes[$typeHint->name])) {
- $value = new Reference($this->types[$typeHint->name]);
- $this->usedTypes[$typeHint->name] = $id;
- } else {
- try {
- $value = $this->createAutowiredDefinition($typeHint, $id);
- $this->usedTypes[$typeHint->name] = $id;
- } catch (RuntimeException $e) {
- if ($parameter->allowsNull()) {
- $value = null;
- } elseif ($parameter->isDefaultValueAvailable()) {
- $value = $parameter->getDefaultValue();
- } else {
- throw $e;
- }
- }
- }
- } 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;
- }
- // 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->notGuessableTypes[$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;
- }
- if (!isset($this->types[$type])) {
- $this->types[$type] = $id;
- return;
- }
- if ($this->types[$type] === $id) {
- return;
- }
- if (!isset($this->notGuessableTypes[$type])) {
- $this->notGuessableTypes[$type] = true;
- $this->types[$type] = (array) $this->types[$type];
- }
- $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 (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));
- }
- $argumentId = sprintf('autowired.%s', $typeHint->name);
- $argumentDefinition = $this->container->register($argumentId, $typeHint->name);
- $argumentDefinition->setPublic(false);
- $this->populateAvailableType($argumentId, $argumentDefinition);
- 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);
- try {
- $reflector = new \ReflectionClass($class);
- } catch (\ReflectionException $e) {
- $reflector = false;
- }
- return $this->reflectionClasses[$id] = $reflector;
- }
- }
|