Symfony Console from the Scratch

This post is Tested

3 min | by Tomáš Votruba

Peer-Reviewed by: Petr Hejna, Jáchym Toušek, Tomáš Fejfar


Symfony Console is the one package you will probably use to build a PHP CLI app. It's of one the easiest Symfony components. Why? You only create Application class, add your Command class and you are ready to go.

Main feature of Symfony Console

This package helps you to create applications like Composer, PHP-CS-Fixer, PHP_CodeSniffer or Statie, that generates this website.

So in general, to build applications where you:

  • need to access CLI,
  • need to be fast - imports, crons, feeds or asynchronous operations
  • and don't need any browser rendering.

2 Classes to Learn

Application - This is the entry point. It contains all commands and routes arguments to them. Something like Application is in Nette or HttpKernel is in Symfony.

Command - Handles input data, processes them and return result to the output. Something like Presenter or Controller. One application can have many commands.

What Belongs to Command?

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

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

This rule will help you to easily avoid:

  • using command to run another command
  • using command in controller
  • using controller in command

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

Ok, now you know this. So lets 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 app and see that it's ready:

$ php bin/console

All good?

3. Create and Register Command

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

$ composer require nette/security
<?php
// 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
        // 0 = success, other values = fail
        return 0;
    }
}

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 argument:

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

You Are One Step Further

Now you should:

  • understand that you need only 1 class to create simple Command
  • see Command like a Controller and should only delegate business logic, not contain it
  • know how to pass argument, process it and return result to the output

Where to go next?

Still hungry for knowledge? Go check Symfony documentation then.

  • Do you need to pass more than 1 value? E.g. bin/console hash-password heslo123 --cost=14? Go check Command Options.

  • Do you want to inform user about progress of slow process? You are looking for Progress Bar Helper.

  • Do you want to use well-known and pretty Symfony output style (like PHP-CS-Fixer does)? Look at Console Style Guide.