Command line PHP using Symfony Console

As a PHP developer, you will often find yourself working with PHP in the command line. The first time I had to use it was because I would get the "Maximum execution time of 30 seconds exceeded" error on a shared server where you could not change the max_execution_time PHP setting. Nowadays building command line scripts is much easier than it used to be. If you search on Packagist you will find a lot of packages to work with the command line but the one that stands out and is the most commonly used is Symfony/Console.

Symfony/Console is a stand alone package that was developed by Fabien Potencier the man behind the Symfony2 framework. It is, of course, part of the Symfony2 framework but also powers Laravel4’s Artisan CLI and is often used in Silex applications by loading the ConsoleServiceProvider.

When is command line PHP usually used?

  • when you have to do huge exports or imports of data

  • clearing previous day cache and logs (cleanup maintenance)

  • updating indexes for a search engine with latest data from the database server

And more.

Installing the Console

It is recommended you install it via Composer. If you don’t know what composer is please read PHP Dependency Management with Composer to get an idea how easy it is to manage dependencies in PHP these days.

In the composer.json file we specify a name and a description of our project, which is optional but a good practice. We then specify that as a requirement for this project the php version needs to be greater or equal than 5.3.3 and to install the symfony/console package having the version 2.4.x-dev.

For autoloading, we are going to use the PSR-0 standard (For more see: http://www.php-fig.org/psr/0 ) that defines a mapping from namespaces to paths which are going to be stored in the src directory.

{
    "name": "phpmaster/console_demo",
    "description": "PhpMaster.com symfony/console tutorial",
    "require": {
        "php": ">=5.3.3",
        "symfony/console": "2.4.x-dev"
    },
    "autoload": {
        "psr-0": {
            "": "src/"
        }
    }
}

After you execute composer install, Composer will create a vendor directory and download the symfony/console package as well as create an autoload.php file we'll be using later on.

Initial setup

A best practice is to have a single file located in the app directory called console which will be an executable file. You can think of it as a bootstrap that gets called from command line and has the name of the desired command as a parameter as well as other parameters or options after the command. The word "command" here corresponds to the method of a class.

First get the location of the PHP executable, which is usually /usr/bin/php on Linux systems.

Create the file app/console and make it executable like so:

touch app/console && chmod +x app/console

Put #!/usr/bin/env php at the beginning of the newly created file, so that the system can automatically tell PHP is the program which should run this script. You can then run it by simply calling

app/console

in the command line. If you don't do the above, you need to call the script through PHP, like so:

php app/console

Console file

Let's write the basic skeleton of our console app.

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

// set to run indefinitely if needed
set_time_limit(0);

/* Optional. It’s better to do it in the php.ini file */
date_default_timezone_set('America/Los_Angeles'); 

// include the composer autoloader
require_once __DIR__ . '/../vendor/autoload.php'; 

// import the Symfony Console Application 
use Symfony\Component\Console\Application; 

$app = new Application();
$app->run();
?>

Now when you run

app/console

you will see following output:

Writing the first command

The first command that we are going to build is to generate all the fibonacci numbers between two given numbers. Utterly useless in web development, but useful for learning the basics.

The Rule :

Example

For input : 6 – 100

The output is : 8, 13, 21, 34, 55, 8

For input : 0 – 7

The output is 0, 1, 1, 2, 3, 5

To have a good code organization we will put all our command class files in the following directory structure:

\src\PhpMaster\Command\FibonacciCommand.php

Directory structure:

The first thing you need to do is to register the new command in the app/console file so that the console will know about it and list it among the available commands:

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

set_time_limit(0);
date_default_timezone_set('America/Los_Angeles'); 

// include the composer autoloader
require_once __DIR__ . '/../vendor/autoload.php'; 

// import the Symfony Console Application 
use Symfony\Component\Console\Application; 
use PhpMaster\Commands\FibonacciCommand;

$app = new Application();
$app->add(new FibonacciCommand());
$app->run();
?>

Input/Output

Input

The input can be done in two ways: arguments and options.

Arguments are the simpler choice, they are more flexible and usually used when you need to pass a single value to the command. An argument can be an environment type like dev, pre-production or production, file type for an export like xml, csv or txt or a single date like 2013-04-22, and so on. If you have multiple arguments the order is not interchangeable.

Examples:

app/console backup:database pre-production 
app/console export:marketing csv
app/console export:accounting 2013-04-22

Options on the other hand constrain you to specify name and value so they are less error prone and are the recommended approach when you have multiple values to pass to the command. When using options, one needs to provide the name of the option (or a shortcut) and then the value.
The order of options is irrelevant. In the following example all the commands do the same thing:

app/console export:accounting --start 2013-02-12 --end 2013-02-01  
app/console export:accounting --end 2013-02-01 --start 2013-02-12
app/console export:accounting -s 2013-02-12 -e 2013-02-01  
app/console export:accounting -e 2013-02-01 -s 2013-02-12

For more information about input you can check http://api.symfony.com/2.0/Symfony/Component/Console/Input.html.

Output

In order to print your result you have the write and writeln methods from OutputInterface at your disposal. You also have the option of putting foreground and background colors on the output using the OutputFormatterStyle.

For more information about output see :
http://api.symfony.com/2.0/Symfony/Component/Console/Output/Output.html

The finished command:

<?php

namespace PhpMaster\Commands;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;

class FibonacciCommand extends Command {

    protected function configure()
    {   
        $start = 0;
        $stop = 100;

        $this->setName("phpmaster:fibonacci")
             ->setDescription("Display the fibonacci numbers between 2 given numbers")
             ->setDefinition(array(
                      new InputOption('start', 's', InputOption::VALUE_OPTIONAL, 'Start number of the range of Fibonacci number', $start),
                      new InputOption('stop', 'e', InputOption::VALUE_OPTIONAL, 'stop number of the range of Fibonacci number', $stop)
                ))
             ->setHelp(<<<EOT
Display the fibonacci numbers between a range of numbers given as parameters

Usage:

<info>php console.php phpmaster:fibonacci 2 18 <env></info>

You can also specify just a number and by default the start number will be 0
<info>php console.php phpmaster:fibonacci 18 <env></info>

If you don't specify a start and a stop number it will set by default [0,100]
<info>php console.php phpmaster:fibonacci<env></info>
EOT
);
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {

        $header_style = new OutputFormatterStyle('white', 'green', array('bold'));
        $output->getFormatter()->setStyle('header', $header_style);

        $start = intval($input->getOption('start'));
        $stop  = intval($input->getOption('stop'));

        if ( ($start >= $stop) || ($start < 0) ) {
           throw new \InvalidArgumentException('Stop number should be greater than start number');
        }

        $output->writeln('<header>Fibonacci numbers between '.$start.' - '.$stop.'</header>');

        $xnM2 = 0; // set x(n-2)
        $xnM1 = 1;  // set x(n-1)
        $xn = 0; // set x(n)
        $totalFiboNr = 0;
        while ($xnM2 <= $stop)
        {
            if($xnM2 >= $start)  {
                $output->writeln('<header>'.$xnM2.'</header>');
                $totalFiboNr++;
            }
            $xn = $xnM1 + $xnM2;
            $xnM2 = $xnM1;
            $xnM1 = $xn;

        }
        $output->writeln('<header>Total of Fibonacci numbers found = '.$totalFiboNr.' </header>');
    }
}

Testing the command:

  1. No inputs options

  1. End range

  1. Start & Stop range

  1. Invalid range

Unit testing

In order to test the commands, the best approach is to use a class that comes with the package: CommandTester ( Symfony\Component\Console\Tester\CommandTester) which asserts the output of the command based on the input very easily.

Debug tip

In order to debug CLI scripts using Xdebug in NetBeans you need to set the XDEBUG_CONFIG variable as follows
On Unix systems:

export XDEBUG_CONFIG="idekey=netbeans-xdebug"

On Windows systems:

set XDEBUG_CONFIG=idekey=netbeans-xdebug
Further reading

In order to get the most of this package, the best place to learn more is in the official docs http://symfony.com/doc/current/components/console/introduction.html

Hope you enjoyed this introduction into Symfony Console. If you'd like more advanced or specific use cases covered, or if you just have some feedback, please let us know in the comments below!

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • http://wikichua.gopagoda.com/ Wiki Chua

    great tutorial

  • Asif Sk

    nice tutorial