ContainerLintCommand.php 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  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\Bundle\FrameworkBundle\Command;
  11. use Symfony\Component\Config\ConfigCache;
  12. use Symfony\Component\Config\FileLocator;
  13. use Symfony\Component\Console\Command\Command;
  14. use Symfony\Component\Console\Exception\RuntimeException;
  15. use Symfony\Component\Console\Input\InputInterface;
  16. use Symfony\Component\Console\Output\OutputInterface;
  17. use Symfony\Component\Console\Style\SymfonyStyle;
  18. use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass;
  19. use Symfony\Component\DependencyInjection\Compiler\PassConfig;
  20. use Symfony\Component\DependencyInjection\Container;
  21. use Symfony\Component\DependencyInjection\ContainerBuilder;
  22. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  23. use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
  24. use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
  25. use Symfony\Component\HttpKernel\Kernel;
  26. final class ContainerLintCommand extends Command
  27. {
  28. protected static $defaultName = 'lint:container';
  29. /**
  30. * @var ContainerBuilder
  31. */
  32. private $containerBuilder;
  33. /**
  34. * {@inheritdoc}
  35. */
  36. protected function configure()
  37. {
  38. $this
  39. ->setDescription('Ensure that arguments injected into services match type declarations')
  40. ->setHelp('This command parses service definitions and ensures that injected values match the type declarations of each services\' class.')
  41. ;
  42. }
  43. /**
  44. * {@inheritdoc}
  45. */
  46. protected function execute(InputInterface $input, OutputInterface $output): int
  47. {
  48. $io = new SymfonyStyle($input, $output);
  49. $errorIo = $io->getErrorStyle();
  50. try {
  51. $container = $this->getContainerBuilder();
  52. } catch (RuntimeException $e) {
  53. $errorIo->error($e->getMessage());
  54. return 2;
  55. }
  56. $container->setParameter('container.build_time', time());
  57. try {
  58. $container->compile();
  59. } catch (InvalidArgumentException $e) {
  60. $errorIo->error($e->getMessage());
  61. return 1;
  62. }
  63. $io->success('The container was lint successfully: all services are injected with values that are compatible with their type declarations.');
  64. return 0;
  65. }
  66. private function getContainerBuilder(): ContainerBuilder
  67. {
  68. if ($this->containerBuilder) {
  69. return $this->containerBuilder;
  70. }
  71. $kernel = $this->getApplication()->getKernel();
  72. $kernelContainer = $kernel->getContainer();
  73. if (!$kernel->isDebug() || !(new ConfigCache($kernelContainer->getParameter('debug.container.dump'), true))->isFresh()) {
  74. if (!$kernel instanceof Kernel) {
  75. throw new RuntimeException(sprintf('This command does not support the application kernel: "%s" does not extend "%s".', get_debug_type($kernel), Kernel::class));
  76. }
  77. $buildContainer = \Closure::bind(function (): ContainerBuilder {
  78. $this->initializeBundles();
  79. return $this->buildContainer();
  80. }, $kernel, \get_class($kernel));
  81. $container = $buildContainer();
  82. $skippedIds = [];
  83. } else {
  84. if (!$kernelContainer instanceof Container) {
  85. throw new RuntimeException(sprintf('This command does not support the application container: "%s" does not extend "%s".', get_debug_type($kernelContainer), Container::class));
  86. }
  87. (new XmlFileLoader($container = new ContainerBuilder($parameterBag = new EnvPlaceholderParameterBag()), new FileLocator()))->load($kernelContainer->getParameter('debug.container.dump'));
  88. $refl = new \ReflectionProperty($parameterBag, 'resolved');
  89. $refl->setAccessible(true);
  90. $refl->setValue($parameterBag, true);
  91. $skippedIds = [];
  92. foreach ($container->getServiceIds() as $serviceId) {
  93. if (0 === strpos($serviceId, '.errored.')) {
  94. $skippedIds[$serviceId] = true;
  95. }
  96. }
  97. }
  98. $container->setParameter('container.build_hash', 'lint_container');
  99. $container->setParameter('container.build_id', 'lint_container');
  100. $container->addCompilerPass(new CheckTypeDeclarationsPass(true, $skippedIds), PassConfig::TYPE_AFTER_REMOVING, -100);
  101. return $this->containerBuilder = $container;
  102. }
  103. }