From d01fa843b84264924ae23b600f361694b054c465 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Tue, 24 Sep 2024 09:28:37 -0700 Subject: [PATCH] Move getBootOptions (etc) from BaseApplication to BootTrait --- lib/src/BaseApplication.php | 2 +- lib/src/Command/BaseCommand.php | 22 +--------- lib/src/Util/BootTrait.php | 72 ++++++++++++++++++++++++++++++++- 3 files changed, 74 insertions(+), 22 deletions(-) diff --git a/lib/src/BaseApplication.php b/lib/src/BaseApplication.php index d93d0b5..dcc881e 100644 --- a/lib/src/BaseApplication.php +++ b/lib/src/BaseApplication.php @@ -72,7 +72,7 @@ protected function getDefaultInputDefinition() { $c = new class() { use BootTrait; }; - $c->configureDefinition($definition); + $c->mergeDefaultBootDefinition($definition); return $definition; } diff --git a/lib/src/Command/BaseCommand.php b/lib/src/Command/BaseCommand.php index d615a0e..e896cb8 100644 --- a/lib/src/Command/BaseCommand.php +++ b/lib/src/Command/BaseCommand.php @@ -20,18 +20,9 @@ class BaseCommand extends Command { use OptionCallbackTrait; use BootTrait; - public function getBootOptions(): array { - return [ - 'auto' => TRUE, - 'default' => 'full|cms-full', - 'allow' => ['full|cms-full', 'full', 'cms-full', 'settings', 'classloader', 'cms-only', 'none'], - ]; - } - public function mergeApplicationDefinition($mergeArgs = TRUE) { parent::mergeApplicationDefinition($mergeArgs); - $bootOptions = $this->getBootOptions(); - $this->getDefinition()->getOption('level')->setDefault($bootOptions['default']); + $this->mergeBootDefinition($this->getDefinition()); } /** @@ -39,16 +30,7 @@ public function mergeApplicationDefinition($mergeArgs = TRUE) { * @param \Symfony\Component\Console\Output\OutputInterface $output */ protected function initialize(InputInterface $input, OutputInterface $output) { - $bootOptions = $this->getBootOptions(); - if (!in_array($input->getOption('level'), $bootOptions['allow'])) { - throw new \LogicException(sprintf("Command called with with level (%s) but only accepts levels (%s)", - $input->getOption('level'), implode(', ', $bootOptions['allow']))); - } - - if (!$this->isBooted() && ($bootOptions['auto'] ?? TRUE)) { - $this->boot($input, $output); - } - + $this->autoboot($input, $output); parent::initialize($input, $output); $this->runOptionCallbacks($input, $output); } diff --git a/lib/src/Util/BootTrait.php b/lib/src/Util/BootTrait.php index 70360f6..300ec9f 100644 --- a/lib/src/Util/BootTrait.php +++ b/lib/src/Util/BootTrait.php @@ -16,16 +16,70 @@ */ trait BootTrait { + /** + * Describe the expected bootstrap behaviors for this command. + * + * - For most commands, you will want to automatically boot CiviCRM/CMS. + * The default implementation will do this. + * - For some special commands (e.g. core-installer or PHP-script-runner), you may + * want more fine-grained control over when/how the system boots. + * + * @var array + */ + protected $bootOptions = [ + // Whether to automatically boot Civi during `initialize()` phase. + 'auto' => TRUE, + + // Default boot level. + 'default' => 'full|cms-full', + + // List of all boot levels that are allowed in this command. + 'allow' => ['full|cms-full', 'full', 'cms-full', 'settings', 'classloader', 'cms-only', 'none'], + ]; + /** * @internal */ - public function configureDefinition($definition, $defaultLevel = 'full|cms-full') { + public function mergeDefaultBootDefinition($definition, $defaultLevel = 'full|cms-full') { + // If we were only dealing with built-in/global commands, then these options could be defined at the command-level. + // However, we also have extension-based commands. The system will boot before we have a chance to discover them. + // By putting these options at the application level, we ensure they will be defined+used. $definition->addOption(new InputOption('level', NULL, InputOption::VALUE_REQUIRED, 'Bootstrap level (none,classloader,settings,full,cms-only,cms-full)', $defaultLevel)); $definition->addOption(new InputOption('hostname', NULL, InputOption::VALUE_REQUIRED, 'Hostname (for a multisite system)')); $definition->addOption(new InputOption('test', 't', InputOption::VALUE_NONE, 'Bootstrap the test database (CIVICRM_UF=UnitTests)')); $definition->addOption(new InputOption('user', 'U', InputOption::VALUE_REQUIRED, 'CMS user')); } + /** + * @internal + */ + public function mergeBootDefinition($definition) { + $bootOptions = $this->getBootOptions(); + $definition->getOption('level')->setDefault($bootOptions['default']); + } + + /** + * Evaluate the $bootOptions. + * + * - If we've already booted, do nothing. + * - If the configuration looks reasonable and if we haven't booted yet, then boot(). + * - If the configuration looks unreasonable, then abort. + */ + protected function autoboot(InputInterface $input, OutputInterface $output): void { + $bootOptions = $this->getBootOptions(); + if (!in_array($input->getOption('level'), $bootOptions['allow'])) { + throw new \LogicException(sprintf("Command called with with level (%s) but only accepts levels (%s)", + $input->getOption('level'), implode(', ', $bootOptions['allow']))); + } + + if (!$this->isBooted() && ($bootOptions['auto'] ?? TRUE)) { + $this->boot($input, $output); + } + } + + /** + * Start CiviCRM and/or CMS. Respect options like --user and --level. + */ public function boot(InputInterface $input, OutputInterface $output) { $logger = $this->bootLogger($output); $logger->debug('Start'); @@ -306,4 +360,20 @@ protected function assertBooted() { } } + /** + * @return array{auto: bool, default: string, allow: string[]} + */ + public function getBootOptions(): array { + return $this->bootOptions; + } + + /** + * @param array{auto: bool, default: string, allow: string[]} $bootOptions + * @return $this + */ + public function setBootOptions(array $bootOptions) { + $this->bootOptions = $bootOptions; + return $this; + } + }