CheckExceptionOnInvalidReferenceBehaviorPass.php 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  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\ContainerBuilder;
  12. use Symfony\Component\DependencyInjection\ContainerInterface;
  13. use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
  14. use Symfony\Component\DependencyInjection\Reference;
  15. /**
  16. * Checks that all references are pointing to a valid service.
  17. *
  18. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  19. */
  20. class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass
  21. {
  22. private $serviceLocatorContextIds = [];
  23. /**
  24. * {@inheritdoc}
  25. */
  26. public function process(ContainerBuilder $container)
  27. {
  28. $this->serviceLocatorContextIds = [];
  29. foreach ($container->findTaggedServiceIds('container.service_locator_context') as $id => $tags) {
  30. $this->serviceLocatorContextIds[$id] = $tags[0]['id'];
  31. $container->getDefinition($id)->clearTag('container.service_locator_context');
  32. }
  33. try {
  34. return parent::process($container);
  35. } finally {
  36. $this->serviceLocatorContextIds = [];
  37. }
  38. }
  39. protected function processValue($value, bool $isRoot = false)
  40. {
  41. if (!$value instanceof Reference) {
  42. return parent::processValue($value, $isRoot);
  43. }
  44. if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $value->getInvalidBehavior() || $this->container->has($id = (string) $value)) {
  45. return $value;
  46. }
  47. $currentId = $this->currentId;
  48. $graph = $this->container->getCompiler()->getServiceReferenceGraph();
  49. if (isset($this->serviceLocatorContextIds[$currentId])) {
  50. $currentId = $this->serviceLocatorContextIds[$currentId];
  51. $locator = $this->container->getDefinition($this->currentId)->getFactory()[0];
  52. foreach ($locator->getArgument(0) as $k => $v) {
  53. if ($v->getValues()[0] === $value) {
  54. if ($k !== $id) {
  55. $currentId = $k.'" in the container provided to "'.$currentId;
  56. }
  57. throw new ServiceNotFoundException($id, $currentId, null, $this->getAlternatives($id));
  58. }
  59. }
  60. }
  61. if ('.' === $currentId[0] && $graph->hasNode($currentId)) {
  62. foreach ($graph->getNode($currentId)->getInEdges() as $edge) {
  63. if (!$edge->getValue() instanceof Reference || ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $edge->getValue()->getInvalidBehavior()) {
  64. continue;
  65. }
  66. $sourceId = $edge->getSourceNode()->getId();
  67. if ('.' !== $sourceId[0]) {
  68. $currentId = $sourceId;
  69. break;
  70. }
  71. }
  72. }
  73. throw new ServiceNotFoundException($id, $currentId, null, $this->getAlternatives($id));
  74. }
  75. private function getAlternatives(string $id): array
  76. {
  77. $alternatives = [];
  78. foreach ($this->container->getServiceIds() as $knownId) {
  79. if ('' === $knownId || '.' === $knownId[0]) {
  80. continue;
  81. }
  82. $lev = levenshtein($id, $knownId);
  83. if ($lev <= \strlen($id) / 3 || false !== strpos($knownId, $id)) {
  84. $alternatives[] = $knownId;
  85. }
  86. }
  87. return $alternatives;
  88. }
  89. }