Building a Command Line Tool with PHP and Symfony Console
Tags: php, symfony, cli, programming
Published: 2024-01-20 17:32:33

Command line tools are a powerful way to automate processes, manage tasks, and enhance your development workflow. In this article, we'll explore how to create a simple command line tool in **PHP** using the **Symfony Console** (`symfony/console`) package.

## Why Symfony Console?
Command-line applications are a cornerstone of automation, providing developers a versatile and efficient way to interact with their systems. Although it is possible to create command-line tools using plain PHP, the Symfony Console library enhances the development process by providing a structured, feature-rich environment. This short article will explore the importance of using the Symfony Console package. It will highlight its capabilities, such as automatically generating help, handling input with options and arguments, defining defaults, and providing a clear structure with command classes.

### Automatic Help Generation
One significant advantage of Symfony Console is its ability to automatically generate help for your commands. Symfony Console can produce comprehensive and user-friendly help documentation by defining descriptions, options, and arguments. This feature reduces manual documentation efforts and ensures users can easily understand and utilize your command line tool.

### Input Management with Options and Arguments
Symfony Console simplifies user input handling by providing a well-defined system for options and arguments. Options are flags that modify the behavior of a command, while arguments are values (string) passed to the command. With Symfony Console, you can easily specify the expected inputs, making your commands more intuitive and user-friendly.

