DefinitionErrorExceptionPass.php 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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\Argument\ArgumentInterface;
  12. use Symfony\Component\DependencyInjection\ContainerBuilder;
  13. use Symfony\Component\DependencyInjection\ContainerInterface;
  14. use Symfony\Component\DependencyInjection\Definition;
  15. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  16. use Symfony\Component\DependencyInjection\Reference;
  17. /**
  18. * Throws an exception for any Definitions that have errors and still exist.
  19. *
  20. * @author Ryan Weaver <ryan@knpuniversity.com>
  21. */
  22. class DefinitionErrorExceptionPass extends AbstractRecursivePass
  23. {
  24. private $erroredDefinitions = [];
  25. private $sourceReferences = [];
  26. /**
  27. * @return void
  28. */
  29. public function process(ContainerBuilder $container)
  30. {
  31. try {
  32. parent::process($container);
  33. $visitedIds = [];
  34. foreach ($this->erroredDefinitions as $id => $definition) {
  35. if ($this->isErrorForRuntime($id, $visitedIds)) {
  36. continue;
  37. }
  38. // only show the first error so the user can focus on it
  39. $errors = $definition->getErrors();
  40. throw new RuntimeException(reset($errors));
  41. }
  42. } finally {
  43. $this->erroredDefinitions = [];
  44. $this->sourceReferences = [];
  45. }
  46. }
  47. /**
  48. * {@inheritdoc}
  49. */
  50. protected function processValue($value, bool $isRoot = false)
  51. {
  52. if ($value instanceof ArgumentInterface) {
  53. parent::processValue($value->getValues());
  54. return $value;
  55. }
  56. if ($value instanceof Reference && $this->currentId !== $targetId = (string) $value) {
  57. if (ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) {
  58. $this->sourceReferences[$targetId][$this->currentId] ?? $this->sourceReferences[$targetId][$this->currentId] = true;
  59. } else {
  60. $this->sourceReferences[$targetId][$this->currentId] = false;
  61. }
  62. return $value;
  63. }
  64. if (!$value instanceof Definition || !$value->hasErrors()) {
  65. return parent::processValue($value, $isRoot);
  66. }
  67. $this->erroredDefinitions[$this->currentId] = $value;
  68. return parent::processValue($value);
  69. }
  70. private function isErrorForRuntime(string $id, array &$visitedIds): bool
  71. {
  72. if (!isset($this->sourceReferences[$id])) {
  73. return false;
  74. }
  75. if (isset($visitedIds[$id])) {
  76. return $visitedIds[$id];
  77. }
  78. $visitedIds[$id] = true;
  79. foreach ($this->sourceReferences[$id] as $sourceId => $isRuntime) {
  80. if ($visitedIds[$sourceId] ?? $visitedIds[$sourceId] = $this->isErrorForRuntime($sourceId, $visitedIds)) {
  81. continue;
  82. }
  83. if (!$isRuntime) {
  84. return false;
  85. }
  86. }
  87. return true;
  88. }
  89. }