Write a console application using Symfony and Pimple

Using Symfony Console and Pimple to build organized command line apps

In this article I will show you how to set up a stand alone command line application by using the Symfony Console component and Pimple.

Sample command line app

Writing console commands for Symfony (full stack framework) is easy and enjoyable. It helps a lot when you need to handle particular collateral tasks that are needed, in a way or another, to make your website running (processing and crunching data, make async requests, create reports, etc.).

Anyway I discovered that writing stand alone command line applications using only the Symfony/Console component is a lot more easy and enjoyable and that a lot of famous command line applications uses it (Composer and Laravel/Artisan just to name a few). Furthermore by using Symfony I became a great fan of the Dependency Injection and Inversion of Control (IoC) design pattern and, as my dependencies started to grow, I wanted to put some sort of container in my command line apps. I decided to go with Pimple: a really simple dependency injection container written by Fabien Potencier, the notorious head behind the Symfony framework and Sensio.

Let’s start

I will demonstrate my approach by creating a simple “hello $name” command line application that will be able to count how many times you greet someone. You can find the whole code in a dedicated GitHub repository.

So we will be able to run

app/console greet Alice

and it will print out

Hello Alice
(First time!)

Yes, intentionally simple! ;)

Let’s start by creating our composer.json file. We will need the Symfony console and the Pimple packages. We also include the Symfony Yaml component as we will store data in a yaml file (obviously we could have been using json but I believe yaml is cooler :P).

{
  "name": "lmammino/symfony-console-pimple",
  "description": "A sample Symfony Console app using Pimple",
  "require": {
    "symfony/console": "dev-master",
    "pimple/pimple": "dev-master",
    "symfony/yaml": "dev-master"
  },
  "license": "MIT",
  "authors": [
    {
      "name": "Luciano Mammino",
      "email": "[email protected]"
    }
  ],
  "autoload": {
    "psr-4": {
      "LMammino\\ConsoleApp\\": "src/"
    }
  }
}

Yes, let’s run composer update to download all the libraries.

Folder structure

Let’s structure our code. We want to separate application and configuration code from the main source code. So we will end up with the following folder structure:

  • app
  • src
  • vendors

The app folder will contain our console executable file, a bootstrap file and a config folder. We will get into the details in a while.

The Greeter service

Let’s just define our core service by Writing the Greeter class. This class defines the business logic of our greeting application.

<?php

namespace LMammino\ConsoleApp;

use Symfony\Component\Yaml\Yaml;

class Greeter
{
    /**
     * @var string $file
     */
    protected $file;

    /**
     * @var array $greetings
     */
    protected $greetings;

    /**
     * Constructor
     *
     * @param string $file
     */
    public function __construct($file)
    {
        $this->file = $file;
        if (file_exists($file)) {
            $this->greetings = Yaml::parse(file_get_contents($file));
        } else {
            $this->greetings = array();
        }
    }

    /**
     * Destructor
     */
    public function __destruct()
    {
        file_put_contents($this->file, Yaml::dump($this->greetings));
    }

    /**
     * Builds the greeting for someone (you can yell on it if you want!)
     *
     * @param  string $name
     * @param  bool   $yell wanna yell?
     * @return string
     */
    public function greet($name, $yell = false)
    {
        $output = sprintf('Hello %s', $name);
        if ($yell) {
            $output = strtoupper($output);
        }

        $name = strtolower($name);
        if (!isset($this->greetings[$name])) {
            $this->greetings[$name] = 1;
        } else {
            $this->greetings[$name]++;
        }

        return $output;
    }

    /**
     * Will tell you how many times you greet someone
     *
     * @param  string $name
     * @return int
     */
    public function countGreetings($name)
    {
        $name = strtolower($name);
        if (!isset($this->greetings[$name])) {
            return 0;
        }

        return $this->greetings[$name];
    }
}

The class is really simple. The main methods are greet and countGreetings that allows you to build the greet string for someone and to count how many times you have greet someone.

