Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d1f9829

Browse files
committedDec 26, 2021
feat: allow attch multi level sub-commands to command,group
1 parent cce9850 commit d1f9829

18 files changed

+510
-279
lines changed
 

‎examples/Command/TestCommand.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class TestCommand extends Command
2323

2424
protected static string $desc = 'this is a test independent command';
2525

26-
protected function commands(): array
26+
protected function subCommands(): array
2727
{
2828
return [
2929
'sub' => static function ($fs, $out): void {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Inhere\Console\Examples\Controller\Attach;
4+
5+
use Inhere\Console\Command;
6+
use Inhere\Console\IO\Input;
7+
use Inhere\Console\IO\Output;
8+
use function vdump;
9+
10+
/**
11+
* class DemoSubCommand
12+
*
13+
* @author inhere
14+
*/
15+
class DemoSubCommand extends Command
16+
{
17+
protected static string $name = 'sub1';
18+
protected static string $desc = 'alone sub command on an group';
19+
20+
public function getOptions(): array
21+
{
22+
return [
23+
'str1' => 'string option1',
24+
's2,str2' => 'string option2',
25+
];
26+
}
27+
28+
/**
29+
* Do execute command
30+
*
31+
* @param Input $input
32+
* @param Output $output
33+
*
34+
* @return void|mixed
35+
*/
36+
protected function execute(Input $input, Output $output)
37+
{
38+
vdump(__METHOD__);
39+
}
40+
}

‎examples/Controller/HomeController.php

+24-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
use Inhere\Console\Component\Symbol\ArtFont;
1313
use Inhere\Console\Controller;
14+
use Inhere\Console\Examples\Controller\Attach\DemoSubCommand;
15+
use Inhere\Console\Handler\CommandWrapper;
1416
use Inhere\Console\Util\Interact;
1517
use Inhere\Console\Util\ProgressBar;
1618
use Inhere\Console\Util\Show;
@@ -23,6 +25,7 @@
2325
use Toolkit\Stdlib\Php;
2426
use function sleep;
2527
use function trigger_error;
28+
use function vdump;
2629

2730
/**
2831
* default command controller. there are some command usage examples(1)
@@ -74,13 +77,30 @@ protected function init(): void
7477
/**
7578
* @return array
7679
*/
77-
protected function options(): array
80+
protected function getOptions(): array
7881
{
7982
return [
8083
'-c, --common' => 'This is a common option for all sub-commands',
8184
];
8285
}
8386

87+
protected function subCommands(): array
88+
{
89+
return [
90+
DemoSubCommand::class,
91+
CommandWrapper::wrap(static function ($fs) {
92+
vdump(__METHOD__, $fs->getOpts());
93+
}, [
94+
'name' => 'sub2',
95+
'desc' => 'an sub command in group controller',
96+
'options' => [
97+
'int1' => 'int;an int option1',
98+
'i2, int2' => 'int;an int option2',
99+
]
100+
]),
101+
];
102+
}
103+
84104
protected function disabledCommands(): array
85105
{
86106
return ['disabled'];
@@ -207,6 +227,9 @@ public function colorCheckCommand(): void
207227
* --font Set the art font name(allow: {internalFonts}).
208228
* --italic bool;Set the art font type is italic.
209229
* --style Set the art font style.
230+
*
231+
* @param FlagsParser $fs
232+
*
210233
* @return int
211234
*/
212235
public function artFontCommand(FlagsParser $fs): int

‎examples/Controller/ShowController.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public static function commandAliases(): array
4343
/**
4444
* @return array
4545
*/
46-
protected function options(): array
46+
protected function getOptions(): array
4747
{
4848
return [
4949
'-c, --common' => 'This is a common option for all sub-commands',

‎src/Command.php

+21-31
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Inhere\Console\Handler\AbstractHandler;
1414
use ReflectionException;
1515
use Toolkit\PFlag\FlagsParser;
16+
use function array_shift;
1617

1718
/**
1819
* Class Command
@@ -38,22 +39,17 @@ abstract class Command extends AbstractHandler implements CommandInterface
3839
*/
3940
protected ?Controller $group = null;
4041

41-
/**
42-
* @var Command|null
43-
*/
44-
protected ?Command $parent = null;
45-
4642
protected function init(): void
4743
{
48-
$this->commandName = self::getName();
44+
$this->commandName = $this->getRealName();
4945

5046
parent::init();
5147
}
5248

5349
/**
5450
* @return array
5551
*/
56-
public function getArguments(): array
52+
protected function getArguments(): array
5753
{
5854
return [];
5955
}
@@ -63,12 +59,13 @@ public function getArguments(): array
6359
*/
6460
protected function beforeInitFlagsParser(FlagsParser $fs): void
6561
{
62+
$fs->addArgsByRules($this->getArguments());
6663
$fs->setStopOnFistArg(false);
6764

6865
// old mode: options and arguments at method annotations
69-
if ($this->compatible) {
70-
$fs->setSkipOnUndefined(true);
71-
}
66+
// if ($this->compatible) {
67+
// $fs->setSkipOnUndefined(true);
68+
// }
7269
}
7370

7471
/**
@@ -93,31 +90,24 @@ protected function afterInitFlagsParser(FlagsParser $fs): void
9390
}
9491

9592
/**
96-
* @param Command $parent
97-
*/
98-
public function setParent(Command $parent): void
99-
{
100-
$this->parent = $parent;
101-
}
102-
103-
/**
104-
* @return $this
93+
* @param array $args
94+
*
95+
* @return mixed
10596
*/
106-
public function getRoot(): Command
97+
protected function doRun(array $args): mixed
10798
{
108-
if ($this->parent) {
109-
return $this->parent->getRoot();
99+
// if input sub-command name
100+
if (isset($args[0])) {
101+
$first = $args[0];
102+
$rName = $this->resolveAlias($first);
103+
104+
if ($this->isSub($rName)) {
105+
array_shift($args);
106+
return $this->dispatchSub($rName, $args);
107+
}
110108
}
111109

112-
return $this;
113-
}
114-
115-
/**
116-
* @return Command|null
117-
*/
118-
public function getParent(): ?Command
119-
{
120-
return $this->parent;
110+
return parent::doRun($args);
121111
}
122112

123113
/**

‎src/Component/PharCompiler.php

-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use Exception;
1717
use FilesystemIterator;
1818
use Generator;
19-
use Inhere\Console\Util\Helper;
2019
use InvalidArgumentException;
2120
use Iterator;
2221
use Phar;

‎src/Component/ReadlineCompleter.php

+12-1
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,13 @@ public function isSupported(): bool
4444

4545
/**
4646
* @param callable $completer
47+
*
48+
* @return ReadlineCompleter
4749
*/
48-
public function setCompleter(callable $completer): void
50+
public function setCompleter(callable $completer): static
4951
{
5052
$this->completer = $completer;
53+
return $this;
5154
}
5255

5356
/**
@@ -113,4 +116,12 @@ public function setHistorySize(int $historySize): void
113116
{
114117
$this->historySize = $historySize;
115118
}
119+
120+
/**
121+
* @return callable
122+
*/
123+
public function getCompleter(): callable
124+
{
125+
return $this->completer;
126+
}
116127
}

‎src/Component/Router.php

+2-15
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Inhere\Console\Contract\RouterInterface;
1717
use Inhere\Console\Controller;
1818
use Inhere\Console\IO\Output;
19+
use Inhere\Console\Util\ConsoleUtil;
1920
use Inhere\Console\Util\Helper;
2021
use InvalidArgumentException;
2122
use Toolkit\PFlag\FlagsParser;
@@ -206,7 +207,7 @@ public function addCommand(string $name, string|Closure|CommandInterface $handle
206207
if ($aliases = $handler::aliases()) {
207208
$config['aliases'] = array_merge($config['aliases'], $aliases);
208209
}
209-
} elseif (!is_object($handler) || !$this->isValidCmdObject($handler)) {
210+
} elseif (!is_object($handler) || !ConsoleUtil::isValidCmdObject($handler)) {
210211
Helper::throwInvalidArgument(
211212
'The command handler must is an subclass of %s OR a Closure OR a sub-object of %s',
212213
Command::class,
@@ -230,20 +231,6 @@ public function addCommand(string $name, string|Closure|CommandInterface $handle
230231
return $this;
231232
}
232233

233-
/**
234-
* @param object $handler
235-
*
236-
* @return bool
237-
*/
238-
private function isValidCmdObject(object $handler): bool
239-
{
240-
if ($handler instanceof Command) {
241-
return true;
242-
}
243-
244-
return method_exists($handler, '__invoke');
245-
}
246-
247234
/**
248235
* @param array $commands
249236
*

‎src/Contract/CommandHandlerInterface.php

+5
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ public function getGroupName(): string;
5959
*/
6060
public function getRealName(): string;
6161

62+
/**
63+
* @return string
64+
*/
65+
public function getRealDesc(): string;
66+
6267
/**
6368
* The real group name.
6469
*

‎src/Controller.php

+10-5
Original file line numberDiff line numberDiff line change
@@ -300,21 +300,26 @@ public function doRun(array $args): mixed
300300
// convert 'boo-foo' to 'booFoo'
301301
$this->action = $action = Str::camelCase($command);
302302
$this->debugf("will run the '%s' group action: %s, subcommand: %s", $name, $action, $command);
303-
$this->actionMethod = $method = $this->getMethodName($action);
303+
$method = $this->getMethodName($action);
304304

305305
// fire event
306306
$this->fire(ConsoleEvent::COMMAND_RUN_BEFORE, $this);
307307
$this->beforeRun();
308308

309309
// check method not exist
310-
// - if command method not exists.
311310
if (!method_exists($this, $method)) {
311+
if ($this->isSub($command)) {
312+
return $this->dispatchSub($command, $args);
313+
}
314+
315+
// if command not exists.
312316
return $this->handleNotFound($name, $action, $args);
313317
}
314318

315319
// init flags for subcommand
316320
$fs = $this->newActionFlags();
317321

322+
$this->actionMethod = $method;
318323
$this->input->setFs($fs);
319324
$this->debugf('load flags by configure method, subcommand: %s', $command);
320325
$this->configure();
@@ -485,9 +490,9 @@ protected function newActionFlags(string $action = ''): FlagsParser
485490
});
486491

487492
// old mode: options and arguments at method annotations
488-
if ($this->compatible) {
489-
$fs->setSkipOnUndefined(true);
490-
}
493+
// if ($this->compatible) {
494+
// $fs->setSkipOnUndefined(true);
495+
// }
491496

492497
// save
493498
$this->subFss[$action] = $fs;

‎src/Decorate/SubCommandsWareTrait.php

+125-36
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@
1313
use Inhere\Console\Command;
1414
use Inhere\Console\Console;
1515
use Inhere\Console\Contract\CommandInterface;
16+
use Inhere\Console\Handler\AbstractHandler;
17+
use Inhere\Console\Handler\CommandWrapper;
18+
use Inhere\Console\Util\ConsoleUtil;
1619
use Inhere\Console\Util\Helper;
1720
use InvalidArgumentException;
21+
use Toolkit\Stdlib\Helper\Assert;
1822
use Toolkit\Stdlib\Obj\Traits\NameAliasTrait;
1923
use function array_keys;
2024
use function array_merge;
@@ -24,7 +28,6 @@
2428
use function is_object;
2529
use function is_string;
2630
use function is_subclass_of;
27-
use function method_exists;
2831
use function preg_match;
2932

3033
/**
@@ -36,6 +39,11 @@ trait SubCommandsWareTrait
3639
{
3740
use NameAliasTrait;
3841

42+
/**
43+
* @var AbstractHandler|null
44+
*/
45+
protected ?AbstractHandler $parent = null;
46+
3947
/**
4048
* @var array
4149
*/
@@ -44,76 +52,110 @@ trait SubCommandsWareTrait
4452
/**
4553
* The sub-commands of the command
4654
*
47-
* @var array
55+
* ```php
4856
* [
4957
* 'name' => [
50-
* 'handler' => MyCommand::class, // allow: string|Closure|CommandInterface
51-
* 'config' => []
58+
* 'handler' => MyCommand::class,
59+
* 'config' => [
60+
* 'name' => 'string',
61+
* 'desc' => 'string',
62+
* 'options' => [],
63+
* 'arguments' => [],
64+
* ]
5265
* ]
5366
* ]
67+
* ```
68+
*
69+
* @var array<string, array{handler:mixed, config:array}>
5470
*/
5571
private array $commands = [];
5672

5773
/**
58-
* Can attach sub-commands
74+
* Can attach sub-commands to current command
5975
*
6076
* @return array
6177
*/
62-
protected function commands(): array
78+
protected function subCommands(): array
6379
{
6480
// [
6581
// 'cmd1' => function(){},
82+
// // class name
6683
// MySubCommand::class,
6784
// 'cmd2' => MySubCommand2::class,
68-
// new FooCommand,
85+
// // no key
86+
// new FooCommand(),
6987
// 'cmd3' => new FooCommand2(),
7088
// ]
7189
return [];
7290
}
7391

7492
/**
7593
* @param string $name
94+
* @param array $args
95+
*
96+
* @return mixed
7697
*/
77-
protected function dispatchCommand(string $name): void
98+
protected function dispatchSub(string $name, array $args): mixed
7899
{
100+
$subInfo = $this->commands[$name];
101+
$this->debugf('dispatch the attached subcommand: %s', $name);
102+
103+
// create and init sub-command
104+
$subCmd = $this->createSubCommand($subInfo);
105+
$subCmd->setParent($this);
106+
$subCmd->setInputOutput($this->input, $this->output);
107+
108+
return $subCmd->run($args);
109+
}
110+
111+
/**
112+
* @param array{name: string, desc: string, options: array, arguments: array} $subInfo
113+
*
114+
* @return Command
115+
*/
116+
protected function createSubCommand(array $subInfo): Command
117+
{
118+
$handler = $subInfo['handler'];
119+
if (is_object($handler)) {
120+
if ($handler instanceof Command) {
121+
return $handler;
122+
}
123+
124+
return CommandWrapper::wrap($handler, $subInfo['config']);
125+
}
126+
127+
// class-string of Command
128+
return new $handler;
79129
}
80130

81131
/**
82132
* Register a app independent console command
83133
*
84-
* @param string|CommandInterface $name
134+
* @param string|class-string $name
85135
* @param string|Closure|CommandInterface|null $handler
86-
* @param array $config
87-
* array:
88-
* - aliases The command aliases
89-
* - description The description message
90-
*
91-
* @throws InvalidArgumentException
136+
* @param array $config
92137
*/
93138
public function addSub(string $name, string|Closure|CommandInterface $handler = null, array $config = []): void
94139
{
95140
if (!$handler && class_exists($name)) {
96141
/** @var Command $name name is an command class */
97142
$handler = $name;
98143
$name = $name::getName();
144+
} elseif (!$name && $handler instanceof Command) {
145+
$name = $handler->getRealName();
146+
} elseif (!$name && class_exists($handler)) {
147+
$name = $handler::getName();
99148
}
100149

101-
if (!$name || !$handler) {
102-
Helper::throwInvalidArgument("Command 'name' and 'handler' cannot be empty! name: $name");
103-
}
150+
Assert::isFalse(!$name || !$handler, "Command 'name' and 'handler' cannot be empty! name: $name");
151+
Assert::isFalse(isset($this->commands[$name]), "Command '$name' have been registered!");
104152

105153
$this->validateName($name);
106154

107-
if (isset($this->commands[$name])) {
108-
Helper::throwInvalidArgument("Command '$name' have been registered!");
109-
}
110-
111155
$config['aliases'] = isset($config['aliases']) ? (array)$config['aliases'] : [];
112156

113157
if (is_string($handler)) {
114-
if (!class_exists($handler)) {
115-
Helper::throwInvalidArgument("The command handler class [$handler] not exists!");
116-
}
158+
Assert::isTrue(class_exists($handler), "The console command class '$handler' not exists!");
117159

118160
if (!is_subclass_of($handler, Command::class)) {
119161
Helper::throwInvalidArgument('The command handler class must is subclass of the: ' . Command::class);
@@ -129,42 +171,89 @@ public function addSub(string $name, string|Closure|CommandInterface $handler =
129171
if ($aliases = $handler::aliases()) {
130172
$config['aliases'] = array_merge($config['aliases'], $aliases);
131173
}
132-
} elseif (!is_object($handler) || !method_exists($handler, '__invoke')) {
174+
} elseif (!is_object($handler) || !ConsoleUtil::isValidCmdObject($handler)) {
133175
Helper::throwInvalidArgument(
134-
'The console command handler must is an subclass of %s OR a Closure OR a object have method __invoke()',
135-
Command::class
176+
'The command handler must is an subclass of %s OR a Closure OR a sub-object of %s',
177+
Command::class,
178+
Command::class,
136179
);
137180
}
138181

182+
// has alias option
183+
if ($config['aliases']) {
184+
$this->setAlias($name, $config['aliases'], true);
185+
}
186+
187+
$config['name'] = $name;
188+
// save
189+
$this->commands[$name] = [
190+
'type' => Console::CMD_SINGLE,
191+
'handler' => $handler,
192+
'config' => $config,
193+
];
194+
}
195+
196+
/**
197+
* @param CommandInterface $handler
198+
*
199+
* @return $this
200+
*/
201+
public function addSubHandler(CommandInterface $handler): static
202+
{
203+
$name = $handler->getRealName();
204+
139205
// is an class name string
140206
$this->commands[$name] = [
141207
'type' => Console::CMD_SINGLE,
142208
'handler' => $handler,
143-
'config' => $config,
209+
'config' => [],
144210
];
145211

146-
// has alias option
147-
if (isset($config['aliases'])) {
148-
$this->setAlias($name, $config['aliases'], true);
149-
}
212+
return $this;
150213
}
151214

152215
/**
153216
* @param array $commands
154-
*
155-
* @throws InvalidArgumentException
156217
*/
157218
public function addCommands(array $commands): void
158219
{
159220
foreach ($commands as $name => $handler) {
160221
if (is_int($name)) {
161-
$this->addSub($handler);
222+
$this->addSub('', $handler);
162223
} else {
163224
$this->addSub($name, $handler);
164225
}
165226
}
166227
}
167228

229+
/**
230+
* @param AbstractHandler $parent
231+
*/
232+
public function setParent(AbstractHandler $parent): void
233+
{
234+
$this->parent = $parent;
235+
}
236+
237+
/**
238+
* @return $this
239+
*/
240+
public function getRoot(): static
241+
{
242+
if ($this->parent) {
243+
return $this->parent->getRoot();
244+
}
245+
246+
return $this;
247+
}
248+
249+
/**
250+
* @return static|null
251+
*/
252+
public function getParent(): ?static
253+
{
254+
return $this->parent;
255+
}
256+
168257
/**********************************************************
169258
* helper methods
170259
**********************************************************/

‎src/Handler/AbstractHandler.php

+27-59
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
use Inhere\Console\IO\Input;
2424
use Inhere\Console\IO\Output;
2525
use Inhere\Console\Util\Helper;
26-
use InvalidArgumentException;
2726
use ReflectionException;
2827
use RuntimeException;
2928
use Throwable;
@@ -34,7 +33,6 @@
3433
use function cli_set_process_title;
3534
use function error_get_last;
3635
use function function_exists;
37-
use function vdump;
3836
use const PHP_OS;
3937

4038
/**
@@ -70,14 +68,6 @@ abstract class AbstractHandler implements CommandHandlerInterface
7068
*/
7169
protected static string $desc = '';
7270

73-
/**
74-
* The command/controller description message
75-
*
76-
* @var string
77-
* @deprecated please use {@see $desc}
78-
*/
79-
protected static string $description = '';
80-
8171
/**
8272
* @var bool Whether enable coroutine. It is require swoole extension.
8373
*/
@@ -101,9 +91,9 @@ abstract class AbstractHandler implements CommandHandlerInterface
10191
protected string $processTitle = '';
10292

10393
/**
104-
* @var DataObject
94+
* @var DataObject|null
10595
*/
106-
protected DataObject $params;
96+
protected ?DataObject $params = null;
10797

10898
/**
10999
* The input command name. maybe is an alias name.
@@ -160,8 +150,8 @@ protected function init(): void
160150
{
161151
// $this->commentsVars = $this->annotationVars();
162152
$this->afterInit();
163-
$this->debugf('attach inner subcommands to "%s"', self::getName());
164-
$this->addCommands($this->commands());
153+
$this->debugf('attach inner subcommands to "%s"', $this->getRealName());
154+
$this->addCommands($this->subCommands());
165155
}
166156

167157
protected function afterInit(): void
@@ -170,7 +160,7 @@ protected function afterInit(): void
170160
}
171161

172162
/**
173-
* command options
163+
* get command options
174164
*
175165
* **Alone Command**
176166
*
@@ -183,7 +173,7 @@ protected function afterInit(): void
183173
*
184174
* @return array
185175
*/
186-
protected function options(): array
176+
protected function getOptions(): array
187177
{
188178
// ['--skip-invalid' => 'Whether ignore invalid arguments and options, when use input definition',]
189179
return [];
@@ -257,13 +247,13 @@ protected function initFlagsParser(Input $input): void
257247
}
258248

259249
$input->setFs($this->flags);
260-
$this->flags->setDesc(self::getDesc());
261-
$this->flags->setScriptName(self::getName());
250+
$this->flags->setDesc($this->getRealDesc());
251+
$this->flags->setScriptName($this->getRealName());
262252

263253
$this->beforeInitFlagsParser($this->flags);
264254

265255
// set options by options()
266-
$optRules = $this->options();
256+
$optRules = $this->getOptions();
267257
$this->flags->addOptsByRules($optRules);
268258

269259
// for render help
@@ -298,11 +288,10 @@ protected function afterInitFlagsParser(FlagsParser $fs): void
298288
* @param array $args
299289
*
300290
* @return mixed
301-
* @throws Throwable
302291
*/
303292
public function run(array $args): mixed
304293
{
305-
$name = self::getName();
294+
$name = $this->getRealName();
306295

307296
try {
308297
$this->initForRun();
@@ -314,7 +303,7 @@ public function run(array $args): mixed
314303
// parse options
315304
$this->flags->lock();
316305
if (!$this->flags->parse($args)) {
317-
return 0; // on error, help
306+
return 0; // on error OR help
318307
}
319308

320309
$args = $this->flags->getRawArgs();
@@ -324,7 +313,7 @@ public function run(array $args): mixed
324313
if ($this->isDetached()) {
325314
ErrorHandler::new()->handle($e);
326315
} else {
327-
throw $e;
316+
throw new RuntimeException('Run error - ' . $e->getMessage(), $e->getCode(), $e);
328317
}
329318
}
330319

@@ -340,23 +329,11 @@ public function run(array $args): mixed
340329
*/
341330
protected function doRun(array $args): mixed
342331
{
343-
if (isset($args[0])) {
344-
$first = $args[0];
345-
$rName = $this->resolveAlias($first);
346-
// vdump($first, $rName);
347-
// TODO
348-
// if ($this->isSub($rName)) {
349-
// }
350-
}
351-
352332
// some prepare check
353-
// - validate input arguments
354333
if (true !== $this->prepare()) {
355334
return -1;
356335
}
357336

358-
// $this->dispatchCommand($name);
359-
360337
// return False to deny goon run.
361338
if (false === $this->beforeExecute()) {
362339
return -1;
@@ -378,6 +355,11 @@ protected function doRun(array $args): mixed
378355
return $result;
379356
}
380357

358+
protected function doExecute(): mixed
359+
{
360+
return '';
361+
}
362+
381363
/**
382364
* coroutine run by swoole go()
383365
*
@@ -435,9 +417,6 @@ protected function afterExecute(): void
435417

436418
/**
437419
* prepare run
438-
*
439-
* @throws InvalidArgumentException
440-
* @throws RuntimeException
441420
*/
442421
protected function prepare(): bool
443422
{
@@ -491,13 +470,10 @@ public function getParams(): DataObject
491470

492471
/**
493472
* @param array $params
494-
*
495-
* @return DataObject
496473
*/
497-
public function initParams(array $params): DataObject
474+
public function initParams(array $params): void
498475
{
499476
$this->params = DataObject::new($params);
500-
return $this->params;
501477
}
502478

503479
/**
@@ -587,6 +563,14 @@ public function getRealName(): string
587563
return self::getName();
588564
}
589565

566+
/**
567+
* @return string
568+
*/
569+
public function getRealDesc(): string
570+
{
571+
return self::getDesc();
572+
}
573+
590574
/**
591575
* @param bool $useReal
592576
*
@@ -632,7 +616,7 @@ final public static function getName(): string
632616
*/
633617
public static function getDesc(): string
634618
{
635-
return static::$desc ?: static::$description;
619+
return static::$desc;
636620
}
637621

638622
/**
@@ -645,22 +629,6 @@ public static function setDesc(string $desc): void
645629
}
646630
}
647631

648-
/**
649-
* @return string
650-
*/
651-
public static function getDescription(): string
652-
{
653-
return self::getDesc();
654-
}
655-
656-
/**
657-
* @param string $desc
658-
*/
659-
public static function setDescription(string $desc): void
660-
{
661-
self::setDesc($desc);
662-
}
663-
664632
/**
665633
* @return bool
666634
*/

‎src/Handler/CallableCommand.php

+1-125
Original file line numberDiff line numberDiff line change
@@ -9,136 +9,12 @@
99

1010
namespace Inhere\Console\Handler;
1111

12-
use Inhere\Console\Command;
13-
use Inhere\Console\IO\Input;
14-
use Inhere\Console\IO\Output;
15-
use BadMethodCallException;
16-
use Toolkit\PFlag\FlagsParser;
17-
1812
/**
1913
* Class CallableCommand - wrap an callable as Command
2014
*
2115
* @package Inhere\Console\Handler
2216
*/
23-
class CallableCommand extends Command
17+
class CallableCommand extends CommandWrapper
2418
{
25-
/**
26-
* @var callable(FlagsParser, Output): void
27-
*/
28-
private $callable;
29-
30-
/**
31-
* @var array{options: array, arguments: array}
32-
*/
33-
protected array $config = [];
34-
35-
// public function new(callable $callable): self
36-
// {
37-
// }
38-
39-
// public function __construct(Input $input, Output $output, InputDefinition $definition = null)
40-
// {
41-
// parent::__construct($input, $output, $definition);
42-
// }
43-
44-
// /**
45-
// * @param callable $cb
46-
// *
47-
// * @return static
48-
// */
49-
// public static function wrap(callable $cb): self
50-
// {
51-
// return (new self())->setCallable($cb);
52-
// }
53-
54-
/**
55-
* @param callable $fn
56-
*
57-
* @return $this
58-
*/
59-
public function withFunc(callable $fn): self
60-
{
61-
return $this->setCallable($fn);
62-
}
63-
64-
/**
65-
* @param callable $fn
66-
*
67-
* @return $this
68-
*/
69-
public function withCustom(callable $fn): self
70-
{
71-
$fn($this);
72-
return $this;
73-
}
74-
75-
/**
76-
* @param array $config
77-
*
78-
* @return $this
79-
*/
80-
public function withConfig(array $config): self
81-
{
82-
$this->config = $config;
83-
return $this;
84-
}
85-
86-
/**
87-
* @param array $options
88-
*
89-
* @return $this
90-
*/
91-
public function withOptions(array $options): self
92-
{
93-
$this->config['options'] = $options;
94-
return $this;
95-
}
96-
97-
/**
98-
* @param array $arguments
99-
*
100-
* @return $this
101-
*/
102-
public function withArguments(array $arguments): self
103-
{
104-
$this->config['arguments'] = $arguments;
105-
return $this;
106-
}
107-
108-
/**
109-
* @param callable $callable
110-
*
111-
* @return CallableCommand
112-
*/
113-
public function setCallable(callable $callable): self
114-
{
115-
$this->callable = $callable;
116-
return $this;
117-
}
118-
119-
/**
120-
* @return array
121-
*/
122-
public function getConfig(): array
123-
{
124-
return $this->config;
125-
}
126-
127-
/**
128-
* Do execute command
129-
*
130-
* @param Input $input
131-
* @param Output $output
132-
*
133-
* @return mixed
134-
*/
135-
protected function execute(Input $input, Output $output): mixed
136-
{
137-
if (!$call = $this->callable) {
138-
throw new BadMethodCallException('The callable property is empty');
139-
}
14019

141-
// call custom callable
142-
return $call($this->flags, $output);
143-
}
14420
}

‎src/Handler/CommandWrapper.php

+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
<?php declare(strict_types=1);
2+
/**
3+
* The file is part of inhere/console
4+
*
5+
* @author https://github.com/inhere
6+
* @homepage https://github.com/inhere/php-console
7+
* @license https://github.com/inhere/php-console/blob/master/LICENSE
8+
*/
9+
10+
namespace Inhere\Console\Handler;
11+
12+
use Inhere\Console\Command;
13+
use Inhere\Console\IO\Input;
14+
use Inhere\Console\IO\Output;
15+
use BadMethodCallException;
16+
use Toolkit\PFlag\FlagsParser;
17+
18+
/**
19+
* Class CommandWrapper - wrap an callable as Command
20+
*
21+
* @package Inhere\Console\Handler
22+
*/
23+
class CommandWrapper extends Command
24+
{
25+
/**
26+
* @var callable(FlagsParser, Output): mixed
27+
*/
28+
private $callable;
29+
30+
/**
31+
* @var array{name: string, desc: string, options: array, arguments: array}
32+
*/
33+
protected array $config = [];
34+
35+
/**
36+
* @param callable(FlagsParser, Output):mixed $handleFn
37+
*
38+
* @return $this
39+
*/
40+
public static function new(callable $handleFn, array $config = []): self
41+
{
42+
return (new self())->withConfig($config)->setCallable($handleFn);
43+
}
44+
45+
/**
46+
* @param callable(FlagsParser, Output):mixed $handleFn
47+
* @param array $config
48+
*
49+
* @return static
50+
*/
51+
public static function wrap(callable $handleFn, array $config = []): self
52+
{
53+
return (new self())->withConfig($config)->setCallable($handleFn);
54+
}
55+
56+
/**
57+
* @param callable $fn
58+
*
59+
* @return $this
60+
*/
61+
public function withCustom(callable $fn): self
62+
{
63+
$fn($this);
64+
return $this;
65+
}
66+
67+
/**
68+
* @param callable(FlagsParser, Output):mixed $fn
69+
*
70+
* @return $this
71+
*/
72+
public function withFunc(callable $fn): self
73+
{
74+
return $this->setCallable($fn);
75+
}
76+
77+
/**
78+
* @param array $config
79+
*
80+
* @return $this
81+
*/
82+
public function withConfig(array $config): self
83+
{
84+
$this->config = $config;
85+
return $this;
86+
}
87+
88+
/**
89+
* @param array $options
90+
*
91+
* @return $this
92+
*/
93+
public function withOptions(array $options): self
94+
{
95+
$this->config['options'] = $options;
96+
return $this;
97+
}
98+
99+
/**
100+
* @param array $arguments
101+
*
102+
* @return $this
103+
*/
104+
public function withArguments(array $arguments): self
105+
{
106+
$this->config['arguments'] = $arguments;
107+
return $this;
108+
}
109+
110+
/**
111+
* @param callable(FlagsParser, Output):mixed $callable
112+
*
113+
* @return static
114+
*/
115+
public function setCallable(callable $callable): self
116+
{
117+
$this->callable = $callable;
118+
return $this;
119+
}
120+
121+
/**
122+
* @return array
123+
*/
124+
protected function getOptions(): array
125+
{
126+
return $this->config['options'] ?? [];
127+
}
128+
129+
/**
130+
* @return array
131+
*/
132+
protected function getArguments(): array
133+
{
134+
return $this->config['arguments'] ?? [];
135+
}
136+
137+
/**
138+
* @return string
139+
*/
140+
public function getRealDesc(): string
141+
{
142+
return $this->config['desc'] ?? '';
143+
}
144+
145+
/**
146+
* @return string
147+
*/
148+
public function getRealName(): string
149+
{
150+
return $this->config['name'] ?? '';
151+
}
152+
153+
/**
154+
* @return array
155+
*/
156+
public function getConfig(): array
157+
{
158+
return $this->config;
159+
}
160+
161+
/**
162+
* Do execute command
163+
*
164+
* @param Input $input
165+
* @param Output $output
166+
*
167+
* @return mixed
168+
*/
169+
protected function execute(Input $input, Output $output): mixed
170+
{
171+
if (!$call = $this->callable) {
172+
throw new BadMethodCallException('The command handler property is empty');
173+
}
174+
175+
// call custom callable
176+
return $call($this->flags, $output);
177+
}
178+
}

‎src/Util/ConsoleUtil.php

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Inhere\Console\Util;
4+
5+
use Inhere\Console\Command;
6+
use function method_exists;
7+
8+
/**
9+
* class ConsoleUtil
10+
*
11+
* @author inhere
12+
*/
13+
class ConsoleUtil
14+
{
15+
/**
16+
* @param object $handler
17+
*
18+
* @return bool
19+
*/
20+
public static function isValidCmdObject(object $handler): bool
21+
{
22+
if ($handler instanceof Command) {
23+
return true;
24+
}
25+
26+
return method_exists($handler, '__invoke');
27+
}
28+
29+
}

‎test/CommandTest.php

+20-2
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,29 @@
1919
*/
2020
class CommandTest extends TestCase
2121
{
22-
public function testBasic(): void
22+
public function testCommand_basic_usage(): void
2323
{
2424
$c = new TestCommand(new Input(), new Output());
2525

2626
$this->assertSame('test1', $c::getName());
27-
$this->assertStringContainsString('desc', $c::getDesc());
27+
$this->assertSame('test1', $c->getRealName());
28+
$this->assertStringContainsString('description', $c::getDesc());
29+
$this->assertStringContainsString('description', $c->getRealDesc());
30+
}
31+
32+
public function testCommand_alone_run(): void
33+
{
34+
$c = new TestCommand(new Input(), new Output());
35+
36+
$str = $c->run([]);
37+
$this->assertEquals('Inhere\ConsoleTest\TestCommand::execute', $str);
38+
}
39+
40+
public function testCommand_alone_run_sub(): void
41+
{
42+
$c = new TestCommand(new Input(), new Output());
43+
44+
$str = $c->run(['sub1']);
45+
$this->assertEquals('Inhere\ConsoleTest\{closure}', $str);
2846
}
2947
}

‎test/ControllerTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ public function testBasic(): void
2323
$c = new TestController(new Input(), new Output());
2424

2525
$this->assertSame('test', $c::getName());
26-
$this->assertStringContainsString('desc', $c::getDescription());
26+
$this->assertStringContainsString('desc', $c::getDesc());
2727
}
2828
}

‎test/TestCommand.php

+13
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
namespace Inhere\ConsoleTest;
1111

1212
use Inhere\Console\Command;
13+
use Inhere\Console\Handler\CommandWrapper;
1314
use Inhere\Console\IO\Input;
1415
use Inhere\Console\IO\Output;
1516

@@ -24,6 +25,18 @@ class TestCommand extends Command
2425

2526
protected static string $desc = 'command description message';
2627

28+
protected function subCommands(): array
29+
{
30+
return [
31+
CommandWrapper::new(static function () {
32+
return __METHOD__;
33+
})->withConfig([
34+
'name' => 'sub1',
35+
'desc' => 'desc for sub1 in test1',
36+
]),
37+
];
38+
}
39+
2740
/**
2841
* do execute command
2942
*

0 commit comments

Comments
 (0)