A modular set of features to reduce configuration boilerplate for your commands:
/**
* Creates a user in the database.
*
* @command create:user email password --r|role[]
*/
final class CreateUserCommand extends InvokableServiceCommand
{
use ConfigureWithDocblocks;
public function __invoke(IO $io, UserRepository $repo): void
{
$repo->createUser($io->argument('email'), $io->argument('password'), $io->option('role'));
$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
This library is a set of modular features that can be used separately or in combination.
TIP: To reduce command boilerplate even further, it is recommended to create an abstract base command for your app that enables all the features you desire. Then have all your app's commands extend this.
This is a helper object that extends SymfonyStyle
and implements InputInterface
(so it implements
InputInterface
, OutputInterface
, and 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
On its own, it isn't very special, but it can be auto-injected into Invokable
commands.
Use this trait to remove the need for extending Command::execute()
and just inject what your need (ie IO
)
into your command's __invoke()
method.
use Symfony\Component\Console\Command\Command;
use Zenstruck\Console\Invokable;
use Zenstruck\Console\IO;
class MyCommand extends \Symfony\Component\Console\Command\Command
{
use Invokable;
public function __invoke(IO $io)
{
$role = $io->option('role');
$io->success('created.');
}
}
You can auto-inject the "raw" input/output:
public function __invoke(IO $io, InputInterface $input, OutputInterface $output)
No return type (or void
) implies a 0
status code. You can return an integer if you want to change this:
public function __invoke(IO $io): int
{
return $success ? 0 : 1;
}
If using the Symfony Framework, you can take Invokable
to the next level by auto-injecting services
into __invoke()
. This allows your commands to behave like
Invokable Service Controllers
(with controller.service_arguments
). Instead of a Request, you inject IO
.
Have your commands extend InvokableServiceCommand
and ensure they are auto-wired/configured.
use App\Repository\UserRepository;
use Psr\Log\LoggerInterface;
use Zenstruck\Console\InvokableServiceCommand;
use Zenstruck\Console\IO;
class CreateUserCommand extends InvokableServiceCommand
{
public function __invoke(IO $io, UserRepository $repo, LoggerInterface $logger): void
{
// ...
}
}
Use this trait to have your command's name auto-generated from the class name:
use Symfony\Component\Console\Command\Command;
use Zenstruck\Console\AutoName;
class CreateUserCommand extends Command
{
use AutoName; // command's name will be "app:create-user"
}
Use this trait to allow your command to be configured by your command class' docblock.
phpdocumentor/reflection-docblock
is required for this feature
(composer install phpdocumentor/reflection-docblock
).
Example:
use Symfony\Component\Console\Command\Command;
use Zenstruck\Console\ConfigureWithDocblocks;
/**
* This is the command's description.
*
* This is the command help
*
* Multiple
*
* lines allowed.
*
* @command my:command
* @alias alias1
* @alias alias2
* @hidden
*
* @argument arg1 First argument is required (this is the argument's "description")
* @argument ?arg2 Second argument is optional
* @argument arg3=default Third argument is optional with a default value
* @argument arg4="default with space" Forth argument is "optional" with a default value (with spaces)
* @argument ?arg5[] Fifth argument is an optional array
*
* @option option1 First option (no value) (this is the option's "description")
* @option option2= Second option (value required)
* @option option3=default Third option with default value
* @option option4="default with space" Forth option with "default" value (with spaces)
* @option o|option5[] Fifth option is an array with a shortcut (-o)
*/
class MyCommand extends Command
{
use ConfigureWithDocblocks;
}
NOTES:
- If the
@command
tag is absent, AutoName is used. - All the configuration can be disabled by using the traditional methods of configuring your command.
- Command's are still lazy using this method of configuration but there is overhead in parsing the docblocks so be aware of this.
You can pack all the above into a single @command
tag. This can act like routing for your console:
/**
* @command |app:my:command|alias1|alias2 arg1 ?arg2 arg3=default arg4="default with space" ?arg5[] --option1 --option2= --option3=default --option4="default with space" --o|option5[]
*/
class MyCommand extends Command
{
use ConfigureWithDocblocks;
}
NOTES:
- The
|
prefix makes the command hidden. - Argument/Option descriptions are not allowed.
TIP: It is recommended to only do this for very simple commands as it isn't as explicit as splitting the tags out.
Add this event subscriber to your Application
's event dispatcher to display a summary after every command is run.
The summary includes the duration of the command and peak memory usage.
If using Symfony, configure it as a service to enable:
# config/packages/zenstruck_console_extra.yaml
Zenstruck\Console\EventListener\CommandSummarySubscriber:
autoconfigure: true
NOTE: This will display a summary after every registered command runs.