From 43df9d25f046113cb8b4c674ecbe567a418232ea Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 18 Mar 2021 13:39:14 +0200 Subject: [PATCH] new command interface --- .../Espo/Core/ApplicationRunners/Command.php | 14 +- .../Core/Console/{Commands => }/Command.php | 7 +- .../Espo/Core/Console/CommandManager.php | 81 +++++----- .../Espo/Core/Console/Commands/AclCheck.php | 39 +++-- .../Espo/Core/Console/Commands/AppInfo.php | 1 + .../Core/Console/Commands/AuthTokenCheck.php | 20 ++- .../Espo/Core/Console/Commands/ClearCache.php | 12 +- .../Espo/Core/Console/Commands/Extension.php | 107 ++++++++---- .../Espo/Core/Console/Commands/Rebuild.php | 14 +- .../Espo/Core/Console/Commands/RunJob.php | 37 +++-- .../Core/Console/Commands/SetPassword.php | 66 ++++---- .../Espo/Core/Console/Commands/Upgrade.php | 26 ++- .../Core/Console/Commands/UpgradeStep.php | 51 ++++-- .../Espo/Core/Console/Commands/Version.php | 13 +- .../CommandNotFound.php} | 17 +- .../Exceptions/CommandNotSpecified.php | 37 +++++ application/Espo/Core/Console/IO.php | 69 ++++++++ application/Espo/Core/Console/Params.php | 152 ++++++++++++++++++ command.php | 9 +- .../Espo/Core/Console/CommandManagerTest.php | 71 +++++--- tests/unit/Espo/Core/Console/ParamsTest.php | 64 ++++++++ 21 files changed, 682 insertions(+), 225 deletions(-) rename application/Espo/Core/Console/{Commands => }/Command.php (92%) rename application/Espo/Core/Console/{Commands/Base.php => Exceptions/CommandNotFound.php} (82%) create mode 100644 application/Espo/Core/Console/Exceptions/CommandNotSpecified.php create mode 100644 application/Espo/Core/Console/IO.php create mode 100644 application/Espo/Core/Console/Params.php create mode 100644 tests/unit/Espo/Core/Console/ParamsTest.php diff --git a/application/Espo/Core/ApplicationRunners/Command.php b/application/Espo/Core/ApplicationRunners/Command.php index 943184c646..5c3afac036 100644 --- a/application/Espo/Core/ApplicationRunners/Command.php +++ b/application/Espo/Core/ApplicationRunners/Command.php @@ -34,6 +34,8 @@ use Espo\Core\{ Console\CommandManager as ConsoleCommandManager, }; +use Exception; + /** * Runs a console command. */ @@ -51,13 +53,11 @@ class Command implements ApplicationRunner public function run() : void { - ob_start(); - - $result = $this->commandManager->run($_SERVER['argv']); - - if (is_string($result)) { - ob_end_clean(); - echo $result; + try { + $this->commandManager->run($_SERVER['argv']); + } + catch (Exception $e) { + echo "Error: " . $e->getMessage() . "\n"; } } } diff --git a/application/Espo/Core/Console/Commands/Command.php b/application/Espo/Core/Console/Command.php similarity index 92% rename from application/Espo/Core/Console/Commands/Command.php rename to application/Espo/Core/Console/Command.php index de2a3f221e..58319ce963 100644 --- a/application/Espo/Core/Console/Commands/Command.php +++ b/application/Espo/Core/Console/Command.php @@ -27,9 +27,12 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -namespace Espo\Core\Console\Commands; +namespace Espo\Core\Console; +/** + * A command. + */ interface Command { - + public function run(Params $params, IO $io) : void; } diff --git a/application/Espo/Core/Console/CommandManager.php b/application/Espo/Core/Console/CommandManager.php index af11a62ada..67006faadb 100644 --- a/application/Espo/Core/Console/CommandManager.php +++ b/application/Espo/Core/Console/CommandManager.php @@ -33,6 +33,8 @@ use Espo\Core\{ InjectableFactory, Utils\Metadata, Utils\Util, + Console\Exceptions\CommandNotSpecified, + Console\Exceptions\CommandNotFound, }; /** @@ -40,9 +42,9 @@ use Espo\Core\{ */ class CommandManager { - protected $injectableFactory; + private $injectableFactory; - protected $metadata; + private $metadata; public function __construct(InjectableFactory $injectableFactory, Metadata $metadata) { @@ -50,67 +52,62 @@ class CommandManager $this->metadata = $metadata; } - public function run(array $argv) + public function run(array $argv) : void + { + $command = $this->getCommandNameFromArgv($argv); + + $params = $this->createParams($argv); + + $io = new IO(); + + $this->createCommand($command)->run($params, $io); + } + + private function getCommandNameFromArgv(array $argv) : string { $command = isset($argv[1]) ? trim($argv[1]) : null; if (!$command) { - $msg = "Command name is not specifed."; - - echo $msg . "\n"; - - exit; + throw new CommandNotSpecified("Command name is not specifed."); } - $command = ucfirst(Util::hyphenToCamelCase($command)); - - $params = $this->getParams($argv); - - $options = $params['options']; - $flagList = $params['flagList']; - $argumentList = $params['argumentList']; - - $className = $this->getClassName($command); - - $obj = $this->injectableFactory->create($className); - - return $obj->run($options, $flagList, $argumentList); + return ucfirst(Util::hyphenToCamelCase($command)); } - protected function getClassName(string $command) : string + private function createCommand(string $command) : Command + { + $className = $this->getClassName($command); + + return $this->injectableFactory->create($className); + } + + private function getClassName(string $command) : string { $className = - $this->metadata->get(['app', 'consoleCommands', lcfirst($command), 'className']) ?? - 'Espo\\Core\\Console\\Commands\\' . $command; + $this->metadata->get(['app', 'consoleCommands', lcfirst($command), 'className']); + + if ($className) { + return $className; + } + + $className = 'Espo\\Core\\Console\\Commands\\' . $command; if (!class_exists($className)) { - $msg = "Command '{$command}' does not exist."; - - echo $msg . "\n"; - - exit; + throw new CommandNotFound("Command '{$command}' does not exist."); } return $className; } - protected function getParams(array $argv) : array + private function createParams(array $argv) : Params { $argumentList = []; $options = []; $flagList = []; - $skipIndex = 1; - - if (isset($argv[0]) && preg_match('/command\.php$/', $argv[0])) { - $skipIndex = 2; - } - - foreach ($argv as $i => $item) { - if ($i < $skipIndex) { - continue; - } + $itemList = array_slice($argv, 2); + foreach ($itemList as $item) { if (strpos($item, '--') === 0 && strpos($item, '=') > 2) { list($name, $value) = explode('=', substr($item, 2)); @@ -129,10 +126,10 @@ class CommandManager } } - return [ + return new Params([ 'argumentList' => $argumentList, 'options' => $options, 'flagList' => $flagList, - ]; + ]); } } diff --git a/application/Espo/Core/Console/Commands/AclCheck.php b/application/Espo/Core/Console/Commands/AclCheck.php index ea21e01ce9..41f46e2ab2 100644 --- a/application/Espo/Core/Console/Commands/AclCheck.php +++ b/application/Espo/Core/Console/Commands/AclCheck.php @@ -29,8 +29,13 @@ namespace Espo\Core\Console\Commands; -use Espo\Core\Portal\Application as PortalApplication; -use Espo\Core\Container; +use Espo\Core\{ + Portal\Application as PortalApplication, + Container, + Console\Command, + Console\Params, + Console\IO, +}; class AclCheck implements Command { @@ -41,23 +46,25 @@ class AclCheck implements Command $this->container = $container; } - public function run(array $options) : ?string + public function run(Params $params, IO $io) : void { + $options = $params->getOptions(); + $userId = $options['userId'] ?? null; $scope = $options['scope'] ?? null; $id = $options['id'] ?? null; $action = $options['action'] ?? null; if (empty($userId)) { - return null; + return; } if (empty($scope)) { - return null; + return; } if (empty($id)) { - return null; + return; } $container = $this->container; @@ -67,7 +74,7 @@ class AclCheck implements Command $user = $entityManager->getEntity('User', $userId); if (!$user) { - return null; + return; } if ($user->isPortal()) { @@ -75,33 +82,37 @@ class AclCheck implements Command foreach ($portalIdList as $portalId) { $application = new PortalApplication($portalId); + $containerPortal = $application->getContainer(); + $entityManager = $containerPortal->get('entityManager'); $user = $entityManager->getEntity('User', $userId); if (!$user) { - return null; + return; } $result = $this->check($user, $scope, $id, $action, $containerPortal); if ($result) { - return 'true'; + $io->write('true');; + + return; } } - return null; + return; } if ($this->check($user, $scope, $id, $action, $container)) { - return 'true'; - } + $io->write('true'); - return null; + return; + } } - protected function check($user, $scope, $id, $action, $container) + protected function check($user, $scope, $id, $action, $container) : bool { $entityManager = $container->get('entityManager'); diff --git a/application/Espo/Core/Console/Commands/AppInfo.php b/application/Espo/Core/Console/Commands/AppInfo.php index 8d38344ec6..59bf98e7a8 100644 --- a/application/Espo/Core/Console/Commands/AppInfo.php +++ b/application/Espo/Core/Console/Commands/AppInfo.php @@ -32,6 +32,7 @@ namespace Espo\Core\Console\Commands; use Espo\Core\{ InjectableFactory, Utils\File\Manager as FileManager, + Console\Command, }; class AppInfo implements Command diff --git a/application/Espo/Core/Console/Commands/AuthTokenCheck.php b/application/Espo/Core/Console/Commands/AuthTokenCheck.php index 95b79cf25c..a70f3670d8 100644 --- a/application/Espo/Core/Console/Commands/AuthTokenCheck.php +++ b/application/Espo/Core/Console/Commands/AuthTokenCheck.php @@ -32,11 +32,15 @@ namespace Espo\Core\Console\Commands; use Espo\Core\{ ORM\EntityManager, Authentication\AuthToken\AuthTokenManager, + Console\Command, + Console\Params, + Console\IO, }; class AuthTokenCheck implements Command { protected $entityManager; + protected $authTokenManager; public function __construct(EntityManager $entityManager, AuthTokenManager $authTokenManager) @@ -45,26 +49,26 @@ class AuthTokenCheck implements Command $this->authTokenManager = $authTokenManager; } - public function run(array $options, array $flagList, array $argumentList) : ?string + public function run(Params $params, IO $io) : void { - $token = $argumentList[0] ?? null; + $token = $params->getArgument(0); if (empty($token)) { - return null; + return; } $authToken = $this->authTokenManager->get($token); if (!$authToken) { - return null; + return; } if (!$authToken->isActive()) { - return null; + return; } if (!$authToken->getUserId()) { - return null; + return; } $userId = $authToken->getUserId(); @@ -79,9 +83,9 @@ class AuthTokenCheck implements Command ->findOne(); if (!$user) { - return null; + return; } - return $user->id; + $io->write($user->getId()); } } diff --git a/application/Espo/Core/Console/Commands/ClearCache.php b/application/Espo/Core/Console/Commands/ClearCache.php index 8914079e40..2ee0728fc0 100644 --- a/application/Espo/Core/Console/Commands/ClearCache.php +++ b/application/Espo/Core/Console/Commands/ClearCache.php @@ -29,7 +29,12 @@ namespace Espo\Core\Console\Commands; -use Espo\Core\DataManager; +use Espo\Core\{ + DataManager, + Console\Command, + Console\Params, + Console\IO, +}; class ClearCache implements Command { @@ -40,9 +45,10 @@ class ClearCache implements Command $this->dataManager = $dataManager; } - public function run() + public function run(Params $params, IO $io) : void { $this->dataManager->clearCache(); - echo "Cache has been cleared.\n"; + + $io->writeLine("Cache has been cleared."); } } diff --git a/application/Espo/Core/Console/Commands/Extension.php b/application/Espo/Core/Console/Commands/Extension.php index 720752a504..aa0cf7203a 100644 --- a/application/Espo/Core/Console/Commands/Extension.php +++ b/application/Espo/Core/Console/Commands/Extension.php @@ -29,55 +29,70 @@ namespace Espo\Core\Console\Commands; -use Espo\Core\Exceptions\Error; +use Espo\Core\{ + ExtensionManager, + Container, + Console\Command, + Console\Params, + Console\IO, +}; -use Espo\Core\Container; -use Espo\Core\ExtensionManager; +use Throwable; class Extension implements Command { - protected $extensionManager = null; - - protected $container; + private $container; public function __construct(Container $container) { $this->container = $container; } - public function run(array $options, array $flagList) + public function run(Params $params, IO $io) : void { - if (in_array('u', $flagList)) { + $options = $params->getOptions(); + + if ($params->hasFlag('u')) { // uninstall $name = $options['name'] ?? null; $id = $options['id'] ?? null; + if (!$name && !$id) { $this->out("Can't uninstall. Specify --name=\"Extension Name\".\n"); + return; } + $params = []; + if ($id) { $params['id'] = $id; - } else { + } + else { $params['name'] = $name; } - $params['delete'] = !in_array('k', $flagList); + + $params['delete'] = !$params->hasFlag('k'); $this->runUninstall($params); - return; - } else { - // install - $file = $options['file'] ?? null; - if (!$file) { - $this->out("Can't install. Specify --file=\"path/to/package.zip\".\n"); - return; - } - - $this->runInstall($file); return; } + + // install + + $file = $options['file'] ?? null; + + if (!$file) { + $this->out("Can't install. Specify --file=\"path/to/package.zip\".\n"); + + return; + } + + $this->runInstall($file); + + return; } protected function runInstall(string $file) @@ -86,16 +101,20 @@ class Extension implements Command if (!file_exists($file)) { $this->out("File does not exist.\n"); + return; } $fileData = file_get_contents($file); + $fileData = 'data:application/zip;base64,' . base64_encode($fileData); try { $id = $manager->upload($fileData); - } catch (\Throwable $e) { + } + catch (Throwable $e) { $this->out($e->getMessage() . "\n"); + return; } @@ -106,6 +125,7 @@ class Extension implements Command if (!$name) { $this->out("Can't install. Bad manifest.\n"); + return; } @@ -113,13 +133,16 @@ class Extension implements Command try { $manager->install(['id' => $id]); - } catch (\Throwable $e) { + } + catch (Throwable $e) { $this->out("\n"); $this->out($e->getMessage() . "\n"); + return; } $this->out("\n"); + $this->out("Extension '{$name}' version {$version} is installed.\nExtension ID: '{$id}'.\n"); } @@ -128,31 +151,42 @@ class Extension implements Command $id = $params['id'] ?? null; if ($id) { - $record = $this->getEntityManager()->getRepository('Extension')->where([ - 'id' => $id, - 'isInstalled' => true, - ])->findOne(); + $record = $this->getEntityManager() + ->getRepository('Extension') + ->where([ + 'id' => $id, + 'isInstalled' => true, + ]) + ->findOne(); if (!$record) { $this->out("Extension with ID '{$id}' is not installed.\n"); + return; } $name = $record->get('name'); - } else { + } + else { $name = $params['name'] ?? null; + if (!$name) { $this->out("Can't uninstall. No --name or --id specified.\n"); + return; } - $record = $this->getEntityManager()->getRepository('Extension')->where([ - 'name' => $name, - 'isInstalled' => true, - ])->findOne(); + $record = $this->getEntityManager() + ->getRepository('Extension') + ->where([ + 'name' => $name, + 'isInstalled' => true, + ]) + ->findOne(); if (!$record) { $this->out("Extension '{$name}' is not installed.\n"); + return; } @@ -165,9 +199,11 @@ class Extension implements Command try { $manager->uninstall(['id' => $id]); - } catch (\Throwable $e) { + } + catch (Throwable $e) { $this->out("\n"); $this->out($e->getMessage() . "\n"); + return; } @@ -176,14 +212,17 @@ class Extension implements Command if ($params['delete'] ?? false) { try { $manager->delete(['id' => $id]); - } catch (\Throwable $e) { + } + catch (Throwable $e) { $this->out($e->getMessage() . "\n"); $this->out("Extension '{$name}' is uninstalled but could not be deleted.\n"); + return; } $this->out("Extension '{$name}' is uninstalled and deleted.\n"); - } else { + } + else { $this->out("Extension '{$name}' is uninstalled.\n"); } } diff --git a/application/Espo/Core/Console/Commands/Rebuild.php b/application/Espo/Core/Console/Commands/Rebuild.php index e431a1cb40..cbf0e73d1a 100644 --- a/application/Espo/Core/Console/Commands/Rebuild.php +++ b/application/Espo/Core/Console/Commands/Rebuild.php @@ -29,20 +29,26 @@ namespace Espo\Core\Console\Commands; -use Espo\Core\DataManager; +use Espo\Core\{ + DataManager, + Console\Command, + Console\Params, + Console\IO, +}; class Rebuild implements Command { - protected $dataManager; + private $dataManager; public function __construct(DataManager $dataManager) { $this->dataManager = $dataManager; } - public function run() + public function run(Params $params, IO $io) : void { $this->dataManager->rebuild(); - echo "Rebuild has been done.\n"; + + $io->writeLine("Rebuild has been done."); } } diff --git a/application/Espo/Core/Console/Commands/RunJob.php b/application/Espo/Core/Console/Commands/RunJob.php index 944bbcc338..0fde4559e7 100644 --- a/application/Espo/Core/Console/Commands/RunJob.php +++ b/application/Espo/Core/Console/Commands/RunJob.php @@ -29,14 +29,20 @@ namespace Espo\Core\Console\Commands; -use Espo\Core\CronManager; -use Espo\Core\Utils\Util; -use Espo\Core\ORM\EntityManager; +use Espo\Core\{ + ORM\EntityManager, + Utils\Util, + CronManager, + Console\Command, + Console\Params, + Console\IO, +}; class RunJob implements Command { - protected $cronManager; - protected $entityManager; + private $cronManager; + + private $entityManager; public function __construct(CronManager $cronManager, EntityManager $entityManager) { @@ -44,8 +50,11 @@ class RunJob implements Command $this->entityManager = $entityManager; } - public function run(array $options, array $flags, array $argumentList) + public function run(Params $params, IO $io) : void { + $options = $params->getOptions(); + $argumentList = $params->getArgumentList(); + $jobName = $options['job'] ?? null; $targetId = $options['targetId'] ?? null; $targetType = $options['targetType'] ?? null; @@ -54,7 +63,11 @@ class RunJob implements Command $jobName = $argumentList[0]; } - if (!$jobName) echo "No job specified.\n"; + if (!$jobName) { + $io->writeLine("Error: No job specified."); + + return; + } $jobName = ucfirst(Util::hyphenToCamelCase($jobName)); @@ -69,10 +82,12 @@ class RunJob implements Command $result = $this->cronManager->runJob($job); - if ($result) { - echo "Job '{$jobName}' has been executed.\n"; - } else { - echo "Job '{$jobName}' failed to execute.\n"; + if (!$result) { + $io->writeLine("Error: Job '{$jobName}' failed to execute."); + + return; } + + $io->writeLine("Job '{$jobName}' has been executed."); } } diff --git a/application/Espo/Core/Console/Commands/SetPassword.php b/application/Espo/Core/Console/Commands/SetPassword.php index ddc5d7b389..a7b1dbe837 100644 --- a/application/Espo/Core/Console/Commands/SetPassword.php +++ b/application/Espo/Core/Console/Commands/SetPassword.php @@ -29,13 +29,19 @@ namespace Espo\Core\Console\Commands; -use Espo\Core\Container; -use Espo\Core\ORM\EntityManager; -use Espo\Core\Utils\PasswordHash; +use Espo\Core\{ + ORM\EntityManager, + Utils\PasswordHash, + Console\Command, + Console\Params, + Console\IO, +}; class SetPassword implements Command { - protected $entityManager; + private $entityManager; + + private $passwordHash; public function __construct(EntityManager $entityManager, PasswordHash $passwordHash) { @@ -43,38 +49,46 @@ class SetPassword implements Command $this->passwordHash = $passwordHash; } - public function run(array $options, array $flagList, array $argumentList) + public function run(Params $params, IO $io) : void { - $userName = $argumentList[0] ?? null; + $userName = $params->getArgument(0); if (!$userName) { - $this->out("User name must be specified.\n"); - die; + $io->writeLine("User name must be specified."); + + return; } $em = $this->entityManager; - $user = $em->getRepository('User')->where(['userName' => $userName])->findOne(); + $user = $em->getRepository('User') + ->where(['userName' => $userName]) + ->findOne(); if (!$user) { - $this->out("User '{$userName}' not found.\n"); - die; + $io->writeLine("User '{$userName}' not found."); + + return; } if (!in_array($user->get('type'), ['admin', 'super-admin', 'portal', 'regular'])) { - $this->out("Can't set password for user of type '".$user->get('type')."'.\n"); - die; + $userType = $user->get('type'); + + $io->writeLine( + "Can't set password for a user of the type '{$userType}'." + ); + + return; } - $this->out("Enter a new password:\n"); + $io->writeLine("Enter a new password:"); - $password = $this->ask(); - - $password = trim($password); + $password = $io->readLine(); if (!$password) { - $this->out("Password can not be empty.\n"); - die; + $io->writeLine("Password can not be empty."); + + return; } $hash = $this->passwordHash; @@ -83,18 +97,6 @@ class SetPassword implements Command $em->saveEntity($user); - $this->out("Password for user '{$userName}' is changed.\n"); - } - - protected function ask() - { - $input = fgets(\STDIN); - - return rtrim($input, "\n"); - } - - protected function out($string) - { - fwrite(\STDOUT, $string); + $io->writeLine("Password for user '{$userName}' is changed."); } } diff --git a/application/Espo/Core/Console/Commands/Upgrade.php b/application/Espo/Core/Console/Commands/Upgrade.php index 9520b9d4f4..c58ae5d279 100644 --- a/application/Espo/Core/Console/Commands/Upgrade.php +++ b/application/Espo/Core/Console/Commands/Upgrade.php @@ -36,6 +36,9 @@ use Espo\Core\{ Utils\Util, Utils\File\Manager as FileManager, Utils\Config, + Console\Command, + Console\Params, + Console\IO, }; use Symfony\Component\Process\PhpExecutableFinder; @@ -61,6 +64,7 @@ class Upgrade implements Command ]; private $fileManager; + private $config; public function __construct(FileManager $fileManager, Config $config) @@ -69,25 +73,29 @@ class Upgrade implements Command $this->config = $config; } - public function run(array $options, array $flagList, array $argumentList) + public function run(Params $params, IO $io) : void { - $params = $this->normalizeParams($options, $flagList, $argumentList); + $options = $params->getOptions(); + $flagList = $params->getFlagList(); + $argumentList = $params->getArgumentList(); + + $upgradeParams = $this->normalizeParams($options, $flagList, $argumentList); $fromVersion = $this->config->get('version'); - $toVersion = $params->toVersion ?? null; + $toVersion = $upgradeParams->toVersion ?? null; $versionInfo = $this->getVersionInfo($toVersion); $nextVersion = $versionInfo->nextVersion ?? null; $lastVersion = $versionInfo->lastVersion ?? null; - $packageFile = $this->getPackageFile($params, $versionInfo); + $packageFile = $this->getPackageFile($upgradeParams, $versionInfo); if (!$packageFile) { return; } - if ($params->localMode) { + if ($upgradeParams->localMode) { $upgradeId = $this->upload($packageFile); $manifest = $this->getUpgradeManager()->getManifestById($upgradeId); @@ -97,7 +105,7 @@ class Upgrade implements Command fwrite(\STDOUT, "Current version is {$fromVersion}.\n"); - if (!$params->skipConfirmation) { + if (!$upgradeParams->skipConfirmation) { fwrite(\STDOUT, "EspoCRM will be upgraded to version {$nextVersion} now. Enter [Y] to continue.\n"); if (!$this->confirm()) { @@ -126,7 +134,7 @@ class Upgrade implements Command fwrite(\STDOUT, "Upgrading... This may take a while..."); try { - $this->runUpgradeProcess($upgradeId, $params); + $this->runUpgradeProcess($upgradeId, $upgradeParams); } catch (Throwable $e) { $errorMessage = $e->getMessage(); @@ -134,12 +142,13 @@ class Upgrade implements Command fwrite(\STDOUT, "\n"); - if (!$params->keepPackageFile) { + if (!$upgradeParams->keepPackageFile) { $this->fileManager->unlink($packageFile); } if (isset($errorMessage)) { $errorMessage = !empty($errorMessage) ? $errorMessage : "Error: An unexpected error occurred."; + fwrite(\STDOUT, $errorMessage . "\n"); return; @@ -264,6 +273,7 @@ class Upgrade implements Command protected function runUpgradeProcess(string $upgradeId, object $params = null) { $params = $params ?? (object) []; + $useSingleProcess = property_exists($params, 'singleProcess') ? $params->singleProcess : false; $stepList = !empty($params->step) ? [$params->step] : $this->upgradeStepList; diff --git a/application/Espo/Core/Console/Commands/UpgradeStep.php b/application/Espo/Core/Console/Commands/UpgradeStep.php index 0e7758c950..98c68cbaac 100644 --- a/application/Espo/Core/Console/Commands/UpgradeStep.php +++ b/application/Espo/Core/Console/Commands/UpgradeStep.php @@ -29,7 +29,16 @@ namespace Espo\Core\Console\Commands; -use Espo\Core\Container; +use Espo\Core\{ + Container, + Application, + UpgradeManager, + Console\Command, + Console\Params, + Console\IO, +}; + +use Exception; class UpgradeStep implements Command { @@ -45,39 +54,49 @@ class UpgradeStep implements Command return $this->container; } - public function run(array $options) + public function run(Params $params, IO $io) : void { + $options = $params->getOptions(); + if (empty($options['step'])) { echo "Step is not specified.\n"; + return; } if (empty($options['id'])) { echo "Upgrade ID is not specified.\n"; + return; } $stepName = $options['step']; $upgradeId = $options['id']; - return $this->runUpgradeStep($stepName, ['id' => $upgradeId]); - } + $result = $this->runUpgradeStep($stepName, ['id' => $upgradeId]); - protected function runUpgradeStep($stepName, array $params) - { - $app = new \Espo\Core\Application(); - $app->setupSystemUser(); + if (!$result) { + echo "false"; - $upgradeManager = new \Espo\Core\UpgradeManager($app->getContainer()); - - try { - $result = $upgradeManager->runInstallStep($stepName, $params); // throw Exception on error - } catch (\Exception $e) { - die("Error: " . $e->getMessage()); + return; } - if (is_bool($result)) { - $result = $result ? "true" : "false"; + echo "true"; + } + + protected function runUpgradeStep($stepName, array $params) : bool + { + $app = new Application(); + + $app->setupSystemUser(); + + $upgradeManager = new UpgradeManager($app->getContainer()); + + try { + $result = $upgradeManager->runInstallStep($stepName, $params); + } + catch (Exception $e) { + die("Error: " . $e->getMessage()); } return $result; diff --git a/application/Espo/Core/Console/Commands/Version.php b/application/Espo/Core/Console/Commands/Version.php index 0dd3c4d817..c6dc0463a9 100644 --- a/application/Espo/Core/Console/Commands/Version.php +++ b/application/Espo/Core/Console/Commands/Version.php @@ -29,18 +29,23 @@ namespace Espo\Core\Console\Commands; -use Espo\Core\Utils\Config; +use Espo\Core\{ + Utils\Config, + Console\Command, + Console\Params, + Console\IO, +}; class Version implements Command { - protected $config; + private $config; public function __construct(Config $config) { $this->config = $config; } - public function run() + public function run(Params $params, IO $io) : void { $version = $this->config->get('version'); @@ -48,6 +53,6 @@ class Version implements Command return; } - echo $version . "\n"; + $io->writeLine($version); } } diff --git a/application/Espo/Core/Console/Commands/Base.php b/application/Espo/Core/Console/Exceptions/CommandNotFound.php similarity index 82% rename from application/Espo/Core/Console/Commands/Base.php rename to application/Espo/Core/Console/Exceptions/CommandNotFound.php index b255a8d6d9..cc4f6e675c 100644 --- a/application/Espo/Core/Console/Commands/Base.php +++ b/application/Espo/Core/Console/Exceptions/CommandNotFound.php @@ -27,20 +27,11 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -namespace Espo\Core\Console\Commands; +namespace Espo\Core\Console\Exceptions; -/** @deprecated */ -abstract class Base +use RuntimeException; + +class CommandNotFound extends RuntimeException { - private $container; - public function __construct(\Espo\Core\Container $container) - { - $this->container = $container; - } - - protected function getContainer() - { - return $this->container; - } } diff --git a/application/Espo/Core/Console/Exceptions/CommandNotSpecified.php b/application/Espo/Core/Console/Exceptions/CommandNotSpecified.php new file mode 100644 index 0000000000..c5d831ed4a --- /dev/null +++ b/application/Espo/Core/Console/Exceptions/CommandNotSpecified.php @@ -0,0 +1,37 @@ + + */ + private $options; + + /** + * @var array + */ + private $flagList; + + /** + * @var array + */ + private $argumentList; + + public function __construct(array $params) + { + $this->options = $params['options'] ?? []; + $this->flagList = $params['flagList'] ?? []; + $this->argumentList = $params['argumentList'] ?? []; + } + + /** + * @return array + */ + public function getOptions() : array + { + return $this->options; + } + + /** + * @return array + */ + public function getFlagList() : array + { + return $this->flagList; + } + + /** + * @return array + */ + public function getArgumentList() : array + { + return $this->argumentList; + } + + /** + * Has an option. + */ + public function hasOption(string $name) : bool + { + return array_key_exists($name, $this->options); + } + + /** + * Get an option. + */ + public function getOption(string $name) : ?string + { + return $this->options[$name] ?? null; + } + + /** + * Has a flag. + */ + public function hasFlag(string $name) : bool + { + return in_array($name, $this->flagList); + } + + /** + * Get an argument by index. + */ + public function getArgument(int $index) : ?string + { + return $this->argumentList[$index] ?? null; + } + + public static function fromArgv(array $argv) : self + { + $argumentList = []; + $options = []; + $flagList = []; + + $skipIndex = 2; + + foreach ($argv as $i => $item) { + if ($i < $skipIndex) { + continue; + } + + if (strpos($item, '--') === 0 && strpos($item, '=') > 2) { + list($name, $value) = explode('=', substr($item, 2)); + + $name = Util::hyphenToCamelCase($name); + + $options[$name] = $value; + } + else if (strpos($item, '--') === 0) { + $flagList[] = Util::hyphenToCamelCase(substr($item, 2)); + } + else if (strpos($item, '-') === 0) { + $flagList[] = substr($item, 1); + } + else { + $argumentList[] = $item; + } + } + + return new self([ + 'argumentList' => $argumentList, + 'options' => $options, + 'flagList' => $flagList, + ]); + } +} diff --git a/command.php b/command.php index 9739b8a1e5..39308911fe 100644 --- a/command.php +++ b/command.php @@ -34,11 +34,4 @@ use Espo\Core\{ ApplicationRunners\Command, }; -ob_start(); - -$result = (new Application())->run(Command::class); - -if (is_string($result)) { - ob_end_clean(); - echo $result; -} +(new Application())->run(Command::class); diff --git a/tests/unit/Espo/Core/Console/CommandManagerTest.php b/tests/unit/Espo/Core/Console/CommandManagerTest.php index 66292dbc45..302fbe9dde 100644 --- a/tests/unit/Espo/Core/Console/CommandManagerTest.php +++ b/tests/unit/Espo/Core/Console/CommandManagerTest.php @@ -29,44 +29,77 @@ namespace tests\unit\Espo\Core\Console; -use tests\unit\ReflectionHelper; +use Espo\Core\InjectableFactory; +use Espo\Core\Utils\Metadata; +use Espo\Core\Console\CommandManager; +use Espo\Core\Console\Command; +use Espo\Core\Console\Params; +use Espo\Core\Console\IO; class CommandManagerTest extends \PHPUnit\Framework\TestCase { + private $injectableFactory; + + private $metadata; + + private $manager; protected function setUp() : void { - $injectableFactory = - $this->getMockBuilder('\\Espo\\Core\\InjectableFactory')->disableOriginalConstructor()->getMock(); - $metadata = - $this->getMockBuilder('\\Espo\\Core\\Utils\\Metadata')->disableOriginalConstructor()->getMock(); + $this->injectableFactory = $this->createMock(InjectableFactory::class); - $this->object = new \Espo\Core\Console\CommandManager($injectableFactory, $metadata); + $this->metadata = $this->createMock(Metadata::class); - $this->reflection = new ReflectionHelper($this->object); + $this->manager = new CommandManager($this->injectableFactory, $this->metadata); + + $this->command = $this->createMock(Command::class); } - protected function tearDown() : void + private function initTest(array $argv) { + $className = 'Test'; + + $this->metadata + ->expects($this->once()) + ->method('get') + ->with(['app', 'consoleCommands', 'commandName', 'className']) + ->willReturn($className); + + $this->injectableFactory + ->expects($this->once()) + ->method('create') + ->with($className) + ->willReturn($this->command); + + $expectedParams = new Params([ + 'argumentList' => ['a1', 'a2'], + 'flagList' => ['flag', 'flagA', 'f'], + 'options' => [ + 'optionOne' => 'test', + ], + ]); + + $io = new IO(); + + $this->command + ->expects($this->once()) + ->method('run') + ->with($expectedParams, $io); + + $this->manager->run($argv); } - public function testGetParams1() + public function testWithCommandPhp() { $argv = ['command.php', 'command-name', 'a1', 'a2', '--flag', '--flag-a', '-f', '--option-one=test']; - $params = $this->reflection->invokeMethod('getParams', [$argv]); - $this->assertEquals(['a1', 'a2'], $params['argumentList']); - $this->assertEquals(['flag', 'flagA', 'f'], $params['flagList']); - $this->assertEquals(['optionOne' => 'test'], $params['options']); + $this->initTest($argv); } - public function testGetParams2() + public function testWithoutCommandPhp() { - $argv = ['command-name', 'a1', 'a2', '--flag', '--flag-a', '-f', '--option-one=test']; - $params = $this->reflection->invokeMethod('getParams', [$argv]); + $argv = ['bin/command', 'command-name', 'a1', 'a2', '--flag', '--flag-a', '-f', '--option-one=test']; - $this->assertEquals(['a1', 'a2'], $params['argumentList']); - $this->assertEquals(['flag', 'flagA', 'f'], $params['flagList']); - $this->assertEquals(['optionOne' => 'test'], $params['options']); + $this->initTest($argv); } } diff --git a/tests/unit/Espo/Core/Console/ParamsTest.php b/tests/unit/Espo/Core/Console/ParamsTest.php new file mode 100644 index 0000000000..861d9757f4 --- /dev/null +++ b/tests/unit/Espo/Core/Console/ParamsTest.php @@ -0,0 +1,64 @@ + ['a1', 'a2'], + 'flagList' => ['f1', 'f2', 'f3'], + 'options' => [ + 'optionOne' => 'test', + ], + ]; + + $params = new Params($raw); + + $this->assertEquals($raw['argumentList'], $params->getArgumentList()); + $this->assertEquals($raw['flagList'], $params->getFlagList()); + $this->assertEquals($raw['options'], $params->getOptions()); + + $this->assertTrue($params->hasFlag('f1')); + $this->assertFalse($params->hasFlag('f0')); + + $this->assertTrue($params->hasOption('optionOne')); + $this->assertFalse($params->hasOption('optionZero')); + + $this->assertEquals('test', $params->getOption('optionOne')); + $this->assertEquals(null, $params->getOption('optionTwo')); + + $this->assertEquals('a1', $params->getArgument(0)); + $this->assertEquals(null, $params->getArgument(4)); + } +}