tobento / service-console
使用Symfony Console作为默认实现的命令行界面。
Requires
- php: >=8.0
- psr/container: ^2.0
- psr/event-dispatcher: ^1.0
- symfony/console: ^6.0
- tobento/service-autowire: ^1.0.9
Requires (Dev)
- mockery/mockery: ^1.6
- phpunit/phpunit: ^9.5
- tobento/service-collection: ^1.0
- tobento/service-container: ^1.0.6
- tobento/service-event: ^1.0
- vimeo/psalm: ^4.0
README
使用Symfony Console作为默认实现的命令行界面。
目录
入门
添加运行此命令的控制台服务项目的最新版本。
composer require tobento/service-console
需求
- PHP 8.0或更高版本
亮点
- 框架无关,与任何项目兼容
- 解耦设计
文档
控制台
创建控制台
use Tobento\Service\Console\Symfony; use Tobento\Service\Console\ConsoleInterface; use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; $console = new Symfony\Console( name: 'app', container: $container, // ContainerInterface // you may define a event dispatcher: eventDispatcher: $eventDispatcher, // EventDispatcherInterface ); var_dump($console instanceof ConsoleInterface); // bool(true)
添加命令
在创建命令之后,您可以在控制台中添加它们
$console->addCommand(SampleCommand::class); // or $console->addCommand(new SampleCommand());
运行控制台
$console->run();
执行命令
您可能想执行一个命令而不是运行控制台。
use Tobento\Service\Console\ExecutedInterface; $executed = $console->execute( command: SampleCommand::class, // passing arguments and options as array: input: [ // passing arguments: 'username' => 'Tom', // with array value: 'username' => ['Tom', 'Tim'], // passing options: '--some-option' => 'value', // with array value: '--some-option' => ['value'], ], // or you may pass the command, arguments and options as string input: 'command:name Tom --bar=1' ); var_dump($executed instanceof ExecutedInterface); // bool(true) $command = $executed->command(); // string $code = $executed->code(); // int $output = $executed->output(); // string
带有命令类的示例
use Tobento\Service\Console\Command; use Tobento\Service\Console\InteractorInterface; $command = (new Command(name: 'name')) ->handle(function(InteractorInterface $io): int { // do sth: return 0; }); $console->execute(command: $command);
带有命令名称的示例
$console->addCommand(SampleCommand::class); $console->execute(command: 'sample');
创建命令
命令
您可以使用Command::class
来创建简单的命令。
use Tobento\Service\Console\Command; use Tobento\Service\Console\CommandInterface; use Tobento\Service\Console\InteractorInterface; $command = (new Command(name: 'mail:send')) // you may set a description: ->description('Send an email to a user(s)') // you may set a usage text: ->usage('Send emails ...') // you may add an argument(s): ->argument( name: 'user', description: 'The Id(s) of the user', variadic: true, ) // you may add an option(s): ->option( name: 'queue', description: 'Whether the email should be queued', ) // handle the command: ->handle(function(InteractorInterface $io, MailerInterface $mailer): int { // retrieve input arguments and options: $userIds = $io->argument('user'); $queue = $io->option('queue'); // send emails using the mailer... // you may write some output: $io->write(sprintf( 'email(s) send to user ids %s queued [%s]', implode(',', $userIds), $queue ? 'true' : 'false', )); return Command::SUCCESS; // return Command::FAILURE; // return Command::INVALID; }); var_dump($command instanceof CommandInterface); // bool(true)
查看交互器部分了解更多信息。
参数和选项
参数详细说明
use Tobento\Service\Console\Command; $command = (new Command(name: 'sample')) ->argument( // The name of the argument: name: 'name', // you may define a description: description: 'Some description', // you may define a default value(s) (null default): value: ['foo', 'bar'], // mixed // set if the argument is optional (false default): optional: true, // if true expecting multiple values (false default): variadic: true, // not supported yet! suggestedValues: null, );
选项详细说明
use Tobento\Service\Console\Command; $command = (new Command(name: 'sample')) ->option( // The name of the option: name: 'name', // you may define a description: description: 'Some description', // you may define a default value(s) (null default): value: ['foo', 'bar'], // mixed // variadic: variadic: null, // (default) // acts as boolean value, if exists true, otherwise false. variadic: false, // optional value (e.g. --name or --name=foo) if not specified default value is used. variadic: true, // is variadic expecting multiple values (e.g. --name=foo --name=bar). // if not specified default values are used. // not supported yet! suggestedValues: null, );
抽象命令
只需扩展AbstractCommand::class
即可创建更复杂的命令。
use Tobento\Service\Console\AbstractCommand; use Tobento\Service\Console\InteractorInterface; class SendEmails extends AbstractCommand { /** * The command name. */ public const NAME = 'email:send'; /** * The command description. */ public const DESC = 'Send an email to a user(s)'; /** * The command usage text. */ public const USAGE = 'Send emails ...'; /** * Create a new instance. */ public function __construct() { // you may add an argument(s): $this->argument( name: 'user', description: 'The Id(s) of the user', variadic: true, ); // you may add an option(s): $this->option( name: 'queue', description: 'Whether the email should be queued', ); } /** * Handle the command. * * @param InteractorInterface $io * @return int The exit status code: * 0 SUCCESS * 1 FAILURE If some error happened during the execution * 2 INVALID To indicate incorrect command usage e.g. invalid options */ public function handle(InteractorInterface $io, MailerInterface $mailer): int { // retrieve input arguments and options: $userIds = $io->argument('user'); $queue = $io->option('queue'); // send emails using the mailer... // you may write some output: $io->write(sprintf( 'email(s) send to user ids %s queued [%s]', implode(',', $userIds), $queue ? 'true' : 'false', )); return 0; // or use the available constants: // return static::SUCCESS; // return static::FAILURE; // return static::INVALID; } }
查看参数和选项以获取更多详细信息。
使用签名
您可以使用SIGNATURE
作为定义您命令名称、描述、参数和选项的另一种方式。
use Tobento\Service\Console\AbstractCommand; use Tobento\Service\Console\InteractorInterface; class SendEmails extends AbstractCommand { /** * The signature of the console command. */ public const SIGNATURE = ' mail:send | Send an email to a user(s) {user : The Id(s) of the user} {--queue : Whether the email should be queued} '; /** * Handle the command. * * @param InteractorInterface $io * @return int The exit status code: * 0 SUCCESS * 1 FAILURE If some error happened during the execution * 2 INVALID To indicate incorrect command usage e.g. invalid options */ public function handle(InteractorInterface $io): int { return 0; } }
参数
{name}
必需,期望单个值{name?}
可选,期望单个值{name[]}
必需且可变,期望多个值{name[]?}
可选且可变,期望多个值{name=}
可选,默认值为null
{name=foo}
可选,默认值为foo
{name=[foo,bar]}
可选且可变,默认值为foo
和bar
选项
{--name}
作为布尔值,如果存在则为true
,否则为false
{--n|name}
以n
作为短名称{--name=}
默认值为null
{--name=foo}
默认值为foo
{--name[]}
可变,期望多个值{--name=[foo,bar]}
可变,默认值为foo
和bar
通常情况下,选项是可选的!
交互器
交互器让您在处理命令的同时与控制台的输入和输出进行交互
use Tobento\Service\Console\Command; use Tobento\Service\Console\InteractorInterface; $command = (new Command(name: 'mail:send')) // handle the command: ->handle(function(InteractorInterface $io): int { // ... // use the interactor $io to interact // ... });
检索参数和选项值
检索指定的输入参数和选项。参见参数和选项。
// Argument(s): $value = $io->argument(name: 'name'); // all values indexed by the argument name: $values = $io->arguments(); // Option(s): $value = $io->option(name: 'name'); // all values indexed by the option name: $values = $io->options();
参数详细说明
// Argument with variadic: false $value = $io->argument(name: 'name'); // NULL, if the argument was not passed when running the command // Not NULL, if the argument was passed when running the command // Argument with variadic: true $value = $io->argument(name: 'name'); // Array empty if the argument was not passed when running the command
选项详细说明
// Option with variadic: null $value = $io->option(name: 'name'); // bool(false), if the option was not passed when running the command // bool(true), if the option was passed when running the command // Option with variadic: false $value = $io->option(name: 'name'); // NULL, if the option was not passed when running the command // Not NULL, if the option was passed when running the command // Option with variadic: true $value = $io->option(name: 'name'); // Array, empty if the option was not passed when running the command
检索原始输入
您可以使用rawInput
方法检索传递给命令的原始输入。
// if this command was run as: // php ap command:name foo --bar --baz=1 $rawInput = $io->rawInput(); // ['command:name', 'foo', '--bar', '--baz=1'] // you may exclude the command name: $rawInput = $io->rawInput(withoutCommandName: false); // ['foo', '--bar', '--baz=1']
写入输出
$io->write('Some text'); $io->write('Some Text', 'newline'); $io->write('Some Text', newline: 1); // Write a single blank line: $io->newLine(); // Write three blank lines: $io->newLine(num: 3); // Write specific messages: $io->info('An info message'); $io->comment('A comment message'); $io->warning('A warning message'); $io->error('An error message'); $io->success('A success message');
写入格式化输出
您可以使用以下方式编写格式化的输出
$io->write('<comment>Some text</comment>'); $io->write('<fg=red>Some text</>'); $io->write('<bg=red>Some text</>'); $io->write('<fg=white;bg=red>Some text</>');
编写表格
$io->table( headers: ['Name', 'Email'], rows: [ ['Tom', 'tom@example.com'], ], );
提问
$name = $io->ask('What is your name?'); // With validator: $name = $io->ask('What is your name?', validator: function(string $answer): void { if ($answer !== 'something') { throw new \Exception('Your answer is incorrect'); } }); // With max attempts: $name = $io->ask('What is your name?', attempts: 2);
密保问题
您可以提出一个密保问题
$password = $io->secret('What is the password?'); // With validator: $name = $io->secret('What is your name?', validator: function(string $answer): void { if ($answer !== 'something') { throw new \Exception('Your answer is incorrect'); } }); // With max attempts: $name = $io->secret('What is your name?', attempts: 2);
确认问题
您可以提出确认问题
if ($io->confirm('Do you wish to continue?', default: true)) { // ... }
选择问题
您可以提出选择问题
$color = $io->choice( question: 'What color do you wish to use?', choices: ['red', 'blue'], default: 'red', multiselect: true, );
进度条
$io->progressStart(max: 5); foreach (range(0, 5) as $number) { sleep(1); $io->progressAdvance(step: 1); } $io->progressFinish();
详细程度级别
quiet
无消息输出normal
正常输出v
低详细程度vv
中等详细程度vvv
高详细程度
if ($io->isVerbose('vv')) { $io->write('Some Text'); } // or: $io->write('Some Text', 'v'); $io->write('Some Text', 'vv'); $io->write('Some Text', 'vvv');
命令参数
忽略验证错误参数
您可以将IgnoreValidationErrors
参数添加到忽略验证错误,这在某些情况下可能很有用。
use Tobento\Service\Console\Command; use Tobento\Service\Console\InteractorInterface; use Tobento\Service\Console\Parameter; $command = (new Command(name: 'mail:send')) ->parameter(new Parameter\IgnoreValidationErrors()); // handle the command: ->handle(function(InteractorInterface $io): int { return Command::SUCCESS; });
锁定
尚不支持。
信号
尚不支持。
事件
如果您在控制台定义了事件监听器,您可以监听以下事件
测试
您可以使用TestCommand::class
来测试命令。
您可以通过查看Tobento\Service\Console\Test\TestCommandTest::class
来获取示例。
use PHPUnit\Framework\TestCase; use Tobento\Service\Console\Test\TestCommand; class SampleCommandTest extends TestCase { public function testCommand() { (new TestCommand(command: SampleCommand::class)) // output expectations: ->expectsOutput('lorem') ->doesntExpectOutput('ipsum') ->expectsOutputToContain('lorem') ->doesntExpectOutputToContain('ipsum') ->expectsTable( headers: ['Name', 'Email'], rows: [ ['Tim', 'tom@example.com'], ], ) // questions expectations: ->expectsQuestion('What is your name?', answer: 'Tom') ->expectsQuestion('What colors do you wish to use?', answer: ['red', 'yellow']) ->expectsQuestion('Do you wish to continue?', answer: true) // exit code expectation: ->expectsExitCode(0) // execute test: ->execute(); } }
传递输入参数和选项
use PHPUnit\Framework\TestCase; use Tobento\Service\Console\Test\TestCommand; use Tobento\Service\Console\CommandInterface; class SampleCommandTest extends TestCase { public function testCommand() { (new TestCommand( command: SampleCommand::class, // string|CommandInterface input: [ // passing arguments: 'username' => 'Tom', // with array value: 'username' => ['Tom', 'Tim'], // passing options: '--some-option' => 'value', // with array value: '--some-option' => ['value'], // pass null for options with variadic: null '--some-option' => null, ], )) // set expectations: ->expectsOutput('lorem') ->expectsExitCode(0) // execute test: ->execute(); } }
传递容器或控制台
use PHPUnit\Framework\TestCase; use Tobento\Service\Console\Test\TestCommand; use Tobento\Service\Console\ConsoleInterface; use Psr\Container\ContainerInterface; class SampleCommandTest extends TestCase { public function testCommand() { (new TestCommand(command: SampleCommand::class)) ->expectsExitCode(0) // if no dependencies ->execute() // null // passing the console to test on: ->execute($console) // ConsoleInterface // or just passing the container // (recommended way as console independent): ->execute($container); // ContainerInterface } }
带有输入
您可以使用返回新TestCommand::class
实例的withInput
方法
use PHPUnit\Framework\TestCase; use Tobento\Service\Console\Test\TestCommand; class SampleCommandTest extends TestCase { private function command(): TestCommand { return new TestCommand(command: SampleCommand::class); } public function testCommand() { $this->command() ->withInput([ 'username' => 'Tom', ]) ->expectsExitCode(0) ->execute(); } }
Symfony
Symfony Console
use Tobento\Service\Console\Symfony; use Tobento\Service\Console\ConsoleInterface; use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; $console = new Symfony\Console( name: 'app', container: $container, // ContainerInterface // you may define a event dispatcher: eventDispatcher: $eventDispatcher, // EventDispatcherInterface ); var_dump($console instanceof ConsoleInterface); // bool(true)
Symfony自定义交互器
您可以使用interactorFactory
参数创建自定义交互器
use Tobento\Service\Console\Symfony; use Tobento\Service\Console\ConsoleInterface; use Tobento\Service\Console\InteractorInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Psr\Container\ContainerInterface; $interactorFactory = function( Command $command, InputInterface $input, OutputInterface $output ): InteractorInterface { return new Symfony\Interactor( command: $command, input: $input, output: $output, style: null, // null|SymfonyStyle ); // or create another Interactor fitting your needs }; $console = new Symfony\Console( name: 'app', container: $container, // ContainerInterface interactorFactory: $interactorFactory, ); var_dump($console instanceof ConsoleInterface); // bool(true)