AutowirePass.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\DependencyInjection\Compiler;
  11. use Symfony\Component\DependencyInjection\ContainerBuilder;
  12. use Symfony\Component\DependencyInjection\Definition;
  13. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  14. use Symfony\Component\DependencyInjection\Reference;
  15. /**
  16. * Guesses constructor arguments of services definitions and try to instantiate services if necessary.
  17. *
  18. * @author Kévin Dunglas <dunglas@gmail.com>
  19. */
  20. class AutowirePass implements CompilerPassInterface
  21. {
  22. private $container;
  23. private $reflectionClasses = array();
  24. private $definedTypes = array();
  25. private $types;
  26. private $notGuessableTypes = array();
  27. /**
  28. * {@inheritdoc}
  29. */
  30. public function process(ContainerBuilder $container)
  31. {
  32. $this->container = $container;
  33. foreach ($container->getDefinitions() as $id => $definition) {
  34. if ($definition->isAutowired()) {
  35. $this->completeDefinition($id, $definition);
  36. }
  37. }
  38. // Free memory and remove circular reference to container
  39. $this->container = null;
  40. $this->reflectionClasses = array();
  41. $this->definedTypes = array();
  42. $this->types = null;
  43. $this->notGuessableTypes = array();
  44. }
  45. /**
  46. * Wires the given definition.
  47. *
  48. * @param string $id
  49. * @param Definition $definition
  50. *
  51. * @throws RuntimeException
  52. */
  53. private function completeDefinition($id, Definition $definition)
  54. {
  55. if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
  56. return;
  57. }
  58. $this->container->addClassResource($reflectionClass);
  59. if (!$constructor = $reflectionClass->getConstructor()) {
  60. return;
  61. }
  62. $arguments = $definition->getArguments();
  63. foreach ($constructor->getParameters() as $index => $parameter) {
  64. if (array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
  65. continue;
  66. }
  67. try {
  68. if (!$typeHint = $parameter->getClass()) {
  69. // no default value? Then fail
  70. if (!$parameter->isOptional()) {
  71. 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));
  72. }
  73. // specifically pass the default value
  74. $arguments[$index] = $parameter->getDefaultValue();
  75. continue;
  76. }
  77. if (null === $this->types) {
  78. $this->populateAvailableTypes();
  79. }
  80. if (isset($this->types[$typeHint->name])) {
  81. $value = new Reference($this->types[$typeHint->name]);
  82. } else {
  83. try {
  84. $value = $this->createAutowiredDefinition($typeHint, $id);
  85. } catch (RuntimeException $e) {
  86. if ($parameter->allowsNull()) {
  87. $value = null;
  88. } elseif ($parameter->isDefaultValueAvailable()) {
  89. $value = $parameter->getDefaultValue();
  90. } else {
  91. throw $e;
  92. }
  93. }
  94. }
  95. } catch (\ReflectionException $reflectionException) {
  96. // Typehint against a non-existing class
  97. if (!$parameter->isDefaultValueAvailable()) {
  98. throw new RuntimeException(sprintf('Cannot autowire argument %s for %s because the type-hinted class does not exist (%s).', $index + 1, $definition->getClass(), $reflectionException->getMessage()), 0, $reflectionException);
  99. }
  100. $value = $parameter->getDefaultValue();
  101. }
  102. $arguments[$index] = $value;
  103. }
  104. // it's possible index 1 was set, then index 0, then 2, etc
  105. // make sure that we re-order so they're injected as expected
  106. ksort($arguments);
  107. $definition->setArguments($arguments);
  108. }
  109. /**
  110. * Populates the list of available types.
  111. */
  112. private function populateAvailableTypes()
  113. {
  114. $this->types = array();
  115. foreach ($this->container->getDefinitions() as $id => $definition) {
  116. $this->populateAvailableType($id, $definition);
  117. }
  118. }
  119. /**
  120. * Populates the list of available types for a given definition.
  121. *
  122. * @param string $id
  123. * @param Definition $definition
  124. */
  125. private function populateAvailableType($id, Definition $definition)
  126. {
  127. // Never use abstract services
  128. if ($definition->isAbstract()) {
  129. return;
  130. }
  131. foreach ($definition->getAutowiringTypes() as $type) {
  132. $this->definedTypes[$type] = true;
  133. $this->types[$type] = $id;
  134. }
  135. if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
  136. return;
  137. }
  138. foreach ($reflectionClass->getInterfaces() as $reflectionInterface) {
  139. $this->set($reflectionInterface->name, $id);
  140. }
  141. do {
  142. $this->set($reflectionClass->name, $id);
  143. } while ($reflectionClass = $reflectionClass->getParentClass());
  144. }
  145. /**
  146. * Associates a type and a service id if applicable.
  147. *
  148. * @param string $type
  149. * @param string $id
  150. */
  151. private function set($type, $id)
  152. {
  153. if (isset($this->definedTypes[$type]) || isset($this->notGuessableTypes[$type])) {
  154. return;
  155. }
  156. if (isset($this->types[$type])) {
  157. if ($this->types[$type] === $id) {
  158. return;
  159. }
  160. unset($this->types[$type]);
  161. $this->notGuessableTypes[$type] = true;
  162. return;
  163. }
  164. $this->types[$type] = $id;
  165. }
  166. /**
  167. * Registers a definition for the type if possible or throws an exception.
  168. *
  169. * @param \ReflectionClass $typeHint
  170. * @param string $id
  171. *
  172. * @return Reference A reference to the registered definition
  173. *
  174. * @throws RuntimeException
  175. */
  176. private function createAutowiredDefinition(\ReflectionClass $typeHint, $id)
  177. {
  178. if (isset($this->notGuessableTypes[$typeHint->name]) || !$typeHint->isInstantiable()) {
  179. throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s".', $typeHint->name, $id));
  180. }
  181. $argumentId = sprintf('autowired.%s', $typeHint->name);
  182. $argumentDefinition = $this->container->register($argumentId, $typeHint->name);
  183. $argumentDefinition->setPublic(false);
  184. $this->populateAvailableType($argumentId, $argumentDefinition);
  185. $this->completeDefinition($argumentId, $argumentDefinition);
  186. return new Reference($argumentId);
  187. }
  188. /**
  189. * Retrieves the reflection class associated with the given service.
  190. *
  191. * @param string $id
  192. * @param Definition $definition
  193. *
  194. * @return \ReflectionClass|null
  195. */
  196. private function getReflectionClass($id, Definition $definition)
  197. {
  198. if (isset($this->reflectionClasses[$id])) {
  199. return $this->reflectionClasses[$id];
  200. }
  201. // Cannot use reflection if the class isn't set
  202. if (!$class = $definition->getClass()) {
  203. return;
  204. }
  205. $class = $this->container->getParameterBag()->resolveValue($class);
  206. try {
  207. return $this->reflectionClasses[$id] = new \ReflectionClass($class);
  208. } catch (\ReflectionException $reflectionException) {
  209. // return null
  210. }
  211. }
  212. }