new command interface

This commit is contained in:
Yuri Kuznetsov
2021-03-18 13:39:14 +02:00
parent 250ac0e8ba
commit 43df9d25f0
21 changed files with 682 additions and 225 deletions

View File

@@ -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";
}
}
}

View File

@@ -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;
}

View File

@@ -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,
];
]);
}
}

View File

@@ -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');

View File

@@ -32,6 +32,7 @@ namespace Espo\Core\Console\Commands;
use Espo\Core\{
InjectableFactory,
Utils\File\Manager as FileManager,
Console\Command,
};
class AppInfo implements Command

View File

@@ -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());
}
}

View File

@@ -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.");
}
}

View File

@@ -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");
}
}

View File

@@ -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.");
}
}

View File

@@ -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.");
}
}

View File

@@ -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.");
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,37 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Console\Exceptions;
use RuntimeException;
class CommandNotSpecified extends RuntimeException
{
}

View File

@@ -0,0 +1,69 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Console;
use const STDOUT;
use const PHP_EOL;
/**
* Input/Output methods.
*/
class IO
{
/**
* Write a string to output.
*/
public function write(string $string) : void
{
fwrite(STDOUT, $string);
}
/**
* Write a string followed by the current line terminator to output.
*/
public function writeLine(string $string) : void
{
fwrite(STDOUT, $string . PHP_EOL);
}
/**
* Read a line from input. A string is trimmed.
*/
public function readLine() : string
{
$resource = fopen('php://stdin', 'r');
$string = trim(fgets($resource));
fclose($resource);
return $string;
}
}

View File

@@ -0,0 +1,152 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Console;
/**
* Command parameters.
*/
class Params
{
/**
* @var array<string,string>
*/
private $options;
/**
* @var array<string>
*/
private $flagList;
/**
* @var array<string>
*/
private $argumentList;
public function __construct(array $params)
{
$this->options = $params['options'] ?? [];
$this->flagList = $params['flagList'] ?? [];
$this->argumentList = $params['argumentList'] ?? [];
}
/**
* @return array<string,string>
*/
public function getOptions() : array
{
return $this->options;
}
/**
* @return array<string>
*/
public function getFlagList() : array
{
return $this->flagList;
}
/**
* @return array<string>
*/
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,
]);
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -0,0 +1,64 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\Core\Console;
use Espo\Core\Console\Params;
class ParamsTest extends \PHPUnit\Framework\TestCase
{
public function testParams()
{
$raw = [
'argumentList' => ['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));
}
}