ResolveBindingsPass.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  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\Argument\BoundArgument;
  12. use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
  13. use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
  14. use Symfony\Component\DependencyInjection\ContainerBuilder;
  15. use Symfony\Component\DependencyInjection\Definition;
  16. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  17. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  18. use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
  19. use Symfony\Component\DependencyInjection\Reference;
  20. use Symfony\Component\DependencyInjection\TypedReference;
  21. /**
  22. * @author Guilhem Niot <guilhem.niot@gmail.com>
  23. */
  24. class ResolveBindingsPass extends AbstractRecursivePass
  25. {
  26. private $usedBindings = [];
  27. private $unusedBindings = [];
  28. private $errorMessages = [];
  29. /**
  30. * {@inheritdoc}
  31. */
  32. public function process(ContainerBuilder $container)
  33. {
  34. $this->usedBindings = $container->getRemovedBindingIds();
  35. try {
  36. parent::process($container);
  37. foreach ($this->unusedBindings as [$key, $serviceId, $bindingType, $file]) {
  38. $argumentType = $argumentName = $message = null;
  39. if (false !== strpos($key, ' ')) {
  40. [$argumentType, $argumentName] = explode(' ', $key, 2);
  41. } elseif ('$' === $key[0]) {
  42. $argumentName = $key;
  43. } else {
  44. $argumentType = $key;
  45. }
  46. if ($argumentType) {
  47. $message .= sprintf('of type "%s" ', $argumentType);
  48. }
  49. if ($argumentName) {
  50. $message .= sprintf('named "%s" ', $argumentName);
  51. }
  52. if (BoundArgument::DEFAULTS_BINDING === $bindingType) {
  53. $message .= 'under "_defaults"';
  54. } elseif (BoundArgument::INSTANCEOF_BINDING === $bindingType) {
  55. $message .= 'under "_instanceof"';
  56. } else {
  57. $message .= sprintf('for service "%s"', $serviceId);
  58. }
  59. if ($file) {
  60. $message .= sprintf(' in file "%s"', $file);
  61. }
  62. $message = sprintf('A binding is configured for an argument %s, but no corresponding argument has been found. It may be unused and should be removed, or it may have a typo.', $message);
  63. if ($this->errorMessages) {
  64. $message .= sprintf("\nCould be related to%s:", 1 < \count($this->errorMessages) ? ' one of' : '');
  65. }
  66. foreach ($this->errorMessages as $m) {
  67. $message .= "\n - ".$m;
  68. }
  69. throw new InvalidArgumentException($message);
  70. }
  71. } finally {
  72. $this->usedBindings = [];
  73. $this->unusedBindings = [];
  74. $this->errorMessages = [];
  75. }
  76. }
  77. /**
  78. * {@inheritdoc}
  79. */
  80. protected function processValue($value, bool $isRoot = false)
  81. {
  82. if ($value instanceof TypedReference && $value->getType() === (string) $value) {
  83. // Already checked
  84. $bindings = $this->container->getDefinition($this->currentId)->getBindings();
  85. $name = $value->getName();
  86. if (isset($name, $bindings[$name = $value.' $'.$name])) {
  87. return $this->getBindingValue($bindings[$name]);
  88. }
  89. if (isset($bindings[$value->getType()])) {
  90. return $this->getBindingValue($bindings[$value->getType()]);
  91. }
  92. return parent::processValue($value, $isRoot);
  93. }
  94. if (!$value instanceof Definition || !$bindings = $value->getBindings()) {
  95. return parent::processValue($value, $isRoot);
  96. }
  97. $bindingNames = [];
  98. foreach ($bindings as $key => $binding) {
  99. [$bindingValue, $bindingId, $used, $bindingType, $file] = $binding->getValues();
  100. if ($used) {
  101. $this->usedBindings[$bindingId] = true;
  102. unset($this->unusedBindings[$bindingId]);
  103. } elseif (!isset($this->usedBindings[$bindingId])) {
  104. $this->unusedBindings[$bindingId] = [$key, $this->currentId, $bindingType, $file];
  105. }
  106. if (preg_match('/^(?:(?:array|bool|float|int|string|([^ $]++)) )\$/', $key, $m)) {
  107. $bindingNames[substr($key, \strlen($m[0]))] = $binding;
  108. }
  109. if (!isset($m[1])) {
  110. continue;
  111. }
  112. if (null !== $bindingValue && !$bindingValue instanceof Reference && !$bindingValue instanceof Definition && !$bindingValue instanceof TaggedIteratorArgument && !$bindingValue instanceof ServiceLocatorArgument) {
  113. throw new InvalidArgumentException(sprintf('Invalid value for binding key "%s" for service "%s": expected null, "%s", "%s", "%s" or ServiceLocatorArgument, "%s" given.', $key, $this->currentId, Reference::class, Definition::class, TaggedIteratorArgument::class, get_debug_type($bindingValue)));
  114. }
  115. }
  116. if ($value->isAbstract()) {
  117. return parent::processValue($value, $isRoot);
  118. }
  119. $calls = $value->getMethodCalls();
  120. try {
  121. if ($constructor = $this->getConstructor($value, false)) {
  122. $calls[] = [$constructor, $value->getArguments()];
  123. }
  124. } catch (RuntimeException $e) {
  125. $this->errorMessages[] = $e->getMessage();
  126. $this->container->getDefinition($this->currentId)->addError($e->getMessage());
  127. return parent::processValue($value, $isRoot);
  128. }
  129. foreach ($calls as $i => $call) {
  130. [$method, $arguments] = $call;
  131. if ($method instanceof \ReflectionFunctionAbstract) {
  132. $reflectionMethod = $method;
  133. } else {
  134. try {
  135. $reflectionMethod = $this->getReflectionMethod($value, $method);
  136. } catch (RuntimeException $e) {
  137. if ($value->getFactory()) {
  138. continue;
  139. }
  140. throw $e;
  141. }
  142. }
  143. foreach ($reflectionMethod->getParameters() as $key => $parameter) {
  144. if (\array_key_exists($key, $arguments) && '' !== $arguments[$key]) {
  145. continue;
  146. }
  147. $typeHint = ProxyHelper::getTypeHint($reflectionMethod, $parameter);
  148. if (\array_key_exists($k = ltrim($typeHint, '\\').' $'.$parameter->name, $bindings)) {
  149. $arguments[$key] = $this->getBindingValue($bindings[$k]);
  150. continue;
  151. }
  152. if (\array_key_exists('$'.$parameter->name, $bindings)) {
  153. $arguments[$key] = $this->getBindingValue($bindings['$'.$parameter->name]);
  154. continue;
  155. }
  156. if ($typeHint && '\\' === $typeHint[0] && isset($bindings[$typeHint = substr($typeHint, 1)])) {
  157. $arguments[$key] = $this->getBindingValue($bindings[$typeHint]);
  158. continue;
  159. }
  160. if (isset($bindingNames[$parameter->name])) {
  161. $bindingKey = array_search($binding, $bindings, true);
  162. $argumentType = substr($bindingKey, 0, strpos($bindingKey, ' '));
  163. $this->errorMessages[] = sprintf('Did you forget to add the type "%s" to argument "$%s" of method "%s::%s()"?', $argumentType, $parameter->name, $reflectionMethod->class, $reflectionMethod->name);
  164. }
  165. }
  166. if ($arguments !== $call[1]) {
  167. ksort($arguments);
  168. $calls[$i][1] = $arguments;
  169. }
  170. }
  171. if ($constructor) {
  172. [, $arguments] = array_pop($calls);
  173. if ($arguments !== $value->getArguments()) {
  174. $value->setArguments($arguments);
  175. }
  176. }
  177. if ($calls !== $value->getMethodCalls()) {
  178. $value->setMethodCalls($calls);
  179. }
  180. return parent::processValue($value, $isRoot);
  181. }
  182. /**
  183. * @return mixed
  184. */
  185. private function getBindingValue(BoundArgument $binding)
  186. {
  187. [$bindingValue, $bindingId] = $binding->getValues();
  188. $this->usedBindings[$bindingId] = true;
  189. unset($this->unusedBindings[$bindingId]);
  190. return $bindingValue;
  191. }
  192. }