How to Create Custom CLI Commands Using the Symfony Console Component
In this article, we're going to explore how you could create custom command-line interface (CLI) commands in your PHP applications using the Symfony Console component. After installing the necessary libraries, we'll create a few examples to demonstrate the concepts of the Console component.
In fact, this component is used by several PHP frameworks to develop CLI applications, and a few popular frameworks are already using this component as a starting point.
What Is the Console Component?
The Symfony Console component allows you to create custom CLI commands in your PHP applications. If you have ever worked with Laravel or Symfony, you might be aware of the CLI tools they provide in order to ease day-to-day operations like:
- generating scaffolding code
- clearing caches
- installing, enabling, and disabling add-on services
- running database migrations
- and more
In the case of Laravel, for example, it comes with the artisan
tool which provides plenty of utility commands that make our life easier. You may be surprised to know that the artisan
tool is built on top of the Symfony Console component itself! In fact, there are many frameworks that leverage the Console component to build their command-line tools.
In this article, we're going to explore the basics of the Console component so that you can create custom CLI commands in your PHP applications. To start with, we'll install the Console component using Composer. After installation, we'll build a few examples for demonstration purposes.
Installation and Configuration
In this section, we're going to install the Console component, which is required in order to create CLI commands in your PHP applications. I assume that you've installed Composer in your system—we'll need it to install the Console component which is available from Packagist.
Once you've installed Composer, go ahead and install the Console component using the following command.
1 |
$composer require symfony/console
|
That should have created the composer.json file, which should look like this:
1 |
{
|
2 |
"require": { |
3 |
"symfony/console": "^5.4" |
4 |
}
|
5 |
}
|
Let's modify the composer.json file to look like the following one:
1 |
{
|
2 |
"require": { |
3 |
"symfony/console": "^5.4" |
4 |
},
|
5 |
"autoload": { |
6 |
"psr-4": { |
7 |
"Console\\": "src" |
8 |
},
|
9 |
"classmap": ["src"] |
10 |
}
|
11 |
}
|
As we've added a new classmap
entry, let's go ahead and update the Composer autoloader by running the following command.
1 |
$composer dump -o |
Now, you can use the Console
namespace to autoload classes under the src directory.
Your First HelloWorld Command
Creating CLI commands using the Console component is a two-step process.
- First, you need to create a console application which loads the necessary dependencies and registers your custom commands.
- Next, you need to create separate files for all the commands that you have registered with the console application.
Create the Console Application
In this section, we'll create our custom console application. The proposed directory structure of our console application looks like this.
1 |
|-- bin |
2 |
| `-- console |
3 |
|-- composer.json |
4 |
|-- composer.lock |
5 |
|-- src |
6 |
| `-- App |
7 |
| `-- Commands |
8 |
| |-- ClearcacheCommand.php |
9 |
| `-- HelloworldCommand.php |
10 |
`-- vendor |
Go ahead and create the main application file bin/console with the following contents. Please note that there's no file extension, and also make sure that it's executable as well since we'll need to run it from the command line.
1 |
#!/usr/bin/env php |
2 |
<?php
|
3 |
require_once __DIR__ . '/../vendor/autoload.php'; |
4 |
|
5 |
use Symfony\Component\Console\Application; |
6 |
|
7 |
$app = new Application(); |
8 |
$app->run(); |
The first line in the file #!/usr/bin/env php
makes sure that it's run under the PHP environment. Go ahead and try to run it and see how it goes.
1 |
$bin/console
|
2 |
Console Tool |
3 |
|
4 |
Usage: |
5 |
command [options] [arguments] |
6 |
|
7 |
Options: |
8 |
-h, --help Display help for the given command. When no command is given display help for the list command |
9 |
-q, --quiet Do not output any message |
10 |
-V, --version Display this application version |
11 |
--ansi|--no-ansi Force (or disable --no-ansi) ANSI output |
12 |
-n, --no-interaction Do not ask any interactive question |
13 |
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug |
14 |
|
15 |
Available commands: |
16 |
completion Dump the shell completion script |
17 |
help Display help for a command |
18 |
list List commands
|
Not bad! With just a few lines of code, you have a custom console application rolling at your disposal! But it's not doing anything useful at the moment. In the next section, we'll see how you can create custom commands and register them with our custom console application.
Create the Hello World Command File
Let's go ahead and create our first custom command: HelloworldCommand
. Create the src/App/Commands/HelloworldCommand.php file with the following contents.
1 |
<?php
|
2 |
namespace Console\App\Commands; |
3 |
|
4 |
use Symfony\Component\Console\Command\Command; |
5 |
use Symfony\Component\Console\Input\InputInterface; |
6 |
use Symfony\Component\Console\Output\OutputInterface; |
7 |
use Symfony\Component\Console\Input\InputArgument; |
8 |
|
9 |
class HelloworldCommand extends Command |
10 |
{
|
11 |
protected function configure() |
12 |
{
|
13 |
$this->setName('hello-world') |
14 |
->setDescription('Prints Hello-World!') |
15 |
->setHelp('Demonstration of custom commands created by Symfony Console component.') |
16 |
->addArgument('username', InputArgument::REQUIRED, 'Pass the username.'); |
17 |
}
|
18 |
|
19 |
protected function execute(InputInterface $input, OutputInterface $output) |
20 |
{
|
21 |
$output->writeln(sprintf('Hello World!, %s', $input->getArgument('username'))); |
22 |
return Command::SUCCESS; |
23 |
}
|
24 |
}
|
There are two main methods that you should create while creating your custom command: configure
and execute
.
As the name suggests, the configure
method allows you to configure your command so that you can set up the command name, a short description of the command, help text, and more. You can also configure arguments for your command if you want to pass parameters while running a command.
In the above example, the command name is set to hello-world
. Also, we want to pass a username as the first argument, and hence we've configured it using the addArgument
method. Also, as we've passed InputArgument::REQUIRED
in the second argument, a user must pass this argument while executing this command, otherwise the command won't be executed and would result in an error.
On the other hand, the execute
method contains the application logic of the command. In our case, we've kept it pretty simple by displaying Hello World as the output of the command.
It's also important to note that the execute
method must return an integer, which will be used as the command exit status. And thus, we've passed Command::SUCCESS
as the return value of this command, which indicates that the command was executed successfully. You can also use other available statuses like Command::FAILURE
and Command::INVALID
.
Before you can run this command, you need to register it with the console application that we created in the previous section. Let's quickly revise the bin/console file to look like the following one.
1 |
#!/usr/bin/env php |
2 |
<?php
|
3 |
require_once __DIR__ . '/../vendor/autoload.php'; |
4 |
|
5 |
use Symfony\Component\Console\Application; |
6 |
use Console\App\Commands\HelloworldCommand; |
7 |
|
8 |
$app = new Application(); |
9 |
$app->add(new HelloworldCommand()); |
10 |
$app->run(); |
As you can see, we've used the add
method of the Application
object to add the HelloworldCommand
command. Let's quickly list all the available commands.
1 |
Console Tool |
2 |
|
3 |
Usage: |
4 |
command [options] [arguments] |
5 |
|
6 |
Options: |
7 |
-h, --help Display help for the given command. When no command is given display help for the list command |
8 |
-q, --quiet Do not output any message |
9 |
-V, --version Display this application version |
10 |
--ansi|--no-ansi Force (or disable --no-ansi) ANSI output |
11 |
-n, --no-interaction Do not ask any interactive question |
12 |
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug |
13 |
|
14 |
Available commands: |
15 |
completion Dump the shell completion script |
16 |
hello-world Prints Hello-World! |
17 |
help Display help for a command |
18 |
list List commands |
As expected, the hello-world
command appears in the list of available commands! Go ahead and run it!
1 |
$bin/console hello-world tutsplus |
2 |
Hello World!, tutsplus |
So that's how you can set up basic commands!
A Real-World Example: Clear Cache Command
In the previous section, we built the hello-world
command to demonstrate the concepts of the Console component. In this section, we'll create a real-world example to demonstrate how you could build a command to clear caches in your application.
Create the Clear Cache Command File
Go ahead and create the src/App/Commands/ClearcacheCommand.php file with the following contents.
1 |
<?php
|
2 |
namespace Console\App\Commands; |
3 |
|
4 |
use Symfony\Component\Console\Command\Command; |
5 |
use Symfony\Component\Console\Input\InputInterface; |
6 |
use Symfony\Component\Console\Output\OutputInterface; |
7 |
use Symfony\Component\Console\Input\InputArgument; |
8 |
use Symfony\Component\Console\Input\InputOption; |
9 |
|
10 |
class ClearcacheCommand extends Command |
11 |
{
|
12 |
protected function configure() |
13 |
{
|
14 |
$this->setName('clear-cache') |
15 |
->setDescription('Clears the application cache.') |
16 |
->setHelp('Allows you to delete the application cache. Pass the --groups parameter to clear caches of specific groups.') |
17 |
->addOption( |
18 |
'groups', |
19 |
'g', |
20 |
InputOption::VALUE_OPTIONAL, |
21 |
'Pass the comma separated group names if you don\'t want to clear all caches.', |
22 |
''
|
23 |
);
|
24 |
}
|
25 |
|
26 |
protected function execute(InputInterface $input, OutputInterface $output) |
27 |
{
|
28 |
$output->writeln('Cache is about to cleared...'); |
29 |
|
30 |
if ($input->getOption('groups')) |
31 |
{
|
32 |
$groups = explode(",", $input->getOption('groups')); |
33 |
|
34 |
if (is_array($groups) && count($groups)) |
35 |
{
|
36 |
foreach ($groups as $group) |
37 |
{
|
38 |
$output->writeln(sprintf('%s cache is cleared', $group)); |
39 |
}
|
40 |
}
|
41 |
}
|
42 |
else
|
43 |
{
|
44 |
$output->writeln('All caches are cleared.'); |
45 |
}
|
46 |
|
47 |
$output->writeln('Complete.'); |
48 |
|
49 |
return Command::SUCCESS; |
50 |
}
|
51 |
}
|
The configure
method is pretty much the same, except that we've used the addOption
method to add an option to our command. Thus, you could pass group values by using the --groups
parameter.
On the other hand, the execute
method contains the application logic of our command.
If you want to clear the cache of specific groups, you need to pass group names along with the --group
parameter. On the other hand, skip the --group
parameter if you want to clear all caches. You may have noticed that we've kept the --group
parameter optional by providing the InputOption::VALUE_OPTIONAL
value in the third argument of the addOption
method.
Registration and Testing With the Console Application
Before we run it, let's register the command with our console application.
1 |
#!/usr/bin/env php |
2 |
<?php
|
3 |
require_once __DIR__ . '/../vendor/autoload.php'; |
4 |
|
5 |
use Symfony\Component\Console\Application; |
6 |
use Console\App\Commands\HelloworldCommand; |
7 |
use Console\App\Commands\ClearcacheCommand; |
8 |
|
9 |
$app = new Application(); |
10 |
$app->add(new HelloworldCommand()); |
11 |
$app->add(new ClearcacheCommand()); |
12 |
$app->run(); |
Now, go ahead and run the bin/console clear-cache
command to clear all caches!
1 |
$bin/console clear-cache
|
2 |
Cache is about to cleared... |
3 |
All caches are cleared. |
4 |
Complete. |
Next, if you want to clear specific caches, you could try something like this.
1 |
$bin/console clear-cache --groups=group1,group2 |
2 |
Cache is about to cleared... |
3 |
group1 cache is cleared |
4 |
group2 cache is cleared |
5 |
Complete. |
Of course, you will need to implement the actual logic to clear caches, but that should serve as a good starting point.
How to Add a Progress Bar to Your Command
When you prepare commands that may take a long time to run, it's useful to show progress information. In this section, we'll quickly see how you can add a progress bar to your commands.
We'll revisit the example we discussed in the previous section. Let's replace the src/App/Commands/ClearcacheCommand.php file with the following contents.
1 |
<?php
|
2 |
namespace Console\App\Commands; |
3 |
|
4 |
use Symfony\Component\Console\Command\Command; |
5 |
use Symfony\Component\Console\Input\InputInterface; |
6 |
use Symfony\Component\Console\Output\OutputInterface; |
7 |
use Symfony\Component\Console\Input\InputArgument; |
8 |
use Symfony\Component\Console\Input\InputOption; |
9 |
use Symfony\Component\Console\Helper\ProgressBar; |
10 |
|
11 |
class ClearcacheCommand extends Command |
12 |
{
|
13 |
protected function configure() |
14 |
{
|
15 |
$this->setName('clear-cache') |
16 |
->setDescription('Clears the application cache.') |
17 |
->setHelp('Allows you to delete the application cache. Pass the --groups parameter to clear caches of specific groups.') |
18 |
->addOption( |
19 |
'groups', |
20 |
'g', |
21 |
InputOption::VALUE_OPTIONAL, |
22 |
'Pass the comma separated group names if you don\'t want to clear all caches.', |
23 |
''
|
24 |
);
|
25 |
}
|
26 |
|
27 |
protected function execute(InputInterface $input, OutputInterface $output) |
28 |
{
|
29 |
if ($input->getOption('groups')) |
30 |
{
|
31 |
$groups = explode(",", $input->getOption('groups')); |
32 |
$progressBar = new ProgressBar($output, count($groups)); |
33 |
|
34 |
$progressBar->start(); |
35 |
|
36 |
if (is_array($groups) && count($groups)) |
37 |
{
|
38 |
foreach ($groups as $group) |
39 |
{
|
40 |
sleep(5); |
41 |
$progressBar->advance(); |
42 |
}
|
43 |
}
|
44 |
|
45 |
$progressBar->finish(); |
46 |
}
|
47 |
else
|
48 |
{
|
49 |
$output->writeln('All caches are cleared.'); |
50 |
}
|
51 |
|
52 |
$output->writeln(''); |
53 |
|
54 |
return Command::SUCCESS; |
55 |
}
|
56 |
}
|
We've used the Symfony\Component\Console\Helper\ProgressBar
class to implement the progress bar.
Next, we've initialized the progress bar with the following statement.
1 |
$progressBar = new ProgressBar($output, count($groups)); |
In the first argument, you need to pass the $output
object, and the second argument is the total number of units in the progress bar. In our case, we'll count the number of cache groups that a user wants to clear, and pass the same number in the second argument.
Next, you need to start the progress bar with the start
method. To advance a progress bar, you need to use the advance
method. Finally, the finish
method completes the progress bar. So whenever a command is completed successfully, you can call this method so that the progress bar is refreshed with 100% completion.
In our example, we've used the sleep
command so you can see the progress bar during command execution.
Go ahead and run the clear-cache
command, as shown in the following snippet, which should display the progress bar.
1 |
$bin/console clear-cache --groups=group1,group2,group3,group4,group5 |
2 |
5/5 [============================] 100%
|
As we've passed five groups in the --groups
option, the progress bar is initialized with five units. With our logic, the progress bar should be increased by 20% every five seconds, and it'll reach 100% after 25 seconds.
So that's how you can implement a progress bar with your commands.
Conclusion
Today, we went through one of the popular components provided by the Symfony framework: the Console Component. It's really a useful component should you wish to develop your own CLI application to help you execute your day-to-day utility tasks with ease.
In the first half, we went through the installation and configuration of the component. Then, in the second half, we created a couple of examples of console commands.