N°6934 - Symfony 6.4 - upgrade Symfony bundles to 6.4 (#580)

* Update Symfony lib to version ~6.4.0
* Update code missing return type
* Add an iTop general configuration entry to store application secret (Symfony mandatory parameter)
* Use dependency injection in ExceptionListener & UserProvider classes
This commit is contained in:
bdalsass
2023-12-05 13:56:56 +01:00
committed by GitHub
parent 863ab4560c
commit 27ce51ab07
1392 changed files with 44869 additions and 27799 deletions

View File

@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\TableSeparator;
@@ -27,18 +28,12 @@ use Symfony\Component\HttpKernel\KernelInterface;
*
* @final
*/
#[AsCommand(name: 'about', description: 'Display information about the current project')]
class AboutCommand extends Command
{
protected static $defaultName = 'about';
protected static $defaultDescription = 'Display information about the current project';
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$this
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOT'
The <info>%command.name%</info> command displays information about the current Symfony project.
@@ -49,9 +44,6 @@ EOT
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
@@ -75,7 +67,7 @@ EOT
new TableSeparator(),
['<info>Kernel</>'],
new TableSeparator(),
['Type', \get_class($kernel)],
['Type', $kernel::class],
['Environment', $kernel->getEnvironment()],
['Debug', $kernel->isDebug() ? 'true' : 'false'],
['Charset', $kernel->getCharset()],
@@ -88,9 +80,9 @@ EOT
['Version', \PHP_VERSION],
['Architecture', (\PHP_INT_SIZE * 8).' bits'],
['Intl locale', class_exists(\Locale::class, false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a'],
['Timezone', date_default_timezone_get().' (<comment>'.(new \DateTime())->format(\DateTime::W3C).'</>)'],
['OPcache', \extension_loaded('Zend OPcache') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'],
['APCu', \extension_loaded('apcu') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'],
['Timezone', date_default_timezone_get().' (<comment>'.(new \DateTimeImmutable())->format(\DateTimeInterface::W3C).'</>)'],
['OPcache', \extension_loaded('Zend OPcache') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL) ? 'true' : 'false'],
['APCu', \extension_loaded('apcu') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOL) ? 'true' : 'false'],
['Xdebug', \extension_loaded('xdebug') ? 'true' : 'false'],
];
@@ -109,6 +101,10 @@ EOT
if (is_file($path)) {
$size = filesize($path) ?: 0;
} else {
if (!is_dir($path)) {
return 'n/a';
}
$size = 0;
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS | \RecursiveDirectoryIterator::FOLLOW_SYMLINKS)) as $file) {
if ($file->isReadable()) {
@@ -122,15 +118,15 @@ EOT
private static function isExpired(string $date): bool
{
$date = \DateTime::createFromFormat('d/m/Y', '01/'.$date);
$date = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.$date);
return false !== $date && new \DateTime() > $date->modify('last day of this month 23:59:59');
return false !== $date && new \DateTimeImmutable() > $date->modify('last day of this month 23:59:59');
}
private static function daysBeforeExpiration(string $date): string
{
$date = \DateTime::createFromFormat('d/m/Y', '01/'.$date);
$date = \DateTimeImmutable::createFromFormat('d/m/Y', '01/'.$date);
return (new \DateTime())->diff($date->modify('last day of this month 23:59:59'))->format('in %R%a days');
return (new \DateTimeImmutable())->diff($date->modify('last day of this month 23:59:59'))->format('in %R%a days');
}
}

View File

@@ -29,18 +29,16 @@ use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
abstract class AbstractConfigCommand extends ContainerDebugCommand
{
/**
* @param OutputInterface|StyleInterface $output
* @return void
*/
protected function listBundles($output)
protected function listBundles(OutputInterface|StyleInterface $output)
{
$title = 'Available registered bundles with their extension alias if available';
$headers = ['Bundle name', 'Extension alias'];
$rows = [];
$bundles = $this->getApplication()->getKernel()->getBundles();
usort($bundles, function ($bundleA, $bundleB) {
return strcmp($bundleA->getName(), $bundleB->getName());
});
usort($bundles, fn ($bundleA, $bundleB) => strcmp($bundleA->getName(), $bundleB->getName()));
foreach ($bundles as $bundle) {
$extension = $bundle->getContainerExtension();
@@ -57,10 +55,45 @@ abstract class AbstractConfigCommand extends ContainerDebugCommand
}
}
/**
* @return ExtensionInterface
*/
protected function findExtension(string $name)
protected function listNonBundleExtensions(OutputInterface|StyleInterface $output): void
{
$title = 'Available registered non-bundle extension aliases';
$headers = ['Extension alias'];
$rows = [];
$kernel = $this->getApplication()->getKernel();
$bundleExtensions = [];
foreach ($kernel->getBundles() as $bundle) {
if ($extension = $bundle->getContainerExtension()) {
$bundleExtensions[$extension::class] = true;
}
}
$extensions = $this->getContainerBuilder($kernel)->getExtensions();
foreach ($extensions as $alias => $extension) {
if (isset($bundleExtensions[$extension::class])) {
continue;
}
$rows[] = [$alias];
}
if (!$rows) {
return;
}
if ($output instanceof StyleInterface) {
$output->title($title);
$output->table($headers, $rows);
} else {
$output->writeln($title);
$table = new Table($output);
$table->setHeaders($headers)->setRows($rows)->render();
}
}
protected function findExtension(string $name): ExtensionInterface
{
$bundles = $this->initializeBundles();
$minScore = \INF;
@@ -126,7 +159,10 @@ abstract class AbstractConfigCommand extends ContainerDebugCommand
throw new LogicException($message);
}
public function validateConfiguration(ExtensionInterface $extension, $configuration)
/**
* @return void
*/
public function validateConfiguration(ExtensionInterface $extension, mixed $configuration)
{
if (!$configuration) {
throw new \LogicException(sprintf('The extension with alias "%s" does not have its getConfiguration() method setup.', $extension->getAlias()));
@@ -137,7 +173,7 @@ abstract class AbstractConfigCommand extends ContainerDebugCommand
}
}
private function initializeBundles()
private function initializeBundles(): array
{
// Re-build bundle manually to initialize DI extensions that can be extended by other bundles in their build() method
// as this method is not called when the container is loaded from the cache.

View File

@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
@@ -33,17 +34,15 @@ use Symfony\Component\HttpKernel\KernelInterface;
*
* @final
*/
#[AsCommand(name: 'assets:install', description: 'Install bundle\'s web assets under a public directory')]
class AssetsInstallCommand extends Command
{
public const METHOD_COPY = 'copy';
public const METHOD_ABSOLUTE_SYMLINK = 'absolute symlink';
public const METHOD_RELATIVE_SYMLINK = 'relative symlink';
protected static $defaultName = 'assets:install';
protected static $defaultDescription = 'Install bundle\'s web assets under a public directory';
private $filesystem;
private $projectDir;
private Filesystem $filesystem;
private string $projectDir;
public function __construct(Filesystem $filesystem, string $projectDir)
{
@@ -53,10 +52,7 @@ class AssetsInstallCommand extends Command
$this->projectDir = $projectDir;
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$this
->setDefinition([
@@ -65,7 +61,6 @@ class AssetsInstallCommand extends Command
->addOption('symlink', null, InputOption::VALUE_NONE, 'Symlink the assets instead of copying them')
->addOption('relative', null, InputOption::VALUE_NONE, 'Make relative symlinks')
->addOption('no-cleanup', null, InputOption::VALUE_NONE, 'Do not remove the assets of the bundles that no longer exist')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOT'
The <info>%command.name%</info> command installs bundle assets into a given
directory (e.g. the <comment>public</comment> directory).
@@ -89,9 +84,6 @@ EOT
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
/** @var KernelInterface $kernel */
@@ -204,7 +196,7 @@ EOT
try {
$this->symlink($originDir, $targetDir, true);
$method = self::METHOD_RELATIVE_SYMLINK;
} catch (IOException $e) {
} catch (IOException) {
$method = $this->absoluteSymlinkWithFallback($originDir, $targetDir);
}
@@ -221,7 +213,7 @@ EOT
try {
$this->symlink($originDir, $targetDir);
$method = self::METHOD_ABSOLUTE_SYMLINK;
} catch (IOException $e) {
} catch (IOException) {
// fall back to copy
$method = $this->hardCopy($originDir, $targetDir);
}
@@ -234,7 +226,7 @@ EOT
*
* @throws IOException if link cannot be created
*/
private function symlink(string $originDir, string $targetDir, bool $relative = false)
private function symlink(string $originDir, string $targetDir, bool $relative = false): void
{
if ($relative) {
$this->filesystem->mkdir(\dirname($targetDir));

View File

@@ -26,7 +26,7 @@ use Symfony\Component\HttpKernel\KernelInterface;
*/
trait BuildDebugContainerTrait
{
protected $containerBuilder;
protected ContainerBuilder $container;
/**
* Loads the ContainerBuilder from the cache.
@@ -35,22 +35,29 @@ trait BuildDebugContainerTrait
*/
protected function getContainerBuilder(KernelInterface $kernel): ContainerBuilder
{
if ($this->containerBuilder) {
return $this->containerBuilder;
if (isset($this->container)) {
return $this->container;
}
if (!$kernel->isDebug() || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) {
if (!$kernel->isDebug() || !$kernel->getContainer()->getParameter('debug.container.dump') || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) {
$buildContainer = \Closure::bind(function () {
$this->initializeBundles();
return $this->buildContainer();
}, $kernel, \get_class($kernel));
}, $kernel, $kernel::class);
$container = $buildContainer();
$container->getCompilerPassConfig()->setRemovingPasses([]);
$container->getCompilerPassConfig()->setAfterRemovingPasses([]);
$container->compile();
} else {
(new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump'));
$buildContainer = \Closure::bind(function () {
$containerBuilder = $this->getContainerBuilder();
$this->prepareContainer($containerBuilder);
return $containerBuilder;
}, $kernel, $kernel::class);
$container = $buildContainer();
(new XmlFileLoader($container, new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump'));
$locatorPass = new ServiceLocatorTagPass();
$locatorPass->process($container);
@@ -59,6 +66,6 @@ trait BuildDebugContainerTrait
$container->getCompilerPassConfig()->setBeforeRemovingPasses([]);
}
return $this->containerBuilder = $container;
return $this->container = $container;
}
}

View File

@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
@@ -33,13 +34,11 @@ use Symfony\Component\HttpKernel\RebootableInterface;
*
* @final
*/
#[AsCommand(name: 'cache:clear', description: 'Clear the cache')]
class CacheClearCommand extends Command
{
protected static $defaultName = 'cache:clear';
protected static $defaultDescription = 'Clear the cache';
private $cacheClearer;
private $filesystem;
private CacheClearerInterface $cacheClearer;
private Filesystem $filesystem;
public function __construct(CacheClearerInterface $cacheClearer, Filesystem $filesystem = null)
{
@@ -49,17 +48,13 @@ class CacheClearCommand extends Command
$this->filesystem = $filesystem ?? new Filesystem();
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$this
->setDefinition([
new InputOption('no-warmup', '', InputOption::VALUE_NONE, 'Do not warm up the cache'),
new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command clears and warms up the application cache for a given environment
and debug mode:
@@ -71,9 +66,6 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$fs = $this->filesystem;
@@ -92,7 +84,7 @@ EOF
}
$useBuildDir = $realBuildDir !== $realCacheDir;
$oldBuildDir = substr($realBuildDir, 0, -1).('~' === substr($realBuildDir, -1) ? '+' : '~');
$oldBuildDir = substr($realBuildDir, 0, -1).(str_ends_with($realBuildDir, '~') ? '+' : '~');
if ($useBuildDir) {
$fs->remove($oldBuildDir);
@@ -122,7 +114,7 @@ EOF
// the warmup cache dir name must have the same length as the real one
// to avoid the many problems in serialized resources files
$warmupDir = substr($realBuildDir, 0, -1).('_' === substr($realBuildDir, -1) ? '-' : '_');
$warmupDir = substr($realBuildDir, 0, -1).(str_ends_with($realBuildDir, '_') ? '-' : '_');
if ($output->isVerbose() && $fs->exists($warmupDir)) {
$io->comment('Clearing outdated warmup directory...');
@@ -137,14 +129,7 @@ EOF
if ($output->isVerbose()) {
$io->comment('Warming up optional cache...');
}
$warmer = $kernel->getContainer()->get('cache_warmer');
// non optional warmers already ran during container compilation
$warmer->enableOnlyOptionalWarmers();
$preload = (array) $warmer->warmUp($realCacheDir);
if ($preload && file_exists($preloadFile = $realCacheDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) {
Preloader::append($preloadFile, $preload);
}
$this->warmupOptionals($realCacheDir, $realBuildDir, $io);
}
} else {
$fs->mkdir($warmupDir);
@@ -153,7 +138,14 @@ EOF
if ($output->isVerbose()) {
$io->comment('Warming up cache...');
}
$this->warmup($warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers'));
$this->warmup($warmupDir, $realBuildDir);
if (!$input->getOption('no-optional-warmers')) {
if ($output->isVerbose()) {
$io->comment('Warming up optional cache...');
}
$this->warmupOptionals($useBuildDir ? $realCacheDir : $warmupDir, $warmupDir, $io);
}
}
if (!$fs->exists($warmupDir.'/'.$containerDir)) {
@@ -219,7 +211,7 @@ EOF
}
}
foreach ($mounts as $mount) {
if (0 === strpos($dir, $mount)) {
if (str_starts_with($dir, $mount)) {
return true;
}
}
@@ -227,7 +219,7 @@ EOF
return false;
}
private function warmup(string $warmupDir, string $realBuildDir, bool $enableOptionalWarmers = true)
private function warmup(string $warmupDir, string $realBuildDir): void
{
// create a temporary kernel
$kernel = $this->getApplication()->getKernel();
@@ -236,18 +228,6 @@ EOF
}
$kernel->reboot($warmupDir);
// warmup temporary dir
if ($enableOptionalWarmers) {
$warmer = $kernel->getContainer()->get('cache_warmer');
// non optional warmers already ran during container compilation
$warmer->enableOnlyOptionalWarmers();
$preload = (array) $warmer->warmUp($warmupDir);
if ($preload && file_exists($preloadFile = $warmupDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) {
Preloader::append($preloadFile, $preload);
}
}
// fix references to cached files with the real cache directory name
$search = [$warmupDir, str_replace('\\', '\\\\', $warmupDir)];
$replace = str_replace('\\', '/', $realBuildDir);
@@ -258,4 +238,17 @@ EOF
}
}
}
private function warmupOptionals(string $cacheDir, string $warmupDir, SymfonyStyle $io): void
{
$kernel = $this->getApplication()->getKernel();
$warmer = $kernel->getContainer()->get('cache_warmer');
// non optional warmers already ran during container compilation
$warmer->enableOnlyOptionalWarmers();
$preload = (array) $warmer->warmUp($cacheDir, $warmupDir, $io);
if ($preload && file_exists($preloadFile = $warmupDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) {
Preloader::append($preloadFile, $preload);
}
}
}

View File

@@ -12,12 +12,14 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer;
@@ -27,13 +29,11 @@ use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer;
*
* @author Nicolas Grekas <p@tchwork.com>
*/
#[AsCommand(name: 'cache:pool:clear', description: 'Clear cache pools')]
final class CachePoolClearCommand extends Command
{
protected static $defaultName = 'cache:pool:clear';
protected static $defaultDescription = 'Clear cache pools';
private $poolClearer;
private $poolNames;
private Psr6CacheClearer $poolClearer;
private ?array $poolNames;
/**
* @param string[]|null $poolNames
@@ -46,16 +46,14 @@ final class CachePoolClearCommand extends Command
$this->poolNames = $poolNames;
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$this
->setDefinition([
new InputArgument('pools', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'A list of cache pools or cache pool clearers'),
new InputArgument('pools', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'A list of cache pools or cache pool clearers'),
])
->setDescription(self::$defaultDescription)
->addOption('all', null, InputOption::VALUE_NONE, 'Clear all cache pools')
->addOption('exclude', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'A list of cache pools or cache pool clearers to exclude')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command clears the given cache pools or cache pool clearers.
@@ -65,9 +63,6 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
@@ -75,7 +70,25 @@ EOF
$pools = [];
$clearers = [];
foreach ($input->getArgument('pools') as $id) {
$poolNames = $input->getArgument('pools');
$excludedPoolNames = $input->getOption('exclude');
if ($input->getOption('all')) {
if (!$this->poolNames) {
throw new InvalidArgumentException('Could not clear all cache pools, try specifying a specific pool or cache clearer.');
}
if (!$excludedPoolNames) {
$io->comment('Clearing all cache pools...');
}
$poolNames = $this->poolNames;
} elseif (!$poolNames) {
throw new InvalidArgumentException('Either specify at least one pool name, or provide the --all option to clear all pools.');
}
$poolNames = array_diff($poolNames, $excludedPoolNames);
foreach ($poolNames as $id) {
if ($this->poolClearer->hasPool($id)) {
$pools[$id] = $id;
} else {

View File

@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
@@ -25,13 +26,11 @@ use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer;
*
* @author Pierre du Plessis <pdples@gmail.com>
*/
#[AsCommand(name: 'cache:pool:delete', description: 'Delete an item from a cache pool')]
final class CachePoolDeleteCommand extends Command
{
protected static $defaultName = 'cache:pool:delete';
protected static $defaultDescription = 'Delete an item from a cache pool';
private $poolClearer;
private $poolNames;
private Psr6CacheClearer $poolClearer;
private ?array $poolNames;
/**
* @param string[]|null $poolNames
@@ -44,17 +43,13 @@ final class CachePoolDeleteCommand extends Command
$this->poolNames = $poolNames;
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$this
->setDefinition([
new InputArgument('pool', InputArgument::REQUIRED, 'The cache pool from which to delete an item'),
new InputArgument('key', InputArgument::REQUIRED, 'The cache key to delete from the pool'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> deletes an item from a given cache pool.
@@ -64,9 +59,6 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);

View File

@@ -0,0 +1,109 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
use Symfony\Contracts\Service\ServiceProviderInterface;
/**
* @author Kevin Bond <kevinbond@gmail.com>
*/
#[AsCommand(name: 'cache:pool:invalidate-tags', description: 'Invalidate cache tags for all or a specific pool')]
final class CachePoolInvalidateTagsCommand extends Command
{
private ServiceProviderInterface $pools;
private array $poolNames;
public function __construct(ServiceProviderInterface $pools)
{
parent::__construct();
$this->pools = $pools;
$this->poolNames = array_keys($pools->getProvidedServices());
}
protected function configure(): void
{
$this
->addArgument('tags', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'The tags to invalidate')
->addOption('pool', 'p', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'The pools to invalidate on')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command invalidates tags from taggable pools. By default, all pools
have the passed tags invalidated. Pass <info>--pool=my_pool</info> to invalidate tags on a specific pool.
php %command.full_name% tag1 tag2
php %command.full_name% tag1 tag2 --pool=cache2 --pool=cache1
EOF)
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$pools = $input->getOption('pool') ?: $this->poolNames;
$tags = $input->getArgument('tags');
$tagList = implode(', ', $tags);
$errors = false;
foreach ($pools as $name) {
$io->comment(sprintf('Invalidating tag(s): <info>%s</info> from pool <comment>%s</comment>.', $tagList, $name));
try {
$pool = $this->pools->get($name);
} catch (ServiceNotFoundException) {
$io->error(sprintf('Pool "%s" not found.', $name));
$errors = true;
continue;
}
if (!$pool instanceof TagAwareCacheInterface) {
$io->error(sprintf('Pool "%s" is not taggable.', $name));
$errors = true;
continue;
}
if (!$pool->invalidateTags($tags)) {
$io->error(sprintf('Cache tag(s) "%s" could not be invalidated for pool "%s".', $tagList, $name));
$errors = true;
}
}
if ($errors) {
$io->error('Done but with errors.');
return 1;
}
$io->success('Successfully invalidated cache tags.');
return 0;
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestOptionValuesFor('pool')) {
$suggestions->suggestValues($this->poolNames);
}
}
}

View File

@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -21,12 +22,10 @@ use Symfony\Component\Console\Style\SymfonyStyle;
*
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
#[AsCommand(name: 'cache:pool:list', description: 'List available cache pools')]
final class CachePoolListCommand extends Command
{
protected static $defaultName = 'cache:pool:list';
protected static $defaultDescription = 'List available cache pools';
private $poolNames;
private array $poolNames;
/**
* @param string[] $poolNames
@@ -38,13 +37,9 @@ final class CachePoolListCommand extends Command
$this->poolNames = $poolNames;
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$this
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command lists all available cache pools.
EOF
@@ -52,16 +47,11 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->table(['Pool name'], array_map(function ($pool) {
return [$pool];
}, $this->poolNames));
$io->table(['Pool name'], array_map(fn ($pool) => [$pool], $this->poolNames));
return 0;
}

View File

@@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -22,12 +23,10 @@ use Symfony\Component\Console\Style\SymfonyStyle;
*
* @author Rob Frawley 2nd <rmf@src.run>
*/
#[AsCommand(name: 'cache:pool:prune', description: 'Prune cache pools')]
final class CachePoolPruneCommand extends Command
{
protected static $defaultName = 'cache:pool:prune';
protected static $defaultDescription = 'Prune cache pools';
private $pools;
private iterable $pools;
/**
* @param iterable<mixed, PruneableInterface> $pools
@@ -39,13 +38,9 @@ final class CachePoolPruneCommand extends Command
$this->pools = $pools;
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$this
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command deletes all expired items from all pruneable pools.
@@ -55,9 +50,6 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);

View File

@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -18,6 +19,7 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Dumper\Preloader;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
/**
* Warmup the cache.
@@ -26,12 +28,10 @@ use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate;
*
* @final
*/
#[AsCommand(name: 'cache:warmup', description: 'Warm up an empty cache')]
class CacheWarmupCommand extends Command
{
protected static $defaultName = 'cache:warmup';
protected static $defaultDescription = 'Warm up an empty cache';
private $cacheWarmer;
private CacheWarmerAggregate $cacheWarmer;
public function __construct(CacheWarmerAggregate $cacheWarmer)
{
@@ -40,16 +40,12 @@ class CacheWarmupCommand extends Command
$this->cacheWarmer = $cacheWarmer;
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$this
->setDefinition([
new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command warms up the cache.
@@ -60,9 +56,6 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
@@ -73,10 +66,16 @@ EOF
if (!$input->getOption('no-optional-warmers')) {
$this->cacheWarmer->enableOptionalWarmers();
}
$cacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir');
$preload = $this->cacheWarmer->warmUp($cacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir'));
if ($kernel instanceof WarmableInterface) {
$kernel->warmUp($cacheDir);
}
if ($preload && file_exists($preloadFile = $cacheDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) {
$preload = $this->cacheWarmer->warmUp($cacheDir);
$buildDir = $kernel->getContainer()->getParameter('kernel.build_dir');
if ($preload && $cacheDir === $buildDir && file_exists($preloadFile = $buildDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) {
Preloader::append($preloadFile, $preload);
}

View File

@@ -13,11 +13,14 @@ namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Compiler\ValidateEnvPlaceholdersPass;
@@ -33,23 +36,22 @@ use Symfony\Component\Yaml\Yaml;
*
* @final
*/
#[AsCommand(name: 'debug:config', description: 'Dump the current configuration for an extension')]
class ConfigDebugCommand extends AbstractConfigCommand
{
protected static $defaultName = 'debug:config';
protected static $defaultDescription = 'Dump the current configuration for an extension';
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$commentedHelpFormats = array_map(fn ($format) => sprintf('<comment>%s</comment>', $format), $this->getAvailableFormatOptions());
$helpFormats = implode('", "', $commentedHelpFormats);
$this
->setDefinition([
new InputArgument('name', InputArgument::OPTIONAL, 'The bundle name or the extension alias'),
new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'),
new InputOption('resolve-env', null, InputOption::VALUE_NONE, 'Display resolved environment variable values instead of placeholders'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), class_exists(Yaml::class) ? 'txt' : 'json'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
->setHelp(<<<EOF
The <info>%command.name%</info> command dumps the current configuration for an
extension/bundle.
@@ -58,6 +60,11 @@ Either the extension alias or bundle name can be used:
<info>php %command.full_name% framework</info>
<info>php %command.full_name% FrameworkBundle</info>
The <info>--format</info> option specifies the format of the configuration,
these are "{$helpFormats}".
<info>php %command.full_name% framework --format=json</info>
For dumping a specific option, add its path as second argument:
<info>php %command.full_name% framework serializer.enabled</info>
@@ -67,9 +74,6 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
@@ -77,14 +81,7 @@ EOF
if (null === $name = $input->getArgument('name')) {
$this->listBundles($errorIo);
$kernel = $this->getApplication()->getKernel();
if ($kernel instanceof ExtensionInterface
&& ($kernel instanceof ConfigurationInterface || $kernel instanceof ConfigurationExtensionInterface)
&& $kernel->getAlias()
) {
$errorIo->table(['Kernel Extension'], [[$kernel->getAlias()]]);
}
$this->listNonBundleExtensions($errorIo);
$errorIo->comment('Provide the name of a bundle as the first argument of this command to dump its configuration. (e.g. <comment>debug:config FrameworkBundle</comment>)');
$errorIo->comment('For dumping a specific option, add its path as the second argument of this command. (e.g. <comment>debug:config FrameworkBundle serializer</comment> to dump the <comment>framework.serializer</comment> configuration)');
@@ -96,14 +93,24 @@ EOF
$extensionAlias = $extension->getAlias();
$container = $this->compileContainer();
$config = $this->getConfig($extension, $container);
$config = $this->getConfig($extension, $container, $input->getOption('resolve-env'));
$format = $input->getOption('format');
if (\in_array($format, ['txt', 'yml'], true) && !class_exists(Yaml::class)) {
$errorIo->error('Setting the "format" option to "txt" or "yaml" requires the Symfony Yaml component. Try running "composer install symfony/yaml" or use "--format=json" instead.');
return 1;
}
if (null === $path = $input->getArgument('path')) {
$io->title(
sprintf('Current configuration for %s', $name === $extensionAlias ? sprintf('extension with alias "%s"', $extensionAlias) : sprintf('"%s"', $name))
);
if ('txt' === $input->getOption('format')) {
$io->title(
sprintf('Current configuration for %s', $name === $extensionAlias ? sprintf('extension with alias "%s"', $extensionAlias) : sprintf('"%s"', $name))
);
}
$io->writeln(Yaml::dump([$extensionAlias => $config], 10));
$io->writeln($this->convertToFormat([$extensionAlias => $config], $format));
return 0;
}
@@ -118,18 +125,26 @@ EOF
$io->title(sprintf('Current configuration for "%s.%s"', $extensionAlias, $path));
$io->writeln(Yaml::dump($config, 10));
$io->writeln($this->convertToFormat($config, $format));
return 0;
}
private function convertToFormat(mixed $config, string $format): string
{
return match ($format) {
'txt', 'yaml' => Yaml::dump($config, 10),
'json' => json_encode($config, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE),
default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))),
};
}
private function compileContainer(): ContainerBuilder
{
$kernel = clone $this->getApplication()->getKernel();
$kernel->boot();
$method = new \ReflectionMethod($kernel, 'buildContainer');
$method->setAccessible(true);
$container = $method->invoke($kernel);
$container->getCompiler()->compile($container);
@@ -140,10 +155,8 @@ EOF
* Iterate over configuration until the last step of the given path.
*
* @throws LogicException If the configuration does not exist
*
* @return mixed
*/
private function getConfigForPath(array $config, string $path, string $alias)
private function getConfigForPath(array $config, string $path, string $alias): mixed
{
$steps = explode('.', $path);
@@ -176,12 +189,12 @@ EOF
// Fall back to default config if the extension has one
if (!$extension instanceof ConfigurationExtensionInterface) {
if (!$extension instanceof ConfigurationExtensionInterface && !$extension instanceof ConfigurationInterface) {
throw new \LogicException(sprintf('The extension with alias "%s" does not have configuration.', $extensionAlias));
}
$configs = $container->getExtensionConfig($extensionAlias);
$configuration = $extension->getConfiguration($configs, $container);
$configuration = $extension instanceof ConfigurationInterface ? $extension : $extension->getConfiguration($configs, $container);
$this->validateConfiguration($extension, $configuration);
return (new Processor())->processConfiguration($configuration, $configs);
@@ -190,7 +203,8 @@ EOF
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('name')) {
$suggestions->suggestValues($this->getAvailableBundles(!preg_match('/^[A-Z]/', $input->getCompletionValue())));
$suggestions->suggestValues($this->getAvailableExtensions());
$suggestions->suggestValues($this->getAvailableBundles());
return;
}
@@ -200,27 +214,43 @@ EOF
$config = $this->getConfig($this->findExtension($name), $this->compileContainer());
$paths = array_keys(self::buildPathsCompletion($config));
$suggestions->suggestValues($paths);
} catch (LogicException $e) {
} catch (LogicException) {
}
}
if ($input->mustSuggestOptionValuesFor('format')) {
$suggestions->suggestValues($this->getAvailableFormatOptions());
}
}
private function getAvailableBundles(bool $alias): array
private function getAvailableExtensions(): array
{
$kernel = $this->getApplication()->getKernel();
$extensions = [];
foreach ($this->getContainerBuilder($kernel)->getExtensions() as $alias => $extension) {
$extensions[] = $alias;
}
return $extensions;
}
private function getAvailableBundles(): array
{
$availableBundles = [];
foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) {
$availableBundles[] = $alias ? $bundle->getContainerExtension()->getAlias() : $bundle->getName();
$availableBundles[] = $bundle->getName();
}
return $availableBundles;
}
private function getConfig(ExtensionInterface $extension, ContainerBuilder $container)
private function getConfig(ExtensionInterface $extension, ContainerBuilder $container, bool $resolveEnvs = false): mixed
{
return $container->resolveEnvPlaceholders(
$container->getParameterBag()->resolveValue(
$this->getConfigForExtension($extension, $container)
)
), $resolveEnvs ?: null
);
}
@@ -229,7 +259,7 @@ EOF
$completionPaths = [];
foreach ($paths as $key => $values) {
if (\is_array($values)) {
$completionPaths = $completionPaths + self::buildPathsCompletion($values, $prefix.$key.'.');
$completionPaths += self::buildPathsCompletion($values, $prefix.$key.'.');
} else {
$completionPaths[$prefix.$key] = null;
}
@@ -237,4 +267,9 @@ EOF
return $completionPaths;
}
private function getAvailableFormatOptions(): array
{
return ['txt', 'yaml', 'json'];
}
}

View File

@@ -14,6 +14,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Dumper\XmlReferenceDumper;
use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Exception\InvalidArgumentException;
@@ -22,8 +23,6 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\Yaml\Yaml;
/**
@@ -35,24 +34,21 @@ use Symfony\Component\Yaml\Yaml;
*
* @final
*/
#[AsCommand(name: 'config:dump-reference', description: 'Dump the default configuration for an extension')]
class ConfigDumpReferenceCommand extends AbstractConfigCommand
{
protected static $defaultName = 'config:dump-reference';
protected static $defaultDescription = 'Dump the default configuration for an extension';
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$commentedHelpFormats = array_map(fn ($format) => sprintf('<comment>%s</comment>', $format), $this->getAvailableFormatOptions());
$helpFormats = implode('", "', $commentedHelpFormats);
$this
->setDefinition([
new InputArgument('name', InputArgument::OPTIONAL, 'The Bundle name or the extension alias'),
new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (yaml or xml)', 'yaml'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'yaml'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
->setHelp(<<<EOF
The <info>%command.name%</info> command dumps the default configuration for an
extension/bundle.
@@ -61,9 +57,8 @@ Either the extension alias or bundle name can be used:
<info>php %command.full_name% framework</info>
<info>php %command.full_name% FrameworkBundle</info>
With the <info>--format</info> option specifies the format of the configuration,
this is either <comment>yaml</comment> or <comment>xml</comment>.
When the option is not provided, <comment>yaml</comment> is used.
The <info>--format</info> option specifies the format of the configuration,
these are "{$helpFormats}".
<info>php %command.full_name% FrameworkBundle --format=xml</info>
@@ -77,8 +72,6 @@ EOF
}
/**
* {@inheritdoc}
*
* @throws \LogicException
*/
protected function execute(InputInterface $input, OutputInterface $output): int
@@ -88,14 +81,7 @@ EOF
if (null === $name = $input->getArgument('name')) {
$this->listBundles($errorIo);
$kernel = $this->getApplication()->getKernel();
if ($kernel instanceof ExtensionInterface
&& ($kernel instanceof ConfigurationInterface || $kernel instanceof ConfigurationExtensionInterface)
&& $kernel->getAlias()
) {
$errorIo->table(['Kernel Extension'], [[$kernel->getAlias()]]);
}
$this->listNonBundleExtensions($errorIo);
$errorIo->comment([
'Provide the name of a bundle as the first argument of this command to dump its default configuration. (e.g. <comment>config:dump-reference FrameworkBundle</comment>)',
@@ -152,7 +138,7 @@ EOF
break;
default:
$io->writeln($message);
throw new InvalidArgumentException('Only the yaml and xml formats are supported.');
throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions())));
}
$io->writeln(null === $path ? $dumper->dump($configuration, $extension->getNamespace()) : $dumper->dumpAtPath($configuration, $path));
@@ -163,6 +149,7 @@ EOF
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('name')) {
$suggestions->suggestValues($this->getAvailableExtensions());
$suggestions->suggestValues($this->getAvailableBundles());
}
@@ -171,13 +158,24 @@ EOF
}
}
private function getAvailableExtensions(): array
{
$kernel = $this->getApplication()->getKernel();
$extensions = [];
foreach ($this->getContainerBuilder($kernel)->getExtensions() as $alias => $extension) {
$extensions[] = $alias;
}
return $extensions;
}
private function getAvailableBundles(): array
{
$bundles = [];
foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) {
$bundles[] = $bundle->getName();
$bundles[] = $bundle->getContainerExtension()->getAlias();
}
return $bundles;

View File

@@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
@@ -32,17 +33,12 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
*
* @internal
*/
#[AsCommand(name: 'debug:container', description: 'Display current services for an application')]
class ContainerDebugCommand extends Command
{
use BuildDebugContainerTrait;
protected static $defaultName = 'debug:container';
protected static $defaultDescription = 'Display current services for an application';
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$this
->setDefinition([
@@ -56,11 +52,10 @@ class ContainerDebugCommand extends Command
new InputOption('types', null, InputOption::VALUE_NONE, 'Display types (classes/interfaces) available in the container'),
new InputOption('env-var', null, InputOption::VALUE_REQUIRED, 'Display a specific environment variable used in the container'),
new InputOption('env-vars', null, InputOption::VALUE_NONE, 'Display environment variables used in the container'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'),
new InputOption('deprecations', null, InputOption::VALUE_NONE, 'Display deprecations generated when compiling and warming up the container'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays all configured <comment>public</comment> services:
@@ -116,9 +111,6 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
@@ -134,19 +126,26 @@ EOF
$options = ['env-vars' => true, 'name' => $envVar];
} elseif ($input->getOption('types')) {
$options = [];
$options['filter'] = [$this, 'filterToServiceTypes'];
$options['filter'] = $this->filterToServiceTypes(...);
} elseif ($input->getOption('parameters')) {
$parameters = [];
foreach ($object->getParameterBag()->all() as $k => $v) {
$parameterBag = $object->getParameterBag();
foreach ($parameterBag->all() as $k => $v) {
$parameters[$k] = $object->resolveEnvPlaceholders($v);
}
$object = new ParameterBag($parameters);
if ($parameterBag instanceof ParameterBag) {
foreach ($parameterBag->allDeprecated() as $k => $deprecation) {
$object->deprecate($k, ...$deprecation);
}
}
$options = [];
} elseif ($parameter = $input->getOption('parameter')) {
$options = ['parameter' => $parameter];
} elseif ($input->getOption('tags')) {
$options = ['group_by' => 'tags'];
} elseif ($tag = $input->getOption('tag')) {
$tag = $this->findProperTagName($input, $errorIo, $object, $tag);
$options = ['tag' => $tag];
} elseif ($name = $input->getArgument('name')) {
$name = $this->findProperServiceName($input, $errorIo, $object, $name, $input->getOption('show-hidden'));
@@ -168,6 +167,21 @@ EOF
try {
$helper->describe($io, $object, $options);
if ('txt' === $options['format'] && isset($options['id'])) {
if ($object->hasDefinition($options['id'])) {
$definition = $object->getDefinition($options['id']);
if ($definition->isDeprecated()) {
$errorIo->warning($definition->getDeprecation($options['id'])['message'] ?? sprintf('The "%s" service is deprecated.', $options['id']));
}
}
if ($object->hasAlias($options['id'])) {
$alias = $object->getAlias($options['id']);
if ($alias->isDeprecated()) {
$errorIo->warning($alias->getDeprecation($options['id'])['message'] ?? sprintf('The "%s" alias is deprecated.', $options['id']));
}
}
}
if (isset($options['id']) && isset($kernel->getContainer()->getRemovedIds()[$options['id']])) {
$errorIo->note(sprintf('The "%s" service or alias has been removed or inlined when the container was compiled.', $options['id']));
}
@@ -195,8 +209,7 @@ EOF
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestOptionValuesFor('format')) {
$helper = new DescriptorHelper();
$suggestions->suggestValues($helper->getFormats());
$suggestions->suggestValues($this->getAvailableFormatOptions());
return;
}
@@ -235,7 +248,7 @@ EOF
*
* @throws \InvalidArgumentException
*/
protected function validateInput(InputInterface $input)
protected function validateInput(InputInterface $input): void
{
$options = ['tags', 'tag', 'parameters', 'parameter'];
@@ -254,16 +267,16 @@ EOF
}
}
private function findProperServiceName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $builder, string $name, bool $showHidden): string
private function findProperServiceName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $container, string $name, bool $showHidden): string
{
$name = ltrim($name, '\\');
if ($builder->has($name) || !$input->isInteractive()) {
if ($container->has($name) || !$input->isInteractive()) {
return $name;
}
$matchingServices = $this->findServiceIdsContaining($builder, $name, $showHidden);
if (empty($matchingServices)) {
$matchingServices = $this->findServiceIdsContaining($container, $name, $showHidden);
if (!$matchingServices) {
throw new InvalidArgumentException(sprintf('No services found that match "%s".', $name));
}
@@ -274,14 +287,35 @@ EOF
return $io->choice('Select one of the following services to display its information', $matchingServices);
}
private function findServiceIdsContaining(ContainerBuilder $builder, string $name, bool $showHidden): array
private function findProperTagName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $container, string $tagName): string
{
$serviceIds = $builder->getServiceIds();
if (\in_array($tagName, $container->findTags(), true) || !$input->isInteractive()) {
return $tagName;
}
$matchingTags = $this->findTagsContaining($container, $tagName);
if (!$matchingTags) {
throw new InvalidArgumentException(sprintf('No tags found that match "%s".', $tagName));
}
if (1 === \count($matchingTags)) {
return $matchingTags[0];
}
return $io->choice('Select one of the following tags to display its information', $matchingTags);
}
private function findServiceIdsContaining(ContainerBuilder $container, string $name, bool $showHidden): array
{
$serviceIds = $container->getServiceIds();
$foundServiceIds = $foundServiceIdsIgnoringBackslashes = [];
foreach ($serviceIds as $serviceId) {
if (!$showHidden && str_starts_with($serviceId, '.')) {
continue;
}
if (!$showHidden && $container->hasDefinition($serviceId) && $container->getDefinition($serviceId)->hasTag('container.excluded')) {
continue;
}
if (false !== stripos(str_replace('\\', '', $serviceId), $name)) {
$foundServiceIdsIgnoringBackslashes[] = $serviceId;
}
@@ -293,6 +327,19 @@ EOF
return $foundServiceIds ?: $foundServiceIdsIgnoringBackslashes;
}
private function findTagsContaining(ContainerBuilder $container, string $tagName): array
{
$tags = $container->findTags();
$foundTags = [];
foreach ($tags as $tag) {
if (str_contains($tag, $tagName)) {
$foundTags[] = $tag;
}
}
return $foundTags;
}
/**
* @internal
*/
@@ -310,4 +357,9 @@ EOF
return class_exists($serviceId) || interface_exists($serviceId, false);
}
private function getAvailableFormatOptions(): array
{
return (new DescriptorHelper())->getFormats();
}
}

View File

@@ -13,6 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
@@ -20,6 +21,7 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\Compiler\ResolveFactoryClassPass;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
@@ -27,30 +29,18 @@ use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\HttpKernel\Kernel;
#[AsCommand(name: 'lint:container', description: 'Ensure that arguments injected into services match type declarations')]
final class ContainerLintCommand extends Command
{
protected static $defaultName = 'lint:container';
protected static $defaultDescription = 'Ensure that arguments injected into services match type declarations';
private ContainerBuilder $container;
/**
* @var ContainerBuilder
*/
private $containerBuilder;
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$this
->setDescription(self::$defaultDescription)
->setHelp('This command parses service definitions and ensures that injected values match the type declarations of each services\' class.')
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
@@ -81,14 +71,14 @@ final class ContainerLintCommand extends Command
private function getContainerBuilder(): ContainerBuilder
{
if ($this->containerBuilder) {
return $this->containerBuilder;
if (isset($this->container)) {
return $this->container;
}
$kernel = $this->getApplication()->getKernel();
$kernelContainer = $kernel->getContainer();
if (!$kernel->isDebug() || !(new ConfigCache($kernelContainer->getParameter('debug.container.dump'), true))->isFresh()) {
if (!$kernel->isDebug() || !$kernelContainer->getParameter('debug.container.dump') || !(new ConfigCache($kernelContainer->getParameter('debug.container.dump'), true))->isFresh()) {
if (!$kernel instanceof Kernel) {
throw new RuntimeException(sprintf('This command does not support the application kernel: "%s" does not extend "%s".', get_debug_type($kernel), Kernel::class));
}
@@ -97,10 +87,8 @@ final class ContainerLintCommand extends Command
$this->initializeBundles();
return $this->buildContainer();
}, $kernel, \get_class($kernel));
}, $kernel, $kernel::class);
$container = $buildContainer();
$skippedIds = [];
} else {
if (!$kernelContainer instanceof Container) {
throw new RuntimeException(sprintf('This command does not support the application container: "%s" does not extend "%s".', get_debug_type($kernelContainer), Container::class));
@@ -109,26 +97,18 @@ final class ContainerLintCommand extends Command
(new XmlFileLoader($container = new ContainerBuilder($parameterBag = new EnvPlaceholderParameterBag()), new FileLocator()))->load($kernelContainer->getParameter('debug.container.dump'));
$refl = new \ReflectionProperty($parameterBag, 'resolved');
$refl->setAccessible(true);
$refl->setValue($parameterBag, true);
$skippedIds = [];
foreach ($container->getServiceIds() as $serviceId) {
if (str_starts_with($serviceId, '.errored.')) {
$skippedIds[$serviceId] = true;
}
}
$container->getCompilerPassConfig()->setBeforeOptimizationPasses([]);
$container->getCompilerPassConfig()->setOptimizationPasses([]);
$container->getCompilerPassConfig()->setOptimizationPasses([new ResolveFactoryClassPass()]);
$container->getCompilerPassConfig()->setBeforeRemovingPasses([]);
}
$container->setParameter('container.build_hash', 'lint_container');
$container->setParameter('container.build_id', 'lint_container');
$container->addCompilerPass(new CheckTypeDeclarationsPass(true, $skippedIds), PassConfig::TYPE_AFTER_REMOVING, -100);
$container->addCompilerPass(new CheckTypeDeclarationsPass(true), PassConfig::TYPE_AFTER_REMOVING, -100);
return $this->containerBuilder = $container;
return $this->container = $container;
}
}

View File

@@ -12,15 +12,16 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Console\Descriptor\Descriptor;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
use Symfony\Component\DependencyInjection\Attribute\Target;
use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
/**
* A console command for autowiring information.
@@ -29,32 +30,24 @@ use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
*
* @internal
*/
#[AsCommand(name: 'debug:autowiring', description: 'List classes/interfaces you can use for autowiring')]
class DebugAutowiringCommand extends ContainerDebugCommand
{
protected static $defaultName = 'debug:autowiring';
protected static $defaultDescription = 'List classes/interfaces you can use for autowiring';
private $supportsHref;
private $fileLinkFormatter;
private ?FileLinkFormatter $fileLinkFormatter;
public function __construct(string $name = null, FileLinkFormatter $fileLinkFormatter = null)
{
$this->supportsHref = method_exists(OutputFormatterStyle::class, 'setHref');
$this->fileLinkFormatter = $fileLinkFormatter;
parent::__construct($name);
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$this
->setDefinition([
new InputArgument('search', InputArgument::OPTIONAL, 'A search filter'),
new InputOption('all', null, InputOption::VALUE_NONE, 'Show also services that are not aliased'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays the classes and interfaces that
you can use as type-hints for autowiring:
@@ -70,32 +63,35 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$errorIo = $io->getErrorStyle();
$builder = $this->getContainerBuilder($this->getApplication()->getKernel());
$serviceIds = $builder->getServiceIds();
$serviceIds = array_filter($serviceIds, [$this, 'filterToServiceTypes']);
$container = $this->getContainerBuilder($this->getApplication()->getKernel());
$serviceIds = $container->getServiceIds();
$serviceIds = array_filter($serviceIds, $this->filterToServiceTypes(...));
if ($search = $input->getArgument('search')) {
$searchNormalized = preg_replace('/[^a-zA-Z0-9\x7f-\xff $]++/', '', $search);
$serviceIds = array_filter($serviceIds, function ($serviceId) use ($searchNormalized) {
return false !== stripos(str_replace('\\', '', $serviceId), $searchNormalized) && !str_starts_with($serviceId, '.');
});
$serviceIds = array_filter($serviceIds, fn ($serviceId) => false !== stripos(str_replace('\\', '', $serviceId), $searchNormalized) && !str_starts_with($serviceId, '.'));
if (empty($serviceIds)) {
if (!$serviceIds) {
$errorIo->error(sprintf('No autowirable classes or interfaces found matching "%s"', $search));
return 1;
}
}
$reverseAliases = [];
foreach ($container->getAliases() as $id => $alias) {
if ('.' === ($id[0] ?? null)) {
$reverseAliases[(string) $alias][] = $id;
}
}
uasort($serviceIds, 'strnatcmp');
$io->title('Autowirable Types');
@@ -108,28 +104,53 @@ EOF
$previousId = '-';
$serviceIdsNb = 0;
foreach ($serviceIds as $serviceId) {
if ($container->hasDefinition($serviceId) && $container->getDefinition($serviceId)->hasTag('container.excluded')) {
continue;
}
$text = [];
$resolvedServiceId = $serviceId;
if (!str_starts_with($serviceId, $previousId)) {
if (!str_starts_with($serviceId, $previousId.' $')) {
$text[] = '';
if ('' !== $description = Descriptor::getClassDescription($serviceId, $resolvedServiceId)) {
if (isset($hasAlias[$serviceId])) {
$previousId = preg_replace('/ \$.*/', '', $serviceId);
if ('' !== $description = Descriptor::getClassDescription($previousId, $resolvedServiceId)) {
if (isset($hasAlias[$previousId])) {
continue;
}
$text[] = $description;
}
$previousId = $serviceId.' $';
}
$serviceLine = sprintf('<fg=yellow>%s</>', $serviceId);
if ($this->supportsHref && '' !== $fileLink = $this->getFileLink($serviceId)) {
$serviceLine = sprintf('<fg=yellow;href=%s>%s</>', $fileLink, $serviceId);
if ('' !== $fileLink = $this->getFileLink($previousId)) {
$serviceLine = substr($serviceId, \strlen($previousId));
$serviceLine = sprintf('<fg=yellow;href=%s>%s</>', $fileLink, $previousId).('' !== $serviceLine ? sprintf('<fg=yellow>%s</>', $serviceLine) : '');
}
if ($builder->hasAlias($serviceId)) {
if ($container->hasAlias($serviceId)) {
$hasAlias[$serviceId] = true;
$serviceAlias = $builder->getAlias($serviceId);
$serviceLine .= ' <fg=cyan>('.$serviceAlias.')</>';
$serviceAlias = $container->getAlias($serviceId);
$alias = (string) $serviceAlias;
$target = null;
foreach ($reverseAliases[(string) $serviceAlias] ?? [] as $id) {
if (!str_starts_with($id, '.'.$previousId.' $')) {
continue;
}
$target = substr($id, \strlen($previousId) + 3);
if ($previousId.' $'.(new Target($target))->getParsedName() === $serviceId) {
$serviceLine .= ' - <fg=magenta>target:</><fg=cyan>'.$target.'</>';
break;
}
}
if ($container->hasDefinition($serviceAlias) && $decorated = $container->getDefinition($serviceAlias)->getTag('container.decorator')) {
$alias = $decorated[0]['id'];
}
if ($alias !== $target) {
$serviceLine .= ' - <fg=magenta>alias:</><fg=cyan>'.$alias.'</>';
}
if ($serviceAlias->isDeprecated()) {
$serviceLine .= ' - <fg=magenta>deprecated</>';
@@ -137,6 +158,8 @@ EOF
} elseif (!$all) {
++$serviceIdsNb;
continue;
} elseif ($container->getDefinition($serviceId)->isDeprecated()) {
$serviceLine .= ' - <fg=magenta>deprecated</>';
}
$text[] = $serviceLine;
$io->text($text);
@@ -169,9 +192,9 @@ EOF
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('search')) {
$builder = $this->getContainerBuilder($this->getApplication()->getKernel());
$container = $this->getContainerBuilder($this->getApplication()->getKernel());
$suggestions->suggestValues(array_filter($builder->getServiceIds(), [$this, 'filterToServiceTypes']));
$suggestions->suggestValues(array_filter($container->getServiceIds(), $this->filterToServiceTypes(...)));
}
}
}

View File

@@ -13,6 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command;
use Psr\Container\ContainerInterface;
use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
@@ -31,13 +32,12 @@ use Symfony\Contracts\Service\ServiceProviderInterface;
*
* @final
*/
#[AsCommand(name: 'debug:event-dispatcher', description: 'Display configured listeners for an application')]
class EventDispatcherDebugCommand extends Command
{
private const DEFAULT_DISPATCHER = 'event_dispatcher';
protected static $defaultName = 'debug:event-dispatcher';
protected static $defaultDescription = 'Display configured listeners for an application';
private $dispatchers;
private ContainerInterface $dispatchers;
public function __construct(ContainerInterface $dispatchers)
{
@@ -46,19 +46,15 @@ class EventDispatcherDebugCommand extends Command
$this->dispatchers = $dispatchers;
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$this
->setDefinition([
new InputArgument('event', InputArgument::OPTIONAL, 'An event name or a part of the event name'),
new InputOption('dispatcher', null, InputOption::VALUE_REQUIRED, 'To view events of a specific event dispatcher', self::DEFAULT_DISPATCHER),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays all configured listeners:
@@ -73,8 +69,6 @@ EOF
}
/**
* {@inheritdoc}
*
* @throws \LogicException
*/
protected function execute(InputInterface $input, OutputInterface $output): int
@@ -144,7 +138,7 @@ EOF
}
if ($input->mustSuggestOptionValuesFor('format')) {
$suggestions->suggestValues((new DescriptorHelper())->getFormats());
$suggestions->suggestValues($this->getAvailableFormatOptions());
}
}
@@ -161,4 +155,9 @@ EOF
return $output;
}
private function getAvailableFormatOptions(): array
{
return (new DescriptorHelper())->getFormats();
}
}

View File

@@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
@@ -21,7 +22,7 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouterInterface;
@@ -33,14 +34,13 @@ use Symfony\Component\Routing\RouterInterface;
*
* @final
*/
#[AsCommand(name: 'debug:router', description: 'Display current routes for an application')]
class RouterDebugCommand extends Command
{
use BuildDebugContainerTrait;
protected static $defaultName = 'debug:router';
protected static $defaultDescription = 'Display current routes for an application';
private $router;
private $fileLinkFormatter;
private RouterInterface $router;
private ?FileLinkFormatter $fileLinkFormatter;
public function __construct(RouterInterface $router, FileLinkFormatter $fileLinkFormatter = null)
{
@@ -50,19 +50,16 @@ class RouterDebugCommand extends Command
$this->fileLinkFormatter = $fileLinkFormatter;
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$this
->setDefinition([
new InputArgument('name', InputArgument::OPTIONAL, 'A route name'),
new InputOption('show-controllers', null, InputOption::VALUE_NONE, 'Show assigned controllers in overview'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('show-aliases', null, InputOption::VALUE_NONE, 'Show aliases in overview'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw route(s)'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> displays the configured routes:
@@ -74,8 +71,6 @@ EOF
}
/**
* {@inheritdoc}
*
* @throws InvalidArgumentException When route does not exist
*/
protected function execute(InputInterface $input, OutputInterface $output): int
@@ -86,9 +81,7 @@ EOF
$routes = $this->router->getRouteCollection();
$container = null;
if ($this->fileLinkFormatter) {
$container = function () {
return $this->getContainerBuilder($this->getApplication()->getKernel());
};
$container = fn () => $this->getContainerBuilder($this->getApplication()->getKernel());
}
if ($name) {
@@ -100,6 +93,7 @@ EOF
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
'show_controllers' => $input->getOption('show-controllers'),
'show_aliases' => $input->getOption('show-aliases'),
'output' => $io,
]);
@@ -128,6 +122,7 @@ EOF
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
'show_controllers' => $input->getOption('show-controllers'),
'show_aliases' => $input->getOption('show-aliases'),
'output' => $io,
'container' => $container,
]);
@@ -157,8 +152,7 @@ EOF
}
if ($input->mustSuggestOptionValuesFor('format')) {
$helper = new DescriptorHelper();
$suggestions->suggestValues($helper->getFormats());
$suggestions->suggestValues($this->getAvailableFormatOptions());
}
}
@@ -173,4 +167,9 @@ EOF
return $foundRoutes;
}
private function getAvailableFormatOptions(): array
{
return (new DescriptorHelper())->getFormats();
}
}

View File

@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
@@ -29,13 +30,11 @@ use Symfony\Component\Routing\RouterInterface;
*
* @final
*/
#[AsCommand(name: 'router:match', description: 'Help debug routes by simulating a path info match')]
class RouterMatchCommand extends Command
{
protected static $defaultName = 'router:match';
protected static $defaultDescription = 'Help debug routes by simulating a path info match';
private $router;
private $expressionLanguageProviders;
private RouterInterface $router;
private iterable $expressionLanguageProviders;
/**
* @param iterable<mixed, ExpressionFunctionProviderInterface> $expressionLanguageProviders
@@ -48,10 +47,7 @@ class RouterMatchCommand extends Command
$this->expressionLanguageProviders = $expressionLanguageProviders;
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$this
->setDefinition([
@@ -60,7 +56,6 @@ class RouterMatchCommand extends Command
new InputOption('scheme', null, InputOption::VALUE_REQUIRED, 'Set the URI scheme (usually http or https)'),
new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Set the URI host'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> shows which routes match a given request and which don't and for what reason:
@@ -75,9 +70,6 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);

View File

@@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -24,13 +25,11 @@ use Symfony\Component\Console\Style\SymfonyStyle;
*
* @internal
*/
#[AsCommand(name: 'secrets:decrypt-to-local', description: 'Decrypt all secrets and stores them in the local vault')]
final class SecretsDecryptToLocalCommand extends Command
{
protected static $defaultName = 'secrets:decrypt-to-local';
protected static $defaultDescription = 'Decrypt all secrets and stores them in the local vault';
private $vault;
private $localVault;
private AbstractVault $vault;
private ?AbstractVault $localVault;
public function __construct(AbstractVault $vault, AbstractVault $localVault = null)
{
@@ -40,10 +39,9 @@ final class SecretsDecryptToLocalCommand extends Command
parent::__construct();
}
protected function configure()
protected function configure(): void
{
$this
->setDescription(self::$defaultDescription)
->addOption('force', 'f', InputOption::VALUE_NONE, 'Force overriding of secrets that already exist in the local vault')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command decrypts all secrets and copies them in the local vault.

View File

@@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
@@ -23,13 +24,11 @@ use Symfony\Component\Console\Style\SymfonyStyle;
*
* @internal
*/
#[AsCommand(name: 'secrets:encrypt-from-local', description: 'Encrypt all local secrets to the vault')]
final class SecretsEncryptFromLocalCommand extends Command
{
protected static $defaultName = 'secrets:encrypt-from-local';
protected static $defaultDescription = 'Encrypt all local secrets to the vault';
private $vault;
private $localVault;
private AbstractVault $vault;
private ?AbstractVault $localVault;
public function __construct(AbstractVault $vault, AbstractVault $localVault = null)
{
@@ -39,10 +38,9 @@ final class SecretsEncryptFromLocalCommand extends Command
parent::__construct();
}
protected function configure()
protected function configure(): void
{
$this
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command encrypts all locally overridden secrets to the vault.

View File

@@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -26,13 +27,11 @@ use Symfony\Component\Console\Style\SymfonyStyle;
*
* @internal
*/
#[AsCommand(name: 'secrets:generate-keys', description: 'Generate new encryption keys')]
final class SecretsGenerateKeysCommand extends Command
{
protected static $defaultName = 'secrets:generate-keys';
protected static $defaultDescription = 'Generate new encryption keys';
private $vault;
private $localVault;
private AbstractVault $vault;
private ?AbstractVault $localVault;
public function __construct(AbstractVault $vault, AbstractVault $localVault = null)
{
@@ -42,10 +41,9 @@ final class SecretsGenerateKeysCommand extends Command
parent::__construct();
}
protected function configure()
protected function configure(): void
{
$this
->setDescription(self::$defaultDescription)
->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.')
->addOption('rotate', 'r', InputOption::VALUE_NONE, 'Re-encrypt existing secrets with the newly generated keys.')
->setHelp(<<<'EOF'

View File

@@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Dumper;
use Symfony\Component\Console\Input\InputInterface;
@@ -27,13 +28,11 @@ use Symfony\Component\Console\Style\SymfonyStyle;
*
* @internal
*/
#[AsCommand(name: 'secrets:list', description: 'List all secrets')]
final class SecretsListCommand extends Command
{
protected static $defaultName = 'secrets:list';
protected static $defaultDescription = 'List all secrets';
private $vault;
private $localVault;
private AbstractVault $vault;
private ?AbstractVault $localVault;
public function __construct(AbstractVault $vault, AbstractVault $localVault = null)
{
@@ -43,10 +42,9 @@ final class SecretsListCommand extends Command
parent::__construct();
}
protected function configure()
protected function configure(): void
{
$this
->setDescription(self::$defaultDescription)
->addOption('reveal', 'r', InputOption::VALUE_NONE, 'Display decrypted values alongside names')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command list all stored secrets.
@@ -72,14 +70,12 @@ EOF
}
$secrets = $this->vault->list($reveal);
$localSecrets = null !== $this->localVault ? $this->localVault->list($reveal) : null;
$localSecrets = $this->localVault?->list($reveal);
$rows = [];
$dump = new Dumper($output);
$dump = static function (?string $v) use ($dump) {
return null === $v ? '******' : $dump($v);
};
$dump = fn ($v) => null === $v ? '******' : $dump($v);
foreach ($secrets as $name => $value) {
$rows[$name] = [$name, $dump($value)];

View File

@@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
@@ -28,13 +29,11 @@ use Symfony\Component\Console\Style\SymfonyStyle;
*
* @internal
*/
#[AsCommand(name: 'secrets:remove', description: 'Remove a secret from the vault')]
final class SecretsRemoveCommand extends Command
{
protected static $defaultName = 'secrets:remove';
protected static $defaultDescription = 'Remove a secret from the vault';
private $vault;
private $localVault;
private AbstractVault $vault;
private ?AbstractVault $localVault;
public function __construct(AbstractVault $vault, AbstractVault $localVault = null)
{
@@ -44,10 +43,9 @@ final class SecretsRemoveCommand extends Command
parent::__construct();
}
protected function configure()
protected function configure(): void
{
$this
->setDescription(self::$defaultDescription)
->addArgument('name', InputArgument::REQUIRED, 'The name of the secret')
->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.')
->setHelp(<<<'EOF'

View File

@@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
@@ -29,13 +30,11 @@ use Symfony\Component\Console\Style\SymfonyStyle;
*
* @internal
*/
#[AsCommand(name: 'secrets:set', description: 'Set a secret in the vault')]
final class SecretsSetCommand extends Command
{
protected static $defaultName = 'secrets:set';
protected static $defaultDescription = 'Set a secret in the vault';
private $vault;
private $localVault;
private AbstractVault $vault;
private ?AbstractVault $localVault;
public function __construct(AbstractVault $vault, AbstractVault $localVault = null)
{
@@ -45,10 +44,9 @@ final class SecretsSetCommand extends Command
parent::__construct();
}
protected function configure()
protected function configure(): void
{
$this
->setDescription(self::$defaultDescription)
->addArgument('name', InputArgument::REQUIRED, 'The name of the secret')
->addArgument('file', InputArgument::OPTIONAL, 'A file where to read the secret from or "-" for reading from STDIN')
->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.')

View File

@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
@@ -38,6 +39,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*
* @final
*/
#[AsCommand(name: 'debug:translation', description: 'Display translation messages information')]
class TranslationDebugCommand extends Command
{
public const EXIT_CODE_GENERAL_ERROR = 64;
@@ -48,17 +50,14 @@ class TranslationDebugCommand extends Command
public const MESSAGE_UNUSED = 1;
public const MESSAGE_EQUALS_FALLBACK = 2;
protected static $defaultName = 'debug:translation';
protected static $defaultDescription = 'Display translation messages information';
private $translator;
private $reader;
private $extractor;
private $defaultTransPath;
private $defaultViewsPath;
private $transPaths;
private $codePaths;
private $enabledLocales;
private TranslatorInterface $translator;
private TranslationReaderInterface $reader;
private ExtractorInterface $extractor;
private ?string $defaultTransPath;
private ?string $defaultViewsPath;
private array $transPaths;
private array $codePaths;
private array $enabledLocales;
public function __construct(TranslatorInterface $translator, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = [])
{
@@ -74,10 +73,7 @@ class TranslationDebugCommand extends Command
$this->enabledLocales = $enabledLocales;
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$this
->setDefinition([
@@ -88,7 +84,6 @@ class TranslationDebugCommand extends Command
new InputOption('only-unused', null, InputOption::VALUE_NONE, 'Display only unused messages'),
new InputOption('all', null, InputOption::VALUE_NONE, 'Load messages from all registered bundles'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command helps finding unused or missing translation
messages and comparing them with the fallback ones by inspecting the
@@ -123,9 +118,6 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
@@ -155,7 +147,7 @@ EOF
if ($this->defaultViewsPath) {
$codePaths[] = $this->defaultViewsPath;
}
} catch (\InvalidArgumentException $e) {
} catch (\InvalidArgumentException) {
// such a bundle does not exist, so treat the argument as path
$path = $input->getArgument('bundle');
@@ -188,7 +180,7 @@ EOF
}
// No defined or extracted messages
if (empty($allMessages) || null !== $domain && empty($allMessages[$domain])) {
if (!$allMessages || null !== $domain && empty($allMessages[$domain])) {
$outputMessage = sprintf('No defined or extracted messages for locale "%s"', $locale);
if (null !== $domain) {
@@ -220,14 +212,14 @@ EOF
$states[] = self::MESSAGE_MISSING;
if (!$input->getOption('only-unused')) {
$exitCode = $exitCode | self::EXIT_CODE_MISSING;
$exitCode |= self::EXIT_CODE_MISSING;
}
}
} elseif ($currentCatalogue->defines($messageId, $domain)) {
$states[] = self::MESSAGE_UNUSED;
if (!$input->getOption('only-missing')) {
$exitCode = $exitCode | self::EXIT_CODE_UNUSED;
$exitCode |= self::EXIT_CODE_UNUSED;
}
}
@@ -241,7 +233,7 @@ EOF
if ($fallbackCatalogue->defines($messageId, $domain) && $value === $fallbackCatalogue->get($messageId, $domain)) {
$states[] = self::MESSAGE_EQUALS_FALLBACK;
$exitCode = $exitCode | self::EXIT_CODE_FALLBACK;
$exitCode |= self::EXIT_CODE_FALLBACK;
break;
}

View File

@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
@@ -38,6 +39,7 @@ use Symfony\Component\Translation\Writer\TranslationWriterInterface;
*
* @final
*/
#[AsCommand(name: 'translation:extract', description: 'Extract missing translations keys from code to translation files')]
class TranslationUpdateCommand extends Command
{
private const ASC = 'asc';
@@ -48,18 +50,15 @@ class TranslationUpdateCommand extends Command
'xlf20' => ['xlf', '2.0'],
];
protected static $defaultName = 'translation:extract|translation:update';
protected static $defaultDescription = 'Extract missing translations keys from code to translation files.';
private $writer;
private $reader;
private $extractor;
private $defaultLocale;
private $defaultTransPath;
private $defaultViewsPath;
private $transPaths;
private $codePaths;
private $enabledLocales;
private TranslationWriterInterface $writer;
private TranslationReaderInterface $reader;
private ExtractorInterface $extractor;
private string $defaultLocale;
private ?string $defaultTransPath;
private ?string $defaultViewsPath;
private array $transPaths;
private array $codePaths;
private array $enabledLocales;
public function __construct(TranslationWriterInterface $writer, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultLocale, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = [])
{
@@ -76,27 +75,21 @@ class TranslationUpdateCommand extends Command
$this->enabledLocales = $enabledLocales;
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$this
->setDefinition([
new InputArgument('locale', InputArgument::REQUIRED, 'The locale'),
new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages'),
new InputOption('prefix', null, InputOption::VALUE_OPTIONAL, 'Override the default prefix', '__'),
new InputOption('output-format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format (deprecated)'),
new InputOption('format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format', 'xlf12'),
new InputOption('dump-messages', null, InputOption::VALUE_NONE, 'Should the messages be dumped in the console'),
new InputOption('force', null, InputOption::VALUE_NONE, 'Should the extract be done'),
new InputOption('clean', null, InputOption::VALUE_NONE, 'Should clean not found messages'),
new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'Specify the domain to extract'),
new InputOption('xliff-version', null, InputOption::VALUE_OPTIONAL, 'Override the default xliff version (deprecated)'),
new InputOption('sort', null, InputOption::VALUE_OPTIONAL, 'Return list of messages sorted alphabetically', 'asc'),
new InputOption('sort', null, InputOption::VALUE_OPTIONAL, 'Return list of messages sorted alphabetically (only works with --dump-messages)', 'asc'),
new InputOption('as-tree', null, InputOption::VALUE_OPTIONAL, 'Dump the messages as a tree-like structure: The given value defines the level where to switch to inline YAML'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command extracts translation strings from templates
of a given bundle or the default translations directory. It can display them or merge
@@ -123,16 +116,12 @@ You can sort the output with the <comment>--sort</> flag:
You can dump a tree-like structure using the yaml format with <comment>--as-tree</> flag:
<info>php %command.full_name% --force --format=yaml --as-tree=3 en AcmeBundle</info>
<info>php %command.full_name% --force --format=yaml --sort=asc --as-tree=3 fr</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
@@ -152,18 +141,10 @@ EOF
return 1;
}
$format = $input->getOption('output-format') ?: $input->getOption('format');
$xliffVersion = $input->getOption('xliff-version') ?? '1.2';
$format = $input->getOption('format');
$xliffVersion = '1.2';
if ($input->getOption('xliff-version')) {
$errorIo->warning(sprintf('The "--xliff-version" option is deprecated since version 5.3, use "--format=xlf%d" instead.', 10 * $xliffVersion));
}
if ($input->getOption('output-format')) {
$errorIo->warning(sprintf('The "--output-format" option is deprecated since version 5.3, use "--format=xlf%d" instead.', 10 * $xliffVersion));
}
if (\in_array($format, array_keys(self::FORMATS), true)) {
if (\array_key_exists($format, self::FORMATS)) {
[$format, $xliffVersion] = self::FORMATS[$format];
}
@@ -198,7 +179,7 @@ EOF
$codePaths[] = $this->defaultViewsPath;
}
$currentName = $foundBundle->getName();
} catch (\InvalidArgumentException $e) {
} catch (\InvalidArgumentException) {
// such a bundle does not exist, so treat the argument as path
$path = $input->getArgument('bundle');
@@ -251,12 +232,8 @@ EOF
$list = array_merge(
array_diff($allKeys, $newKeys),
array_map(function ($id) {
return sprintf('<fg=green>%s</>', $id);
}, $newKeys),
array_map(function ($id) {
return sprintf('<fg=red>%s</>', $id);
}, array_keys($operation->getObsoleteMessages($domain)))
array_map(fn ($id) => sprintf('<fg=green>%s</>', $id), $newKeys),
array_map(fn ($id) => sprintf('<fg=red>%s</>', $id), array_keys($operation->getObsoleteMessages($domain)))
);
$domainMessagesCount = \count($list);

View File

@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
@@ -19,28 +20,31 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Workflow\Definition;
use Symfony\Component\Workflow\Dumper\GraphvizDumper;
use Symfony\Component\Workflow\Dumper\MermaidDumper;
use Symfony\Component\Workflow\Dumper\PlantUmlDumper;
use Symfony\Component\Workflow\Dumper\StateMachineGraphvizDumper;
use Symfony\Component\Workflow\Marking;
use Symfony\Component\Workflow\StateMachine;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @final
*/
#[AsCommand(name: 'workflow:dump', description: 'Dump a workflow')]
class WorkflowDumpCommand extends Command
{
protected static $defaultName = 'workflow:dump';
protected static $defaultDescription = 'Dump a workflow';
/**
* string is the service id.
*
* @var array<string, Definition>
*/
private $workflows = [];
private array $definitions = [];
private ServiceLocator $workflows;
private const DUMP_FORMAT_OPTIONS = [
'puml',
@@ -48,26 +52,30 @@ class WorkflowDumpCommand extends Command
'dot',
];
public function __construct(array $workflows)
public function __construct($workflows)
{
parent::__construct();
$this->workflows = $workflows;
if ($workflows instanceof ServiceLocator) {
$this->workflows = $workflows;
} elseif (\is_array($workflows)) {
$this->definitions = $workflows;
trigger_deprecation('symfony/framework-bundle', '6.2', 'Passing an array of definitions in "%s()" is deprecated. Inject a ServiceLocator filled with all workflows instead.', __METHOD__);
} else {
throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be an array or a ServiceLocator, "%s" given.', __METHOD__, \gettype($workflows)));
}
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$this
->setDefinition([
new InputArgument('name', InputArgument::REQUIRED, 'A workflow name'),
new InputArgument('marking', InputArgument::IS_ARRAY, 'A marking (a list of places)'),
new InputOption('label', 'l', InputOption::VALUE_REQUIRED, 'Label a graph'),
new InputOption('with-metadata', null, InputOption::VALUE_NONE, 'Include the workflow\'s metadata in the dumped graph', null),
new InputOption('dump-format', null, InputOption::VALUE_REQUIRED, 'The dump format ['.implode('|', self::DUMP_FORMAT_OPTIONS).']', 'dot'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command dumps the graphical representation of a
workflow in different formats
@@ -80,24 +88,26 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$workflowName = $input->getArgument('name');
$workflow = null;
if (isset($this->workflows['workflow.'.$workflowName])) {
$workflow = $this->workflows['workflow.'.$workflowName];
if (isset($this->workflows)) {
if (!$this->workflows->has($workflowName)) {
throw new InvalidArgumentException(sprintf('The workflow named "%s" cannot be found.', $workflowName));
}
$workflow = $this->workflows->get($workflowName);
$type = $workflow instanceof StateMachine ? 'state_machine' : 'workflow';
$definition = $workflow->getDefinition();
} elseif (isset($this->definitions['workflow.'.$workflowName])) {
$definition = $this->definitions['workflow.'.$workflowName];
$type = 'workflow';
} elseif (isset($this->workflows['state_machine.'.$workflowName])) {
$workflow = $this->workflows['state_machine.'.$workflowName];
} elseif (isset($this->definitions['state_machine.'.$workflowName])) {
$definition = $this->definitions['state_machine.'.$workflowName];
$type = 'state_machine';
}
if (null === $workflow) {
if (null === $definition) {
throw new InvalidArgumentException(sprintf('No service found for "workflow.%1$s" nor "state_machine.%1$s".', $workflowName));
}
@@ -125,12 +135,11 @@ EOF
$options = [
'name' => $workflowName,
'with-metadata' => $input->getOption('with-metadata'),
'nofooter' => true,
'graph' => [
'label' => $input->getOption('label'),
],
'label' => $input->getOption('label'),
];
$output->writeln($dumper->dump($workflow, $marking, $options));
$output->writeln($dumper->dump($definition, $marking, $options));
return 0;
}
@@ -138,7 +147,11 @@ EOF
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('name')) {
$suggestions->suggestValues(array_keys($this->workflows));
if (isset($this->workflows)) {
$suggestions->suggestValues(array_keys($this->workflows->getProvidedServices()));
} else {
$suggestions->suggestValues(array_keys($this->definitions));
}
}
if ($input->mustSuggestOptionValuesFor('dump-format')) {

View File

@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Translation\Command\XliffLintCommand as BaseLintCommand;
/**
@@ -22,11 +23,9 @@ use Symfony\Component\Translation\Command\XliffLintCommand as BaseLintCommand;
*
* @final
*/
#[AsCommand(name: 'lint:xliff', description: 'Lint an XLIFF file and outputs encountered errors')]
class XliffLintCommand extends BaseLintCommand
{
protected static $defaultName = 'lint:xliff';
protected static $defaultDescription = 'Lints an XLIFF file and outputs encountered errors';
public function __construct()
{
$directoryIteratorProvider = function ($directory, $default) {
@@ -37,17 +36,12 @@ class XliffLintCommand extends BaseLintCommand
return $default($directory);
};
$isReadableProvider = function ($fileOrDirectory, $default) {
return str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory);
};
$isReadableProvider = fn ($fileOrDirectory, $default) => str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory);
parent::__construct(null, $directoryIteratorProvider, $isReadableProvider);
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
parent::configure();

View File

@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Yaml\Command\LintCommand as BaseLintCommand;
/**
@@ -21,11 +22,9 @@ use Symfony\Component\Yaml\Command\LintCommand as BaseLintCommand;
*
* @final
*/
#[AsCommand(name: 'lint:yaml', description: 'Lint a YAML file and outputs encountered errors')]
class YamlLintCommand extends BaseLintCommand
{
protected static $defaultName = 'lint:yaml';
protected static $defaultDescription = 'Lint a YAML file and outputs encountered errors';
public function __construct()
{
$directoryIteratorProvider = function ($directory, $default) {
@@ -36,17 +35,12 @@ class YamlLintCommand extends BaseLintCommand
return $default($directory);
};
$isReadableProvider = function ($fileOrDirectory, $default) {
return str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory);
};
$isReadableProvider = fn ($fileOrDirectory, $default) => str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory);
parent::__construct(null, $directoryIteratorProvider, $isReadableProvider);
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
parent::configure();