23 Star 93 Fork 25

inhere/php-console

Create your Gitee Account
Explore and code with more than 14 million developers,Free private repositories !:)
Sign up
文件
Clone or Download
Controller.php 19.08 KB
Copy Edit Raw Blame History
Inhere authored 2024-12-05 18:47 +08:00 . up: upgrade support php8.4
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
<?php declare(strict_types=1);
/**
* The file is part of inhere/console
*
* @author https://github.com/inhere
* @homepage https://github.com/inhere/php-console
* @license https://github.com/inhere/php-console/blob/master/LICENSE
*/
namespace Inhere\Console;
use Generator;
use Inhere\Console\Contract\ControllerInterface;
use Inhere\Console\Decorate\ControllerHelpTrait;
use Inhere\Console\Exception\ConsoleException;
use Inhere\Console\Handler\AbstractHandler;
use Inhere\Console\IO\Input;
use Inhere\Console\IO\Output;
use Inhere\Console\Util\Helper;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use ReflectionObject;
use Throwable;
use Toolkit\PFlag\FlagsParser;
use Toolkit\PFlag\FlagUtil;
use Toolkit\PFlag\SFlags;
use Toolkit\Stdlib\Obj\ObjectHelper;
use Toolkit\Stdlib\Str;
use function array_flip;
use function array_shift;
use function implode;
use function is_array;
use function is_string;
use function method_exists;
use function sprintf;
use function substr;
use function trim;
use function ucfirst;
/**
* Class Controller
*
* @package Inhere\Console
*/
abstract class Controller extends AbstractHandler implements ControllerInterface
{
use ControllerHelpTrait;
/**
* @var array global options for the group command
*/
protected static array $globalOptions = [
'--show-disabled' => 'Whether display disabled commands',
];
/**
* Action name - real subcommand name, no suffix 'Command'.
*
* eg: updateCommand() -> action: 'update'
*
* @var string
*/
private string $action = '';
/**
* The input group name.
*
* @var string
*/
private string $groupName = '';
/**
* The delimiter. eg: '/' ':'
*
* @var string
*/
private string $delimiter = ':';
/**
* @var string
*/
private string $defaultAction = '';
/**
* The action method name on the controller.
*
* @var string
*/
private string $actionMethod = '';
/**
* @var string
*/
private string $actionSuffix = self::COMMAND_SUFFIX;
/**
* Flags for all action commands
*
* ```php
* [
* action => AbstractFlags,
* action2 => AbstractFlags2,
* ]
* ```
*
* @var FlagsParser[]
* @psalm-var array<string, FlagsParser>
*/
private array $subFss = [];
/**
* @var array From disabledCommands()
*/
private array $disabledCommands = [];
/**
* TODO ...
*
* @var array
*/
private array $attachedCommands = [];
/**
* Metadata for sub-commands. such as: desc, alias
* Notice: you must add metadata on `init()`
*
* [
* 'command real name' => [
* 'desc' => 'sub command description',
* 'alias' => [],
* ],
* ],
*
* @var array
*/
protected array $commandMetas = [];
/**
* Define command alias mapping. please rewrite it on sub-class.
*
* - key is command name, value is aliases.
*
* @return array{string: list<string>}
*/
protected static function commandAliases(): array
{
// Usage:
// - method 1:
// alias => command
// [
// 'i' => 'install',
// 'ins' => 'install',
// ]
//
// - method 2:
// command => alias[]
// [
// 'install' => ['i', 'ins'],
// ]
return [];
}
protected function init(): void
{
parent::init();
// up: load sub-commands alias
$this->loadCommandAliases();
$list = $this->disabledCommands();
$this->groupName = $this->getRealGName();
// save to property
$this->disabledCommands = $list ? array_flip($list) : [];
if (!$this->actionSuffix) {
$this->actionSuffix = self::COMMAND_SUFFIX;
}
}
/**
* define disabled command list.
*
* @return array
*/
protected function disabledCommands(): array
{
// ['command1', 'command2'];
return [];
}
/**
* Will call it on subcommand not found on the group.
*
* @param string $command
* @param array $args
*
* @return bool if return True, will stop goon render group help.
*/
protected function onNotFound(string $command, array $args): bool
{
// TIP: you can add custom logic on sub-command not found.
return false;
}
/**
* @param FlagsParser $fs
*/
protected function afterInitFlagsParser(FlagsParser $fs): void
{
$fs->addOptsByRules(GlobalOption::getGroupOptions());
}
/**
* Run an action with args
*
* Usage:
*
* ```php
* $args = $this->flags->getRawArgs();
* // add option
* $args[] = '--push';
* $this->runActionWithArgs('subcmd', $args);
* ```
*
* @param string $cmd
* @param array $args
*
* @return bool|int|mixed
* @throws Throwable
*/
public function runActionWithArgs(string $cmd, array $args): mixed
{
$args[0] = $cmd;
return $this->doRun($args);
}
protected function beforeRun(): void
{
}
/**
* @param array $args
*
* @return mixed
* @throws Throwable
*/
public function doRun(array $args): mixed
{
$name = self::getName();
if (!$args) {
$command = $this->defaultAction;
// and not default command
if (!$command) {
$this->debugf('cmd: %s - run args is empty, display help for the group', $name);
return $this->showHelp();
}
} else {
$first = $args[0];
if (!FlagUtil::isValidName($first)) {
$this->debugf('cmd: %s - not input subcommand, display help for the group', $name);
return $this->showHelp();
}
$command = $first;
array_shift($args);
}
// update subcommand
$this->commandName = $command;
// update some comment vars
$fullCmd = $this->input->buildFullCmd($name, $command);
$this->setCommentsVar('fullCmd', $fullCmd);
$this->setCommentsVar('fullCommand', $fullCmd);
$this->setCommentsVar('binWithCmd', $this->input->buildCmdPath($name, $command));
// get real sub-command name
$command = $this->resolveAlias($command);
// update the command id.
$this->input->setCommandId("$name:$command");
// convert 'boo-foo' to 'booFoo'
$this->action = $action = Str::camelCase($command);
$this->debugf("cmd: %s - will run the subcommand: %s(action: %s)", $name, $command, $action);
$method = $this->getMethodName($action);
// fire event
$this->fire(ConsoleEvent::COMMAND_RUN_BEFORE, $this);
$this->beforeRun();
// check method not exist
if (!method_exists($this, $method)) {
if ($this->isSub($command)) {
return $this->dispatchSub($command, $args);
}
// if command not exists.
return $this->handleNotFound($name, $command, $args);
}
// init flags for subcommand
$fs = $this->newActionFlags();
$this->actionMethod = $method;
$this->input->setFs($fs);
$this->debugf('load flags by configure method, subcommand: %s', $command);
$this->configure();
// not config flags. load rules from method doc-comments
if ($fs->isEmpty()) {
$this->loadRulesByDocblock($method, $fs);
}
$this->log(Console::VERB_DEBUG, "run subcommand '$name.$command' - parse options", ['args' => $args]);
// parse subcommand flags.
if (!$fs->parse($args)) {
return 0;
}
// do running
return parent::doRun($args);
}
/**
* Load command configure
*/
protected function configure(): void
{
// eg. use `indexConfigure()` for `indexCommand()`
$method = $this->action . self::CONFIGURE_SUFFIX;
if (method_exists($this, $method)) {
$this->$method($this->input);
}
}
/**
* Before controller method execute
*
* @return boolean It MUST return TRUE to continue execute. if return False, will stop run.
*/
protected function beforeAction(): bool
{
return true;
}
/**
* After controller method execute
*/
protected function afterAction(): void
{
}
/**
* Run command action in the group
*
* @param Input $input
* @param Output $output
*
* @return mixed
* @throws ReflectionException
*/
final public function execute(Input $input, Output $output): mixed
{
$action = $this->action;
$group = static::getName();
if ($this->isDisabled($action)) {
$this->debugf('command %s is disabled on the group %s', $action, $group);
$output->error(sprintf("Sorry, The command '%s' is invalid in the group '%s'!", $action, $group));
return -1;
}
$method = $this->getMethodName($action);
// trigger event
$this->fire(ConsoleEvent::SUBCOMMAND_RUN_BEFORE, $this);
// the action method exists and only allow access public method.
// if (method_exists($this, $method)) {
// before run action
if (!$this->beforeAction()) {
$this->debugf('beforeAction() returns FALSE, interrupt processing continues');
return 0;
}
if (method_exists($this, $beforeFunc = 'before' . ucfirst($action))) {
$beforeOk = $this->$beforeFunc($input, $output);
if ($beforeOk === false) {
$this->debugf('%s() returns FALSE, interrupt processing continues', $beforeFunc);
return 0;
}
}
// current action flags
$flags = $this->actionFlags($action);
$rftMethod = new ReflectionMethod($this, $method);
$callArgs = ObjectHelper::buildReflectCallArgs($rftMethod, [
Input::class => $this->input,
Output::class => $this->output,
FlagsParser::class => $flags,
// 'args' => $args,
]);
// call action method
$result = $rftMethod->invokeArgs($this, $callArgs);
// call action method
// $result = $this->$method($input, $output, $flags);
// after run action
if (method_exists($this, $after = 'after' . ucfirst($action))) {
$this->$after($input, $output);
}
$this->afterAction();
return $result;
}
/**
* @param string $group
* @param string $command
* @param array $args
*
* @return int
*/
protected function handleNotFound(string $group, string $command, array $args): int
{
// if user custom handle not found logic.
if ($this->onNotFound($command, $args)) {
$this->debugf('group: %s - user custom handle the subcommand "%s" not found', $group, $command);
return 0;
}
$this->debugf('group: %s - command "%s" is not found on the group', $group, $command);
// if you defined the method '$this->notFoundCallback' , will call it
// if (($notFoundCallback = $this->notFoundCallback) && method_exists($this, $notFoundCallback)) {
// $result = $this->{$notFoundCallback}($action);
// } else {
$this->output->liteError("Sorry, The command '$command' not exist of the group '$group'!");
// find similar command names
$similar = Helper::findSimilar($command, $this->getAllCommandMethods(null, true));
if ($similar) {
$this->output->writef("\nMaybe what you mean is:\n <info>%s</info>", implode(', ', $similar));
} else {
$this->showCommandList();
}
return -1;
}
/**
* @param string $action
*
* @return FlagsParser
*/
protected function newActionFlags(string $action = ''): FlagsParser
{
$action = $action ?: $this->action;
if (!$fs = $this->getActionFlags($action)) {
$fs = new SFlags(['name' => $action]);
$fs->setStopOnFistArg(false);
$fs->setBeforePrintHelp(function (string $text) {
return $this->parseCommentsVars($text);
});
$fs->setHelpRenderer(function (): void {
$this->logf(Console::VERB_DEBUG, 'show subcommand help by input flags: -h, --help');
$this->showHelp();
});
// old mode: options and arguments at method annotations
// if ($this->compatible) {
// $fs->setSkipOnUndefined(true);
// }
// save
$this->subFss[$action] = $fs;
}
return $fs;
}
/**
* @param string $action
*
* @return string
*/
protected function getMethodName(string $action): string
{
return $this->actionSuffix ? $action . ucfirst($this->actionSuffix) : $action;
}
/**
* @return bool
*/
protected function showHelp(): bool
{
return $this->helpCommand() === 0;
}
/**
* @param array $help
*/
protected function beforeRenderCommandHelp(array &$help): void
{
$help['Group Options:'] = FlagUtil::alignOptions($this->flags->getOptsHelpLines());
}
/**
* @param ReflectionClass|null $ref
* @param bool $onlyName
*
* @return ?Generator
*/
protected function getAllCommandMethods(?ReflectionClass $ref = null, bool $onlyName = false): ?Generator
{
$ref = $ref ?: new ReflectionObject($this);
$suffix = $this->actionSuffix;
$suffixLen = Str::len($suffix);
foreach ($ref->getMethods() as $m) {
$mName = $m->getName();
if ($m->isPublic() && substr($mName, -$suffixLen) === $suffix) {
// suffix is empty ?
$cmd = $suffix ? substr($mName, 0, -$suffixLen) : $mName;
if ($onlyName) {
yield $cmd;
} else {
yield $cmd => $m;
}
}
}
}
/**
* @param string $name
*
* @return bool
*/
public function isDisabled(string $name): bool
{
return isset($this->disabledCommands[$name]);
}
/**
* load sub-commands aliases from sub-class::commandAliases()
*/
public function loadCommandAliases(): void
{
$cmdAliases = static::commandAliases();
if (!$cmdAliases) {
return;
}
foreach ($cmdAliases as $name => $item) {
// $name is command, $item is alias list
// eg: ['command1' => ['alias1', 'alias2']]
if (is_array($item)) {
$this->setAlias($name, $item, true);
} elseif (is_string($item)) { // $item is command, $name is alias name
$this->setAlias($item, $name, true);
}
}
}
/**************************************************************************
* getter/setter methods
**************************************************************************/
/**
* @return array
*/
public function getDisabledCommands(): array
{
return $this->disabledCommands;
}
/**
* @return string
*/
public function getGroupName(): string
{
return $this->groupName;
}
/**
* @return string
*/
public function getRealGName(): string
{
return self::getName();
}
/**
* @param string $groupName
*/
public function setGroupName(string $groupName): void
{
$this->groupName = $groupName;
}
/**
* @return string
*/
public function getRealCName(): string
{
return $this->action;
}
/**
* @return string
*/
public function getSubName(): string
{
return $this->commandName;
}
/**
* @param bool $useReal
*
* @return string
*/
public function getCommandId(bool $useReal = true): string
{
if ($useReal) {
return self::getName() . $this->delimiter . $this->action;
}
return $this->groupName . $this->delimiter . $this->commandName;
}
/**
* @return string
*/
public function getAction(): string
{
return $this->action;
}
/**
* @param string $action
*
* @return $this
*/
public function setAction(string $action): self
{
if ($action) {
$this->action = Str::camelCase($action);
}
return $this;
}
/**
* @return string
*/
public function getDefaultAction(): string
{
return $this->defaultAction;
}
/**
* @param string $action
*/
public function setDefaultAction(string $action): void
{
$this->defaultAction = trim($action, $this->delimiter);
}
/**
* @return string
*/
public function getActionSuffix(): string
{
return $this->actionSuffix;
}
/**
* @param string $actionSuffix
*/
public function setActionSuffix(string $actionSuffix): void
{
$this->actionSuffix = $actionSuffix;
}
/**
* @return string
*/
public function getDelimiter(): string
{
return $this->delimiter;
}
/**
* @param string $delimiter
*/
public function setDelimiter(string $delimiter): void
{
$this->delimiter = $delimiter;
}
/**
* @return array
*/
public function getCommandMetas(): array
{
return $this->commandMetas;
}
/**
* @param string $command
* @param array $meta eg: ['desc' => '', 'alias' => []]
*/
public function setCommandMeta(string $command, array $meta): void
{
if ($command) {
$this->commandMetas[$command] = $meta;
}
}
/**
* @param string $key
* @param null $default
* @param string $command if not set, will use $this->action
*
* @return mixed|null
*/
public function getCommandMeta(string $key, $default = null, string $command = ''): mixed
{
$action = $command ?: $this->action;
return $this->commandMetas[$action][$key] ?? $default;
}
/**
* @param string $action
*
* @return FlagsParser|null
*/
public function getActionFlags(string $action): ?FlagsParser
{
$action = $action ?: $this->action;
return $this->subFss[$action] ?? null;
}
/**
* @return FlagsParser
*/
public function curActionFlags(): FlagsParser
{
return $this->actionFlags($this->action);
}
/**
* @param string $action
*
* @return FlagsParser
*/
public function actionFlags(string $action): FlagsParser
{
$action = $action ?: $this->action;
if (!isset($this->subFss[$action])) {
throw new ConsoleException("not found flags parser for the action: $action");
}
return $this->subFss[$action];
}
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
PHP
1
https://gitee.com/inhere/php-console.git
git@gitee.com:inhere/php-console.git
inhere
php-console
php-console
master

Search