zenstruck / console-extra
一套模块化功能,用于减少Symfony命令的配置冗余。
Requires
- php: >=8.1
- symfony/console: ^6.4|^7.0
- symfony/deprecation-contracts: ^2.2|^3.0
- zenstruck/callback: ^1.4.2
Requires (Dev)
- phpdocumentor/reflection-docblock: ^5.2
- phpstan/phpstan: ^1.4
- phpunit/phpunit: ^9.5
- symfony/framework-bundle: ^6.4|^7.0
- symfony/phpunit-bridge: ^6.2|^7.0
- symfony/process: ^6.4|^7.0
- symfony/var-dumper: ^6.4|^7.0
- zenstruck/console-test: ^1.4
Conflicts
README
一套模块化功能,用于减少Symfony命令的配置冗余
#[AsCommand('create:user', 'Creates a user in the database.')] final class CreateUserCommand extends InvokableServiceCommand { use RunsCommands, RunsProcesses; public function __invoke( IO $io, UserManager $userManager, #[Argument] string $email, #[Argument] string $password, #[Option(name: 'role', shortcut: 'r', suggestions: UserMananger::ROLES)] array $roles, ): void { $userManager->createUser($email, $password, $roles); $this->runCommand('another:command'); $this->runProcess('/some/script'); $io->success('Created user.'); } }
bin/console create:user kbond p4ssw0rd -r ROLE_EDITOR -r ROLE_ADMIN
[OK] Created user.
// Duration: < 1 sec, Peak Memory: 10.0 MiB
安装
composer require zenstruck/console-extra
用法
这个库是一组模块化功能,可以单独使用或组合使用。
注意
为了进一步减少命令的冗余,建议为您的应用程序创建一个抽象基命令,以启用您需要的所有功能。然后让所有应用程序的命令扩展这个基命令。
IO
这是一个扩展了 SymfonyStyle
并实现了 InputInterface
(因此实现了 InputInterface
、OutputInterface
和 StyleInterface
)的辅助对象。
use Zenstruck\Console\IO; $io = new IO($input, $output); $io->getOption('role'); // InputInterface $io->writeln('a line'); // OutputInterface $io->success('Created.'); // StyleInterface // additional methods $io->input(); // get the "wrapped" input $io->output(); // get the "wrapped" output
单独来看,它并不特别,但它可以自动注入到 Invokable
命令中。
可调用命令
扩展这个类以消除扩展 Command::execute()
的需要,只需将所需内容注入到命令的 __invoke()
方法中。以下是可以自动注入的参数:
Zenstruck\Console\IO
Symfony\Component\Console\Style\StyleInterface
Symfony\Component\Console\Input\InputInterface
Symfony\Component\Console\Input\OutputInterface
- arguments(参数名称必须与参数名称匹配或使用
Zenstruck\Console\Attribute\Argument
属性) - options(参数名称必须与选项名称匹配或使用
Zenstruck\Console\Attribute\Option
属性)
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Zenstruck\Console\InvokableCommand; use Zenstruck\Console\IO; class MyCommand extends InvokableCommand { // $username/$roles are the argument/option defined below public function __invoke(IO $io, string $username, array $roles) { $io->success('created.'); // even if you don't inject IO, it's available as a method: $this->io(); // IO } public function configure(): void { $this ->addArgument('username', InputArgument::REQUIRED) ->addOption('roles', mode: InputOption::VALUE_IS_ARRAY) ; } }
您可以自动注入“原始”输入/输出
public function __invoke(IO $io, InputInterface $input, OutputInterface $output)
没有返回类型(或 void
)意味着 0
状态代码。如果您想更改此设置,可以返回一个整数
public function __invoke(IO $io): int { return $success ? 0 : 1; }
可调用服务命令
如果您使用Symfony框架,您可以通过将服务自动注入到 __invoke()
中将 InvokableCommand
提升到更高的层次。这允许您的命令表现得像 可调用服务控制器(带有 controller.service_arguments
)。您注入 IO
而不是 Request。
让您的命令扩展 InvokableServiceCommand
并确保它们自动装配/配置。
use App\Service\UserManager; use Psr\Log\LoggerInterface; use Zenstruck\Console\InvokableServiceCommand; use Zenstruck\Console\IO; class CreateUserCommand extends InvokableServiceCommand { public function __invoke(IO $io, UserManager $userManager, LoggerInterface $logger): void { // access container parameters $environment = $this->parameter('kernel.environment'); // ... } }
使用DI属性注入
您可以在您的 __invoke()
参数上使用任何 DI属性。
use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\Target; use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; use Zenstruck\Console\InvokableServiceCommand; class SomeCommand extends InvokableServiceCommand { public function __invoke( #[Autowire('@some.service.id')] SomeService $service, #[Autowire('%kernel.environment%')] string $environment, #[Target('githubApi')] HttpClientInterface $httpClient, #[TaggedIterator('app.handler')] iterable $handlers, ): void { // ... } }
使用属性进行配置
扩展 InvokableCommand
或 InvokableServiceCommand
的命令可以使用属性配置参数和选项
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Zenstruck\Console\Attribute\Argument; use Zenstruck\Console\Attribute\Option; use Zenstruck\Console\InvokableCommand; #[Argument('arg1', description: 'Argument 1 description', mode: InputArgument::REQUIRED)] #[Argument('arg2', description: 'Argument 1 description')] #[Argument('arg3', suggestions: ['suggestion1', 'suggestion2'])] // for auto-completion #[Argument('arg4', suggestions: 'suggestionsForArg4')] // use a method on the command to get suggestions #[Option('option1', description: 'Option 1 description')] #[Option('option2', suggestions: ['suggestion1', 'suggestion2'])] // for auto-completion #[Option('option3', suggestions: 'suggestionsForOption3')] // use a method on the command to get suggestions class MyCommand extends InvokableCommand { // ... private function suggestionsForArg4(): array { return ['suggestion3', 'suggestion4']; } private function suggestionsForOption3(): array { return ['suggestion3', 'suggestion4']; } }
可调用属性
您可以直接在 __invoke()
参数上添加 Option
/Argument
属性来定义和注入参数/选项,而不是在类级别定义。
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Zenstruck\Console\Attribute\Argument; use Zenstruck\Console\Attribute\Option; use Zenstruck\Console\InvokableCommand; #[AsCommand('my:command')] class MyCommand extends InvokableCommand { public function __invoke( #[Argument] string $username, // defined as a required argument (username) #[Argument] string $password = 'p4ssw0rd', // defined as an optional argument (password) with a default (p4ssw0rd) #[Option(name: 'role', shortcut: 'r', suggestions: ['ROLE_EDITOR', 'ROLE_REVIEWER'])] array $roles = [], // defined as an array option that requires values (--r|role[]) #[Option(name: 'super-admin')] bool $superAdmin = false, // defined as a "value-less" option (--super-admin) #[Option] ?bool $force = null, // defined as a "negatable" option (--force/--no-force) #[Option] ?string $name = null, // defined as an option that requires a value (--name=) ): void { // ... } }
注意
选项/参数 模式 和 默认值 由参数的类型提示/默认值检测,不能在属性中定义。
CommandRunner
有一个 CommandRunner
对象可用于在任何地方(例如控制器)简化命令的运行。
use Zenstruck\Console\CommandRunner; /** @var \Symfony\Component\Console\Command\Command $command */ CommandRunner::for($command)->run(); // int (the status after running the command) // pass arguments CommandRunner::for($command, 'arg --opt')->run(); // int
如果应用程序可用,您可以使用它来运行命令。
use Zenstruck\Console\CommandRunner; /** @var \Symfony\Component\Console\Application $application */ CommandRunner::from($application, 'my:command')->run(); // pass arguments/options CommandRunner::from($application, 'my:command arg --opt')->run(); // int
如果您的命令是交互式的,您可以传递输入。
use Zenstruck\Console\CommandRunner; /** @var \Symfony\Component\Console\Application $application */ CommandRunner::from($application, 'my:command')->run([ 'foo', // input 1 '', // input 2 (<enter>) 'y', // input 3 ]);
默认情况下,输出被抑制,您可以可选地捕获输出。
use Zenstruck\Console\CommandRunner; /** @var \Symfony\Component\Console\Application $application */ $output = new \Symfony\Component\Console\Output\BufferedOutput(); CommandRunner::from($application, 'my:command') ->withOutput($output) // any OutputInterface ->run() ; $output->fetch(); // string (the output)
运行命令
您可以通过使用RunsCommands
特质来赋予您的可调用命令运行其他命令(在应用中定义)的能力。这些子命令将使用与父命令相同的输出。
use Symfony\Component\Console\Command; use Zenstruck\Console\InvokableCommand; use Zenstruck\Console\RunsCommands; class MyCommand extends InvokableCommand { use RunsCommands; public function __invoke(): void { $this->runCommand('another:command'); // int (sub-command's run status) // pass arguments/options $this->runCommand('another:command arg --opt'); // pass inputs for interactive commands $this->runCommand('another:command', [ 'foo', // input 1 '', // input 2 (<enter>) 'y', // input 3 ]) } }
RunsProcesses
您可以通过使用RunsProcesses
特质来赋予您的可调用命令运行其他进程(需要symfony/process
)的能力。默认情况下,进程的标准输出是隐藏的,但可以通过将-v
传递给父命令来显示。错误输出始终显示。如果进程失败,则会抛出\RuntimeException
异常。
use Symfony\Component\Console\Command; use Symfony\Component\Process\Process; use Zenstruck\Console\InvokableCommand; use Zenstruck\Console\RunsProcesses; class MyCommand extends InvokableCommand { use RunsProcesses; public function __invoke(): void { $this->runProcess('/some/script'); // construct with array $this->runProcess(['/some/script', 'arg1', 'arg1']); // for full control, pass a Process itself $this->runProcess( Process::fromShellCommandline('/some/script') ->setTimeout(900) ->setWorkingDirectory('/') ); } }
CommandSummarySubscriber
将此事件订阅者添加到您的Application
的事件分发器中,以便在每次运行命令后显示摘要。摘要包括命令的持续时间和峰值内存使用量。
如果使用Symfony,将其配置为服务以启用
# config/packages/zenstruck_console_extra.yaml services: Zenstruck\Console\EventListener\CommandSummarySubscriber: autoconfigure: true
注意
这将显示每次注册的命令运行后的摘要。