ResolveDecoratorStackPass.php 4.3 KB

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