PageRenderTime 26ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php

https://gitlab.com/pr0055/symfonypizza
PHP | 350 lines | 214 code | 57 blank | 79 comment | 30 complexity | 9c29143000e8a974190fa4d5a7543331 MD5 | raw file
  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\Config\AutowireServiceResource;
  12. use Symfony\Component\DependencyInjection\ContainerBuilder;
  13. use Symfony\Component\DependencyInjection\Definition;
  14. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  15. use Symfony\Component\DependencyInjection\Reference;
  16. /**
  17. * Guesses constructor arguments of services definitions and try to instantiate services if necessary.
  18. *
  19. * @author Kévin Dunglas <dunglas@gmail.com>
  20. */
  21. class AutowirePass implements CompilerPassInterface
  22. {
  23. private $container;
  24. private $reflectionClasses = array();
  25. private $definedTypes = array();
  26. private $types;
  27. private $ambiguousServiceTypes = array();
  28. /**
  29. * {@inheritdoc}
  30. */
  31. public function process(ContainerBuilder $container)
  32. {
  33. $throwingAutoloader = function ($class) { throw new \ReflectionException(sprintf('Class %s does not exist', $class)); };
  34. spl_autoload_register($throwingAutoloader);
  35. try {
  36. $this->container = $container;
  37. foreach ($container->getDefinitions() as $id => $definition) {
  38. if ($definition->isAutowired()) {
  39. $this->completeDefinition($id, $definition);
  40. }
  41. }
  42. } finally {
  43. spl_autoload_unregister($throwingAutoloader);
  44. // Free memory and remove circular reference to container
  45. $this->container = null;
  46. $this->reflectionClasses = array();
  47. $this->definedTypes = array();
  48. $this->types = null;
  49. $this->ambiguousServiceTypes = array();
  50. }
  51. }
  52. /**
  53. * Creates a resource to help know if this service has changed.
  54. *
  55. * @param \ReflectionClass $reflectionClass
  56. *
  57. * @return AutowireServiceResource
  58. */
  59. public static function createResourceForClass(\ReflectionClass $reflectionClass)
  60. {
  61. $metadata = array();
  62. if ($constructor = $reflectionClass->getConstructor()) {
  63. $metadata['__construct'] = self::getResourceMetadataForMethod($constructor);
  64. }
  65. // todo - when #17608 is merged, could refactor to private function to remove duplication
  66. // of determining valid "setter" methods
  67. foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
  68. $name = $reflectionMethod->getName();
  69. if ($reflectionMethod->isStatic() || 1 !== $reflectionMethod->getNumberOfParameters() || 0 !== strpos($name, 'set')) {
  70. continue;
  71. }
  72. $metadata[$name] = self::getResourceMetadataForMethod($reflectionMethod);
  73. }
  74. return new AutowireServiceResource($reflectionClass->name, $reflectionClass->getFileName(), $metadata);
  75. }
  76. /**
  77. * Wires the given definition.
  78. *
  79. * @param string $id
  80. * @param Definition $definition
  81. *
  82. * @throws RuntimeException
  83. */
  84. private function completeDefinition($id, Definition $definition)
  85. {
  86. if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
  87. return;
  88. }
  89. if ($this->container->isTrackingResources()) {
  90. $this->container->addResource(static::createResourceForClass($reflectionClass));
  91. }
  92. if (!$constructor = $reflectionClass->getConstructor()) {
  93. return;
  94. }
  95. $arguments = $definition->getArguments();
  96. foreach ($constructor->getParameters() as $index => $parameter) {
  97. if (array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
  98. continue;
  99. }
  100. try {
  101. if (!$typeHint = $parameter->getClass()) {
  102. // no default value? Then fail
  103. if (!$parameter->isOptional()) {
  104. 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));
  105. }
  106. // specifically pass the default value
  107. $arguments[$index] = $parameter->getDefaultValue();
  108. continue;
  109. }
  110. if (null === $this->types) {
  111. $this->populateAvailableTypes();
  112. }
  113. if (isset($this->types[$typeHint->name])) {
  114. $value = new Reference($this->types[$typeHint->name]);
  115. } else {
  116. try {
  117. $value = $this->createAutowiredDefinition($typeHint, $id);
  118. } catch (RuntimeException $e) {
  119. if ($parameter->allowsNull()) {
  120. $value = null;
  121. } elseif ($parameter->isDefaultValueAvailable()) {
  122. $value = $parameter->getDefaultValue();
  123. } else {
  124. throw $e;
  125. }
  126. }
  127. }
  128. } catch (\ReflectionException $e) {
  129. // Typehint against a non-existing class
  130. if (!$parameter->isDefaultValueAvailable()) {
  131. 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);
  132. }
  133. $value = $parameter->getDefaultValue();
  134. }
  135. $arguments[$index] = $value;
  136. }
  137. // it's possible index 1 was set, then index 0, then 2, etc
  138. // make sure that we re-order so they're injected as expected
  139. ksort($arguments);
  140. $definition->setArguments($arguments);
  141. }
  142. /**
  143. * Populates the list of available types.
  144. */
  145. private function populateAvailableTypes()
  146. {
  147. $this->types = array();
  148. foreach ($this->container->getDefinitions() as $id => $definition) {
  149. $this->populateAvailableType($id, $definition);
  150. }
  151. }
  152. /**
  153. * Populates the list of available types for a given definition.
  154. *
  155. * @param string $id
  156. * @param Definition $definition
  157. */
  158. private function populateAvailableType($id, Definition $definition)
  159. {
  160. // Never use abstract services
  161. if ($definition->isAbstract()) {
  162. return;
  163. }
  164. foreach ($definition->getAutowiringTypes() as $type) {
  165. $this->definedTypes[$type] = true;
  166. $this->types[$type] = $id;
  167. }
  168. if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
  169. return;
  170. }
  171. foreach ($reflectionClass->getInterfaces() as $reflectionInterface) {
  172. $this->set($reflectionInterface->name, $id);
  173. }
  174. do {
  175. $this->set($reflectionClass->name, $id);
  176. } while ($reflectionClass = $reflectionClass->getParentClass());
  177. }
  178. /**
  179. * Associates a type and a service id if applicable.
  180. *
  181. * @param string $type
  182. * @param string $id
  183. */
  184. private function set($type, $id)
  185. {
  186. if (isset($this->definedTypes[$type])) {
  187. return;
  188. }
  189. // is this already a type/class that is known to match multiple services?
  190. if (isset($this->ambiguousServiceTypes[$type])) {
  191. $this->addServiceToAmbiguousType($id, $type);
  192. return;
  193. }
  194. // check to make sure the type doesn't match multiple services
  195. if (isset($this->types[$type])) {
  196. if ($this->types[$type] === $id) {
  197. return;
  198. }
  199. // keep an array of all services matching this type
  200. $this->addServiceToAmbiguousType($id, $type);
  201. unset($this->types[$type]);
  202. return;
  203. }
  204. $this->types[$type] = $id;
  205. }
  206. /**
  207. * Registers a definition for the type if possible or throws an exception.
  208. *
  209. * @param \ReflectionClass $typeHint
  210. * @param string $id
  211. *
  212. * @return Reference A reference to the registered definition
  213. *
  214. * @throws RuntimeException
  215. */
  216. private function createAutowiredDefinition(\ReflectionClass $typeHint, $id)
  217. {
  218. if (isset($this->ambiguousServiceTypes[$typeHint->name])) {
  219. $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
  220. $matchingServices = implode(', ', $this->ambiguousServiceTypes[$typeHint->name]);
  221. 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));
  222. }
  223. if (!$typeHint->isInstantiable()) {
  224. $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
  225. 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));
  226. }
  227. $argumentId = sprintf('autowired.%s', $typeHint->name);
  228. $argumentDefinition = $this->container->register($argumentId, $typeHint->name);
  229. $argumentDefinition->setPublic(false);
  230. $this->populateAvailableType($argumentId, $argumentDefinition);
  231. try {
  232. $this->completeDefinition($argumentId, $argumentDefinition);
  233. } catch (RuntimeException $e) {
  234. $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
  235. $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);
  236. throw new RuntimeException($message, 0, $e);
  237. }
  238. return new Reference($argumentId);
  239. }
  240. /**
  241. * Retrieves the reflection class associated with the given service.
  242. *
  243. * @param string $id
  244. * @param Definition $definition
  245. *
  246. * @return \ReflectionClass|false
  247. */
  248. private function getReflectionClass($id, Definition $definition)
  249. {
  250. if (isset($this->reflectionClasses[$id])) {
  251. return $this->reflectionClasses[$id];
  252. }
  253. // Cannot use reflection if the class isn't set
  254. if (!$class = $definition->getClass()) {
  255. return false;
  256. }
  257. $class = $this->container->getParameterBag()->resolveValue($class);
  258. try {
  259. $reflector = new \ReflectionClass($class);
  260. } catch (\ReflectionException $e) {
  261. $reflector = false;
  262. }
  263. return $this->reflectionClasses[$id] = $reflector;
  264. }
  265. private function addServiceToAmbiguousType($id, $type)
  266. {
  267. // keep an array of all services matching this type
  268. if (!isset($this->ambiguousServiceTypes[$type])) {
  269. $this->ambiguousServiceTypes[$type] = array(
  270. $this->types[$type],
  271. );
  272. }
  273. $this->ambiguousServiceTypes[$type][] = $id;
  274. }
  275. private static function getResourceMetadataForMethod(\ReflectionMethod $method)
  276. {
  277. $methodArgumentsMetadata = array();
  278. foreach ($method->getParameters() as $parameter) {
  279. try {
  280. $class = $parameter->getClass();
  281. } catch (\ReflectionException $e) {
  282. // type-hint is against a non-existent class
  283. $class = false;
  284. }
  285. $methodArgumentsMetadata[] = array(
  286. 'class' => $class,
  287. 'isOptional' => $parameter->isOptional(),
  288. 'defaultValue' => $parameter->isOptional() ? $parameter->getDefaultValue() : null,
  289. );
  290. }
  291. return $methodArgumentsMetadata;
  292. }
  293. }