A couple of weeks ago while walking towards lunch with Jelrik we were having a bit of a discussion about the use of the term Command. Not long before that, Jelrik had asked a question about naming of Commands in our Slack channel, which led to some confusion.

The confusion of the term Command

The confusion of the term Command in the Symfony context is not strange. Jelrik was talking about a Command class, and since we’re working with Symfony, both me and other colleagues assumed Jelrik was talking about console commands. Funny enough, he wasn’t.

We’re also using an implementation of command bus in our project, and Jelrik was actually talking about that instead of Symfony console commands.

What is a command?

This triggered a conversation about the confusion and how this can be avoided, which led to the question of what a Symfony console command actually is. The answer seems to be quite easy:

A controller for commandline requests

If we start thinking about console commands in this way, the next question is easy:

Why call it a command?

If a console command is actually a controller, why do we still call it a command. The main answer to this question would be:

My GreetBundle\Command\GreetCommand is automatically recognized up by Symfony

Of course, this is extremely convenient, but on the other hand we’re perhaps breaking with our best practices and application design. When I do code reviews for customers, I often find a pretty good design for the “real” controllers, but the console commands are sometimes 100’s of lines long and completely ignore best practices. Somehow the fact that they are executed on the commandline instead of through a webserver means one can quickly hack together a script.

Let’s call it how it is

So here’s a little proposal for you: Let’s name our console commands for what they are: Controllers. Perhaps then we’ll actually offload the business logic to services, and keep our commands controllers clean. While this does mean we’ll have to manually register our commands, it creates a much clearer overview of what our code is doing.

Let’s go for an example

And what better example than Hello World? 😉

So I’ve created a new Symfony application, and created my fantastic new IngewikkeldHelloWorldBundle inside that application. The bundle has the default directory structure:

  /Controller
    /DefaultController.php
  /Resource
    /config
      /routing.yml
      /services.yml
    /views
      /Default
        /index.html.twig
  /IngewikkeldHelloWorldBundle.php

This basic setup gives me a nice web-based Hello World, but I also want a nice console Hello World. Instead of creating a Command-directory with a new Command class in it, I just create a new Controller:

<?php

namespace IngewikkeldHelloWorldBundle\Controller;

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 GreetController extends Command
{
    protected function configure()
    {
        $this
            ->setName('demo:greet')
            ->setDescription('Greet someone')
            ->addArgument(
                'name',
                InputArgument::OPTIONAL,
                'Who do you want to greet?'
            )
            ->addOption(
                'yell',
                null,
                InputOption::VALUE_NONE,
                'If set, the task will yell in uppercase letters'
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $name = $input->getArgument('name');
        if ($name) {
            $text = 'Hello '.$name;
        } else {
            $text = 'Hello';
        }

        if ($input->getOption('yell')) {
            $text = strtoupper($text);
        }

        $output->writeln($text);
    }
}

To get this Controller to be picked up by Symfony, I register it as a service:

services:
    ingewikkeld.greetcontroller:
        class: IngewikkeldHelloWorldBundle\Controller\GreetController
        tags:
            -  { name: console.command }

The tag here is the magic key to recognition as a console command. It’s not that hard, is it?

Avoid confusion, improve your code

The more I’ve been thinking about this approach, to more I’m starting to like it. Optionally, we could make a subnamespace inside Controller to communicate the purpose even more. Something like:

\IngewikkeldHelloWorldBundle\Controller\Cli\GreetController
\IngewikkeldHelloWorldBundle\Controller\Web\DefaultController

For bundles with a lot of different logic, this could make working with the difference between CLI and web controllers a bit easier. The main idea to call it a controller stays the same though. It makes sense, doesn’t it?


Leave a Reply

Your email address will not be published. Required fields are marked *