Playing with a Custom Console Command
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeLet's make our new console command sing! Start by giving it a better description: "Cast a random spell!":
// ... lines 1 - 11 | |
class RandomSpellCommand extends Command | |
{ | |
// ... lines 14 - 15 | |
protected function configure() | |
{ | |
$this | |
->setDescription('Cast a random spell!') | |
// ... lines 20 - 21 | |
; | |
} | |
// ... lines 24 - 41 | |
} |
For the arguments and options, these describe what you can pass to the command. Like, if we configured two arguments, then we could pass two things, like foo
and bar
after the command. The order of arguments is important. Options are things that start with --
. Some have values and some don't.
Configuring Arguments & Options
Anyways, let's add one argument called your-name
. The argument name is an internal key: you won't see that when using the command. Give it some docs: this is "your name". Let's also add an option called yell
so that users can type --yell
if they want us to scream the spell at them in uppercase:
// ... lines 1 - 11 | |
class RandomSpellCommand extends Command | |
{ | |
// ... lines 14 - 15 | |
protected function configure() | |
{ | |
$this | |
->setDescription('Cast a random spell!') | |
->addArgument('your-name', InputArgument::OPTIONAL, 'Your name') | |
->addOption('yell', null, InputOption::VALUE_NONE, 'Yell?') | |
; | |
} | |
// ... lines 24 - 41 | |
} |
There are more ways to configure this stuff - like you can make an argument optional or required or allow the --yell
flag to have a value... but you get the idea.
Executing the Command
When someone calls our command, Symfony will run execute()
. Let's rename the variable to $yourName
and read out the your-name
argument:
// ... lines 1 - 11 | |
class RandomSpellCommand extends Command | |
{ | |
// ... lines 14 - 24 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
$io = new SymfonyStyle($input, $output); | |
$yourName = $input->getArgument('your-name'); | |
// ... lines 29 - 40 | |
} | |
} |
So if the user passes a first argument, we're going to get it here and then, if we have a name, let's say Hi!
and then $yourName
:
// ... lines 1 - 11 | |
class RandomSpellCommand extends Command | |
{ | |
// ... lines 14 - 24 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
$io = new SymfonyStyle($input, $output); | |
$yourName = $input->getArgument('your-name'); | |
if ($yourName) { | |
$io->note(sprintf('Hi %s!', $yourName)); | |
} | |
// ... lines 33 - 40 | |
} | |
} |
Cool! For the random spell part, I'll paste some code to get it:
// ... lines 1 - 11 | |
class RandomSpellCommand extends Command | |
{ | |
// ... lines 14 - 24 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
$io = new SymfonyStyle($input, $output); | |
$yourName = $input->getArgument('your-name'); | |
if ($yourName) { | |
$io->note(sprintf('Hi %s!', $yourName)); | |
} | |
$spells = [ | |
'alohomora', | |
'confundo', | |
'engorgio', | |
'expecto patronum', | |
'expelliarmus', | |
'impedimenta', | |
'reparo', | |
]; | |
$spell = $spells[array_rand($spells)]; | |
// ... lines 45 - 52 | |
} | |
} |
Let's check to see if the user passed a --yell
flag: if we have a yell
option, then $spell = strtoupper($spell)
:
// ... lines 1 - 11 | |
class RandomSpellCommand extends Command | |
{ | |
// ... lines 14 - 24 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
$io = new SymfonyStyle($input, $output); | |
$yourName = $input->getArgument('your-name'); | |
if ($yourName) { | |
$io->note(sprintf('Hi %s!', $yourName)); | |
} | |
$spells = [ | |
// ... lines 35 - 41 | |
]; | |
$spell = $spells[array_rand($spells)]; | |
if ($input->getOption('yell')) { | |
$spell = strtoupper($spell); | |
} | |
// ... lines 49 - 52 | |
} | |
} |
Finally, we can use the $io
variable to output the spell. This is an instance of SymfonyStyle
: it's basically a set or shortcuts for rendering things in a nice way, asking the user questions, printing tables and a lot more. Let's say $io->success($spell)
:
// ... lines 1 - 11 | |
class RandomSpellCommand extends Command | |
{ | |
// ... lines 14 - 24 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
$io = new SymfonyStyle($input, $output); | |
$yourName = $input->getArgument('your-name'); | |
if ($yourName) { | |
$io->note(sprintf('Hi %s!', $yourName)); | |
} | |
$spells = [ | |
// ... lines 35 - 41 | |
]; | |
$spell = $spells[array_rand($spells)]; | |
if ($input->getOption('yell')) { | |
$spell = strtoupper($spell); | |
} | |
$io->success($spell); | |
return 0; | |
} | |
} |
Done! Let's try it! Back at your terminal, start by running the command, but with a --help
option:
php bin/console app:random-spell --help
This tells us everything about our command: the argument, --yell
option and a bunch of other options that are built into every command. Try the command with no flags:
php bin/console app:random-spell
Hello random spell! Try with a name argument:
php bin/console app:random-spell Ryan
Hi command! And of course we can pass --yell
php bin/console app:random-spell Ryan --yell
to tell it to scream at us.
There are many more fun things you can do with a command, like printing lists, progress bars, asking users questions with auto-complete and more. You'll have no problems figuring that stuff out.
Commands are Services
Oh, but I do want to mention one more thing: a command is a good normal, boring service. I love that! So what if we needed to access another service from within a command? Like a database connection? Well... it's the same process as always. Let's log something.
Add a public function __construct
with one argument LoggerInterface $logger
:
// ... lines 1 - 4 | |
use Psr\Log\LoggerInterface; | |
// ... lines 6 - 12 | |
class RandomSpellCommand extends Command | |
{ | |
// ... lines 15 - 17 | |
public function __construct(LoggerInterface $logger) | |
{ | |
// ... lines 20 - 22 | |
} | |
// ... lines 24 - 64 | |
} |
I'll use my new PhpStorm shortcut - actually I need to hit Escape first - then press Alt
+Enter
and go to "Initialize properties" to create that property and set it:
// ... lines 1 - 12 | |
class RandomSpellCommand extends Command | |
{ | |
// ... line 15 | |
private $logger; | |
public function __construct(LoggerInterface $logger) | |
{ | |
$this->logger = $logger; | |
// ... lines 21 - 22 | |
} | |
// ... lines 24 - 64 | |
} |
But there is one unique thing with commands. The parent Command
class has its own constructor, which we need to call. Call parent::__construct()
: we don't need to pass any arguments to it:
// ... lines 1 - 12 | |
class RandomSpellCommand extends Command | |
{ | |
// ... lines 15 - 17 | |
public function __construct(LoggerInterface $logger) | |
{ | |
$this->logger = $logger; | |
parent::__construct(); | |
} | |
// ... lines 24 - 64 | |
} |
I can't think of any other part of Symfony where this is required - it's a quirk of the command system. Anyways, right before we print the success message, say $this->logger->info()
with: "Casting spell" and then $spell
:
// ... lines 1 - 12 | |
class RandomSpellCommand extends Command | |
{ | |
// ... lines 15 - 33 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
// ... lines 36 - 52 | |
$spell = $spells[array_rand($spells)]; | |
if ($input->getOption('yell')) { | |
$spell = strtoupper($spell); | |
} | |
$this->logger->info('Casting spell: '.$spell); | |
// ... lines 60 - 63 | |
} | |
} |
Let's make sure this works! Run the command again.
php bin/console app:random-spell Ryan --yell
Cool, that still works. To see if it logged, we can check the log file directly:
tail var/log/dev.log
I'll move this up a bit... there it is!
Next, let's "make" one more thing with MakerBundle. We're going to create our own Twig filter so we can parse markdown through our caching system.
Can u tell me few ideas how can i use commands in my project? I cannot imagine how can I really use them