diff --git a/doc/plugins.md b/doc/plugins.md
index 0235d803..541d091d 100644
--- a/doc/plugins.md
+++ b/doc/plugins.md
@@ -7,7 +7,9 @@ Cv plugins are PHP files which register event listeners.
```php
// FILE: /etc/cv/plugin/hello-command.php
use Civi\Cv\Cv;
+use Civi\Cv\Command\CvCommand;
use CvDeps\Symfony\Component\Console\Input\InputInterface;
+use CvDeps\Symfony\Component\Console\Input\InputArgument;
use CvDeps\Symfony\Component\Console\Output\OutputInterface;
use CvDeps\Symfony\Component\Console\Command\Command;
@@ -16,15 +18,15 @@ if (empty($CV_PLUGIN['protocol']) || $CV_PLUGIN['protocol'] > 1) {
}
Cv::dispatcher()->addListener('cv.app.commands', function($e) {
- $e['commands'][] = new class extends Command {
- protected function configure() {
- $this->setName('hello')->setDescription('Say a greeting');
- }
- protected function execute(InputInterface $input, OutputInterface $output): int {
- $output->writeln('Hello there!');
+
+ $e['commands'][] = (new CvCommand('hello'))
+ ->setDescription('Say a greeting')
+ ->addArgument('name', InputArgument::REQUIRED, 'Name of the person to greet')
+ ->setCode(function($input, $output) {
+ $output->writeln('Hello, ' . $input->getArgument('name'));
return 0;
- }
- };
+ });
+
});
```
diff --git a/lib/README.md b/lib/README.md
index 5f79d6ce..3ce8352f 100644
--- a/lib/README.md
+++ b/lib/README.md
@@ -78,11 +78,13 @@ For more info about `$options`, see the docblocks.
## Experimental API
-Other classes are included, but their contracts are subject to change.
-
-A particularly interesting one is `BootTrait`. This requires `symfony/console`, and it is used by most `cv` subcommands
-to achieve common behaviors:
-
-1. `BootTrait` defines certain CLI options (`--level`, `--user`, `--hostname`, etc).
-2. `BootTrait` automatically decides between `Bootstrap.php` and `CmsBootstrap.php`.
-3. `BootTrait` passes CLI options through to `Bootstrap.php` or `CmsBootstrap.php`.
+Other classes are included, but their contracts are subject to change. These
+include higher-level helpers for building Symfony Console apps that incorporate
+Civi bootstrap behaviors.
+
+* `BootTrait` has previously suggested as an experimentally available API
+ (circa v0.3.44). It changed significantly (circa v0.3.56), where
+ `configureBootOptions()` was replaced by `$bootOptions`, `mergeDefaultBootDefinition()`,
+ and `mergeBootDefinition()`.
+* As an alternative, consider the classes `BaseApplication` and `CvCommand` if you aim
+ to build a tool using Symfony Console and Cv Lib.
diff --git a/lib/src/BaseApplication.php b/lib/src/BaseApplication.php
index 9c4c668f..dcc881e0 100644
--- a/lib/src/BaseApplication.php
+++ b/lib/src/BaseApplication.php
@@ -2,6 +2,7 @@
namespace Civi\Cv;
use Civi\Cv\Util\AliasFilter;
+use Civi\Cv\Util\BootTrait;
use Civi\Cv\Util\CvArgvInput;
use LesserEvil\ShellVerbosityIsEvil;
use Symfony\Component\Console\Input\InputInterface;
@@ -24,6 +25,8 @@ public static function main(string $name, ?string $binDir, array $argv) {
try {
$application = new static($name);
+ Cv::ioStack()->replace('app', $application);
+ $application->configure();
$argv = AliasFilter::filter($argv);
$result = $application->run(new CvArgvInput($argv), Cv::ioStack()->current('output'));
}
@@ -38,8 +41,7 @@ public static function main(string $name, ?string $binDir, array $argv) {
exit($result);
}
- public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') {
- parent::__construct($name, $version);
+ public function configure() {
$this->setCatchExceptions(TRUE);
$this->setAutoExit(FALSE);
@@ -66,6 +68,12 @@ protected function getDefaultInputDefinition() {
$definition = parent::getDefaultInputDefinition();
$definition->addOption(new InputOption('cwd', NULL, InputOption::VALUE_REQUIRED, 'If specified, use the given directory as working directory.'));
$definition->addOption(new InputOption('site-alias', NULL, InputOption::VALUE_REQUIRED, 'Load site connection data based on its alias'));
+
+ $c = new class() {
+ use BootTrait;
+ };
+ $c->mergeDefaultBootDefinition($definition);
+
return $definition;
}
diff --git a/lib/src/Command/CvCommand.php b/lib/src/Command/CvCommand.php
new file mode 100644
index 00000000..d6bc5616
--- /dev/null
+++ b/lib/src/Command/CvCommand.php
@@ -0,0 +1,38 @@
+mergeBootDefinition($this->getDefinition());
+ }
+
+ /**
+ * @param \Symfony\Component\Console\Input\InputInterface $input
+ * @param \Symfony\Component\Console\Output\OutputInterface $output
+ */
+ protected function initialize(InputInterface $input, OutputInterface $output) {
+ $this->autoboot($input, $output);
+ parent::initialize($input, $output);
+ $this->runOptionCallbacks($input, $output);
+ }
+
+}
diff --git a/lib/src/Cv.php b/lib/src/Cv.php
index bb5e4915..26e30db9 100644
--- a/lib/src/Cv.php
+++ b/lib/src/Cv.php
@@ -79,6 +79,13 @@ public static function ioStack(): IOStack {
return static::$instances[__FUNCTION__];
}
+ /**
+ * @return \CvDeps\Symfony\Component\Console\Application|\Symfony\Component\Console\Application
+ */
+ public static function app() {
+ return static::ioStack()->current('app');
+ }
+
/**
* @return \CvDeps\Symfony\Component\Console\Input\InputInterface|\Symfony\Component\Console\Input\InputInterface
*/
diff --git a/lib/src/Util/BootTrait.php b/lib/src/Util/BootTrait.php
index c3baf46f..2db48498 100644
--- a/lib/src/Util/BootTrait.php
+++ b/lib/src/Util/BootTrait.php
@@ -16,13 +16,70 @@
*/
trait BootTrait {
- public function configureBootOptions($defaultLevel = 'full|cms-full') {
- $this->addOption('level', NULL, InputOption::VALUE_REQUIRED, 'Bootstrap level (none,classloader,settings,full,cms-only,cms-full)', $defaultLevel);
- $this->addOption('hostname', NULL, InputOption::VALUE_REQUIRED, 'Hostname (for a multisite system)');
- $this->addOption('test', 't', InputOption::VALUE_NONE, 'Bootstrap the test database (CIVICRM_UF=UnitTests)');
- $this->addOption('user', 'U', InputOption::VALUE_REQUIRED, 'CMS user');
+ /**
+ * 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 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');
@@ -290,4 +347,33 @@ private function bootLogger(OutputInterface $output): InternalLogger {
return new SymfonyConsoleLogger('BootTrait', $output);
}
+ /**
+ * @return bool
+ */
+ protected function isBooted() {
+ return defined('CIVICRM_DSN');
+ }
+
+ protected function assertBooted() {
+ if (!$this->isBooted()) {
+ throw new \Exception("Error: This command requires bootstrapping, but the system does not appear to be bootstrapped. Perhaps you set --level=none?");
+ }
+ }
+
+ /**
+ * @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 = array_merge($this->bootOptions, $bootOptions);
+ return $this;
+ }
+
}
diff --git a/lib/src/Util/IOStack.php b/lib/src/Util/IOStack.php
index 08f85e56..a8e734b1 100644
--- a/lib/src/Util/IOStack.php
+++ b/lib/src/Util/IOStack.php
@@ -21,16 +21,19 @@ class IOStack {
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
+ * @param \Symfony\Component\Console\Application|null $app
* @return scalar
* Internal identifier for the stack-frame. ID formatting is not guaranteed.
*/
- public function push(\Symfony\Component\Console\Input\InputInterface $input, \Symfony\Component\Console\Output\OutputInterface $output) {
+ public function push(\Symfony\Component\Console\Input\InputInterface $input, \Symfony\Component\Console\Output\OutputInterface $output, ?\Symfony\Component\Console\Application $app = NULL) {
++static::$id;
+ $app = $app ?: ($this->stack[0]['app'] ?? NULL);
array_unshift($this->stack, [
'id' => static::$id,
'input' => $input,
'output' => $output,
'io' => new SymfonyStyle($input, $output),
+ 'app' => $app,
]);
return static::$id;
}
@@ -68,6 +71,10 @@ public function get($id, string $property) {
return NULL;
}
+ public function replace($property, $value) {
+ $this->stack[0][$property] = $value;
+ }
+
public function reset() {
$this->stack = [];
}
diff --git a/lib/src/Util/OptionalOption.php b/lib/src/Util/OptionalOption.php
new file mode 100644
index 00000000..38bf8c92
--- /dev/null
+++ b/lib/src/Util/OptionalOption.php
@@ -0,0 +1,42 @@
+ Means "--refresh=auto"; see $omittedDefault
+ * cv en -r ==> Means "--refresh=yes"; see $activeDefault
+ * cv en -r=yes ==> Means "--refresh=yes"
+ * cv en -r=no ==> Means "--refresh=no"
+ *
+ * @param \CvDeps\Symfony\Component\Console\Input\InputInterface|\Symfony\Component\Console\Input\InputInterface $input
+ * @param array $rawNames
+ * Ex: array('-r', '--refresh').
+ * @param string $omittedDefault
+ * Value to use if option is completely omitted.
+ * @param string $activeDefault
+ * Value to use if option is activated without data.
+ * @return string
+ */
+ public static function parse($input, $rawNames, $omittedDefault, $activeDefault) {
+ $value = NULL;
+ foreach ($rawNames as $rawName) {
+ if ($input->hasParameterOption($rawName)) {
+ if (NULL === $input->getParameterOption($rawName)) {
+ return $activeDefault;
+ }
+ else {
+ return $input->getParameterOption($rawName);
+ }
+ }
+ }
+ return $omittedDefault;
+ }
+
+}
diff --git a/src/Command/AngularHtmlListCommand.php b/src/Command/AngularHtmlListCommand.php
index b191577e..9ced3f51 100644
--- a/src/Command/AngularHtmlListCommand.php
+++ b/src/Command/AngularHtmlListCommand.php
@@ -1,15 +1,13 @@
configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
if (!$input->getOption('user')) {
$output->getErrorOutput()->writeln("For a full list, try passing --user=[username].");
}
diff --git a/src/Command/AngularHtmlShowCommand.php b/src/Command/AngularHtmlShowCommand.php
index 41801d2c..fed17693 100644
--- a/src/Command/AngularHtmlShowCommand.php
+++ b/src/Command/AngularHtmlShowCommand.php
@@ -2,15 +2,12 @@
namespace Civi\Cv\Command;
use Civi\Cv\Util\Process;
-use Civi\Cv\Util\BootTrait;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-class AngularHtmlShowCommand extends BaseCommand {
-
- use BootTrait;
+class AngularHtmlShowCommand extends CvCommand {
/**
* @param string|null $name
@@ -38,11 +35,9 @@ protected function configure() {
cv ang:html:show crmMailing/BlockMailing.html --diff | colordiff
cv ang:html:show "~/crmMailing/BlockMailing.html"
');
- $this->configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
if (!$input->getOption('user')) {
$output->getErrorOutput()->writeln("For a full list, try passing --user=[username].");
}
diff --git a/src/Command/AngularModuleListCommand.php b/src/Command/AngularModuleListCommand.php
index 5bc51f57..73dbe08c 100644
--- a/src/Command/AngularModuleListCommand.php
+++ b/src/Command/AngularModuleListCommand.php
@@ -1,15 +1,13 @@
configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
if (!$input->getOption('user')) {
$output->getErrorOutput()->writeln("For a full list, try passing --user=[username].");
}
diff --git a/src/Command/Api4Command.php b/src/Command/Api4Command.php
index 1c796304..40e0b37c 100644
--- a/src/Command/Api4Command.php
+++ b/src/Command/Api4Command.php
@@ -3,16 +3,14 @@
use Civi\Cv\Encoder;
use Civi\Cv\Util\Api4ArgParser;
-use Civi\Cv\Util\BootTrait;
use Civi\Cv\Util\StructuredOutputTrait;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-class Api4Command extends BaseCommand {
+class Api4Command extends CvCommand {
- use BootTrait;
use StructuredOutputTrait;
/**
@@ -122,7 +120,6 @@ protected function configure() {
NOTE: To change the default output format, set CV_OUTPUT.
");
- $this->configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
@@ -131,8 +128,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$I = '';
$_I = '';
- $this->boot($input, $output);
-
if (!function_exists('civicrm_api4')) {
throw new \RuntimeException("Please enable APIv4 before running APIv4 commands.");
}
diff --git a/src/Command/ApiBatchCommand.php b/src/Command/ApiBatchCommand.php
index 59d9ad9d..8c0bdc71 100644
--- a/src/Command/ApiBatchCommand.php
+++ b/src/Command/ApiBatchCommand.php
@@ -1,14 +1,11 @@
configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
@@ -64,7 +60,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
// Other formats may not work with the fgets() loop.
throw new \Exception("api:batch only supports JSON dialog");
}
- $this->boot($input, $output);
$addDefault = function($v) {
$this->defaults = \CRM_Utils_Array::crmArrayMerge($v, $this->defaults);
diff --git a/src/Command/ApiCommand.php b/src/Command/ApiCommand.php
index 14e15e72..6713afb2 100644
--- a/src/Command/ApiCommand.php
+++ b/src/Command/ApiCommand.php
@@ -2,16 +2,14 @@
namespace Civi\Cv\Command;
use Civi\Cv\Encoder;
-use Civi\Cv\Util\BootTrait;
use Civi\Cv\Util\StructuredOutputTrait;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-class ApiCommand extends BaseCommand {
+class ApiCommand extends CvCommand {
- use BootTrait;
use StructuredOutputTrait;
/**
@@ -47,7 +45,6 @@ protected function configure() {
TIP: To display a full backtrace of any errors, pass "-vv" (very verbose).
');
- $this->configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
@@ -56,8 +53,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$I = '';
$_I = '';
- $this->boot($input, $output);
-
list($entity, $action) = explode('.', $input->getArgument('Entity.action'));
$params = $this->parseParams($input);
diff --git a/src/Command/BaseCommand.php b/src/Command/BaseCommand.php
deleted file mode 100644
index 2647d191..00000000
--- a/src/Command/BaseCommand.php
+++ /dev/null
@@ -1,111 +0,0 @@
-runOptionCallbacks($input, $output);
- }
-
- protected function assertBooted() {
- if (!$this->isBooted()) {
- throw new \Exception("Error: This command requires bootstrapping, but the system does not appear to be bootstrapped. Perhaps you set --level=none?");
- }
- }
-
- /**
- * Execute an API call. If it fails, display a formatted error.
- *
- * Note: If there is an error, we still return it softly so that the
- * command can exit gracefully.
- *
- * @param \Symfony\Component\Console\Input\InputInterface $input
- * @param \Symfony\Component\Console\Output\OutputInterface $output
- * @param $entity
- * @param $action
- * @param $params
- * @return mixed
- */
- protected function callApiSuccess(InputInterface $input, OutputInterface $output, $entity, $action, $params) {
- $this->assertBooted();
- $params['debug'] = 1;
- if (!isset($params['version'])) {
- $params['version'] = 3;
- }
- $output->writeln("Calling $entity $action API", OutputInterface::VERBOSITY_DEBUG);
- $result = \civicrm_api($entity, $action, $params);
- if (!empty($result['is_error']) || $output->isDebug()) {
- $data = array(
- 'entity' => $entity,
- 'action' => $action,
- 'params' => $params,
- 'result' => $result,
- );
- if (!empty($result['is_error'])) {
- $output->getErrorOutput()->writeln("Error: API Call Failed: "
- . Encoder::encode($data, 'pretty'));
- }
- else {
- $output->writeln("API success" . Encoder::encode($data, 'pretty'),
- OutputInterface::VERBOSITY_DEBUG);
- }
- }
- return $result;
- }
-
- /**
- * Parse an option's data. This is for options where the default behavior
- * (of total omission) differs from the activated behavior
- * (of an active but unspecified option).
- *
- * Example, suppose we want these interpretations:
- * cv en ==> Means "--refresh=auto"; see $omittedDefault
- * cv en -r ==> Means "--refresh=yes"; see $activeDefault
- * cv en -r=yes ==> Means "--refresh=yes"
- * cv en -r=no ==> Means "--refresh=no"
- *
- * @param \Symfony\Component\Console\Input\InputInterface $input
- * @param array $rawNames
- * Ex: array('-r', '--refresh').
- * @param string $omittedDefault
- * Value to use if option is completely omitted.
- * @param string $activeDefault
- * Value to use if option is activated without data.
- * @return string
- */
- public function parseOptionalOption(InputInterface $input, $rawNames, $omittedDefault, $activeDefault) {
- $value = NULL;
- foreach ($rawNames as $rawName) {
- if ($input->hasParameterOption($rawName)) {
- if (NULL === $input->getParameterOption($rawName)) {
- return $activeDefault;
- }
- else {
- return $input->getParameterOption($rawName);
- }
- }
- }
- return $omittedDefault;
- }
-
- /**
- * @return bool
- */
- protected function isBooted() {
- return defined('CIVICRM_DSN');
- }
-
-}
diff --git a/src/Command/BootCommand.php b/src/Command/BootCommand.php
index 11a8b82a..60dc8df1 100644
--- a/src/Command/BootCommand.php
+++ b/src/Command/BootCommand.php
@@ -1,24 +1,18 @@
setName('php:boot')
->setDescription('Generate PHP bootstrap code');
- $this->configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
-
switch ($input->getOption('level')) {
case 'classloader':
$code = sprintf('require_once %s . "/CRM/Core/ClassLoader.php";', var_export(rtrim($GLOBALS["civicrm_root"], '/'), 1))
diff --git a/src/Command/CliCommand.php b/src/Command/CliCommand.php
index 9dca5f27..1d24a05d 100644
--- a/src/Command/CliCommand.php
+++ b/src/Command/CliCommand.php
@@ -6,24 +6,18 @@
// **********************
use Civi\Cv\Application;
-use Civi\Cv\Util\BootTrait;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-class CliCommand extends BaseCommand {
-
- use BootTrait;
+class CliCommand extends CvCommand {
protected function configure() {
$this
->setName('cli')
->setDescription('Load interactive command line');
- $this->configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
-
$cv = new Application();
$sh = new \Psy\Shell();
$sh->addCommands($cv->createCommands());
diff --git a/src/Command/CoreCheckReqCommand.php b/src/Command/CoreCheckReqCommand.php
index d9836efc..033db002 100644
--- a/src/Command/CoreCheckReqCommand.php
+++ b/src/Command/CoreCheckReqCommand.php
@@ -8,7 +8,7 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-class CoreCheckReqCommand extends BaseCommand {
+class CoreCheckReqCommand extends CvCommand {
use SetupCommandTrait;
use DebugDispatcherTrait;
@@ -35,7 +35,10 @@ protected function configure() {
Example: Show warnings and errors
$ cv core:check-req -we
');
- $this->configureBootOptions('none');
+ }
+
+ public function getBootOptions(): array {
+ return ['default' => 'none', 'allow' => ['none']];
}
protected function execute(InputInterface $input, OutputInterface $output): int {
diff --git a/src/Command/CoreInstallCommand.php b/src/Command/CoreInstallCommand.php
index 76166030..96e6bc24 100644
--- a/src/Command/CoreInstallCommand.php
+++ b/src/Command/CoreInstallCommand.php
@@ -2,6 +2,7 @@
namespace Civi\Cv\Command;
use Civi\Cv\Encoder;
+use Civi\Cv\Util\OptionalOption;
use Civi\Cv\Util\SetupCommandTrait;
use Civi\Cv\Util\DebugDispatcherTrait;
use Symfony\Component\Console\Input\InputInterface;
@@ -9,7 +10,7 @@
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
-class CoreInstallCommand extends BaseCommand {
+class CoreInstallCommand extends CvCommand {
use SetupCommandTrait;
use DebugDispatcherTrait;
@@ -49,7 +50,10 @@ protected function configure() {
$ cv core:install --model=extras.opt-in.versionCheck=1
$ cv core:install -m extras.opt-in.versionCheck=1
');
- $this->configureBootOptions('none');
+ }
+
+ public function getBootOptions(): array {
+ return ['default' => 'none', 'allow' => ['none']];
}
protected function execute(InputInterface $input, OutputInterface $output): int {
@@ -57,7 +61,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$debugMode = FALSE;
- $debugEvent = $this->parseOptionalOption($input, ['--debug-event'], NULL, '');
+ $debugEvent = OptionalOption::parse($input, ['--debug-event'], NULL, '');
if ($debugEvent !== NULL) {
$eventNames = $this->findEventNames($setup->getDispatcher(), $debugEvent);
$this->printEventListeners($output, $setup->getDispatcher(), $eventNames);
diff --git a/src/Command/CoreUninstallCommand.php b/src/Command/CoreUninstallCommand.php
index d6563fca..858b23b7 100644
--- a/src/Command/CoreUninstallCommand.php
+++ b/src/Command/CoreUninstallCommand.php
@@ -1,6 +1,7 @@
configureBootOptions('none');
+ }
+
+ public function getBootOptions(): array {
+ return ['default' => 'none', 'allow' => ['none']];
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$setup = $this->bootSetupSubsystem($input, $output);
- $debugEvent = $this->parseOptionalOption($input, ['--debug-event'], NULL, '');
+ $debugEvent = OptionalOption::parse($input, ['--debug-event'], NULL, '');
if ($debugEvent !== NULL) {
$eventNames = $this->findEventNames($setup->getDispatcher(), $debugEvent);
$this->printEventListeners($output, $setup->getDispatcher(), $eventNames);
diff --git a/src/Command/DebugContainerCommand.php b/src/Command/DebugContainerCommand.php
index 2d2fba3b..87673a99 100644
--- a/src/Command/DebugContainerCommand.php
+++ b/src/Command/DebugContainerCommand.php
@@ -1,7 +1,6 @@
configureBootOptions();
}
- protected function execute(InputInterface $input, OutputInterface $output): int {
+ protected function initialize(InputInterface $input, OutputInterface $output) {
define('CIVICRM_CONTAINER_CACHE', 'never');
$output->getErrorOutput()->writeln('The debug command ignores the container cache.');
- $this->boot($input, $output);
+ parent::initialize($input, $output);
+ }
+ protected function execute(InputInterface $input, OutputInterface $output): int {
$c = $this->getInspectableContainer($input);
$filterPat = $input->getArgument('name');
diff --git a/src/Command/DebugDispatcherCommand.php b/src/Command/DebugDispatcherCommand.php
index 1bbb6192..4ca7f609 100644
--- a/src/Command/DebugDispatcherCommand.php
+++ b/src/Command/DebugDispatcherCommand.php
@@ -1,15 +1,13 @@
configureBootOptions();
}
- protected function execute(InputInterface $input, OutputInterface $output): int {
+ protected function initialize(InputInterface $input, OutputInterface $output) {
define('CIVICRM_CONTAINER_CACHE', 'never');
$output->getErrorOutput()->writeln('The debug command ignores the container cache.');
- $this->boot($input, $output);
+ parent::initialize($input, $output);
+ }
+ protected function execute(InputInterface $input, OutputInterface $output): int {
$container = \Civi::container();
/*
diff --git a/src/Command/EditCommand.php b/src/Command/EditCommand.php
index 6e5f3549..9b2375b9 100644
--- a/src/Command/EditCommand.php
+++ b/src/Command/EditCommand.php
@@ -9,13 +9,10 @@
use Civi\Cv\Config;
use Civi\Cv\Encoder;
use Civi\Cv\Util\CliEditor;
-use Civi\Cv\Util\BootTrait;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-class EditCommand extends BaseCommand {
-
- use BootTrait;
+class EditCommand extends CvCommand {
/**
* @var \Civi\Cv\Util\CliEditor
@@ -26,7 +23,6 @@ protected function configure() {
$this
->setName('vars:edit')
->setDescription('Edit configuration values for this build');
- $this->configureBootOptions();
}
public function __construct($name = NULL) {
@@ -47,8 +43,6 @@ public function __construct($name = NULL) {
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
-
$config = Config::read();
$oldSiteData = empty($config['sites'][CIVICRM_SETTINGS_PATH]) ? array() : $config['sites'][CIVICRM_SETTINGS_PATH];
$oldJson = Encoder::encode($oldSiteData, 'json-pretty');
diff --git a/src/Command/EvalCommand.php b/src/Command/EvalCommand.php
index 69e9d0fb..81bdb571 100644
--- a/src/Command/EvalCommand.php
+++ b/src/Command/EvalCommand.php
@@ -2,15 +2,13 @@
namespace Civi\Cv\Command;
use Civi\Cv\Encoder;
-use Civi\Cv\Util\BootTrait;
use Civi\Cv\Util\StructuredOutputTrait;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-class EvalCommand extends BaseCommand {
+class EvalCommand extends CvCommand {
- use BootTrait;
use StructuredOutputTrait;
protected function configure() {
@@ -37,12 +35,9 @@ protected function configure() {
NOTE: To change the default output format, set CV_OUTPUT.
');
- $this->configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
-
if ($input->getOption('out') === 'auto') {
$hasReturn = preg_match('/^\s*return[ \t\r\n]/', $input->getArgument('code'))
|| preg_match('/[;\{]\s*return[ \t\r\n]/', $input->getArgument('code'));
diff --git a/src/Command/ExtensionDisableCommand.php b/src/Command/ExtensionDisableCommand.php
index abc058a3..8f17c070 100644
--- a/src/Command/ExtensionDisableCommand.php
+++ b/src/Command/ExtensionDisableCommand.php
@@ -1,11 +1,15 @@
configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
- list ($foundKeys, $missingKeys) = $this->parseKeys($input, $output);
+ [$foundKeys, $missingKeys] = $this->parseKeys($input, $output);
// Uninstall what's recognized or what looks like an ext key.
$disableKeys = array_merge($foundKeys, preg_grep('/\./', $missingKeys));
@@ -52,7 +54,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$output->writeln("Disabling extension \"$key\"");
}
- $result = $this->callApiSuccess($input, $output, 'Extension', 'disable', array(
+ $result = VerboseApi::callApi3Success('Extension', 'disable', array(
'keys' => $disableKeys,
));
return empty($result['is_error']) ? 0 : 1;
diff --git a/src/Command/ExtensionDownloadCommand.php b/src/Command/ExtensionDownloadCommand.php
index f5820af6..105f0591 100644
--- a/src/Command/ExtensionDownloadCommand.php
+++ b/src/Command/ExtensionDownloadCommand.php
@@ -1,15 +1,19 @@
configureBootOptions();
+ $this->configureRepoOptions();
}
protected function initialize(InputInterface $input, OutputInterface $output) {
@@ -65,18 +68,15 @@ protected function initialize(InputInterface $input, OutputInterface $output) {
$input->setOption('level', 'none');
$input->setOption('no-install', TRUE);
}
- parent::initialize($input, $output);
- }
-
- protected function execute(InputInterface $input, OutputInterface $output): int {
- $fs = new Filesystem();
-
if ($extRepoUrl = $this->parseRepoUrl($input)) {
global $civicrm_setting;
$civicrm_setting['Extension Preferences']['ext_repo_url'] = $extRepoUrl;
}
+ parent::initialize($input, $output);
+ }
- $this->boot($input, $output);
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $fs = new Filesystem();
if ($input->getOption('to') && !$fs->isAbsolutePath($input->getOption('to'))) {
throw new \RuntimeException("The --to argument requires an absolute path.");
@@ -91,7 +91,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
while (TRUE) {
if ($refresh === 'yes' && $this->isBooted()) {
$output->writeln("Refreshing extension cache");
- $result = $this->callApiSuccess($input, $output, 'Extension', 'refresh', array(
+ $result = VerboseApi::callApi3Success('Extension', 'refresh', array(
'local' => FALSE,
'remote' => TRUE,
));
@@ -100,7 +100,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
}
- list ($downloads, $errors) = $this->parseDownloads($input);
+ [$downloads, $errors] = $this->parseDownloads($input);
if ($refresh == 'auto' && !empty($errors)) {
$output->writeln("Extension cache does not contain requested item(s)");
$refresh = 'yes';
@@ -135,7 +135,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
else {
$output->writeln("Downloading extension \"$key\" ($url)");
$this->assertBooted();
- $result = $this->callApiSuccess($input, $output, 'Extension', 'download', array(
+ $result = VerboseApi::callApi3Success('Extension', 'download', array(
'key' => $key,
'url' => $url,
'install' => !$input->getOption('no-install'),
@@ -145,7 +145,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
case 'install':
$output->writeln("Found extension \"$key\". Enabling.");
- $result = $this->callApiSuccess($input, $output, 'Extension', 'enable', array(
+ $result = VerboseApi::callApi3Success('Extension', 'enable', array(
'key' => $key,
));
break;
@@ -225,7 +225,7 @@ protected function parseDownloads(InputInterface $input) {
$origExpr = $keyOrName;
$url = NULL;
if (strpos($keyOrName, '@') !== FALSE) {
- list ($keyOrName, $url) = explode('@', $keyOrName, 2);
+ [$keyOrName, $url] = explode('@', $keyOrName, 2);
}
if (empty($keyOrName) && !empty($url)) {
diff --git a/src/Command/ExtensionEnableCommand.php b/src/Command/ExtensionEnableCommand.php
index 0de3bba5..73e618e6 100644
--- a/src/Command/ExtensionEnableCommand.php
+++ b/src/Command/ExtensionEnableCommand.php
@@ -1,12 +1,16 @@
configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
-
// Refresh extensions if (a) ---refresh enabled or (b) there's a cache-miss.
$refresh = $input->getOption('refresh') ? 'yes' : 'auto';
- // $refresh = $this->parseOptionalOption($input, array('--refresh', '-r'), 'auto', 'yes');
+ // $refresh = OptionalOption::parse(array('--refresh', '-r'), 'auto', 'yes');
while (TRUE) {
if ($refresh === 'yes') {
$output->writeln("Refreshing extension cache");
- $result = $this->callApiSuccess($input, $output, 'Extension', 'refresh', array(
+ $result = VerboseApi::callApi3Success('Extension', 'refresh', array(
'local' => TRUE,
'remote' => FALSE,
));
@@ -58,7 +59,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
}
- list ($foundKeys, $missingKeys) = $this->parseKeys($input, $output);
+ [$foundKeys, $missingKeys] = $this->parseKeys($input, $output);
if ($refresh == 'auto' && !empty($missingKeys)) {
$output->writeln("Extension cache does not contain requested item(s)");
$refresh = 'yes';
@@ -87,7 +88,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$output->writeln("Enabling extension \"$key\"");
}
- $result = $this->callApiSuccess($input, $output, 'Extension', 'install', array(
+ $result = VerboseApi::callApi3Success('Extension', 'install', array(
'keys' => $foundKeys,
));
return empty($result['is_error']) ? 0 : 1;
diff --git a/src/Command/ExtensionListCommand.php b/src/Command/ExtensionListCommand.php
index b5577451..09e6a63d 100644
--- a/src/Command/ExtensionListCommand.php
+++ b/src/Command/ExtensionListCommand.php
@@ -3,15 +3,18 @@
use Civi\Cv\Cv;
use Civi\Cv\Util\ArrayUtil;
+use Civi\Cv\Util\ExtensionTrait;
use Civi\Cv\Util\Relativizer;
use Civi\Cv\Util\StructuredOutputTrait;
+use Civi\Cv\Util\VerboseApi;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-class ExtensionListCommand extends BaseExtensionCommand {
+class ExtensionListCommand extends CvCommand {
+ use ExtensionTrait;
use StructuredOutputTrait;
/**
@@ -56,11 +59,15 @@ protected function configure() {
include a unique long name ("org.example.foobar") and a unique short
name ("foobar"). However, short names are not strongly guaranteed.
');
- parent::configureRepoOptions();
- $this->configureBootOptions();
+ $this->configureRepoOptions();
}
protected function initialize(InputInterface $input, OutputInterface $output) {
+ if ($extRepoUrl = $this->parseRepoUrl($input)) {
+ global $civicrm_setting;
+ $civicrm_setting['Extension Preferences']['ext_repo_url'] = $extRepoUrl;
+ }
+
parent::initialize($input, $output);
// We apply different defaults for the 'columns' list depending on the output medium.
@@ -82,14 +89,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
? (OutputInterface::OUTPUT_NORMAL | OutputInterface::VERBOSITY_NORMAL)
: (OutputInterface::OUTPUT_NORMAL | OutputInterface::VERBOSITY_VERBOSE);
- list($local, $remote) = $this->parseLocalRemote($input);
-
- if ($extRepoUrl = $this->parseRepoUrl($input)) {
- global $civicrm_setting;
- $civicrm_setting['Extension Preferences']['ext_repo_url'] = $extRepoUrl;
- }
-
- $this->boot($input, $output);
+ [$local, $remote] = $this->parseLocalRemote($input);
if ($remote) {
$output->writeln("Using extension feed \"" . \CRM_Extension_System::singleton()->getBrowser()->getRepositoryUrl() . "\"", $wo);
@@ -97,7 +97,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
if ($input->getOption('refresh')) {
$output->writeln("Refreshing extensions", $wo);
- $result = $this->callApiSuccess($input, $output, 'Extension', 'refresh', array(
+ $result = VerboseApi::callApi3Success('Extension', 'refresh', array(
'local' => $local,
'remote' => $remote,
));
@@ -136,7 +136,7 @@ protected function getRemoteInfos() {
*/
protected function find($input) {
$regex = $input->getArgument('regex');
- list($local, $remote) = $this->parseLocalRemote($input);
+ [$local, $remote] = $this->parseLocalRemote($input);
if ($input->getOption('installed')) {
$statusFilter = array('installed');
diff --git a/src/Command/ExtensionUninstallCommand.php b/src/Command/ExtensionUninstallCommand.php
index 839ab593..161db433 100644
--- a/src/Command/ExtensionUninstallCommand.php
+++ b/src/Command/ExtensionUninstallCommand.php
@@ -1,11 +1,15 @@
configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
- list ($foundKeys, $missingKeys) = $this->parseKeys($input, $output);
+ [$foundKeys, $missingKeys] = $this->parseKeys($input, $output);
// Uninstall what's recognized or what looks like an ext key.
$uninstallKeys = array_merge($foundKeys, preg_grep('/\./', $missingKeys));
@@ -52,14 +54,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$output->writeln("Uninstalling extension \"$key\"");
}
- $result = $this->callApiSuccess($input, $output, 'Extension', 'disable', array(
+ $result = VerboseApi::callApi3Success('Extension', 'disable', array(
'keys' => $uninstallKeys,
));
if (!empty($result['is_error'])) {
return 1;
}
- $result = $this->callApiSuccess($input, $output, 'Extension', 'uninstall', array(
+ $result = VerboseApi::callApi3Success('Extension', 'uninstall', array(
'keys' => $uninstallKeys,
));
return empty($result['is_error']) ? 0 : 1;
diff --git a/src/Command/ExtensionUpgradeDbCommand.php b/src/Command/ExtensionUpgradeDbCommand.php
index 41f87295..5fbbfd81 100644
--- a/src/Command/ExtensionUpgradeDbCommand.php
+++ b/src/Command/ExtensionUpgradeDbCommand.php
@@ -1,10 +1,14 @@
configureBootOptions();
}
- protected function execute(InputInterface $input, OutputInterface $output): int {
+ protected function initialize(InputInterface $input, OutputInterface $output) {
$output->writeln("WARNING: \"ext:upgrade-db\" is deprecated. Use the main \"updb\" command instead.");
- $this->boot($input, $output);
+ parent::initialize($input, $output);
+ }
+ protected function execute(InputInterface $input, OutputInterface $output): int {
$output->writeln("Applying database upgrades from extensions");
- $result = $this->callApiSuccess($input, $output, 'Extension', 'upgrade', array());
+ $result = VerboseApi::callApi3Success('Extension', 'upgrade', array());
if (!empty($result['is_error'])) {
return 1;
}
diff --git a/src/Command/FillCommand.php b/src/Command/FillCommand.php
index fb332d27..2199d163 100644
--- a/src/Command/FillCommand.php
+++ b/src/Command/FillCommand.php
@@ -4,14 +4,11 @@
use Civi\Cv\Config;
use Civi\Cv\Encoder;
use Civi\Cv\SiteConfigReader;
-use Civi\Cv\Util\BootTrait;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-class FillCommand extends BaseCommand {
-
- use BootTrait;
+class FillCommand extends CvCommand {
protected $fields;
@@ -25,7 +22,6 @@ protected function configure() {
->setName('vars:fill')
->setDescription('Generate a configuration file for any missing site data')
->addOption('file', NULL, InputOption::VALUE_REQUIRED, 'Read existing configuration from a file');
- $this->configureBootOptions();
}
public function __construct($name = NULL) {
@@ -59,7 +55,6 @@ public function __construct($name = NULL) {
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
if (!$input->getOption('file')) {
$reader = new SiteConfigReader(CIVICRM_SETTINGS_PATH);
$liveData = $reader->compile(array('buildkit', 'home', 'active'));
diff --git a/src/Command/FlushCommand.php b/src/Command/FlushCommand.php
index b522a90a..d38e1ed1 100644
--- a/src/Command/FlushCommand.php
+++ b/src/Command/FlushCommand.php
@@ -1,14 +1,12 @@
setHelp('
Flush system caches
');
- $this->configureBootOptions();
}
- protected function execute(InputInterface $input, OutputInterface $output): int {
+ protected function initialize(InputInterface $input, OutputInterface $output) {
// The main reason we have this as separate command -- so we can ignore
// stale class-references that might be retained by the container cache.
define('CIVICRM_CONTAINER_CACHE', 'never');
+
+ parent::initialize($input, $output);
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
$this->boot($input, $output);
$params = array();
@@ -34,7 +36,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
$output->writeln("Flushing system caches");
- $result = $this->callApiSuccess($input, $output, 'System', 'flush', $params);
+ $result = VerboseApi::callApi3Success('System', 'flush', $params);
return empty($result['is_error']) ? 0 : 1;
}
diff --git a/src/Command/HttpCommand.php b/src/Command/HttpCommand.php
index c79b78ce..e7ccc5a5 100644
--- a/src/Command/HttpCommand.php
+++ b/src/Command/HttpCommand.php
@@ -2,6 +2,7 @@
namespace Civi\Cv\Command;
+use Civi\Cv\Util\ExtensionTrait;
use Civi\Cv\Util\StructuredOutputTrait;
use Civi\Cv\Util\UrlCommandTrait;
use GuzzleHttp\Client;
@@ -9,8 +10,9 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-class HttpCommand extends BaseExtensionCommand {
+class HttpCommand extends CvCommand {
+ use ExtensionTrait;
use StructuredOutputTrait;
use UrlCommandTrait;
@@ -43,12 +45,9 @@ protected function configure() {
enabling the extension. The extra I/O may influence some scripted
use-cases.
');
- $this->configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
-
$method = $input->getOption('request');
$data = $this->parseRequestData($input);
$headers = $this->parseRequestHeaders($input);
diff --git a/src/Command/PathCommand.php b/src/Command/PathCommand.php
index 20348d85..1eef6dd9 100644
--- a/src/Command/PathCommand.php
+++ b/src/Command/PathCommand.php
@@ -1,13 +1,15 @@
configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
-
if (!$input->getOption('ext') && !$input->getOption('config') && !$input->getOption('dynamic')) {
$output->getErrorOutput()->writeln("No paths specified. Must use -x, -c, or -d. (See also: cv path -h)");
return 1;
diff --git a/src/Command/PipeCommand.php b/src/Command/PipeCommand.php
index 25d4140f..6d64eb72 100644
--- a/src/Command/PipeCommand.php
+++ b/src/Command/PipeCommand.php
@@ -1,14 +1,11 @@
configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
if (!is_callable(['Civi', 'pipe'])) {
fwrite(STDERR, "This version of CiviCRM does not include Civi::pipe() support.\n");
return 1;
diff --git a/src/Command/ScriptCommand.php b/src/Command/ScriptCommand.php
index 08e58f01..df20c771 100644
--- a/src/Command/ScriptCommand.php
+++ b/src/Command/ScriptCommand.php
@@ -2,14 +2,11 @@
namespace Civi\Cv\Command;
use Civi\Cv\Util\Filesystem;
-use Civi\Cv\Util\BootTrait;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-class ScriptCommand extends BaseCommand {
-
- use BootTrait;
+class ScriptCommand extends CvCommand {
protected function configure() {
$this
@@ -18,7 +15,10 @@ protected function configure() {
->setDescription('Execute a PHP script')
->addArgument('script', InputArgument::REQUIRED)
->addArgument('scriptArguments', InputArgument::IS_ARRAY, 'Optional arguments to pass to the script as $argv');
- $this->configureBootOptions();
+ }
+
+ public function getBootOptions(): array {
+ return ['auto' => FALSE] + parent::getBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
diff --git a/src/Command/SettingGetCommand.php b/src/Command/SettingGetCommand.php
index 36cbe2c7..cf7e0458 100644
--- a/src/Command/SettingGetCommand.php
+++ b/src/Command/SettingGetCommand.php
@@ -1,7 +1,6 @@
configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
-
$filter = $this->createSettingFilter($input->getArgument('name'));
$result = [];
diff --git a/src/Command/SettingRevertCommand.php b/src/Command/SettingRevertCommand.php
index 489a3e92..722210c9 100644
--- a/src/Command/SettingRevertCommand.php
+++ b/src/Command/SettingRevertCommand.php
@@ -1,7 +1,6 @@
configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
$errorOutput = is_callable([$output, 'getErrorOutput']) ? $output->getErrorOutput() : $output;
$filter = $this->createSettingFilter($input->getArgument('name'));
diff --git a/src/Command/SettingSetCommand.php b/src/Command/SettingSetCommand.php
index 4db84c66..cd717a0c 100644
--- a/src/Command/SettingSetCommand.php
+++ b/src/Command/SettingSetCommand.php
@@ -1,7 +1,6 @@
configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
@@ -91,7 +88,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$I = '';
$_I = '';
- $this->boot($input, $output);
$errorOutput = is_callable([$output, 'getErrorOutput']) ? $output->getErrorOutput() : $output;
$result = [];
diff --git a/src/Command/ShowCommand.php b/src/Command/ShowCommand.php
index a598aa2f..2f5eea5f 100644
--- a/src/Command/ShowCommand.php
+++ b/src/Command/ShowCommand.php
@@ -2,14 +2,12 @@
namespace Civi\Cv\Command;
use Civi\Cv\SiteConfigReader;
-use Civi\Cv\Util\BootTrait;
use Civi\Cv\Util\StructuredOutputTrait;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-class ShowCommand extends BaseCommand {
+class ShowCommand extends CvCommand {
- use BootTrait;
use StructuredOutputTrait;
protected function configure() {
@@ -17,11 +15,9 @@ protected function configure() {
->setName('vars:show')
->setDescription('Show the configuration of the local CiviCRM installation')
->configureOutputOptions();
- $this->configureBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
$reader = new SiteConfigReader(CIVICRM_SETTINGS_PATH);
$data = $reader->compile(array('buildkit', 'home', 'active'));
$this->sendResult($input, $output, $data);
diff --git a/src/Command/SqlCliCommand.php b/src/Command/SqlCliCommand.php
index f43b259b..367af698 100644
--- a/src/Command/SqlCliCommand.php
+++ b/src/Command/SqlCliCommand.php
@@ -3,14 +3,11 @@
use Civi\Cv\Util\Datasource;
use Civi\Cv\Util\Process;
-use Civi\Cv\Util\BootTrait;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-class SqlCliCommand extends BaseCommand {
-
- use BootTrait;
+class SqlCliCommand extends CvCommand {
protected function configure() {
$this
@@ -38,7 +35,6 @@ protected function configure() {
#ENV[FOO] Produces the numerical value of FOO (or fails)
!ENV[FOO] Produces the raw, unescaped string version of FOO
");
- $this->configureBootOptions();
}
protected function initialize(InputInterface $input, OutputInterface $output) {
@@ -48,8 +44,6 @@ protected function initialize(InputInterface $input, OutputInterface $output) {
}
protected function execute(InputInterface $input, OutputInterface $output): int {
- $this->boot($input, $output);
-
$datasource = new Datasource();
$datasource->loadFromCiviDSN($this->pickDsn($input->getOption('target')));
diff --git a/src/Command/UpgradeCommand.php b/src/Command/UpgradeCommand.php
index cbf3e877..ca5b87a9 100644
--- a/src/Command/UpgradeCommand.php
+++ b/src/Command/UpgradeCommand.php
@@ -6,7 +6,7 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-class UpgradeCommand extends BaseCommand {
+class UpgradeCommand extends CvCommand {
use StructuredOutputTrait;
diff --git a/src/Command/UpgradeDbCommand.php b/src/Command/UpgradeDbCommand.php
index c1f98f89..87be5804 100644
--- a/src/Command/UpgradeDbCommand.php
+++ b/src/Command/UpgradeDbCommand.php
@@ -1,7 +1,6 @@
configureBootOptions();
}
/**
@@ -52,15 +49,13 @@ protected function configure() {
protected function initialize(InputInterface $input, OutputInterface $output) {
$this->input = $input;
$this->output = $output;
- parent::initialize($input, $output);
- }
-
- protected function execute(InputInterface $input, OutputInterface $output): int {
if (!defined('CIVICRM_UPGRADE_ACTIVE')) {
define('CIVICRM_UPGRADE_ACTIVE', 1);
}
- $this->boot($input, $output);
+ parent::initialize($input, $output);
+ }
+ protected function execute(InputInterface $input, OutputInterface $output): int {
if (!ini_get('safe_mode')) {
set_time_limit(0);
}
diff --git a/src/Command/UpgradeDlCommand.php b/src/Command/UpgradeDlCommand.php
index 7b1c7e62..d9482348 100644
--- a/src/Command/UpgradeDlCommand.php
+++ b/src/Command/UpgradeDlCommand.php
@@ -11,7 +11,7 @@
/**
* Command for asking CiviCRM for the appropriate tarball to download.
*/
-class UpgradeDlCommand extends BaseCommand {
+class UpgradeDlCommand extends CvCommand {
use StructuredOutputTrait;
diff --git a/src/Command/UpgradeGetCommand.php b/src/Command/UpgradeGetCommand.php
index 56f358a3..5f4ff0ee 100644
--- a/src/Command/UpgradeGetCommand.php
+++ b/src/Command/UpgradeGetCommand.php
@@ -1,7 +1,6 @@
configureBootOptions();
+ }
+
+ public function getBootOptions(): array {
+ return ['auto' => FALSE] + parent::getBootOptions();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
diff --git a/src/Command/UpgradeReportCommand.php b/src/Command/UpgradeReportCommand.php
index eaf9b367..366fcc2a 100644
--- a/src/Command/UpgradeReportCommand.php
+++ b/src/Command/UpgradeReportCommand.php
@@ -9,7 +9,7 @@
/**
* Command for asking CiviCRM for the appropriate tarball to download.
*/
-class UpgradeReportCommand extends BaseCommand {
+class UpgradeReportCommand extends CvCommand {
const DEFAULT_REPORT_URL = 'https://upgrade.civicrm.org/report';
use StructuredOutputTrait;
diff --git a/src/Command/UrlCommand.php b/src/Command/UrlCommand.php
index b712836a..a5a74936 100644
--- a/src/Command/UrlCommand.php
+++ b/src/Command/UrlCommand.php
@@ -2,6 +2,7 @@
namespace Civi\Cv\Command;
use Civi\Cv\Encoder;
+use Civi\Cv\Util\ExtensionTrait;
use Civi\Cv\Util\Process;
use Civi\Cv\Util\StructuredOutputTrait;
use Civi\Cv\Util\UrlCommandTrait;
@@ -9,8 +10,9 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-class UrlCommand extends BaseExtensionCommand {
+class UrlCommand extends CvCommand {
+ use ExtensionTrait;
use StructuredOutputTrait;
use UrlCommandTrait;
@@ -66,24 +68,20 @@ protected function configure() {
enabling the extension. The extra I/O may influence some scripted
use-cases.
');
- $this->configureBootOptions();
}
protected function initialize(InputInterface $input, OutputInterface $output) {
- parent::initialize($input, $output);
if ($input->getFirstArgument() === 'open') {
$input->setOption('open', TRUE);
}
- }
-
- protected function execute(InputInterface $input, OutputInterface $output): int {
if (in_array($input->getOption('out'), Encoder::getTabularFormats())
&& !in_array($input->getOption('out'), Encoder::getFormats())) {
$input->setOption('tabular', TRUE);
}
+ parent::initialize($input, $output);
+ }
- $this->boot($input, $output);
-
+ protected function execute(InputInterface $input, OutputInterface $output): int {
$rows = $this->createUrls($input, $output);
if ($input->getOption('open')) {
diff --git a/src/Command/BaseExtensionCommand.php b/src/Util/ExtensionTrait.php
similarity index 96%
rename from src/Command/BaseExtensionCommand.php
rename to src/Util/ExtensionTrait.php
index efab304d..ead85905 100644
--- a/src/Command/BaseExtensionCommand.php
+++ b/src/Util/ExtensionTrait.php
@@ -1,14 +1,11 @@
$keys, 1=>$errors).
*/
diff --git a/src/Util/OptionCallbackTrait.php b/src/Util/OptionCallbackTrait.php
index 0b7742e4..7295faf9 100644
--- a/src/Util/OptionCallbackTrait.php
+++ b/src/Util/OptionCallbackTrait.php
@@ -33,6 +33,7 @@ abstract public function getDefinition();
* @param string $name
* The name of the option to
* @param callable $callback
+ * Function(InputInterface $input, OutputInterface, $output, string $optionName)
* @return $this
*/
public function addOptionCallback($name, $callback) {
diff --git a/src/Util/SetupCommandTrait.php b/src/Util/SetupCommandTrait.php
index 6126be06..e7022d0a 100644
--- a/src/Util/SetupCommandTrait.php
+++ b/src/Util/SetupCommandTrait.php
@@ -15,7 +15,6 @@
* civicrm-setup framework.
*/
trait SetupCommandTrait {
- use BootTrait;
/**
* Register any CLI options which affect the initialization of the
diff --git a/src/Util/StructuredOutputTrait.php b/src/Util/StructuredOutputTrait.php
index f9fd553e..66064542 100644
--- a/src/Util/StructuredOutputTrait.php
+++ b/src/Util/StructuredOutputTrait.php
@@ -113,7 +113,7 @@ protected function configureOutputOptions($config = []) {
* @see Encoder::getFormats
*/
protected function sendResult(InputInterface $input, OutputInterface $output, $result) {
- $flat = $this->parseOptionalOption($input, ['--flat'], FALSE, '.');
+ $flat = OptionalOption::parse($input, ['--flat'], FALSE, '.');
if ($flat !== FALSE) {
$result = ArrayUtil::implodeTree($flat, $result);
}
@@ -157,7 +157,7 @@ protected function sendTable(InputInterface $input, OutputInterface $output, $re
return;
}
- $flat = $this->parseOptionalOption($input, ['--flat'], FALSE, '.');
+ $flat = OptionalOption::parse($input, ['--flat'], FALSE, '.');
if ($flat !== FALSE) {
$filtered = ArrayUtil::filterColumns($records, $columns);
$flattened = ArrayUtil::implodeTree($flat, $filtered);
diff --git a/src/Util/VerboseApi.php b/src/Util/VerboseApi.php
new file mode 100644
index 00000000..475e774b
--- /dev/null
+++ b/src/Util/VerboseApi.php
@@ -0,0 +1,47 @@
+writeln("Calling $entity $action API", OutputInterface::VERBOSITY_DEBUG);
+ $result = \civicrm_api($entity, $action, $params);
+ if (!empty($result['is_error']) || $output->isDebug()) {
+ $data = array(
+ 'entity' => $entity,
+ 'action' => $action,
+ 'params' => $params,
+ 'result' => $result,
+ );
+ if (!empty($result['is_error'])) {
+ $output->getErrorOutput()->writeln("Error: API Call Failed: "
+ . Encoder::encode($data, 'pretty'));
+ }
+ else {
+ $output->writeln("API success" . Encoder::encode($data, 'pretty'),
+ OutputInterface::VERBOSITY_DEBUG);
+ }
+ }
+ return $result;
+ }
+
+}
diff --git a/tests/Command/BaseCommandTest.php b/tests/Command/BaseCommandTest.php
deleted file mode 100644
index 808b5a0e..00000000
--- a/tests/Command/BaseCommandTest.php
+++ /dev/null
@@ -1,50 +0,0 @@
-addOption('refresh', array('r'), InputOption::VALUE_OPTIONAL, 'auto');
-
- $input = new ArgvInput($inputArgv, $c->getDefinition());
- $this->assertEquals($expectValue, $c->parseOptionalOption($input, array('-r', '--refresh'), 'auto', 'yes'));
- }
-
-}
diff --git a/tests/Command/BaseExtensionCommandTest.php b/tests/Command/BaseExtensionCommandTest.php
index 83bc2bbc..26f4a81a 100644
--- a/tests/Command/BaseExtensionCommandTest.php
+++ b/tests/Command/BaseExtensionCommandTest.php
@@ -1,6 +1,7 @@
configureRepoOptions();
$input = new ArgvInput($inputArgv, $c->getDefinition());
diff --git a/tests/Plugin/FluentHelloPluginTest.php b/tests/Plugin/FluentHelloPluginTest.php
new file mode 100644
index 00000000..6bc843d6
--- /dev/null
+++ b/tests/Plugin/FluentHelloPluginTest.php
@@ -0,0 +1,39 @@
+setEnv(['CV_PLUGIN_PATH' => preg_replace(';\.php$;', '', __FILE__)]);
+ return $process;
+ }
+
+ public function testRun() {
+ $output = $this->cvOk('hello:normal');
+ $this->assertMatchesRegularExpression('/Hey-yo world via parameter.*Hey-yo world via StyleInterface/s', $output);
+ }
+
+ public function testRunWithName() {
+ $output = $this->cvOk('hello:normal Alice');
+ $this->assertMatchesRegularExpression('/Hey-yo Alice via parameter.*Hey-yo Alice via StyleInterface/s', $output);
+ }
+
+ public function testRun_noboot() {
+ $output = $this->cvOk('hello:noboot');
+ $this->assertMatchesRegularExpression('/Hey-yo world via parameter.*Hey-yo world via StyleInterface/s', $output);
+ }
+
+ public function testRunWithName_noboot() {
+ $output = $this->cvOk('hello:noboot Bob');
+ $this->assertMatchesRegularExpression('/Hey-yo Bob via parameter.*Hey-yo Bob via StyleInterface/s', $output);
+ }
+
+}
diff --git a/tests/Plugin/FluentHelloPluginTest/hello.php b/tests/Plugin/FluentHelloPluginTest/hello.php
new file mode 100644
index 00000000..b4bef8bf
--- /dev/null
+++ b/tests/Plugin/FluentHelloPluginTest/hello.php
@@ -0,0 +1,68 @@
+ 1) {
+ die("Expect CV_PLUGIN API v1");
+}
+
+if (!preg_match(';^[\w_-]+$;', $CV_PLUGIN['appName'])) {
+ throw new \RuntimeException("Invalid CV_PLUGIN[appName]" . json_encode($CV_PLUGIN['appName']));
+}
+
+if (!preg_match(';^([0-9x\.]+(-[\w-]+)?|UNKNOWN)$;', $CV_PLUGIN['appVersion'])) {
+ throw new \RuntimeException("Invalid CV_PLUGIN[appVersion]: " . json_encode($CV_PLUGIN['appVersion']));
+}
+
+if ($CV_PLUGIN['name'] !== 'hello') {
+ throw new \RuntimeException("Invalid CV_PLUGIN[name]");
+}
+if (realpath($CV_PLUGIN['file']) !== realpath(__FILE__)) {
+ throw new \RuntimeException("Invalid CV_PLUGIN[file]");
+}
+
+Cv::dispatcher()->addListener('*.app.boot', function ($e) {
+ Cv::io()->writeln("Hey-yo during initial bootstrap!");
+});
+
+Cv::dispatcher()->addListener('cv.app.commands', function ($e) {
+
+ $e['commands'][] = (new CvCommand('hello:normal'))
+ ->setDescription('Say a greeting')
+ ->addArgument('name')
+ ->setCode(function($input, $output) {
+ // ASSERT: With setCode(), it's OK to use un-hinted inputs.
+ if ($input->getArgument('name') !== Cv::input()->getArgument('name')) {
+ throw new \RuntimeException("Argument \"name\" is inconsistent!");
+ }
+ if (!Civi\Core\Container::isContainerBooted()) {
+ throw new \LogicException("Container should have been booted by CvCommand!");
+ }
+ $name = $input->getArgument('name') ?: 'world';
+ $output->writeln("Hey-yo $name via parameter!");
+ Cv::io()->writeln("Hey-yo $name via StyleInterface!");
+ return 0;
+ });
+
+ $e['commands'][] = (new CvCommand('hello:noboot'))
+ ->setDescription('Say a greeting')
+ ->addArgument('name')
+ ->setBootOptions(['auto' => FALSE])
+ ->setCode(function(InputInterface $input, OutputInterface $output) {
+ // ASSERT: With setCode(), it's OK to use hinted inputs.
+ if ($input->getArgument('name') !== Cv::input()->getArgument('name')) {
+ throw new \RuntimeException("Argument \"name\" is inconsistent!");
+ }
+ if (class_exists('Civi\Core\Container')) {
+ throw new \LogicException("Container should not have been booted by CvCommand!");
+ }
+ $name = $input->getArgument('name') ?: 'world';
+ $output->writeln("Hey-yo $name via parameter!");
+ Cv::io()->writeln("Hey-yo $name via StyleInterface!");
+ return 0;
+ });
+
+});
diff --git a/tests/Util/OptionalOptionTest.php b/tests/Util/OptionalOptionTest.php
new file mode 100644
index 00000000..2f8f9c0b
--- /dev/null
+++ b/tests/Util/OptionalOptionTest.php
@@ -0,0 +1,65 @@
+push(...$this->createInputOutput($inputArgv));
+ try {
+ $this->assertEquals($expectValue, OptionalOption::parse(Cv::input(), ['-r', '--refresh'], 'auto', 'yes'));
+ }
+ finally {
+ Cv::ioStack()->pop();
+ }
+ }
+
+ /**
+ * @return array
+ * [0 => InputInterface, 1 => OutputInterface]
+ */
+ protected function createInputOutput(array $argv = NULL): array {
+ $input = new ArgvInput($argv);
+ $input->setInteractive(FALSE);
+ $output = new NullOutput();
+ return [$input, $output];
+ }
+
+}