Standalone Symfony Console from the Scratch

This post was updated at June 2020 with fresh know-how.
What is new?

Updated with Symfony 5.1 syntax.


Symfony Console is the one package you will use to build a PHP CLI app. It's one of the easiest Symfony components.

Why? You only create Application class, add 1 Command class, and you are ready to go.

When & Why use Symfony Console?

This package helps you to create applications like Composer, ECS, Rector or Symfony Static Dumper that generates this website.

So in general, to build applications where you:

2 Classes to Learn

1 application can have many commands.

What Belongs to Command?

Before diving into our first Command, there is an essential rule that I want to share with you. In many tutorials, you find business logic inside Commands. That is convenient in the begging, but challenging to unlearn later, building more commands.

When I wrote commands is something like Presenter or Controller, I talked about Delegator Pattern. Like Controller, it should only delegate arguments to other services and return the result to the output.

This rule will help you to avoid:

Very common, very coupled. You would never use the Controller inside another controller, right?

Ok, now you know this. So let's create your first Command!

Create First Command in 3 Steps

1. Install via Composer

composer require symfony/console

2. Create Console Application

Conventional location is bin/console (having .php suffix is also fine):

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

require_once __DIR__ . '/../vendor/autoload.php';

// Create the Application
$application = new Symfony\Component\Console\Application;

// Run it
$application->run();

Now we can run the app and see that it's ready:

php bin/console

All good? Good!

3. Create and Register Command

Let's create Command that will safely hash any password you enter.

composer require nette/security
<?php

declare(strict_types=1);

// src/Command/HashPasswordCommand.php

namespace App\Command;

use Nette\Security\Passwords;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class HashPasswordCommand extends Command
{
    /**
     * In this method setup command, description, and its parameters
     */
    protected function configure()
    {
        $this->setName('hash-password');
        $this->setDescription('Hashes provided password with BCRYPT and prints to output.');
        $this->addArgument('password', InputArgument::REQUIRED, 'Password to be hashed.');
    }

    /**
     * Here all logic happens
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $password = $input->getArgument('password');

        $hashedPassword = Passwords::hash($password);

        $output->writeln(sprintf(
            'Your hashed password is: %s', $hashedPassword
        ));

        // return value is important when using CI, to fail the build when the command fails
        // in case of fail: "return self::FAILURE;"
        return self::SUCCESS;
    }
}

Configure autoloading, add the following to the composer.json file:

{
    "autoload": {
        "psr-4": {
            "App\\": "src"
        }
    }
}

Dump the autoloader

composer dump-autoload

And update our bin/console file:

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

require_once __DIR__ . '/../vendor/autoload.php';

// Create the Application
$application = new Symfony\Component\Console\Application();

// Register all Commands
$application->add(new App\Command\HashPasswordCommand());

// Run it
$application->run();

Now you can run it from CLI with your password as an argument:

php bin/console hash-password heslo123
Your hashed password is: $2y$10$NZVuDpvFbqhsBhR1AZZzX.xUHKhr5qtP1qGKjqRM4S9Xakxn1Xgy2

You Are One Step Further

Now you should:

Where to go next?

Still hungry for knowledge? Check Symfony documentation then.


Happy coding!




Do you learn from my contents or use open-souce packages like Rector every day?
Consider supporting it on GitHub Sponsors. I'd really appreciate it!