ResolveDecoratorStackPass.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  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\Alias;
  12. use Symfony\Component\DependencyInjection\ChildDefinition;
  13. use Symfony\Component\DependencyInjection\ContainerBuilder;
  14. use Symfony\Component\DependencyInjection\Definition;
  15. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  16. use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
  17. use Symfony\Component\DependencyInjection\Reference;
  18. /**
  19. * @author Nicolas Grekas <p@tchwork.com>
  20. */
  21. class ResolveDecoratorStackPass implements CompilerPassInterface
  22. {
  23. private $tag;
  24. public function __construct(string $tag = 'container.stack')
  25. {
  26. if (0 < \func_num_args()) {
  27. trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
  28. }
  29. $this->tag = $tag;
  30. }
  31. public function process(ContainerBuilder $container)
  32. {
  33. $stacks = [];
  34. foreach ($container->findTaggedServiceIds($this->tag) as $id => $tags) {
  35. $definition = $container->getDefinition($id);
  36. if (!$definition instanceof ChildDefinition) {
  37. throw new InvalidArgumentException(sprintf('Invalid service "%s": only definitions with a "parent" can have the "%s" tag.', $id, $this->tag));
  38. }
  39. if (!$stack = $definition->getArguments()) {
  40. throw new InvalidArgumentException(sprintf('Invalid service "%s": the stack of decorators is empty.', $id));
  41. }
  42. $stacks[$id] = $stack;
  43. }
  44. if (!$stacks) {
  45. return;
  46. }
  47. $resolvedDefinitions = [];
  48. foreach ($container->getDefinitions() as $id => $definition) {
  49. if (!isset($stacks[$id])) {
  50. $resolvedDefinitions[$id] = $definition;
  51. continue;
  52. }
  53. foreach (array_reverse($this->resolveStack($stacks, [$id]), true) as $k => $v) {
  54. $resolvedDefinitions[$k] = $v;
  55. }
  56. $alias = $container->setAlias($id, $k);
  57. if ($definition->getChanges()['public'] ?? false) {
  58. $alias->setPublic($definition->isPublic());
  59. }
  60. if ($definition->isDeprecated()) {
  61. $alias->setDeprecated(...array_values($definition->getDeprecation('%alias_id%')));
  62. }
  63. }
  64. $container->setDefinitions($resolvedDefinitions);
  65. }
  66. private function resolveStack(array $stacks, array $path): array
  67. {
  68. $definitions = [];
  69. $id = end($path);
  70. $prefix = '.'.$id.'.';
  71. if (!isset($stacks[$id])) {
  72. return [$id => new ChildDefinition($id)];
  73. }
  74. if (key($path) !== $searchKey = array_search($id, $path)) {
  75. throw new ServiceCircularReferenceException($id, \array_slice($path, $searchKey));
  76. }
  77. foreach ($stacks[$id] as $k => $definition) {
  78. if ($definition instanceof ChildDefinition && isset($stacks[$definition->getParent()])) {
  79. $path[] = $definition->getParent();
  80. $definition = unserialize(serialize($definition)); // deep clone
  81. } elseif ($definition instanceof Definition) {
  82. $definitions[$decoratedId = $prefix.$k] = $definition;
  83. continue;
  84. } elseif ($definition instanceof Reference || $definition instanceof Alias) {
  85. $path[] = (string) $definition;
  86. } else {
  87. throw new InvalidArgumentException(sprintf('Invalid service "%s": unexpected value of type "%s" found in the stack of decorators.', $id, get_debug_type($definition)));
  88. }
  89. $p = $prefix.$k;
  90. foreach ($this->resolveStack($stacks, $path) as $k => $v) {
  91. $definitions[$decoratedId = $p.$k] = $definition instanceof ChildDefinition ? $definition->setParent($k) : new ChildDefinition($k);
  92. $definition = null;
  93. }
  94. array_pop($path);
  95. }
  96. if (1 === \count($path)) {
  97. foreach ($definitions as $k => $definition) {
  98. $definition->setPublic(false)->setTags([])->setDecoratedService($decoratedId);
  99. }
  100. $definition->setDecoratedService(null);
  101. }
  102. return $definitions;
  103. }
  104. }