diff --git a/.tools/psalm/baseline.xml b/.tools/psalm/baseline.xml
index 2a21c2b792..f0a9eeac41 100644
--- a/.tools/psalm/baseline.xml
+++ b/.tools/psalm/baseline.xml
@@ -1604,9 +1604,6 @@
-
-
-
@@ -1676,7 +1673,7 @@
- getArgument('table')]]>
+
@@ -1770,11 +1767,6 @@
-
-
-
-
-
diff --git a/rector.php b/rector.php
index 82c7528094..0765c5b47c 100644
--- a/rector.php
+++ b/rector.php
@@ -416,8 +416,6 @@
new MethodCallRename(Addon\Addon::class, 'getInstalledPackages', 'getInstalledAddons'),
new MethodCallRename(Addon\Addon::class, 'getAvailablePackages', 'getAvailableAddons'),
new MethodCallRename(Addon\Addon::class, 'getSetupPackages', 'getSetupAddons'),
- new MethodCallRename(Console\Command\AbstractCommand::class, 'getPackage', 'getAddon'),
- new MethodCallRename(Console\Command\AbstractCommand::class, 'setPackage', 'setAddon'),
new MethodCallRename(ApiFunction\Result::class, 'toJSON', 'toJson'),
new MethodCallRename(ApiFunction\Result::class, 'fromJSON', 'fromJson'),
@@ -524,6 +522,8 @@
new MethodCallToPropertyFetch(ApiFunction\Result::class, 'getMessage', 'message'),
new MethodCallToPropertyFetch(ApiFunction\Result::class, 'requiresReboot', 'requiresReboot'),
+ new MethodCallToPropertyFetch(Console\Command\AbstractCommand::class, 'getPackage', 'addon'),
+
new MethodCallToPropertyFetch(Cronjob\Type\AbstractType::class, 'getMessage', 'message'),
new MethodCallToPropertyFetch(Cronjob\Type\AbstractType::class, 'hasMessage', 'message'),
new MethodCallToPropertyFetch(Cronjob\CronjobExecutor::class, 'getMessage', 'message'),
diff --git a/schemas/package.json b/schemas/package.json
index 7f5127fdff..558380f7f7 100644
--- a/schemas/package.json
+++ b/schemas/package.json
@@ -23,13 +23,6 @@
"default_config": {
"description": "Default values for Redaxo\\Core\\Config",
"type": "object"
- },
- "console_commands": {
- "description": "Console command names and their corresponding class",
- "type": "object",
- "additionalProperties": {
- "type": "string"
- }
}
},
"additionalProperties": true,
diff --git a/src/Console/Application.php b/src/Console/Application.php
index 64f8be8d9d..866c9802fb 100644
--- a/src/Console/Application.php
+++ b/src/Console/Application.php
@@ -18,6 +18,7 @@
use Redaxo\Core\Filesystem\Path;
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Command\LazyCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
@@ -68,6 +69,11 @@ public function doRun(InputInterface $input, OutputInterface $output): int
#[Override]
protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output): int
{
+ // Unwrap the LazyCommand returned by the CommandLoader to get the actual command instance.
+ if ($command instanceof LazyCommand) {
+ $command = $command->getCommand();
+ }
+
if ($command instanceof AbstractCommand) {
$this->loadPackages($command);
}
@@ -105,7 +111,7 @@ private function loadPackages(AbstractCommand $command): void
if ('ydeploy:migrate' === $command->getName()) {
// boot only the ydeploy package, which provides the migrate command
- $command->getAddon()->boot();
+ $command->addon->boot();
return;
}
diff --git a/src/Console/Command/AbstractCommand.php b/src/Console/Command/AbstractCommand.php
index c5836dc829..44be940d3c 100644
--- a/src/Console/Command/AbstractCommand.php
+++ b/src/Console/Command/AbstractCommand.php
@@ -3,32 +3,25 @@
namespace Redaxo\Core\Console\Command;
use Redaxo\Core\Addon\Addon;
+use Redaxo\Core\Exception\LogicException;
+use ReflectionObject;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\Console\Style\SymfonyStyle;
+use function sprintf;
+use function str_starts_with;
+
+use const DIRECTORY_SEPARATOR;
use const ENT_QUOTES;
abstract class AbstractCommand extends Command
{
- protected ?Addon $addon = null;
-
- /** @internal */
- public function setAddon(?Addon $addon = null): void
- {
- $this->addon = $addon;
- }
-
- /** @return Addon|null In core commands it returns `null`, otherwise the corresponding addon object */
- public function getAddon(): ?Addon
- {
- return $this->addon;
- }
-
- protected function getStyle(InputInterface $input, OutputInterface $output): SymfonyStyle
- {
- return new SymfonyStyle($input, $output);
+ /**
+ * The addon the command belongs to, resolved lazily from the location of the command class.
+ *
+ * Only available for addon commands; reading it on a core command throws a {@see LogicException}.
+ */
+ public private(set) Addon $addon {
+ get => $this->addon ??= $this->resolveAddon();
}
/**
@@ -45,4 +38,21 @@ protected function decodeMessage(string $message): string
return htmlspecialchars_decode($message, ENT_QUOTES);
}
+
+ private function resolveAddon(): Addon
+ {
+ $file = new ReflectionObject($this)->getFileName();
+
+ if (false !== $file) {
+ $file = realpath($file) ?: $file;
+
+ foreach (Addon::getAvailableAddons() as $addon) {
+ if (str_starts_with($file, $addon->path . DIRECTORY_SEPARATOR)) {
+ return $addon;
+ }
+ }
+ }
+
+ throw new LogicException(sprintf('Command "%s" does not belong to an addon.', $this::class));
+ }
}
diff --git a/src/Console/Command/AddonActivateCommand.php b/src/Console/Command/AddonActivateCommand.php
index f350846615..d04fc049d7 100644
--- a/src/Console/Command/AddonActivateCommand.php
+++ b/src/Console/Command/AddonActivateCommand.php
@@ -2,53 +2,32 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\Addon\Addon;
use Redaxo\Core\Addon\AddonManager;
+use Symfony\Component\Console\Attribute\Argument;
+use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputArgument;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
/**
* @internal
*/
-class AddonActivateCommand extends AbstractCommand
+#[AsCommand(name: 'addon:activate', description: 'Activates the selected addon')]
+final class AddonActivateCommand extends AbstractCommand
{
- #[Override]
- protected function configure(): void
- {
- $this
- ->setDescription('Activates the selected addon')
- ->addArgument('addon-id', InputArgument::REQUIRED, 'The id of the addon, e.g. "yform"', null, static function () {
- $packageNames = [];
-
- foreach (Addon::getRegisteredAddons() as $package) {
- if ($package->isAvailable()) {
- continue;
- }
-
- $packageNames[] = $package->name;
- }
-
- return $packageNames;
- });
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = $this->getStyle($input, $output);
-
- $packageId = $input->getArgument('addon-id');
-
+ public function __invoke(
+ SymfonyStyle $io,
+ #[Argument('The id of the addon, e.g. "yform"', suggestedValues: static function (): array {
+ return array_keys(array_filter(Addon::getRegisteredAddons(), static fn (Addon $addon): bool => !$addon->isAvailable()));
+ })] string $addon,
+ ): int {
// the package manager don't know new packages in the addon folder
// so we need to make them available
AddonManager::synchronizeWithFileSystem();
- $package = Addon::get($packageId);
+ $package = Addon::get($addon);
if (!$package) {
- $io->error('Addon "' . $packageId . '" doesn\'t exists!');
+ $io->error('Addon "' . $addon . '" doesn\'t exists!');
return Command::FAILURE;
}
diff --git a/src/Console/Command/AddonDeactivateCommand.php b/src/Console/Command/AddonDeactivateCommand.php
index c893268ff7..aee5a6ded1 100644
--- a/src/Console/Command/AddonDeactivateCommand.php
+++ b/src/Console/Command/AddonDeactivateCommand.php
@@ -2,53 +2,32 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\Addon\Addon;
use Redaxo\Core\Addon\AddonManager;
+use Symfony\Component\Console\Attribute\Argument;
+use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputArgument;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
/**
* @internal
*/
-class AddonDeactivateCommand extends AbstractCommand
+#[AsCommand(name: 'addon:deactivate', description: 'Deactivates the selected addon')]
+final class AddonDeactivateCommand extends AbstractCommand
{
- #[Override]
- protected function configure(): void
- {
- $this
- ->setDescription('Deactivates the selected addon')
- ->addArgument('addon-id', InputArgument::REQUIRED, 'The id of the addon, e.g. "yform"', null, static function () {
- $packageNames = [];
-
- foreach (Addon::getRegisteredAddons() as $package) {
- if (!$package->isAvailable()) {
- continue;
- }
-
- $packageNames[] = $package->name;
- }
-
- return $packageNames;
- });
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = $this->getStyle($input, $output);
-
- $packageId = $input->getArgument('addon-id');
-
+ public function __invoke(
+ SymfonyStyle $io,
+ #[Argument('The name of the addon, e.g. "yform"', suggestedValues: static function (): array {
+ return array_keys(Addon::getAvailableAddons());
+ })] string $addon,
+ ): int {
// the package manager don't know new packages in the addon folder
// so we need to make them available
AddonManager::synchronizeWithFileSystem();
- $package = Addon::get($packageId);
+ $package = Addon::get($addon);
if (!$package) {
- $io->error('Addon "' . $packageId . '" doesn\'t exists!');
+ $io->error('Addon "' . $addon . '" doesn\'t exists!');
return Command::FAILURE;
}
diff --git a/src/Console/Command/AddonInstallCommand.php b/src/Console/Command/AddonInstallCommand.php
index 2de21aec3a..2a45c572ec 100644
--- a/src/Console/Command/AddonInstallCommand.php
+++ b/src/Console/Command/AddonInstallCommand.php
@@ -2,58 +2,45 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\Addon\Addon;
use Redaxo\Core\Addon\AddonManager;
+use Symfony\Component\Console\Attribute\Argument;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
-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\Question\ConfirmationQuestion;
+use Symfony\Component\Console\Style\SymfonyStyle;
/**
* @internal
*/
-class AddonInstallCommand extends AbstractCommand
+#[AsCommand(name: 'addon:install', description: 'Installs the selected addon')]
+final class AddonInstallCommand extends AbstractCommand
{
- #[Override]
- protected function configure(): void
- {
- $this
- ->setDescription('Installs the selected addon')
- ->addArgument('addon-id', InputArgument::REQUIRED, 'The id of the addon, e.g. "yform"', null, static function () {
- $packageNames = [];
-
- foreach (Addon::getRegisteredAddons() as $package) {
- // allow all packages, because we support --re-intall for already installed ones
- $packageNames[] = $package->name;
- }
-
- return $packageNames;
- })
- ->addOption('re-install', '-r', InputOption::VALUE_NONE, 'Allows to reinstall the addon without asking the User');
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = $this->getStyle($input, $output);
-
- $packageId = $input->getArgument('addon-id');
-
+ public function __invoke(
+ InputInterface $input,
+ OutputInterface $output,
+ SymfonyStyle $io,
+ #[Argument('The name of the addon, e.g. "yform"', suggestedValues: static function (): array {
+ // allow all packages, because we support --re-intall for already installed ones
+ return array_keys(Addon::getRegisteredAddons());
+ })] string $addon,
+ #[Option('Allows to reinstall the addon without asking the User', shortcut: 'r')] bool $reInstall = false,
+ ): int {
// the package manager don't know new packages in the addon folder
// so we need to make them available
AddonManager::synchronizeWithFileSystem();
- $package = Addon::get($packageId);
+ $package = Addon::get($addon);
if (!$package) {
- $io->error('Addon "' . $packageId . '" doesn\'t exists!');
+ $io->error('Addon "' . $addon . '" doesn\'t exists!');
return Command::FAILURE;
}
- if ($package->isInstalled() && !$input->getOption('re-install')) {
+ if ($package->isInstalled() && !$reInstall) {
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Addon "' . $package->name . '" is already installed. Should it be reinstalled? (y/n) ', false);
diff --git a/src/Console/Command/AddonListCommand.php b/src/Console/Command/AddonListCommand.php
index 583d219deb..fc4d68952a 100644
--- a/src/Console/Command/AddonListCommand.php
+++ b/src/Console/Command/AddonListCommand.php
@@ -2,52 +2,34 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\Addon\Addon;
use Redaxo\Core\Addon\AddonManager;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Command\Command;
-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 function count;
/**
* @internal
*/
-class AddonListCommand extends AbstractCommand
+#[AsCommand(name: 'addon:list', description: 'List available addons')]
+final class AddonListCommand extends AbstractCommand
{
- #[Override]
- protected function configure(): void
- {
- $this
- ->setDescription('List available addons')
- ->addOption('search', 's', InputOption::VALUE_REQUIRED, 'filter list')
- ->addOption('addon', 'p', InputOption::VALUE_REQUIRED, 'search for exactly this addon-id')
- ->addOption('installed-only', 'i', InputOption::VALUE_NONE, 'only list installed addons')
- ->addOption('activated-only', 'a', InputOption::VALUE_NONE, 'only list active addons')
- ->addOption('error-when-empty', null, InputOption::VALUE_NONE, 'if no addon matches your filter the command exits with error-code 1, otherwise with 0')
- ->addOption('json', null, InputOption::VALUE_NONE, 'output table as json')
- ;
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = $this->getStyle($input, $output);
-
+ public function __invoke(
+ SymfonyStyle $io,
+ #[Option('filter list', shortcut: 's')] ?string $search = null,
+ #[Option('search for exactly this addon', shortcut: 'p')] ?string $addon = null,
+ #[Option('only list installed addons', shortcut: 'i')] bool $installedOnly = false,
+ #[Option('only list active addons', shortcut: 'a')] bool $activatedOnly = false,
+ #[Option('if no addon matches your filter the command exits with error-code 1, otherwise with 0')] bool $errorWhenEmpty = false,
+ #[Option('output table as json')] bool $json = false,
+ ): int {
// the package manager don't know new packages in the addon folder
// so we need to make them available
AddonManager::synchronizeWithFileSystem();
- $search = $input->getOption('search');
- $packageId = $input->getOption('addon');
-
- $installedOnly = false !== $input->getOption('installed-only');
- $activatedOnly = false !== $input->getOption('activated-only');
- $jsonOutput = false !== $input->getOption('json');
- $usingExitCode = false !== $input->getOption('error-when-empty');
-
$packages = Addon::getRegisteredAddons();
$rows = [];
@@ -61,12 +43,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int
'license' => $package->getLicense(),
];
- if (!$jsonOutput) {
+ if (!$json) {
$rowdata['installed'] = $rowdata['installed'] ? 'yes' : 'no';
$rowdata['activated'] = $rowdata['activated'] ? 'yes' : 'no';
}
- if (null !== $packageId && $packageId !== $package->name) {
+ if (null !== $addon && $addon !== $package->name) {
continue;
}
@@ -85,12 +67,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$rows[] = $rowdata;
}
- if ($jsonOutput) {
+ if ($json) {
$io->writeln(json_encode($rows));
- return $usingExitCode && 0 === count($rows) ? Command::FAILURE : Command::SUCCESS;
+ return $errorWhenEmpty && 0 === count($rows) ? Command::FAILURE : Command::SUCCESS;
}
$io->table(['addon-id', 'author', 'version', 'installed', 'activated', 'license'], $rows);
- return $usingExitCode && 0 === count($rows) ? Command::FAILURE : Command::SUCCESS;
+ return $errorWhenEmpty && 0 === count($rows) ? Command::FAILURE : Command::SUCCESS;
}
}
diff --git a/src/Console/Command/AddonUninstallCommand.php b/src/Console/Command/AddonUninstallCommand.php
index 65b75809cf..ed0e3189e2 100644
--- a/src/Console/Command/AddonUninstallCommand.php
+++ b/src/Console/Command/AddonUninstallCommand.php
@@ -2,53 +2,32 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\Addon\Addon;
use Redaxo\Core\Addon\AddonManager;
+use Symfony\Component\Console\Attribute\Argument;
+use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputArgument;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
/**
* @internal
*/
-class AddonUninstallCommand extends AbstractCommand
+#[AsCommand(name: 'addon:uninstall', description: 'Uninstalls the selected addon')]
+final class AddonUninstallCommand extends AbstractCommand
{
- #[Override]
- protected function configure(): void
- {
- $this
- ->setDescription('Uninstalls the selected addon')
- ->addArgument('addon-id', InputArgument::REQUIRED, 'The id of the addon, e.g. "yform"', null, static function () {
- $packageNames = [];
-
- foreach (Addon::getRegisteredAddons() as $package) {
- if (!$package->isInstalled()) {
- continue;
- }
-
- $packageNames[] = $package->name;
- }
-
- return $packageNames;
- });
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = $this->getStyle($input, $output);
-
- $packageId = $input->getArgument('addon-id');
-
+ public function __invoke(
+ SymfonyStyle $io,
+ #[Argument('The name of the addon, e.g. "yform"', suggestedValues: static function (): array {
+ return array_keys(Addon::getInstalledAddons());
+ })] string $addon,
+ ): int {
// the package manager don't know new packages in the addon folder
// so we need to make them available
AddonManager::synchronizeWithFileSystem();
- $package = Addon::get($packageId);
+ $package = Addon::get($addon);
if (!$package) {
- $io->error('Addon "' . $packageId . '" doesn\'t exists!');
+ $io->error('Addon "' . $addon . '" doesn\'t exists!');
return Command::FAILURE;
}
diff --git a/src/Console/Command/AssetsCompileStylesCommand.php b/src/Console/Command/AssetsCompileStylesCommand.php
index 81b263c886..5aa95b8674 100644
--- a/src/Console/Command/AssetsCompileStylesCommand.php
+++ b/src/Console/Command/AssetsCompileStylesCommand.php
@@ -2,27 +2,19 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\Backend\Style;
+use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
/**
* @internal
*/
-class AssetsCompileStylesCommand extends AbstractCommand
+#[AsCommand(name: 'assets:compile-styles', description: 'Converts Backend SCSS files to CSS')]
+final class AssetsCompileStylesCommand extends AbstractCommand
{
- #[Override]
- protected function configure(): void
+ public function __invoke(SymfonyStyle $io): int
{
- $this->setDescription('Converts Backend SCSS files to CSS');
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = $this->getStyle($input, $output);
$io->title('Backend style scss compiler');
Style::compile();
diff --git a/src/Console/Command/AssetsSyncCommand.php b/src/Console/Command/AssetsSyncCommand.php
index 6c61b2ac7f..14d1e4dcb9 100644
--- a/src/Console/Command/AssetsSyncCommand.php
+++ b/src/Console/Command/AssetsSyncCommand.php
@@ -8,9 +8,8 @@
use Redaxo\Core\Filesystem\File;
use Redaxo\Core\Filesystem\Finder;
use Redaxo\Core\Filesystem\Path;
+use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use function sprintf;
@@ -18,13 +17,13 @@
/**
* @internal
*/
-class AssetsSyncCommand extends AbstractCommand
+#[AsCommand(name: 'assets:sync', description: 'Sync assets within the assets-dir with the sources-dir')]
+final class AssetsSyncCommand extends AbstractCommand
{
#[Override]
protected function configure(): void
{
$this
- ->setDescription('Sync assets within the assets-dir with the sources-dir')
->setHelp(sprintf(
'Sync folders and files of /%s with source assets folders',
rtrim(Path::relative(Path::assets()), '/'),
@@ -32,11 +31,9 @@ protected function configure(): void
;
}
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
+ public function __invoke(SymfonyStyle $io): int
{
$created = $updated = $errored = 0;
- $io = $this->getStyle($input, $output);
foreach (Addon::getInstalledAddons() as $package) {
$assetsPublicPath = $package->getAssetsPath();
diff --git a/src/Console/Command/AvailableInSetupInterface.php b/src/Console/Command/AvailableInSetupInterface.php
new file mode 100644
index 0000000000..4a170c0336
--- /dev/null
+++ b/src/Console/Command/AvailableInSetupInterface.php
@@ -0,0 +1,12 @@
+setDescription('Clears the redaxo core cache');
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
+ public function __invoke(SymfonyStyle $io): int
{
$successMsg = Cache::delete();
- $io = $this->getStyle($input, $output);
$io->success($this->decodeMessage($successMsg));
return Command::SUCCESS;
diff --git a/src/Console/Command/ConfigGetCommand.php b/src/Console/Command/ConfigGetCommand.php
index 38bd09eeda..8df95436d6 100644
--- a/src/Console/Command/ConfigGetCommand.php
+++ b/src/Console/Command/ConfigGetCommand.php
@@ -2,40 +2,31 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\Addon\Addon;
use Redaxo\Core\Core;
+use Symfony\Component\Console\Attribute\Argument;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Command\Command;
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 function is_array;
/**
* @internal
*/
-class ConfigGetCommand extends AbstractCommand implements StandaloneInterface
+#[AsCommand(name: 'config:get', description: 'Get config variables')]
+final class ConfigGetCommand extends AbstractCommand implements StandaloneInterface, AvailableInSetupInterface
{
- #[Override]
- protected function configure(): void
- {
- $this->setDescription('Get config variables')
- ->addArgument('config-key', InputArgument::REQUIRED, 'config path separated by periods, e.g. "setup" or "db.1.host"')
- ->addOption('type', 't', InputOption::VALUE_REQUIRED, 'php type of the returned value, e.g. "octal"', 'string')
- ->addOption('addon', 'p', InputOption::VALUE_REQUIRED, 'addon to inspect, defaults to redaxo-core', 'core')
- ;
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = $this->getStyle($input, $output);
- $key = $input->getArgument('config-key');
- $type = $input->getOption('type');
-
+ public function __invoke(
+ SymfonyStyle $io,
+ OutputInterface $output,
+ #[Argument('config path separated by periods, e.g. "setup" or "db.1.host"')] string $key,
+ #[Option('php type of the returned value, e.g. "octal"', shortcut: 't')] string $type = 'string',
+ #[Option('addon to inspect, defaults to redaxo-core', name: 'addon', shortcut: 'p')] string $package = 'core',
+ ): int {
if (!$key) {
throw new InvalidArgumentException('config-key is required');
}
@@ -43,7 +34,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$path = explode('.', $key);
$propertyKey = array_shift($path);
- $package = $input->getOption('addon');
if ('core' === $package) {
$config = Core::getProperty($propertyKey);
} else {
diff --git a/src/Console/Command/ConfigSetCommand.php b/src/Console/Command/ConfigSetCommand.php
index 7dff269e5f..8aa2b8052f 100644
--- a/src/Console/Command/ConfigSetCommand.php
+++ b/src/Console/Command/ConfigSetCommand.php
@@ -2,16 +2,15 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\Filesystem\File;
use Redaxo\Core\Filesystem\Path;
use Redaxo\Core\Util\Type;
+use Symfony\Component\Console\Attribute\Argument;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Command\Command;
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 function count;
use function in_array;
@@ -20,43 +19,31 @@
/**
* @internal
*/
-class ConfigSetCommand extends AbstractCommand implements StandaloneInterface
+#[AsCommand(
+ name: 'config:set',
+ description: 'Set config variables',
+ help: <<<'EOF'
+ Set config variables in config.yml.
+
+ Example: enable setup
+ %command.full_name% --type boolean setup true
+
+ Example: set password min length to 8
+ %command.full_name% --type integer password_policy.length.min 8
+
+ Example: set error email
+ %command.full_name% error_email mail@example.org
+ EOF,
+)]
+final class ConfigSetCommand extends AbstractCommand implements StandaloneInterface, AvailableInSetupInterface
{
- #[Override]
- protected function configure(): void
- {
- $this->setDescription('Set config variables')
- ->addArgument('config-key', InputArgument::REQUIRED, 'config path separated by periods, e.g. "setup" or "db.1.host"')
- ->addArgument('value', InputArgument::OPTIONAL, 'new value for config key, e.g. "somestring" or "1"')
- ->addOption('type', 't', InputOption::VALUE_REQUIRED, 'php type of new value, e.g. "bool", "octal" or "int"', 'string')
- ->addOption('unset', null, InputOption::VALUE_NONE, 'sets the config key to null')
- ->setHelp(<<<'EOF'
- Set config variables in config.yml.
-
- Example: enable setup
- %command.full_name% --type boolean setup true
-
- Example: set password min length to 8
- %command.full_name% --type integer password_policy.length.min 8
-
- Example: set error email
- %command.full_name% error_email mail@example.org
-
- EOF
- )
- ;
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = $this->getStyle($input, $output);
-
- $key = $input->getArgument('config-key');
- $value = $input->getArgument('value');
- $unset = $input->getOption('unset');
- $type = $input->getOption('type');
-
+ public function __invoke(
+ SymfonyStyle $io,
+ #[Argument('config path separated by periods, e.g. "setup" or "db.1.host"')] string $key,
+ #[Argument('new value for config key, e.g. "somestring" or "1"')] ?string $value = null,
+ #[Option('php type of new value, e.g. "bool", "octal" or "int"', shortcut: 't')] string $type = 'string',
+ #[Option('sets the config key to null')] bool $unset = false,
+ ): int {
if (null === $value && false === $unset) {
throw new InvalidArgumentException('No new value specified');
}
diff --git a/src/Console/Command/CronjobRunCommand.php b/src/Console/Command/CronjobRunCommand.php
index e64eb7824f..dcb6fc8e24 100644
--- a/src/Console/Command/CronjobRunCommand.php
+++ b/src/Console/Command/CronjobRunCommand.php
@@ -2,15 +2,13 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\Core;
use Redaxo\Core\Cronjob\CronjobManager;
use Redaxo\Core\Database\Sql;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Input\InputOption;
-use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Style\SymfonyStyle;
@@ -20,29 +18,19 @@
/**
* @internal
*/
-class CronjobRunCommand extends AbstractCommand
+#[AsCommand(name: 'cronjob:run', description: 'Executes cronjobs of the "script" environment')]
+final class CronjobRunCommand extends AbstractCommand
{
- #[Override]
- protected function configure(): void
- {
- $this
- ->setDescription('Executes cronjobs of the "script" environment')
- ->addOption('job', null, InputOption::VALUE_OPTIONAL, 'Execute single job (selected interactively or given by id)', false)
- ;
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = $this->getStyle($input, $output);
-
+ public function __invoke(
+ SymfonyStyle $io,
+ #[Option('Execute single job (selected interactively or given by id)')] bool|string $job = false,
+ ): int {
// indicator constant, kept for BC
define('REX_CRONJOB_SCRIPT', true);
- $job = $input->getOption('job');
-
if (false !== $job) {
- return $this->executeSingleJob($io, (int) $job);
+ // `true` means the option was given without a value (--job) -> select the job interactively
+ return $this->executeSingleJob($io, true === $job ? null : (int) $job);
}
$manager = CronjobManager::factory();
diff --git a/src/Console/Command/DatabaseConnectionOptionsCommand.php b/src/Console/Command/DatabaseConnectionOptionsCommand.php
index e970cf600e..05e74fbcd3 100644
--- a/src/Console/Command/DatabaseConnectionOptionsCommand.php
+++ b/src/Console/Command/DatabaseConnectionOptionsCommand.php
@@ -2,40 +2,33 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\Core;
+use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
-class DatabaseConnectionOptionsCommand extends AbstractCommand implements StandaloneInterface
+#[AsCommand(
+ name: 'db:connection-options',
+ description: 'Dumps the db connection options for the mysql cli tool',
+ help: <<<'EOF'
+ Dumps the db connection options for the mysql cli tool.
+
+ Example: run interactive mysql shell
+ %command.full_name% | xargs -o mysql
+
+ Example: dump the database
+ %command.full_name% | xargs mysqldump > dump.sql
+
+ Example: import a dump file
+ %command.full_name% | xargs sh -c 'mysql "$0" "$@" < dump.sql'
+ EOF,
+)]
+final class DatabaseConnectionOptionsCommand extends AbstractCommand implements StandaloneInterface, AvailableInSetupInterface
{
- #[Override]
- protected function configure(): void
- {
- $this
- ->setDescription('Dumps the db connection options for the mysql cli tool')
- ->setHelp(<<<'EOF'
- Dumps the db connection options for the mysql cli tool.
-
- Example: run interactive mysql shell
- %command.full_name% | xargs -o mysql
-
- Example: dump the database
- %command.full_name% | xargs mysqldump > dump.sql
-
- Example: import a dump file
- %command.full_name% | xargs sh -c 'mysql "$0" "$@" < dump.sql'
- EOF
- )
- ;
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
+ public function __invoke(OutputInterface $output): int
{
$db = Core::getDbConfig(1);
diff --git a/src/Console/Command/DatabaseDumpSchemaCommand.php b/src/Console/Command/DatabaseDumpSchemaCommand.php
index cc4e1242bc..23232c59a0 100644
--- a/src/Console/Command/DatabaseDumpSchemaCommand.php
+++ b/src/Console/Command/DatabaseDumpSchemaCommand.php
@@ -2,39 +2,33 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\Core;
use Redaxo\Core\Database\SchemaDumper;
use Redaxo\Core\Database\Sql;
use Redaxo\Core\Database\Table;
+use Symfony\Component\Console\Attribute\Argument;
+use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
-use Symfony\Component\Console\Input\InputArgument;
-use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
use function sprintf;
/**
* @internal
*/
-class DatabaseDumpSchemaCommand extends AbstractCommand
+#[AsCommand(name: 'db:dump-schema', description: 'Dumps the schema of db tables as php code')]
+final class DatabaseDumpSchemaCommand extends AbstractCommand
{
- #[Override]
- protected function configure(): void
- {
- $this
- ->setDescription('Dumps the schema of db tables as php code')
- ->addArgument('table', InputArgument::REQUIRED, 'Database table', null, static function () {
- return Sql::factory()->getTables(Core::getTablePrefix());
- })
- ;
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $table = Table::get($input->getArgument('table'));
+ public function __invoke(
+ OutputInterface $output,
+ SymfonyStyle $io,
+ #[Argument('Database table', suggestedValues: static function (): array {
+ return Sql::factory()->getTables(Core::getTablePrefix());
+ })] string $table,
+ ): int {
+ $table = Table::get($table);
if (!$table->exists()) {
throw new InvalidArgumentException(sprintf('Table "%s" does not exist.', $table->getName()));
@@ -44,7 +38,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$output->write($generator->dumpTable($table));
- $io = $this->getStyle($input, $output)->getErrorStyle();
+ $io = $io->getErrorStyle();
$io->success('Generated schema for table "' . $table->getName() . '".');
return Command::SUCCESS;
diff --git a/src/Console/Command/DatabaseSetConnectionCommand.php b/src/Console/Command/DatabaseSetConnectionCommand.php
index d68acd55d2..61fcd657c5 100644
--- a/src/Console/Command/DatabaseSetConnectionCommand.php
+++ b/src/Console/Command/DatabaseSetConnectionCommand.php
@@ -2,58 +2,52 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\Database\Sql;
use Redaxo\Core\Filesystem\File;
use Redaxo\Core\Filesystem\Path;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Input\InputOption;
-use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
/**
* @internal
*/
-class DatabaseSetConnectionCommand extends AbstractCommand implements StandaloneInterface
+#[AsCommand(
+ name: 'db:set-connection',
+ description: 'Sets database connection credentials.',
+ help: 'Checks by default if a database connection can be established with the new settings.',
+)]
+final class DatabaseSetConnectionCommand extends AbstractCommand implements StandaloneInterface, AvailableInSetupInterface
{
- #[Override]
- protected function configure(): void
- {
- $this
- ->setDescription('Sets database connection credentials.')
- ->setHelp('Checks by default if a database connection can be established with the new settings.')
- ->addOption('host', null, InputOption::VALUE_REQUIRED, 'database host')
- ->addOption('login', null, InputOption::VALUE_REQUIRED, 'database user')
- ->addOption('password', null, InputOption::VALUE_REQUIRED, 'database password')
- ->addOption('database', null, InputOption::VALUE_REQUIRED, 'database name')
- ->addOption('force', 'f', InputOption::VALUE_NONE, 'Save credentials even if validation fails.');
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = $this->getStyle($input, $output);
-
+ public function __invoke(
+ SymfonyStyle $io,
+ #[Option('database host')] ?string $host = null,
+ #[Option('database user')] ?string $login = null,
+ #[Option('database password')] ?string $password = null,
+ #[Option('database name')] ?string $database = null,
+ #[Option('Save credentials even if validation fails.', shortcut: 'f')] bool $force = false,
+ ): int {
$configFile = Path::coreData('config.yml');
$config = File::getConfig($configFile);
$db = ($config['db'][1] ?? []) + ['host' => '', 'login' => '', 'password' => '', 'name' => ''];
$changed = false;
- if (null !== $host = $input->getOption('host')) {
+ if (null !== $host) {
$db['host'] = $host;
$changed = true;
}
- if (null !== $login = $input->getOption('login')) {
+ if (null !== $login) {
$db['login'] = $login;
$changed = true;
}
- if (null !== $password = $input->getOption('password')) {
+ if (null !== $password) {
$db['password'] = $password;
$changed = true;
}
- if (null !== $database = $input->getOption('database')) {
+ if (null !== $database) {
$db['name'] = $database;
$changed = true;
}
@@ -73,7 +67,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
if (true !== $settingsValid) {
$io->error("Can't connect to database:\n" . $settingsValid);
- if (!$input->getOption('force')) {
+ if (!$force) {
return Command::FAILURE;
}
} else {
diff --git a/src/Console/Command/ListCommand.php b/src/Console/Command/ListCommand.php
index f03234a2a2..81ccfe6cb2 100644
--- a/src/Console/Command/ListCommand.php
+++ b/src/Console/Command/ListCommand.php
@@ -13,7 +13,7 @@
/**
* @internal
*/
-class ListCommand extends SymfonyListCommand
+final class ListCommand extends SymfonyListCommand
{
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
diff --git a/src/Console/Command/MigrateCommand.php b/src/Console/Command/MigrateCommand.php
index a275b94430..0db55f92a2 100644
--- a/src/Console/Command/MigrateCommand.php
+++ b/src/Console/Command/MigrateCommand.php
@@ -2,7 +2,6 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\Addon\Addon;
use Redaxo\Core\Addon\AddonManager;
use Redaxo\Core\Core;
@@ -13,9 +12,9 @@
use Redaxo\Core\Setup\Setup;
use Redaxo\Core\Translation\I18n;
use Redaxo\Core\Util\Version;
+use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
use Throwable;
use function sprintf;
@@ -23,19 +22,11 @@
/**
* @internal
*/
-class MigrateCommand extends AbstractCommand implements StandaloneInterface
+#[AsCommand(name: 'migrate', description: 'Runs install scripts of core and addons to ensure schema is up to date')]
+final class MigrateCommand extends AbstractCommand implements StandaloneInterface
{
- #[Override]
- protected function configure(): void
+ public function __invoke(SymfonyStyle $io): int
{
- $this->setDescription('Runs install scripts of core and addons to ensure schema is up to date');
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = $this->getStyle($input, $output);
-
// verify the database server meets the minimum version requirements
$sql = Sql::factory();
$dbType = $sql->getDbType();
diff --git a/src/Console/Command/SetupCheckCommand.php b/src/Console/Command/SetupCheckCommand.php
index 6f7fa58f0f..8b6b54cea3 100644
--- a/src/Console/Command/SetupCheckCommand.php
+++ b/src/Console/Command/SetupCheckCommand.php
@@ -2,14 +2,13 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use PDOException;
use Redaxo\Core\Filesystem\File;
use Redaxo\Core\Filesystem\Path;
use Redaxo\Core\Setup\Setup;
use Redaxo\Core\Translation\I18n;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Style\SymfonyStyle;
use function count;
@@ -18,21 +17,12 @@
/**
* @internal
*/
-class SetupCheckCommand extends AbstractCommand
+#[AsCommand(name: 'setup:check', description: 'Check the commandline interface (CLI) environment for REDAXO requirements')]
+final class SetupCheckCommand extends AbstractCommand implements AvailableInSetupInterface
{
- #[Override]
- protected function configure(): void
- {
- $this
- ->setDescription('Check the commandline interface (CLI) environment for REDAXO requirements')
- ;
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
+ public function __invoke(SymfonyStyle $io): int
{
$exitCode = 0;
- $io = $this->getStyle($input, $output);
$errors = Setup::checkEnvironment();
if (0 == count($errors)) {
diff --git a/src/Console/Command/SetupRunCommand.php b/src/Console/Command/SetupRunCommand.php
index f16ee0047d..bd021b172a 100644
--- a/src/Console/Command/SetupRunCommand.php
+++ b/src/Console/Command/SetupRunCommand.php
@@ -3,7 +3,6 @@
namespace Redaxo\Core\Console\Command;
use DateTimeZone;
-use Override;
use PDOException;
use Redaxo\Core\Backup\Backup;
use Redaxo\Core\Core;
@@ -17,11 +16,11 @@
use Redaxo\Core\Setup\Setup;
use Redaxo\Core\Translation\I18n;
use Redaxo\Core\Util\Type;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Input\InputOption;
-use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
@@ -39,44 +38,38 @@
/**
* @internal
*/
-class SetupRunCommand extends AbstractCommand implements OnlySetupAddonsInterface
+#[AsCommand(name: 'setup:run', description: 'Perform redaxo setup')]
+final class SetupRunCommand extends AbstractCommand implements OnlySetupAddonsInterface, AvailableInSetupInterface
{
private SymfonyStyle $io;
private InputInterface $input;
private bool $forceAsking = false;
- #[Override]
- protected function configure(): void
- {
- $this
- ->setDescription('Perform redaxo setup')
- ->addOption('lang', null, InputOption::VALUE_REQUIRED, 'System language e.g. "de_de" or "en_gb"', null, static fn () => I18n::getLocales())
- ->addOption('agree-license', null, InputOption::VALUE_NONE, 'Accept license terms and conditions') // BC, not used anymore
- ->addOption('server', null, InputOption::VALUE_REQUIRED, 'Website URL e.g. "https://example.org/"')
- ->addOption('servername', null, InputOption::VALUE_REQUIRED, 'Website name')
- ->addOption('error-email', null, InputOption::VALUE_REQUIRED, 'Error mail address e.g. "info@example.org"')
- ->addOption('timezone', null, InputOption::VALUE_REQUIRED, 'Timezone e.g. "Europe/Berlin"', null, static fn () => DateTimeZone::listIdentifiers())
- ->addOption('db-host', null, InputOption::VALUE_REQUIRED, 'Database hostname e.g. "localhost" or "127.0.0.1"')
- ->addOption('db-login', null, InputOption::VALUE_REQUIRED, 'Database username e.g. "root"')
- ->addOption('db-password', null, InputOption::VALUE_REQUIRED, 'Database user password')
- ->addOption('db-name', null, InputOption::VALUE_REQUIRED, 'Database name e.g. "redaxo"')
- ->addOption('db-createdb', null, InputOption::VALUE_REQUIRED, 'Creates the database "yes" or "no"', null, ['yes', 'no'])
- ->addOption('db-setup', null, InputOption::VALUE_REQUIRED, 'Database setup mode e.g. "normal", "override" or "import"', null, ['normal', 'override', 'import'])
- ->addOption('db-import', null, InputOption::VALUE_REQUIRED, 'Database import filename if "import" is used as --db-setup')
- ->addOption('db-ssl-ca', null, InputOption::VALUE_OPTIONAL, 'Path to SSL Certificate Authority file or use without value to enable CA mode', false)
- ->addOption('db-ssl-key', null, InputOption::VALUE_REQUIRED, 'Path to SSL key file')
- ->addOption('db-ssl-cert', null, InputOption::VALUE_REQUIRED, 'Path to SSL certificate file')
- ->addOption('db-ssl-verify-server-cert', null, InputOption::VALUE_REQUIRED, 'Verify SSL server certificate (yes/no)', null, ['yes', 'no'])
- ->addOption('admin-username', null, InputOption::VALUE_REQUIRED, 'Creates a redaxo admin user with the given username')
- ->addOption('admin-password', null, InputOption::VALUE_REQUIRED, 'Sets the password for the admin user account')
- ;
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = $this->getStyle($input, $output);
-
+ public function __invoke(
+ InputInterface $input,
+ SymfonyStyle $io,
+ #[Option('System language e.g. "de_de" or "en_gb"', suggestedValues: I18n::getLocales(...))] ?string $lang = null,
+ #[Option('Accept license terms and conditions')] bool $agreeLicense = false, // BC, not used anymore
+ #[Option('Website URL e.g. "https://example.org/"')] ?string $server = null,
+ #[Option('Website name')] ?string $servername = null,
+ #[Option('Error mail address e.g. "info@example.org"')] ?string $errorEmail = null,
+ #[Option('Timezone e.g. "Europe/Berlin"', suggestedValues: static function () {
+ return DateTimeZone::listIdentifiers();
+ })] ?string $timezone = null,
+ #[Option('Database hostname e.g. "localhost" or "127.0.0.1"')] ?string $dbHost = null,
+ #[Option('Database username e.g. "root"')] ?string $dbLogin = null,
+ #[Option('Database user password')] ?string $dbPassword = null,
+ #[Option('Database name e.g. "redaxo"')] ?string $dbName = null,
+ #[Option('Creates the database "yes" or "no"', suggestedValues: ['yes', 'no'])] ?string $dbCreatedb = null,
+ #[Option('Database setup mode e.g. "normal", "override" or "import"', suggestedValues: ['normal', 'override', 'import'])] ?string $dbSetup = null,
+ #[Option('Database import filename if "import" is used as --db-setup')] ?string $dbImport = null,
+ #[Option('Path to SSL Certificate Authority file or use without value to enable CA mode')] bool|string $dbSslCa = false,
+ #[Option('Path to SSL key file')] ?string $dbSslKey = null,
+ #[Option('Path to SSL certificate file')] ?string $dbSslCert = null,
+ #[Option('Verify SSL server certificate (yes/no)', suggestedValues: ['yes', 'no'])] ?string $dbSslVerifyServerCert = null,
+ #[Option('Creates a redaxo admin user with the given username')] ?string $adminUsername = null,
+ #[Option('Sets the password for the admin user account')] ?string $adminPassword = null,
+ ): int {
$this->io = $io;
$this->input = $input;
@@ -255,8 +248,7 @@ static function ($value) use ($timezones) {
$sslRequired = $input->isInteractive() && $this->io->confirm('Configure SSL database connection?', false);
$sslConfigured = false; // Track if any SSL option was configured
- $sslCa = $input->getOption('db-ssl-ca');
- if ($sslRequired && ($this->forceAsking || false === $sslCa)) {
+ if ($sslRequired && ($this->forceAsking || false === $dbSslCa)) {
/** @var string $sslCaChoice */
$sslCaChoice = $this->io->choice('SSL Certificate Authority', [
'none' => 'No CA verification',
@@ -281,18 +273,18 @@ static function ($value) use ($timezones) {
}));
$sslConfigured = true;
}
- } elseif (false === $sslCa) {
+ } elseif (false === $dbSslCa) {
$config['db'][1]['ssl_ca'] = null;
- } elseif (null === $sslCa || true === $sslCa) {
+ } elseif (true === $dbSslCa) {
$config['db'][1]['ssl_ca'] = true;
$io->success('Using SSL system CA verification');
$sslConfigured = true;
} else {
- if (!is_file($sslCa) || !is_readable($sslCa)) {
- throw new InvalidArgumentException('SSL CA file not found or not readable: ' . $sslCa);
+ if (!is_file($dbSslCa) || !is_readable($dbSslCa)) {
+ throw new InvalidArgumentException('SSL CA file not found or not readable: ' . $dbSslCa);
}
- $config['db'][1]['ssl_ca'] = $sslCa;
- $io->success(sprintf('Using SSL CA file "%s"', $sslCa));
+ $config['db'][1]['ssl_ca'] = $dbSslCa;
+ $io->success(sprintf('Using SSL CA file "%s"', $dbSslCa));
$sslConfigured = true;
}
@@ -321,7 +313,7 @@ static function ($value) use ($timezones) {
$config['db'][1][$key] = $value;
}
- $sslVerifyServerCert = $input->getOption('db-ssl-verify-server-cert');
+ $sslVerifyServerCert = $dbSslVerifyServerCert;
if (
$sslRequired && $sslConfigured
&& (null === $sslVerifyServerCert || $this->forceAsking)
diff --git a/src/Console/Command/SystemReportCommand.php b/src/Console/Command/SystemReportCommand.php
index 5e487d1600..0bf0584419 100644
--- a/src/Console/Command/SystemReportCommand.php
+++ b/src/Console/Command/SystemReportCommand.php
@@ -2,15 +2,15 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\SystemReport;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidOptionException;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableStyle;
-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 function in_array;
use function is_bool;
@@ -21,26 +21,18 @@
/**
* @internal
*/
-class SystemReportCommand extends AbstractCommand
+#[AsCommand(name: 'system:report', description: 'Shows the system report')]
+final class SystemReportCommand extends AbstractCommand
{
- #[Override]
- protected function configure(): void
- {
- $this
- ->setDescription('Shows the system report')
- ->addOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format ("cli", "markdown")', 'cli', ['cli', 'markdown'])
- ;
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $formats = ['cli', 'markdown'];
-
- $format = $input->getOption('format');
-
- if (!in_array($format, $formats, true)) {
- throw new InvalidOptionException(sprintf('Invalid value "%s" for --format option, allowed values: %s', $format, implode(', ', $formats)));
+ private const array FORMATS = ['cli', 'markdown'];
+
+ public function __invoke(
+ OutputInterface $output,
+ SymfonyStyle $io,
+ #[Option('Output format ("cli", "markdown")', shortcut: 'f', suggestedValues: self::FORMATS)] string $format = 'cli',
+ ): int {
+ if (!in_array($format, self::FORMATS, true)) {
+ throw new InvalidOptionException(sprintf('Invalid value "%s" for --format option, allowed values: %s', $format, implode(', ', self::FORMATS)));
}
$report = SystemReport::factory();
@@ -51,8 +43,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return Command::SUCCESS;
}
- $io = $this->getStyle($input, $output);
-
$io->title('System report');
$tables = [];
diff --git a/src/Console/Command/UserCreateCommand.php b/src/Console/Command/UserCreateCommand.php
index 512fb0aa7c..00fd39dfae 100644
--- a/src/Console/Command/UserCreateCommand.php
+++ b/src/Console/Command/UserCreateCommand.php
@@ -2,45 +2,33 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\Core;
use Redaxo\Core\Database\Sql;
use Redaxo\Core\Security\BackendLogin;
use Redaxo\Core\Security\BackendPasswordPolicy;
+use Symfony\Component\Console\Attribute\Argument;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Command\Command;
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 function sprintf;
/**
* @internal
*/
-class UserCreateCommand extends AbstractCommand
+#[AsCommand(name: 'user:create', description: 'Create a new user')]
+final class UserCreateCommand extends AbstractCommand
{
- #[Override]
- protected function configure(): void
- {
- $this
- ->setDescription('Create a new user')
- ->addArgument('login', InputArgument::REQUIRED, 'Login')
- ->addArgument('password', InputArgument::OPTIONAL, 'Password')
- ->addOption('name', null, InputOption::VALUE_REQUIRED, 'Name')
- ->addOption('admin', null, InputOption::VALUE_NONE, 'Grant admin permissions')
- ->addOption('password-change-required', null, InputOption::VALUE_NONE, 'Require password change after login')
- ;
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = $this->getStyle($input, $output);
-
- $login = $input->getArgument('login');
-
+ public function __invoke(
+ SymfonyStyle $io,
+ #[Argument('Login')] string $login,
+ #[Argument('Password')] ?string $password = null,
+ #[Option('Name')] ?string $name = null,
+ #[Option('Grant admin permissions')] bool $admin = false,
+ #[Option('Require password change after login')] bool $passwordChangeRequired = false,
+ ): int {
$user = Sql::factory();
$user
->setTable(Core::getTable('user'))
@@ -53,7 +41,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$passwordPolicy = BackendPasswordPolicy::factory();
- $password = $input->getArgument('password');
if ($password && true !== $msg = $passwordPolicy->check($password)) {
throw new InvalidArgumentException($msg);
}
@@ -75,7 +62,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
throw new InvalidArgumentException('Missing password.');
}
- $name = $input->getOption('name');
if (!$name) {
$name = $login;
}
@@ -83,18 +69,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$passwordHash = BackendLogin::passwordHash($password);
$user = Sql::factory();
- // $user->setDebug();
$user->setTable(Core::getTablePrefix() . 'user');
$user->setValue('name', $name);
$user->setValue('login', $login);
$user->setValue('password', $passwordHash);
- $user->setValue('admin', $input->getOption('admin') ? 1 : 0);
+ $user->setValue('admin', $admin ? 1 : 0);
$user->setValue('login_tries', 0);
$user->addGlobalCreateFields('console');
$user->addGlobalUpdateFields('console');
$user->setDateTimeValue('password_changed', time());
$user->setArrayValue('previous_passwords', $passwordPolicy->updatePreviousPasswords(null, $passwordHash));
- $user->setValue('password_change_required', (int) $input->getOption('password-change-required'));
+ $user->setValue('password_change_required', (int) $passwordChangeRequired);
$user->setValue('status', '1');
$user->insert();
diff --git a/src/Console/Command/UserDeleteCommand.php b/src/Console/Command/UserDeleteCommand.php
index 7b865bb7b2..2c8e504fd7 100644
--- a/src/Console/Command/UserDeleteCommand.php
+++ b/src/Console/Command/UserDeleteCommand.php
@@ -2,42 +2,32 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\Core;
use Redaxo\Core\Database\Sql;
use Redaxo\Core\ExtensionPoint\Extension;
use Redaxo\Core\ExtensionPoint\ExtensionPoint;
use Redaxo\Core\Security\User;
+use Symfony\Component\Console\Attribute\Argument;
+use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputArgument;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
use function sprintf;
/**
* @internal
*/
+#[AsCommand(name: 'user:delete', description: 'Deletes an user by the specified login name.')]
final class UserDeleteCommand extends AbstractCommand
{
- #[Override]
- protected function configure(): void
- {
- $this
- ->setDescription('Deletes an user by the specified login name.')
- ->addArgument('user', InputArgument::REQUIRED, 'Username', null, static function () {
- /** @var list */
- return array_column(Sql::factory()->getArray('SELECT login FROM ' . Core::getTable('user')), 'login');
- })
- ;
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = $this->getStyle($input, $output);
-
- $username = $input->getArgument('user');
+ public function __invoke(
+ SymfonyStyle $io,
+ #[Argument('Username', suggestedValues: static function (): array {
+ /** @var list */
+ return array_column(Sql::factory()->getArray('SELECT login FROM ' . Core::getTable('user')), 'login');
+ })] string $user,
+ ): int {
+ $username = $user;
$user = User::forLogin($username);
diff --git a/src/Console/Command/UserListCommand.php b/src/Console/Command/UserListCommand.php
index 5295dc5c97..7dfab1f7b6 100644
--- a/src/Console/Command/UserListCommand.php
+++ b/src/Console/Command/UserListCommand.php
@@ -2,39 +2,31 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\Core;
use Redaxo\Core\Database\Sql;
+use Symfony\Component\Console\Attribute\Argument;
+use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
-use Symfony\Component\Console\Input\InputArgument;
-use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
use function sprintf;
/**
* @internal
*/
+#[AsCommand(name: 'user:list', description: 'List all users or a specific user by login name')]
final class UserListCommand extends AbstractCommand
{
- #[Override]
- protected function configure(): void
- {
- $this
- ->setDescription('List all users or a specific user by login name')
- ->addArgument('user', InputArgument::OPTIONAL, 'Username', null, static function () {
- /** @var list */
- return array_column(Sql::factory()->getArray('SELECT login FROM' . Core::getTable('user')), 'login');
- });
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = $this->getStyle($input, $output);
-
- $username = $input->getArgument('user');
+ public function __invoke(
+ OutputInterface $output,
+ SymfonyStyle $io,
+ #[Argument('Username', suggestedValues: static function (): array {
+ /** @var list */
+ return array_column(Sql::factory()->getArray('SELECT login FROM ' . Core::getTable('user')), 'login');
+ })] ?string $user = null,
+ ): int {
$sql = Sql::factory();
$query = '
SELECT
@@ -46,13 +38,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int
`lastlogin`
FROM ' . Core::getTable('user') . '
';
- if ($username) {
+ if ($user) {
$sql->setQuery($query . ' WHERE login = :login', [
- 'login' => $username,
+ 'login' => $user,
]);
if (0 === $sql->getRows()) {
- $io->error(sprintf('The user "%s" does not exist.', $username));
+ $io->error(sprintf('The user "%s" does not exist.', $user));
return Command::FAILURE;
}
} else {
diff --git a/src/Console/Command/UserSetPasswordCommand.php b/src/Console/Command/UserSetPasswordCommand.php
index 80bf2a02d5..6e7005ab15 100644
--- a/src/Console/Command/UserSetPasswordCommand.php
+++ b/src/Console/Command/UserSetPasswordCommand.php
@@ -2,7 +2,6 @@
namespace Redaxo\Core\Console\Command;
-use Override;
use Redaxo\Core\Core;
use Redaxo\Core\Database\Sql;
use Redaxo\Core\ExtensionPoint\Extension;
@@ -10,40 +9,31 @@
use Redaxo\Core\Security\BackendLogin;
use Redaxo\Core\Security\BackendPasswordPolicy;
use Redaxo\Core\Security\User;
+use Symfony\Component\Console\Attribute\Argument;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Command\Command;
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 function sprintf;
/**
* @internal
*/
-class UserSetPasswordCommand extends AbstractCommand
+#[AsCommand(name: 'user:set-password', description: 'Sets a new password for a user')]
+final class UserSetPasswordCommand extends AbstractCommand
{
- #[Override]
- protected function configure(): void
- {
- $this
- ->setDescription('Sets a new password for a user')
- ->addArgument('user', InputArgument::REQUIRED, 'Username', null, static function () {
- /** @var list */
- return array_column(Sql::factory()->getArray('SELECT login FROM ' . Core::getTable('user')), 'login');
- })
- ->addArgument('password', InputArgument::OPTIONAL, 'Password')
- ->addOption('password-change-required', null, InputOption::VALUE_NONE, 'Require password change after login')
- ;
- }
-
- #[Override]
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = $this->getStyle($input, $output);
-
- $username = $input->getArgument('user');
+ public function __invoke(
+ SymfonyStyle $io,
+ #[Argument('Username', suggestedValues: static function (): array {
+ /** @var list */
+ return array_column(Sql::factory()->getArray('SELECT login FROM ' . Core::getTable('user')), 'login');
+ })] string $user,
+ #[Argument('Password')] ?string $password = null,
+ #[Option('Require password change after login')] bool $passwordChangeRequired = false,
+ ): int {
+ $username = $user;
$user = Sql::factory();
$user
@@ -60,8 +50,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$passwordPolicy = BackendPasswordPolicy::factory();
- $password = $input->getArgument('password');
-
if ($password && true !== $msg = $passwordPolicy->check($password, $id)) {
throw new InvalidArgumentException($msg);
}
@@ -93,7 +81,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
->addGlobalUpdateFields('console')
->setDateTimeValue('password_changed', time())
->setArrayValue('previous_passwords', $passwordPolicy->updatePreviousPasswords($user, $passwordHash))
- ->setValue('password_change_required', (int) $input->getOption('password-change-required'))
+ ->setValue('password_change_required', (int) $passwordChangeRequired)
->update();
Extension::registerPoint(new ExtensionPoint('PASSWORD_UPDATED', '', [
diff --git a/src/Console/CommandLoader.php b/src/Console/CommandLoader.php
index f0fc36e523..236d50f3a6 100644
--- a/src/Console/CommandLoader.php
+++ b/src/Console/CommandLoader.php
@@ -3,121 +3,85 @@
namespace Redaxo\Core\Console;
use Override;
-use Redaxo\Core\Addon\Addon;
+use Redaxo\Core\ClassDiscovery;
use Redaxo\Core\Console\Command\AbstractCommand;
-use Redaxo\Core\Console\Command\AddonActivateCommand;
-use Redaxo\Core\Console\Command\AddonDeactivateCommand;
-use Redaxo\Core\Console\Command\AddonInstallCommand;
-use Redaxo\Core\Console\Command\AddonListCommand;
-use Redaxo\Core\Console\Command\AddonUninstallCommand;
-use Redaxo\Core\Console\Command\AssetsCompileStylesCommand;
-use Redaxo\Core\Console\Command\AssetsSyncCommand;
-use Redaxo\Core\Console\Command\CacheClearCommand;
-use Redaxo\Core\Console\Command\ConfigGetCommand;
-use Redaxo\Core\Console\Command\ConfigSetCommand;
-use Redaxo\Core\Console\Command\CronjobRunCommand;
-use Redaxo\Core\Console\Command\DatabaseConnectionOptionsCommand;
-use Redaxo\Core\Console\Command\DatabaseDumpSchemaCommand;
-use Redaxo\Core\Console\Command\DatabaseSetConnectionCommand;
-use Redaxo\Core\Console\Command\MigrateCommand;
-use Redaxo\Core\Console\Command\SetupCheckCommand;
-use Redaxo\Core\Console\Command\SetupRunCommand;
-use Redaxo\Core\Console\Command\SystemReportCommand;
-use Redaxo\Core\Console\Command\UserCreateCommand;
-use Redaxo\Core\Console\Command\UserDeleteCommand;
-use Redaxo\Core\Console\Command\UserListCommand;
-use Redaxo\Core\Console\Command\UserSetPasswordCommand;
+use Redaxo\Core\Console\Command\AvailableInSetupInterface;
use Redaxo\Core\Core;
-use Redaxo\Core\Exception\RuntimeException;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Command\LazyCommand;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\Console\Exception\CommandNotFoundException;
-use function gettype;
-use function is_array;
+use function array_shift;
+use function array_slice;
+use function explode;
+use function is_a;
use function sprintf;
/**
+ * Discovers all commands that are marked with the {@see AsCommand} attribute and extend {@see AbstractCommand},
+ * both in the core and in the active addons. Addons therefore register their commands simply by adding the
+ * attribute to a command class — no `package.yml` configuration is required.
+ *
+ * Commands are returned as {@see LazyCommand}, so that listing the commands (e.g. `console list`) does not
+ * instantiate every command class — only the command that is actually executed is instantiated.
+ *
* @internal
*/
final class CommandLoader implements CommandLoaderInterface
{
- /** @var array, addon?: Addon}> */
+ /** @var array, name: string, aliases: list, hidden: bool, description: string}> */
private array $commands = [];
public function __construct()
{
- $commands = [
- 'cache:clear' => CacheClearCommand::class,
- 'config:get' => ConfigGetCommand::class,
- 'config:set' => ConfigSetCommand::class,
- 'db:connection-options' => DatabaseConnectionOptionsCommand::class,
- 'db:set-connection' => DatabaseSetConnectionCommand::class,
- 'setup:check' => SetupCheckCommand::class,
- 'setup:run' => SetupRunCommand::class,
- ];
+ $isSetup = Core::isSetup();
- if (!Core::isSetup()) {
- $commands = array_merge($commands, [
- 'assets:sync' => AssetsSyncCommand::class,
- 'assets:compile-styles' => AssetsCompileStylesCommand::class,
- 'cronjob:run' => CronjobRunCommand::class,
- 'db:dump-schema' => DatabaseDumpSchemaCommand::class,
- 'addon:activate' => AddonActivateCommand::class,
- 'addon:deactivate' => AddonDeactivateCommand::class,
- 'addon:list' => AddonListCommand::class,
- 'addon:install' => AddonInstallCommand::class,
- 'addon:uninstall' => AddonUninstallCommand::class,
- 'migrate' => MigrateCommand::class,
- 'system:report' => SystemReportCommand::class,
- 'user:create' => UserCreateCommand::class,
- 'user:delete' => UserDeleteCommand::class,
- 'user:list' => UserListCommand::class,
- 'user:set-password' => UserSetPasswordCommand::class,
- ]);
- }
-
- foreach ($commands as $command => $class) {
- $this->commands[$command] = ['class' => $class];
- }
-
- foreach (Addon::getAvailableAddons() as $addon) {
- /** @var array> $commands */
- $commands = $addon->getProperty('console_commands');
-
- if (!$commands) {
+ foreach (ClassDiscovery::getInstance()->discoverByAttribute(AsCommand::class, AbstractCommand::class) as $class => $attribute) {
+ // Before the setup is completed only the explicitly marked commands are available.
+ if ($isSetup && !is_a($class, AvailableInSetupInterface::class, true)) {
continue;
}
- if (!is_array($commands)) {
- throw new RuntimeException('Expecting "console_commands" property to be an array, got "' . gettype($commands) . '" from package.yml of "' . $addon->name . '"');
+ // The name may contain aliases, separated by "|" (and an empty first segment for hidden commands).
+ $names = explode('|', $attribute->name);
+ $hidden = '' === $names[0];
+ if ($hidden) {
+ array_shift($names);
}
- foreach ($commands as $command => $class) {
- $this->commands[$command] = [
- 'addon' => $addon,
- 'class' => $class,
- ];
+ $command = [
+ 'class' => $class,
+ 'name' => $names[0],
+ 'aliases' => array_slice($names, 1),
+ 'hidden' => $hidden,
+ 'description' => $attribute->description ?? '',
+ ];
+
+ foreach ($names as $name) {
+ $this->commands[$name] = $command;
}
}
}
#[Override]
- public function get(string $name): AbstractCommand
+ public function get(string $name): Command
{
if (!isset($this->commands[$name])) {
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
}
- $class = $this->commands[$name]['class'];
-
- $command = new $class();
- $command->setName($name);
-
- if (isset($this->commands[$name]['addon'])) {
- $command->setAddon($this->commands[$name]['addon']);
- }
+ $command = $this->commands[$name];
+ $class = $command['class'];
- return $command;
+ return new LazyCommand(
+ $command['name'],
+ $command['aliases'],
+ $command['description'],
+ $command['hidden'],
+ static fn (): AbstractCommand => new $class(),
+ );
}
#[Override]
diff --git a/tests/Console/Command/ConfigGetCommandTest.php b/tests/Console/Command/ConfigGetCommandTest.php
index 9e8b67af06..2bbf31e0a2 100644
--- a/tests/Console/Command/ConfigGetCommandTest.php
+++ b/tests/Console/Command/ConfigGetCommandTest.php
@@ -15,7 +15,7 @@ public function testKeyFound(string $expectedValue, string $key): void
{
$commandTester = new CommandTester(new ConfigGetCommand());
$commandTester->execute([
- 'config-key' => $key,
+ 'key' => $key,
]);
self::assertEquals($expectedValue, $commandTester->getDisplay(true));
self::assertEquals(0, $commandTester->getStatusCode());
@@ -34,7 +34,7 @@ public function testKeyNotFound(): void
{
$commandTester = new CommandTester(new ConfigGetCommand());
$commandTester->execute([
- 'config-key' => 'foo.bar',
+ 'key' => 'foo.bar',
]);
self::assertEquals(1, $commandTester->getStatusCode());
}
@@ -43,7 +43,7 @@ public function testAddonKeyFound(): void
{
$commandTester = new CommandTester(new ConfigGetCommand());
$commandTester->execute([
- 'config-key' => 'author',
+ 'key' => 'author',
'--addon' => 'test', ],
);
self::assertEquals("\"Yakamara Media GmbH & Co. KG, REDAXO team & community\"\n", $commandTester->getDisplay(true));
diff --git a/tests/Console/Command/ConfigSetCommandTest.php b/tests/Console/Command/ConfigSetCommandTest.php
index 8d70c53b88..6a5a37c81e 100644
--- a/tests/Console/Command/ConfigSetCommandTest.php
+++ b/tests/Console/Command/ConfigSetCommandTest.php
@@ -35,7 +35,7 @@ public function testSetBoolean(bool $expectedValue, string $value): void
$commandTester = new CommandTester(new ConfigSetCommand());
$commandTester->execute([
'--type' => 'bool',
- 'config-key' => 'test',
+ 'key' => 'test',
'value' => $value,
]);
$config = File::getConfig(Path::coreData('config.yml'));