ResolveInstanceofConditionalsPass.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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\ChildDefinition;
  12. use Symfony\Component\DependencyInjection\ContainerBuilder;
  13. use Symfony\Component\DependencyInjection\Definition;
  14. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  15. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  16. /**
  17. * Applies instanceof conditionals to definitions.
  18. *
  19. * @author Nicolas Grekas <p@tchwork.com>
  20. */
  21. class ResolveInstanceofConditionalsPass implements CompilerPassInterface
  22. {
  23. /**
  24. * {@inheritdoc}
  25. */
  26. public function process(ContainerBuilder $container)
  27. {
  28. foreach ($container->getAutoconfiguredInstanceof() as $interface => $definition) {
  29. if ($definition->getArguments()) {
  30. throw new InvalidArgumentException(sprintf('Autoconfigured instanceof for type "%s" defines arguments but these are not supported and should be removed.', $interface));
  31. }
  32. }
  33. $tagsToKeep = [];
  34. if ($container->hasParameter('container.behavior_describing_tags')) {
  35. $tagsToKeep = $container->getParameter('container.behavior_describing_tags');
  36. }
  37. foreach ($container->getDefinitions() as $id => $definition) {
  38. $container->setDefinition($id, $this->processDefinition($container, $id, $definition, $tagsToKeep));
  39. }
  40. if ($container->hasParameter('container.behavior_describing_tags')) {
  41. $container->getParameterBag()->remove('container.behavior_describing_tags');
  42. }
  43. }
  44. private function processDefinition(ContainerBuilder $container, string $id, Definition $definition, array $tagsToKeep): Definition
  45. {
  46. $instanceofConditionals = $definition->getInstanceofConditionals();
  47. $autoconfiguredInstanceof = $definition->isAutoconfigured() ? $container->getAutoconfiguredInstanceof() : [];
  48. if (!$instanceofConditionals && !$autoconfiguredInstanceof) {
  49. return $definition;
  50. }
  51. if (!$class = $container->getParameterBag()->resolveValue($definition->getClass())) {
  52. return $definition;
  53. }
  54. $conditionals = $this->mergeConditionals($autoconfiguredInstanceof, $instanceofConditionals, $container);
  55. $definition->setInstanceofConditionals([]);
  56. $shared = null;
  57. $instanceofTags = [];
  58. $instanceofCalls = [];
  59. $instanceofBindings = [];
  60. $reflectionClass = null;
  61. $parent = $definition instanceof ChildDefinition ? $definition->getParent() : null;
  62. foreach ($conditionals as $interface => $instanceofDefs) {
  63. if ($interface !== $class && !($reflectionClass ?? $reflectionClass = $container->getReflectionClass($class, false) ?: false)) {
  64. continue;
  65. }
  66. if ($interface !== $class && !is_subclass_of($class, $interface)) {
  67. continue;
  68. }
  69. foreach ($instanceofDefs as $key => $instanceofDef) {
  70. /** @var ChildDefinition $instanceofDef */
  71. $instanceofDef = clone $instanceofDef;
  72. $instanceofDef->setAbstract(true)->setParent($parent ?: '.abstract.instanceof.'.$id);
  73. $parent = '.instanceof.'.$interface.'.'.$key.'.'.$id;
  74. $container->setDefinition($parent, $instanceofDef);
  75. $instanceofTags[] = [$interface, $instanceofDef->getTags()];
  76. $instanceofBindings = $instanceofDef->getBindings() + $instanceofBindings;
  77. foreach ($instanceofDef->getMethodCalls() as $methodCall) {
  78. $instanceofCalls[] = $methodCall;
  79. }
  80. $instanceofDef->setTags([]);
  81. $instanceofDef->setMethodCalls([]);
  82. $instanceofDef->setBindings([]);
  83. if (isset($instanceofDef->getChanges()['shared'])) {
  84. $shared = $instanceofDef->isShared();
  85. }
  86. }
  87. }
  88. if ($parent) {
  89. $bindings = $definition->getBindings();
  90. $abstract = $container->setDefinition('.abstract.instanceof.'.$id, $definition);
  91. $definition->setBindings([]);
  92. $definition = serialize($definition);
  93. if (Definition::class === \get_class($abstract)) {
  94. // cast Definition to ChildDefinition
  95. $definition = substr_replace($definition, '53', 2, 2);
  96. $definition = substr_replace($definition, 'Child', 44, 0);
  97. }
  98. /** @var ChildDefinition $definition */
  99. $definition = unserialize($definition);
  100. $definition->setParent($parent);
  101. if (null !== $shared && !isset($definition->getChanges()['shared'])) {
  102. $definition->setShared($shared);
  103. }
  104. // Don't add tags to service decorators
  105. $i = \count($instanceofTags);
  106. while (0 <= --$i) {
  107. [$interface, $tags] = $instanceofTags[$i];
  108. foreach ($tags as $k => $v) {
  109. if (null === $definition->getDecoratedService() || $interface === $definition->getClass() || \in_array($k, $tagsToKeep, true)) {
  110. foreach ($v as $v) {
  111. if ($definition->hasTag($k) && \in_array($v, $definition->getTag($k))) {
  112. continue;
  113. }
  114. $definition->addTag($k, $v);
  115. }
  116. }
  117. }
  118. }
  119. $definition->setMethodCalls(array_merge($instanceofCalls, $definition->getMethodCalls()));
  120. $definition->setBindings($bindings + $instanceofBindings);
  121. // reset fields with "merge" behavior
  122. $abstract
  123. ->setBindings([])
  124. ->setArguments([])
  125. ->setMethodCalls([])
  126. ->setDecoratedService(null)
  127. ->setTags([])
  128. ->setAbstract(true);
  129. }
  130. return $definition;
  131. }
  132. private function mergeConditionals(array $autoconfiguredInstanceof, array $instanceofConditionals, ContainerBuilder $container): array
  133. {
  134. // make each value an array of ChildDefinition
  135. $conditionals = array_map(function ($childDef) { return [$childDef]; }, $autoconfiguredInstanceof);
  136. foreach ($instanceofConditionals as $interface => $instanceofDef) {
  137. // make sure the interface/class exists (but don't validate automaticInstanceofConditionals)
  138. if (!$container->getReflectionClass($interface)) {
  139. throw new RuntimeException(sprintf('"%s" is set as an "instanceof" conditional, but it does not exist.', $interface));
  140. }
  141. if (!isset($autoconfiguredInstanceof[$interface])) {
  142. $conditionals[$interface] = [];
  143. }
  144. $conditionals[$interface][] = $instanceofDef;
  145. }
  146. return $conditionals;
  147. }
  148. }