DecoratorServicePass.php 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  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\ContainerBuilder;
  13. use Symfony\Component\DependencyInjection\ContainerInterface;
  14. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  15. use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
  16. use Symfony\Component\DependencyInjection\Reference;
  17. /**
  18. * Overwrites a service but keeps the overridden one.
  19. *
  20. * @author Christophe Coevoet <stof@notk.org>
  21. * @author Fabien Potencier <fabien@symfony.com>
  22. * @author Diego Saint Esteben <diego@saintesteben.me>
  23. */
  24. class DecoratorServicePass extends AbstractRecursivePass
  25. {
  26. private $innerId = '.inner';
  27. public function __construct(?string $innerId = '.inner')
  28. {
  29. if (0 < \func_num_args()) {
  30. trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
  31. }
  32. $this->innerId = $innerId;
  33. }
  34. public function process(ContainerBuilder $container)
  35. {
  36. $definitions = new \SplPriorityQueue();
  37. $order = \PHP_INT_MAX;
  38. foreach ($container->getDefinitions() as $id => $definition) {
  39. if (!$decorated = $definition->getDecoratedService()) {
  40. continue;
  41. }
  42. $definitions->insert([$id, $definition], [$decorated[2], --$order]);
  43. }
  44. $decoratingDefinitions = [];
  45. $tagsToKeep = $container->hasParameter('container.behavior_describing_tags')
  46. ? $container->getParameter('container.behavior_describing_tags')
  47. : ['proxy', 'container.do_not_inline', 'container.service_locator', 'container.service_subscriber', 'container.service_subscriber.locator'];
  48. foreach ($definitions as [$id, $definition]) {
  49. $decoratedService = $definition->getDecoratedService();
  50. [$inner, $renamedId] = $decoratedService;
  51. $invalidBehavior = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
  52. $definition->setDecoratedService(null);
  53. if (!$renamedId) {
  54. $renamedId = $id.'.inner';
  55. }
  56. $this->currentId = $renamedId;
  57. $this->processValue($definition);
  58. $definition->innerServiceId = $renamedId;
  59. $definition->decorationOnInvalid = $invalidBehavior;
  60. // we create a new alias/service for the service we are replacing
  61. // to be able to reference it in the new one
  62. if ($container->hasAlias($inner)) {
  63. $alias = $container->getAlias($inner);
  64. $public = $alias->isPublic();
  65. $container->setAlias($renamedId, new Alias((string) $alias, false));
  66. $decoratedDefinition = $container->findDefinition($alias);
  67. } elseif ($container->hasDefinition($inner)) {
  68. $decoratedDefinition = $container->getDefinition($inner);
  69. $public = $decoratedDefinition->isPublic();
  70. $decoratedDefinition->setPublic(false);
  71. $container->setDefinition($renamedId, $decoratedDefinition);
  72. $decoratingDefinitions[$inner] = $decoratedDefinition;
  73. } elseif (ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $invalidBehavior) {
  74. $container->removeDefinition($id);
  75. continue;
  76. } elseif (ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) {
  77. $public = $definition->isPublic();
  78. $decoratedDefinition = null;
  79. } else {
  80. throw new ServiceNotFoundException($inner, $id);
  81. }
  82. if ($decoratedDefinition && $decoratedDefinition->isSynthetic()) {
  83. throw new InvalidArgumentException(sprintf('A synthetic service cannot be decorated: service "%s" cannot decorate "%s".', $id, $inner));
  84. }
  85. if (isset($decoratingDefinitions[$inner])) {
  86. $decoratingDefinition = $decoratingDefinitions[$inner];
  87. $decoratingTags = $decoratingDefinition->getTags();
  88. $resetTags = [];
  89. // Behavior-describing tags must not be transferred out to decorators
  90. foreach ($tagsToKeep as $containerTag) {
  91. if (isset($decoratingTags[$containerTag])) {
  92. $resetTags[$containerTag] = $decoratingTags[$containerTag];
  93. unset($decoratingTags[$containerTag]);
  94. }
  95. }
  96. $definition->setTags(array_merge($decoratingTags, $definition->getTags()));
  97. $decoratingDefinition->setTags($resetTags);
  98. $decoratingDefinitions[$inner] = $definition;
  99. }
  100. $container->setAlias($inner, $id)->setPublic($public);
  101. }
  102. foreach ($decoratingDefinitions as $inner => $definition) {
  103. $definition->addTag('container.decorator', ['id' => $inner]);
  104. }
  105. }
  106. protected function processValue($value, bool $isRoot = false)
  107. {
  108. if ($value instanceof Reference && $this->innerId === (string) $value) {
  109. return new Reference($this->currentId, $value->getInvalidBehavior());
  110. }
  111. return parent::processValue($value, $isRoot);
  112. }
  113. }