Fun with Commands
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 SubscribeTime to make our command a bit more fun! Give it a description: "Returns some article stats":
// ... lines 1 - 11 | |
class ArticleStatsCommand extends Command | |
{ | |
// ... lines 14 - 15 | |
protected function configure() | |
{ | |
$this | |
->setDescription('Returns some article stats!') | |
// ... lines 20 - 21 | |
; | |
} | |
// ... lines 24 - 45 | |
} |
Each command can have arguments - which are strings passed after the command and options, which are prefixed with --
, like --option1
:
php bin/console article:stats arg1 arg2 --option1 --opt2=khan
Rename the argument to slug
, change it to InputArgument::REQUIRED
- which means that you must pass this argument to the command, and give it a description: "The article's slug":
// ... lines 1 - 11 | |
class ArticleStatsCommand extends Command | |
{ | |
// ... lines 14 - 15 | |
protected function configure() | |
{ | |
$this | |
->setDescription('Returns some article stats!') | |
->addArgument('slug', InputArgument::OPTIONAL, 'The article\'s slug') | |
// ... line 21 | |
; | |
} | |
// ... lines 24 - 45 | |
} |
Rename the option to format
: I want to be able to say --format=json
to get the article stats as JSON. Change this to VALUE_REQUIRED
: instead of just --format
, this means we need to say --format=something
. Update its description, and give it a default value: text
:
// ... lines 1 - 11 | |
class ArticleStatsCommand extends Command | |
{ | |
// ... lines 14 - 15 | |
protected function configure() | |
{ | |
$this | |
->setDescription('Returns some article stats!') | |
->addArgument('slug', InputArgument::OPTIONAL, 'The article\'s slug') | |
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'text') | |
; | |
} | |
// ... lines 24 - 45 | |
} |
Perfect! We're not using these options yet, but we can already go back and run the command with a --help
flag:
php bin/console article:stats --help
Actually, you can add --help
to any command to get all the info about it - like the description, arguments and options... including a bunch of options that apply to all commands.
Customizing our Command
Ok, so the configure()
method is where we set things up. But execute()
is where the magic happens. We can do whatever we want here!
To get the argument value, update the getArgument()
call to slug
and rename the variable too:
// ... lines 1 - 11 | |
class ArticleStatsCommand extends Command | |
{ | |
// ... lines 14 - 24 | |
protected function execute(InputInterface $input, OutputInterface $output) | |
{ | |
$io = new SymfonyStyle($input, $output); | |
$slug = $input->getArgument('slug'); | |
// ... lines 29 - 44 | |
} | |
} |
Let's just invent some article "data": give this array a slug
key and, how about, hearts
set to a random number between 10 and 100:
// ... lines 1 - 11 | |
class ArticleStatsCommand extends Command | |
{ | |
// ... lines 14 - 24 | |
protected function execute(InputInterface $input, OutputInterface $output) | |
{ | |
$io = new SymfonyStyle($input, $output); | |
$slug = $input->getArgument('slug'); | |
$data = [ | |
'slug' => $slug, | |
'hearts' => rand(10, 100), | |
]; | |
// ... lines 34 - 44 | |
} | |
} |
Clear out the rest of the code, and then add a switch
statement on $input->getOption('format')
. Here's the plan: we're going to support two different formats: text
- don't forget the break
- and json
:
// ... lines 1 - 11 | |
class ArticleStatsCommand extends Command | |
{ | |
// ... lines 14 - 24 | |
protected function execute(InputInterface $input, OutputInterface $output) | |
{ | |
$io = new SymfonyStyle($input, $output); | |
$slug = $input->getArgument('slug'); | |
$data = [ | |
'slug' => $slug, | |
'hearts' => rand(10, 100), | |
]; | |
switch ($input->getOption('format')) { | |
case 'text': | |
// ... line 37 | |
break; | |
case 'json': | |
// ... line 40 | |
break; | |
// ... lines 42 - 43 | |
} | |
} | |
} |
If someone tries to use a different format, yell at them!
What kind of crazy format is that?
// ... lines 1 - 11 | |
class ArticleStatsCommand extends Command | |
{ | |
// ... lines 14 - 24 | |
protected function execute(InputInterface $input, OutputInterface $output) | |
{ | |
$io = new SymfonyStyle($input, $output); | |
$slug = $input->getArgument('slug'); | |
$data = [ | |
'slug' => $slug, | |
'hearts' => rand(10, 100), | |
]; | |
switch ($input->getOption('format')) { | |
case 'text': | |
// ... line 37 | |
break; | |
case 'json': | |
// ... line 40 | |
break; | |
default: | |
throw new \Exception('What kind of crazy format is that!?'); | |
} | |
} | |
} |
Printing Things
Notice that execute()
has two arguments: $input
and $output
:
// ... lines 1 - 6 | |
use Symfony\Component\Console\Input\InputInterface; | |
// ... line 8 | |
use Symfony\Component\Console\Output\OutputInterface; | |
// ... lines 10 - 11 | |
class ArticleStatsCommand extends Command | |
{ | |
// ... lines 14 - 24 | |
protected function execute(InputInterface $input, OutputInterface $output) | |
{ | |
// ... lines 27 - 44 | |
} | |
} |
Input lets us read arguments and options. And, you can even use it to ask questions interactively. $output
is all about printing things. To make both of these even easier to use, we have a special SymfonyStyle
object that's full of shortcut methods:
// ... lines 1 - 11 | |
class ArticleStatsCommand extends Command | |
{ | |
// ... lines 14 - 24 | |
protected function execute(InputInterface $input, OutputInterface $output) | |
{ | |
$io = new SymfonyStyle($input, $output); | |
// ... lines 28 - 44 | |
} | |
} |
For example, to print a list of things, just say $io->listing()
and pass the array:
// ... lines 1 - 11 | |
class ArticleStatsCommand extends Command | |
{ | |
// ... lines 14 - 24 | |
protected function execute(InputInterface $input, OutputInterface $output) | |
{ | |
$io = new SymfonyStyle($input, $output); | |
// ... lines 28 - 34 | |
switch ($input->getOption('format')) { | |
case 'text': | |
$io->listing($data); | |
break; | |
// ... lines 39 - 43 | |
} | |
} | |
} |
For json
, to print raw text, use $io->write()
- then json_encode($data)
:
// ... lines 1 - 11 | |
class ArticleStatsCommand extends Command | |
{ | |
// ... lines 14 - 24 | |
protected function execute(InputInterface $input, OutputInterface $output) | |
{ | |
$io = new SymfonyStyle($input, $output); | |
// ... lines 28 - 34 | |
switch ($input->getOption('format')) { | |
case 'text': | |
$io->listing($data); | |
break; | |
case 'json': | |
$io->write(json_encode($data)); | |
break; | |
// ... lines 42 - 43 | |
} | |
} | |
} |
And... we're done! Let's try this out! Find your terminal and run:
php bin/console article:stats khaaaaaan
Nice! And now pass --format=json
:
php bin/console article:stats khaaaaaan --format=json
Woohoo!
Printing a Table
But... this listing isn't very helpful: it just prints out the values, not the keys. The article has 88... what?
Instead of using listing, let's create a table.
Start with an empty $rows
array. Now loop over the data as $key => $val
and start adding rows with $key
and $val
:
// ... lines 1 - 11 | |
class ArticleStatsCommand extends Command | |
{ | |
// ... lines 14 - 24 | |
protected function execute(InputInterface $input, OutputInterface $output) | |
{ | |
$io = new SymfonyStyle($input, $output); | |
// ... lines 28 - 34 | |
switch ($input->getOption('format')) { | |
case 'text': | |
$rows = []; | |
foreach ($data as $key => $val) { | |
$rows[] = [$key, $val]; | |
} | |
// ... line 41 | |
break; | |
// ... lines 43 - 47 | |
} | |
} | |
} |
We're doing this because the SymfonyStyle object has an awesome method called ->table()
. Pass it an array of headers - Key
and Value
, then $rows
:
// ... lines 1 - 5 | |
use Symfony\Component\Console\Input\InputArgument; | |
// ... lines 7 - 11 | |
class ArticleStatsCommand extends Command | |
{ | |
// ... lines 14 - 24 | |
protected function execute(InputInterface $input, OutputInterface $output) | |
{ | |
$io = new SymfonyStyle($input, $output); | |
// ... lines 28 - 34 | |
switch ($input->getOption('format')) { | |
case 'text': | |
$rows = []; | |
foreach ($data as $key => $val) { | |
$rows[] = [$key, $val]; | |
} | |
$io->table(['Key', 'Value'], $rows); | |
break; | |
// ... lines 43 - 47 | |
} | |
} | |
} |
Let's rock! Try the command again without the --format
option:
php bin/console article:stats khaaaaaan
Yes! So much better! And yea, that $io
variable has a bunch of other features, like interactive questions, a progress bar and more. Not only are commands fun, but they're super easy to create thanks to MakerBundle.
Oh my gosh, you did it! You made it through Symfony Fundamentals! This was serious work that will seriously unlock you for everything else you do with Symfony! We now understand the configuration system and - most importantly - services. Guess what? Commands are services. So if you needed your SlackClient
service, you would just add a __construct()
method and autowire it!
Tip
When you do this, you need to call parent::__construct()
. Commands are a rare
case where there is a parent constructor!
With our new knowledge, let's keep going and start mastering features, like the Doctrine ORM, form system, API stuff and a lot more.
Alright guys, seeya next time!
Thnk you !