This article was peer reviewed by Wern Ancheta. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
It’s undeniable how useful console commands can be when developing software. Not too long ago we re-introduced the Symfony Console component.
This component allows us to create structured and testable CLI commands. We created some simple commands and tested them; but when our commands become bigger and more complex, we need a different set of tools.
This is what we are going to look at today: advanced Symfony console tools.
Let’s create a command that we can use to show some of these features. Most of the basic functionality was shown in the re-introduction to the Symfony console article, so be sure to check it before advancing – it’s a quick but useful read!
<span>composer require symfony/console </span>
Essential information about Composer can be found here, and if you’re not familiar with well designed isolated PHP environments in which to develop your PHP apps like Vagrant, we have a fantastic book explaining it all in depth available for purchase here.
Let’s create a command for an all time favorite: Fizzbuzz.
Fizzbuzz is a simple problem often used in programming interviews to assert the programming competence of the interviewee. The definition of Fizzbuzz normally comes in the following form:
Write a program that prints the numbers from 1 to x. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five, print “FizzBuzz”.
Our command will receive an argument which will be the top limit for Fizzbuzz.
First of all, let’s create our Fizzbuzz class.
<span>composer require symfony/console </span>
Pretty straightforward. The firstNFizzbuzz() method prints the results of Fizzbuzz for a $maxValue of numbers. It does this by calling the calculateFizzBuzz() method recursively.
Next, let’s write our command. Create a FizzCommand.php file with the following contents:
<span><span><?php </span></span><span><span>declare(strict_types=1); </span></span><span> </span><span><span>namespace FizzBuzz; </span></span><span> </span><span><span>class Fizzbuzz{ </span></span><span> </span><span> <span>public function isFizz(int $value): bool{ </span></span><span> <span>if($value % 3 === 0){ </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> <span>return false; </span></span><span> <span>} </span></span><span> </span><span> <span>public function isBuzz(int $value): bool{ </span></span><span> <span>if($value % 5 === 0){ </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> <span>return false; </span></span><span> <span>} </span></span><span> </span><span> <span>public function calculateFizzBuzz(int $number): bool{ </span></span><span> <span>if($this->isFizz($number) && $this->isBuzz($number)){ </span></span><span> <span>echo "FizzBuzz \n"; </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> <span>if($this->isFizz($number)){ </span></span><span> <span>echo "Fizz \n"; </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> <span>if($this->isBuzz($number)){ </span></span><span> <span>echo "Buzz \n"; </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> <span>echo $number . "\n"; </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> </span><span> <span>public function firstNFizzbuzz(int $maxValue): void{ </span></span><span> <span>$startValue = 1; </span></span><span> </span><span> <span>while($startValue <= $maxValue){ </span></span><span> <span>$this->calculateFizzBuzz($startValue); </span></span><span> <span>$startValue++; </span></span><span> <span>} </span></span><span> <span>} </span></span><span><span>} </span></span>
And finally our console file.
<span><span><?php </span></span><span> </span><span><span>namespace FizzBuzz; </span></span><span> </span><span><span>use Symfony<span>\Component\Console\Command\Command</span>; </span></span><span><span>use Symfony<span>\Component\Console\Input\InputInterface</span>; </span></span><span><span>use Symfony<span>\Component\Console\Output\OutputInterface</span>; </span></span><span><span>use Symfony<span>\Component\Console\Input\InputArgument</span>; </span></span><span> </span><span><span>use FizzBuzz<span>\Fizzbuzz</span>; </span></span><span> </span><span><span>class FizzCommand extends Command{ </span></span><span> </span><span> <span>protected function configure(){ </span></span><span> <span>$this->setName("FizzBuzz:FizzBuzz") </span></span><span> <span>->setDescription("Runs Fizzbuzz") </span></span><span> <span>->addArgument('Limit', InputArgument<span>::</span>REQUIRED, 'What is the limit you wish for Fizzbuzz?'); </span></span><span> <span>} </span></span><span> </span><span> <span>protected function execute(InputInterface $input, OutputInterface $output){ </span></span><span> </span><span> <span>$fizzy = new FizzBuzz(); </span></span><span> <span>$input = $input->getArgument('Limit'); </span></span><span> </span><span> <span>$result = $fizzy->firstNFizzbuzz($input); </span></span><span> <span>} </span></span><span> </span><span><span>} </span></span>
Here we create a new Console Application and register our FizzCommand() into it. Don’t forget to make this file executable.
We can now check if our command is correctly registered by running the ./console command. We can also execute our command with ./console FizzBuzz:Fizzbuzz 25. This will calculate and print the Fizzbuzz results from 1 to 25.
Up until now, we haven’t done anything new. But there are a couple of ways we can improve our command. First of all, the command is not very intuitive. How do we know that we have to pass the limit to the command? For that, the Symfony Console offers us the Question helper.
The Question helper provides functionality to ask the user for more information. This way we can interactively collect information for the execution of our commands.
Let’s change our command to, instead of receiving a limit of execution through the command execution prompt, ask the user for a limit. For that, the question helper has a single method: ask(). This method receives as arguments an InputInterface, an OutputInterface and a question.
Let’s change the FizzCommand.php file so it looks like this:
#!/usr/bin/env php <span><span><?php </span></span><span> </span><span><span>require_once __DIR__ . '/vendor/autoload.php'; </span></span><span> </span><span><span>use Symfony<span>\Component\Console\Application</span>; </span></span><span><span>use FizzBuzz<span>\FizzCommand</span>; </span></span><span> </span><span><span>$app = new Application(); </span></span><span><span>$app->add(new FizzCommand()); </span></span><span><span>$app->run(); </span></span>
We no longer expect an argument on the configure() method. We instantiate a new Question with a default of 25 and use it on the ask() method we talked about earlier.
Now we have an interactive command that asks for a limit before executing Fizzbuzz.
The question helper also gives us functionality to validate the answers. So let’s use it to make sure the limit is an integer.
<span><span><?php </span></span><span> </span><span><span>namespace FizzBuzz; </span></span><span> </span><span><span>use Symfony<span>\Component\Console\Command\Command</span>; </span></span><span><span>use Symfony<span>\Component\Console\Input\InputInterface</span>; </span></span><span><span>use Symfony<span>\Component\Console\Output\OutputInterface</span>; </span></span><span><span>use Symfony<span>\Component\Console\Input\InputArgument</span>; </span></span><span><span>use Symfony<span>\Component\Console\Question\Question</span>; </span></span><span> </span><span><span>use FizzBuzz<span>\Fizzbuzz</span>; </span></span><span> </span><span><span>class FizzCommand extends Command{ </span></span><span> </span><span> <span>protected function configure(){ </span></span><span> <span>$this->setName("FizzBuzz:FizzBuzz") </span></span><span> <span>->setDescription("Runs Fizzbuzz"); </span></span><span> <span>} </span></span><span> </span><span> <span>protected function execute(InputInterface $input, OutputInterface $output){ </span></span><span> </span><span> <span>$fizzy = new FizzBuzz(); </span></span><span> </span><span> <span>$helper = $this->getHelper('question'); </span></span><span> <span>$question = new Question('Please select a limit for this execution: ', 25); </span></span><span> <span>$limit = $helper->ask($input, $output, $question); </span></span><span> </span><span> <span>$result = $fizzy->firstNFizzbuzz($limit); </span></span><span> <span>} </span></span><span><span>} </span></span>
Not only are we making sure that our limit is an integer by using the setValidator() function, we are also normalizing the input in case the user inserts some blank spaces and also setting the maximum amount of attempts permitted to two.
The question helper offers a lot more functionality like letting the user choose from a list of answers, multiple answers, hiding the user answer, and autocompletion. The official documentation has a lot more information on that.
Another very useful function provided by the console component is the possibility to display tabular data.
To display a table we need to use the Table class; set the headers and rows, and finally render the table. This can be very useful when it comes to showing structured data. Let’s imagine we want to create a command to show the conversions for some metric systems.
Let’s add MetricsCommand.php to our new php file.
<span>protected function execute(InputInterface $input, OutputInterface $output){ </span> <span>$fizzy = new FizzBuzz(); </span> <span>$helper = $this->getHelper('question'); </span> <span>$question = new Question('Please select a limit for this execution: ', 25); </span> <span>$question->setValidator(function ($answer) { </span> <span>if (!is_numeric($answer)) { </span> <span>throw new <span>\RuntimeException</span>('The limit should be an integer.'); </span> <span>} </span> <span>return $answer; </span> <span>}); </span> <span>$question->setNormalizer(function ($value) { </span> <span>return $value ? trim($value) : ''; </span> <span>}); </span> <span>$question->setMaxAttempts(2); </span> <span>$limit = $helper->ask($input, $output, $question); </span> <span>$result = $fizzy->firstNFizzbuzz($limit); </span> <span>} </span>
And our new console file:
<span>composer require symfony/console </span>
It’s a very simple command: it renders a table with some values converted from inches to centimeters. If we run our command using ./console Metrics, the result will be something like this:
The Table class also offers us different separator styles for our tables. Check this page if you want to know more. .
While questions and tables can be very useful, the most important element might be the progress bar. Progress bars give us feedback about the execution of the command and allow us to have a clear view of how long we might have to wait for an operation to finish.
Progress bars are essential for longer running commands. To use them, we need the ProgressBar, pass it a total number of units (if we actually know how many units we expect) and advance it as the command executes.
A simple command with a progress bar may look like this:
<span><span><?php </span></span><span><span>declare(strict_types=1); </span></span><span> </span><span><span>namespace FizzBuzz; </span></span><span> </span><span><span>class Fizzbuzz{ </span></span><span> </span><span> <span>public function isFizz(int $value): bool{ </span></span><span> <span>if($value % 3 === 0){ </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> <span>return false; </span></span><span> <span>} </span></span><span> </span><span> <span>public function isBuzz(int $value): bool{ </span></span><span> <span>if($value % 5 === 0){ </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> <span>return false; </span></span><span> <span>} </span></span><span> </span><span> <span>public function calculateFizzBuzz(int $number): bool{ </span></span><span> <span>if($this->isFizz($number) && $this->isBuzz($number)){ </span></span><span> <span>echo "FizzBuzz \n"; </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> <span>if($this->isFizz($number)){ </span></span><span> <span>echo "Fizz \n"; </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> <span>if($this->isBuzz($number)){ </span></span><span> <span>echo "Buzz \n"; </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> <span>echo $number . "\n"; </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> </span><span> <span>public function firstNFizzbuzz(int $maxValue): void{ </span></span><span> <span>$startValue = 1; </span></span><span> </span><span> <span>while($startValue <= $maxValue){ </span></span><span> <span>$this->calculateFizzBuzz($startValue); </span></span><span> <span>$startValue++; </span></span><span> <span>} </span></span><span> <span>} </span></span><span><span>} </span></span>
And the respective console:
<span><span><?php </span></span><span> </span><span><span>namespace FizzBuzz; </span></span><span> </span><span><span>use Symfony<span>\Component\Console\Command\Command</span>; </span></span><span><span>use Symfony<span>\Component\Console\Input\InputInterface</span>; </span></span><span><span>use Symfony<span>\Component\Console\Output\OutputInterface</span>; </span></span><span><span>use Symfony<span>\Component\Console\Input\InputArgument</span>; </span></span><span> </span><span><span>use FizzBuzz<span>\Fizzbuzz</span>; </span></span><span> </span><span><span>class FizzCommand extends Command{ </span></span><span> </span><span> <span>protected function configure(){ </span></span><span> <span>$this->setName("FizzBuzz:FizzBuzz") </span></span><span> <span>->setDescription("Runs Fizzbuzz") </span></span><span> <span>->addArgument('Limit', InputArgument<span>::</span>REQUIRED, 'What is the limit you wish for Fizzbuzz?'); </span></span><span> <span>} </span></span><span> </span><span> <span>protected function execute(InputInterface $input, OutputInterface $output){ </span></span><span> </span><span> <span>$fizzy = new FizzBuzz(); </span></span><span> <span>$input = $input->getArgument('Limit'); </span></span><span> </span><span> <span>$result = $fizzy->firstNFizzbuzz($input); </span></span><span> <span>} </span></span><span> </span><span><span>} </span></span>
This a very simple command. We set up the bar and loop through a sleep() function. The final output will look like this:
More information on progress bars can be found in the official documentation.
Customizing progress bars can be useful to provide extra information while the user waits.
By default, the information shown in the progress bar depends on the level of verbosity of the OutputInterface instance. So, if we want to show different levels of information we can use the setFormat() method.
#!/usr/bin/env php <span><span><?php </span></span><span> </span><span><span>require_once __DIR__ . '/vendor/autoload.php'; </span></span><span> </span><span><span>use Symfony<span>\Component\Console\Application</span>; </span></span><span><span>use FizzBuzz<span>\FizzCommand</span>; </span></span><span> </span><span><span>$app = new Application(); </span></span><span><span>$app->add(new FizzCommand()); </span></span><span><span>$app->run(); </span></span>
The built-in formats are: normal, verbose, very_verbose and debug.
If we use use normal format for example, the result will look like this:
We can also set our own format.
The progress bar is a string that’s composed of different specific placeholders. We can combine those specific placeholders to create our own progress bars. The available placeholders are: current, max, bar, percent, elapsed, remaining, estimated, memory and message. So if, for instance, we wanted to copy the exact same default progress bar, we could use the following:
<span><span><?php </span></span><span> </span><span><span>namespace FizzBuzz; </span></span><span> </span><span><span>use Symfony<span>\Component\Console\Command\Command</span>; </span></span><span><span>use Symfony<span>\Component\Console\Input\InputInterface</span>; </span></span><span><span>use Symfony<span>\Component\Console\Output\OutputInterface</span>; </span></span><span><span>use Symfony<span>\Component\Console\Input\InputArgument</span>; </span></span><span><span>use Symfony<span>\Component\Console\Question\Question</span>; </span></span><span> </span><span><span>use FizzBuzz<span>\Fizzbuzz</span>; </span></span><span> </span><span><span>class FizzCommand extends Command{ </span></span><span> </span><span> <span>protected function configure(){ </span></span><span> <span>$this->setName("FizzBuzz:FizzBuzz") </span></span><span> <span>->setDescription("Runs Fizzbuzz"); </span></span><span> <span>} </span></span><span> </span><span> <span>protected function execute(InputInterface $input, OutputInterface $output){ </span></span><span> </span><span> <span>$fizzy = new FizzBuzz(); </span></span><span> </span><span> <span>$helper = $this->getHelper('question'); </span></span><span> <span>$question = new Question('Please select a limit for this execution: ', 25); </span></span><span> <span>$limit = $helper->ask($input, $output, $question); </span></span><span> </span><span> <span>$result = $fizzy->firstNFizzbuzz($limit); </span></span><span> <span>} </span></span><span><span>} </span></span>
There’s a lot more to customizing progress bars – read about it here.
Another very useful feature to have is the ability to run a command inside a command. For example, we might have a command which depends on another command to successfully run, or a succession of commands we might want to run in a sequence.
For example, imagine we wanted to create a command to run our fizzbuzz command. We would need to create a new command inside our /src folder and inside the execute() method, have the following:
<span>composer require symfony/console </span>
Since our FizzBuzz command doesn’t receive any arguments, that would be enough. In case our command needed arguments we would have to create an array of arguments and use the ArrayInput class to pass them.
Other than that it’s all about using the find() method with our command name to find and register our command.
Coloring and styling the output can be useful for alerting or informing the user about something in the command’s execution. For that, we just need to add the following tags to our writeln() method, just like the following:
<span><span><?php </span></span><span><span>declare(strict_types=1); </span></span><span> </span><span><span>namespace FizzBuzz; </span></span><span> </span><span><span>class Fizzbuzz{ </span></span><span> </span><span> <span>public function isFizz(int $value): bool{ </span></span><span> <span>if($value % 3 === 0){ </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> <span>return false; </span></span><span> <span>} </span></span><span> </span><span> <span>public function isBuzz(int $value): bool{ </span></span><span> <span>if($value % 5 === 0){ </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> <span>return false; </span></span><span> <span>} </span></span><span> </span><span> <span>public function calculateFizzBuzz(int $number): bool{ </span></span><span> <span>if($this->isFizz($number) && $this->isBuzz($number)){ </span></span><span> <span>echo "FizzBuzz \n"; </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> <span>if($this->isFizz($number)){ </span></span><span> <span>echo "Fizz \n"; </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> <span>if($this->isBuzz($number)){ </span></span><span> <span>echo "Buzz \n"; </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> <span>echo $number . "\n"; </span></span><span> <span>return true; </span></span><span> <span>} </span></span><span> </span><span> <span>public function firstNFizzbuzz(int $maxValue): void{ </span></span><span> <span>$startValue = 1; </span></span><span> </span><span> <span>while($startValue <= $maxValue){ </span></span><span> <span>$this->calculateFizzBuzz($startValue); </span></span><span> <span>$startValue++; </span></span><span> <span>} </span></span><span> <span>} </span></span><span><span>} </span></span>
There’s also the option to define our own styles using the OutputFormatterStyle class:
<span><span><?php </span></span><span> </span><span><span>namespace FizzBuzz; </span></span><span> </span><span><span>use Symfony<span>\Component\Console\Command\Command</span>; </span></span><span><span>use Symfony<span>\Component\Console\Input\InputInterface</span>; </span></span><span><span>use Symfony<span>\Component\Console\Output\OutputInterface</span>; </span></span><span><span>use Symfony<span>\Component\Console\Input\InputArgument</span>; </span></span><span> </span><span><span>use FizzBuzz<span>\Fizzbuzz</span>; </span></span><span> </span><span><span>class FizzCommand extends Command{ </span></span><span> </span><span> <span>protected function configure(){ </span></span><span> <span>$this->setName("FizzBuzz:FizzBuzz") </span></span><span> <span>->setDescription("Runs Fizzbuzz") </span></span><span> <span>->addArgument('Limit', InputArgument<span>::</span>REQUIRED, 'What is the limit you wish for Fizzbuzz?'); </span></span><span> <span>} </span></span><span> </span><span> <span>protected function execute(InputInterface $input, OutputInterface $output){ </span></span><span> </span><span> <span>$fizzy = new FizzBuzz(); </span></span><span> <span>$input = $input->getArgument('Limit'); </span></span><span> </span><span> <span>$result = $fizzy->firstNFizzbuzz($input); </span></span><span> <span>} </span></span><span> </span><span><span>} </span></span>
More information on styling the output can be found here.
From styling to helpers, we saw a lot of functionality that the Symfony console provides out of the box. After today, there’s absolutely no excuse to have badly documented command line tools!
Which helpers and components of the Console do you frequently use? How do you start your CLI tools? Is the Symfony Console enough for you, or do you prefer an alternative?
Symfony Console is a component of the Symfony PHP framework that provides a way to create command-line interfaces (CLI). Unlike other PHP console applications, Symfony Console offers a structured and object-oriented approach to building CLI applications. It provides a set of classes to define commands, handle input and output, and manage the application’s lifecycle. Additionally, Symfony Console supports color formatting, progress bars, tables, and other advanced console features, which are not commonly found in other PHP console applications.
Creating a custom command in Symfony Console involves extending the Command class and implementing the configure() and execute() methods. The configure() method is used to define the command name, arguments, and options, while the execute() method contains the logic of the command. Once the command class is created, it can be added to the application using the add() method.
Symfony Console provides the InputInterface and OutputInterface to handle input and output in a command. The InputInterface provides methods to get the command arguments and options, while the OutputInterface provides methods to write to the console. You can also use the InputArgument and InputOption classes to define the command arguments and options.
Helpers in Symfony Console are classes that provide additional functionality for commands. They can be accessed using the getHelper() method of the command. Symfony Console includes several built-in helpers, such as the QuestionHelper for interactive input, the ProgressBarHelper for progress bars, and the TableHelper for tabular data.
Symfony Console provides the CommandTester class to test commands. The CommandTester class allows you to execute a command with specific input and capture the output. You can then make assertions on the output to verify the command’s behavior.
Errors in Symfony Console can be handled using exceptions. If an error occurs during the execution of a command, you can throw an exception. Symfony Console will catch the exception and display an error message to the user.
The Symfony Console component is a standalone component, which means it can be used outside of the Symfony framework. You can install it using Composer and use it to build CLI applications in any PHP project.
Symfony Console provides several ways to customize the appearance of the console output. You can use color codes to change the text color, format codes to change the text style, and tags to create sections. You can also use the ProgressBar and Table classes to create progress bars and tables.
To create a console application with multiple commands, you can add multiple command classes to the application. Each command class should extend the Command class and implement the configure() and execute() methods. You can then use the add() method of the application to add the commands.
The Symfony Console component can be used to automate tasks by creating commands that perform specific tasks and running these commands from the command line or from a script. You can also schedule commands to run at specific intervals using a task scheduler like cron.
The above is the detailed content of Symfony Console Beyond the Basics - Helpers and Other Tools. For more information, please follow other related articles on the PHP Chinese website!