Note that this class needs to know on costruction which file to use to read and store the greetings count. This will be something we will configure through Pimple as a container parameter.

The GreetCommand

Now we have a service with the main business logic, let’s just write a Symfony command to run it:

<?php

namespace LMammino\ConsoleApp\Command;

use LMammino\ConsoleApp\Greeter;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class GreetCommand extends Command
{
    /**
     * @var \LMammino\ConsoleApp\Greeter $greeter
     */
    protected $greeter;

    /**
     * Constructor
     *
     * @param Greeter $greeter
     */
    public function __construct(Greeter $greeter)
    {
        parent::__construct();
        $this->greeter = $greeter;
    }

    /**
     * {@inheritDoc}
     */
    protected function configure()
    {
        $this->setName('greet')
            ->setDescription('Greet someone')
            ->addArgument('name', InputArgument::OPTIONAL, 'The name of the one you want to greet', 'World')
            ->addOption('yell', 'Y', InputOption::VALUE_NONE, 'If set will scream out the greeting. Use with caution!');
    }

    /**
     * {@inheritDoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $name = $input->getArgument('name');
        $yell = $input->getOption('yell');

        $output->writeln($this->greeter->greet($name, $yell));
        if (1 === ($count = $this->greeter->countGreetings($name))) {
            $output->writeln('(First time!)');
        } else {
            $output->writeln(sprintf('(%d times)', $count));
        }
    }
}

The command is totally self explainatory! It just defines the greet command offering a name argument and a yell option (both optional). The point here is that our command has a dependency on the Greeter class we wrote before. So we need to pass it on construction (or we need to configure our Pimple container to do so).

Ladies and gents, the Pimple container!

Finally it’s time to write our Pimple container. Before getting to the code let’s recap things a bit. We have a parameter (the name of the greetings count file) and two services (the Greeter service and the GreetCommand). We will create a app/config/container.php file to define our parameters and services with Pimple:

<?php

$c = new Pimple();

$c['parameters'] = array(
    'greetings.file' => 'greetings.yaml'
);

$c['greeter'] = function($c) {
    return new \LMammino\ConsoleApp\Greeter($c['parameters']['greetings.file']);
};

$c['command.greet'] = function($c) {
    return new \LMammino\ConsoleApp\Command\GreetCommand($c['greeter']);
};

$c['commands'] = function($c) {
    return array(
        $c['command.greet']
    );
};

$c['application'] = function($c) {
    $application = new \Symfony\Component\Console\Application();
    $application->addCommands($c['commands']);
    return $application;
};

return $c;

If you were not familiar with Pimple you should have noticed the simple syntax of this container. We just need to create a Pimple instance and it acts like an array. In this “array” we put parameters as simple data (scalar or array values) and service definitions as functions that return instantiated services. Let’s check our definitions one by one:

  • $c['parameters'] contains the applications parameters (in a more complex application with a lot of parameters you can load the values from an external configuration file)
  • $c['greeter'] defines the construction of our Greeter service
  • $c['command.greet'] defines the construction of our greet command
  • $c['commands'] is an helper definition that returns an array with all the commands that we want to add in our application
  • $c['application'] defines the creation of our command line application

Ok, we are almost done. We just need to write our bootstrap file and our console executable file.

The bootstrap file

The file app/bootstrap.php is used to load the composer autoloader class and our container:

<?php

set_time_limit(0);

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

$container = require(__DIR__ . '/config/container.php');

set_time_limit(0) ensures that our script will not be killed after a certain amount of seconds (if your php.ini wants so). It’s almost useless in this particular case (our command will run in a few milliseconds) but adding it in Php command line applications is a good practice (especially when you have to deal with long running tasks).

The executable console file

The last step needed to make our application executable is to write the app/console file. This is a php file that can be executed from the command line (you need to chmod +x it).

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

require __DIR__ . '/bootstrap.php';

$application = $container['application'];
$application->run();

By using a container it just need to load our “application” service and call run() on it.

Note that the first “shebang” line (#!/usr/bin/env php) allows us to run this file by calling app/console (so that you can avoid to call the php interpreter explicitly).

Conclusions

This app is very simple and it will be easy to build even without adopting a container. Anyway I think this approach guarantees a good organization for your code and will became really useful when your command line application start to grow in terms of complexity. I recently had to build a command line app that uses Doctrine and JMS/Serializer (plus several other dependecies). I can say that adopting a container like Pimple helped me a lot to keep things organized and services decoupled.

Just to make a final recap I think this approach guarantees several benefits:

  • Write “container agnostic” commands (they does not know the container, but have only the required dependencies injected)
  • Attach new commands from configuration: you just need to add them to the $container['commands'] array
  • Help you a lot to write thin commands (yes, I think commands acts like controllers and they should be “thin” too), because you have a simple way to declare services and their dependencies and you are able to inject only the needed ones in every command
  • Allow you to have parameters and configuration (useful when you have to enstablish a connection with a database or use external resources that needs configuration such as an external API)

That should be all. Feel free to comment this post or to contribute to the sample app repository if you feel that this approach can be tweaked.

UPDATE (March 16, 2014):
Javier Egiluz, great Symfony evangelist, pointed out that one of his command line applications, easybook, uses the Symfony Console component in conjunction with Pimple. So if you want to have a look to a more complete and realistic (and complex :P) use case I really suggest you to have a look at the easybook code base.

Have a nice weekend!

Sharing is caring!

If you got value from this article, please consider sharing it with your friends and colleagues.

Found a typo or something that can be improved?

In the spirit of Open Source, you can contribute to this article by submitting a PR on GitHub.

You might also like

Cover picture for a blog post titled 6 Rules of thumb to build blazing fast web server applications

6 Rules of thumb to build blazing fast web server applications

This post highlights 6 important rules to keep in mind when developing performant web applications: avoid premature optimization, do the minimum required work, defer non-critical tasks, leverage caching, avoid N+1 queries, and design for horizontal scaling. Following these guidelines will help you write efficient code from the start and build apps ready to handle growth.

Calendar Icon

Cover picture for a blog post titled Symfony, edit the Response globally using the Kernel Response event

Symfony, edit the Response globally using the Kernel Response event

The Symfony HttpKernel Component allows interacting with the response generation through events. The Kernel Response event permits modifying the response before sending it out. Two examples show how to use it to add custom headers and cookies without touching controller logic.

Calendar Icon

Cover picture for a blog post titled Transparent pixel response with Symfony, how to track email opening

Transparent pixel response with Symfony, how to track email opening

This blog post explains how to implement email open tracking in Symfony using a transparent tracking pixel. It provides code examples for generating a tracking image response and handling the tracking logic in a controller.

Calendar Icon

Cover picture for a blog post titled Integrating Twig.js and BazingaJsTranslationBundle

Integrating Twig.js and BazingaJsTranslationBundle

The post explains how to integrate twig.js with BazingaJsTranslationBundle to handle translations consistently between PHP and JavaScript. It shows how to build a custom Twig extension to translate strings with the Bazinga Translator object and handle differences in parameter formatting.

Calendar Icon

Cover picture for a blog post titled Symfony security: authentication made simple (well, maybe!)

Symfony security: authentication made simple (well, maybe!)

This post collects resources and provides a graph to understand how Symfony authentication works behind the scenes, from the initial request to the final authenticated token. It clarifies the relationships between key classes like firewall, authentication provider and authentication listener.

Calendar Icon

Cover picture for a blog post titled 6 Tips to Build Fast Web Applications (Php Dublin March 2016 Talk)

6 Tips to Build Fast Web Applications (Php Dublin March 2016 Talk)

This post shares 6 tips to build fast web applications based on a talk at Php Dublin in March 2016. It includes slides and covers topics like caching, compression, database optimization, and more.

Calendar Icon