### Consistency through docopt conventions
Symfony Console closely follows the well-established [docopt](http://docopt.org/) conventions. Docopt, based on longstanding conventions from help messages and man pages, ensures a consistent and intuitive interface for describing a program's interface. Symfony Console's adherence to docopt conventions guarantees that your command line tools maintain a standardized and predictable user experience, simplifying development and user interaction.

### Default Values and Input Validation
The ability to define default values for options and arguments is crucial for maintaining flexibility while ensuring a consistent user experience. Symfony Console allows you to set default values effortlessly, reducing the need for repetitive code. Additionally, the library provides input validation, helping you handle user input gracefully and prevent unexpected errors.

### Convenient Helper Functions for User Input and Output
Symfony Console offers a robust set of helper functions designed to facilitate the management of user input and output, providing seamless interaction with the tool during execution. For instance, these helpers enable features such as requesting additional information from the user. From formatting and coloring output to prompting users for input, these utilities significantly improve the overall user experience and contribute to streamlining the development process.

### Structured and Class-Based Commands
Symfony Console encourages a clean and organized structure for your command line applications. Commands are defined as classes, allowing for modularity, code reuse, and testability. This structured approach makes it easier to maintain and extend your command line tools as your project evolves.

## Installation
To get started, you'll need to install the Symfony Console package using Composer. Open your terminal and run the following command:

```php
composer require symfony/console
```

## Creating Your First Command

Let's create a simple `HelloWorldCommand` as a quick example.
We want to create a src/Commands folder to store all the commands we want to build. Let's start with the first, creating the new PHP file (e.g., `HelloWorldCommand.php`) in the `src/Commands` directory:

```shell
mkdir -p src/Commands
```
And creating the new file `src/Commands/HelloWorldCommand.php`, adding the following code:


```php
<?php

// 01 giving a name space
//Remember to set MyExample in the autoload.psr-4 in composer.json

namespace MyExample\Commands;

// 02 Importing the Command base class
use Symfony\Component\Console\Command\Command;
// 03 Importing the input/output interfaces
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

// 04 Defining the class extending the Command base class
class HelloWorldCommand extends Command
{
    // 05 Implementing the configure method
    protected function configure()
    {
        $this
            // 06 defining the command name
            ->setName('hello')
            // 07 defining the description of the command
            ->setDescription('Prints Hello')
            // 08 defining the help (shown with -h option)
            ->setHelp('This command prints a simple greeting.');
    }

    // 09 implementing the execute method
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // 10 using the Output for writing something
        $output->writeln("Hello, " . get_current_user() . "!");
        $output->writeln("It's " . date("l"));
        // 11 returning the success status
        return Command::SUCCESS;
    }
}

```

With the `HelloWorldCommand.php` file, we created a very simple class that extends the `Command` class provided by the Symfony Console package. Walking through the code, we:
- **01 setting a namespace** with `namespace MyExample\Commands;` for allowing the autoload mechanism to load our new classes under the `src` directory, we will set `autoload.psr-4` in the `composer.json` file later;
- **02 Importing** the `Command` base class provided by the Symfony Console package;
- **03 Importing** the input / output interfaces provided by the Symfony Console package;
- **04 Defining the HelloWorldCommand class** extending the `Command` base class provided by the Symfony Console package;
- **05 Implementing** the `configure` method where you can set some properties of your command like the name, the description, the help message etc;
- **06 defining the command name** via `setName()` method;
- **07 defining the command's description** via `setDescription()` method;
- **08 defining the help message** (shown with `-h` option) via `setHelp()` method;
- **09 implementing the execute method** with `InputInterface` and `OutputInterface` parameters, returning an integer;
- **10 printing a message** using the Output via the `$output->writeln()` method;
- **11 returning the status** with `Command::SUCCESS`.


## Setting autoload in your composer.json file

Because we are starting to build all the sources and the class from scratch, we need to set up the autoload configuration correctly in our composer.json file. Because we are using the `MyExample` namespace, and we want to load automatically the classes stored in the `src/` directory when a class with `MyExample` namespace is used, we can define autoload as `"MyExample\\": "src/"`, here we have the `composer.json` file: 

```json
{
    "require": {
        "symfony/console": "^7.0"
    },
    "autoload": {
        "psr-4": {
            "MyExample\\": "src/"
        }
    }
}

```

When you change some configuration with the autoloader, I suggest you run the `dump-autoload` composer command for dumping and re-generating the autoloader configuration:

```shell
composer dump-autoload
```
Now we can create the main command file for laucnihng correctly the Command class we created.

## Creating the command line script

Now, let's create the main file that will run our command. Create a file (e.g., `my-cli`) and add the following code:

```php
#!/usr/bin/env php
<?php

use MyExample\Commands\HelloWorldCommand;
use Symfony\Component\Console\Application;

if (file_exists(__DIR__ . '/../../autoload.php')) {
    require __DIR__ . '/../../autoload.php';
} else {
    require __DIR__ . '/vendor/autoload.php';
}

/**
 * Start the console application.
 */
$app = new Application('Hello World', '1.0.0');
//$app->setDefaultCommand("hello");

$app->add(new HelloWorldCommand());
// $app->add(new AnotherCommand());

$app->run();

```

The "require the autoload" file will bootstrap the classes/packages autoloader correctly.
Then, you can initialize the application with `new Application`, setting the application's name and version.
Then, you can add all the commands you had implemented. In our example, we just created the `HelloWorldCommand` class, and then you can run the command via `$app->run();`.

## Executing the command line

Now, if you named your main file as `my-cli`, you can launch your new command with `php` (the interpreter) , `my-cli` the name of your main script and `hello` the name of your command class:

```shell
php my-cli hello
```


![Executing the new command](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2bflpwps9gcltcynkwlm.png)

You can also use the `-h` option to see the help generated by the Symfony Console using the information you set in the configure method:

```shell
php my-cli hello -h
```

![Executing the new command line tool with the -h option](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o3qfh7b5o3f5azc05vki.png)

## A bonus tip
If you want to publish your command line tool on Packagist you can define the `bin` option in the `composer.json` file.

```json
{
    "require": {
        "symfony/console": "^7.0"
    },
    "bin": [
        "bin/my-cli"
    ],
    "autoload": {
        "psr-4": {
            "MyExample\\": "src/"
        }
    }
}

```

With the `bin` option, Composer installs the package executable files (`my-cli`) in the `vendor/bin` directory for all projects that depend on that specific package. This method conveniently brings out valuable scripts that might otherwise remain hidden in the depths of the `vendor/bin` directory.

## Wrapping up

With Symfony Console package, creating command line applications/tools in PHP becomes a breeze. This quick start article provides a foundation for building your own command line tools efficiently. Explore Symfony Console's documentation for more advanced features and possibilities. Happy coding!

## References

- The Symfony Console component: https://symfony.com/doc/current/components/console.html
- The docopt Command-line interface description language: http://docopt.org/
- Composer autoloading: https://getcomposer.org/doc/01-basic-usage.md#autoloading
- Setting a vendor binary: https://getcomposer.org/doc/articles/vendor-binaries.md

See you on Twitter / X https://twitter.com/RmeetsH and / or Mastodon [php social](https://phpc.social/@roberto) 

🎉🎉🎉