Skip to main content

Drupal Console VS old buddy Drush

Nov 13, 2016

Introduction

As you may notice, with Drupal 8 stable release new development tool started gaining popularity. Drupal Console -- a Symfony’s Console child with a bunch of Drupal integrations. Actually, Drupal Console looks pretty powerful, even though it's missing some Drush functionality.
However, the biggest issue I see right now is a lack of integration. Lots of modules are implementing Drush hooks to provide CLI functionality to their users. And I suppose we won’t ever see the total migration from Drush to Drupal Console or vice versa.

 

Commands Coverage

Initially, I wanted to present a full commands comparison table but it could be pretty huge and non-informative. Instead of it, I will provide just a summary of my investigation.


What misses Drush and presented in Drupal Console.
The most important functionality of Drupal Console is code generation. It provides few wizards allowing to generate boilerplate modules, controllers, forms and even entities. This functionality was adopted from Symfony scaffolding methods. It’s quite useful and improves a quality of development a lot.
Unfortunately, Drush doesn’t have any solid way to generate a boilerplate code. And we need to add a scoring point to Drupal Console.


What is missed in Drupal Console and old buddy Drush provides?
In my point, the only thing absent in Drupal Console is a custom modules integration. Historically, Drush was the CLI of choice in a Drupal world. So everybody, who needed a CLI access introduced Drush commands using simple Drush API. And in Drupal 8 we still can see a huge amount of modules continuing the tradition.
In my opinion, this won’t ever change and the only option we have is to build a bridge between two worlds. Or definitely, we can just have two different tools for the same purpose.

 

Command execution speed tests

Disclaimer:
I used “time” utility to measure the execution time. I used my working Macbook with usual LAMP stack. It’s not a 100% clean test but both tools were in the same position and tested on the same Drupal installation.


Clearing the cache.
The most beloved command in Drupal society, isn’t it?

 

drush cr

drupal cache:rebuild all

Real 0m 10.401s 0m 10.084s
User 0m 8.160s 0m 7.339s
Sys 0m 0.614s 0m 0.512s

Module downloading.
Another popular usage of command line tools and it’s worth testing since it uses HTTP requests.

 

drush dl captcha

drupal module:download captcha

--path=modules/contrib 
--latest 
--no-interaction

Real 0m 2.235s 0m 2.555s
User 0m 1.189s 0m 1.573s
Sys 0m 0.667s 0m 0.307s

Module installation.
Next test in cycle which we need to perform. Interacts with the DB.

 

drush cr

drupal cache:rebuild all

Real 0m 10.401s 0m 10.084s
User 0m 8.160s 0m 7.339s
Sys 0m 0.614s 0m 0.512s

Module uninstallation.
The next test in the cycle which we need to perform. Interacts with the DB.

 

drush cr

drupal cache:rebuild all

Real 0m 10.401s 0m 10.084s
User 0m 8.160s 0m 7.339s
Sys 0m 0.614s 0m 0.512s

At the end, Drush gets 4 points of 12 and Drupal Console gets 8 of 12. So without a doubt Console wins the performance test.
However, to be completely fair, we need to include speed of typing a command here. In general, Drupal Console has much longer command than Drush (I used full-length commands in this test).
Since, both of tools are for developers only, such kind of performance tests do not provide any vital information. So, I think we can count that both Drupal Console and Drush are pretty equal in performance measures.


An interesting thing about Drupal Console. It won’t work without a database connection specified. Even “drupal list” command throws an exception.

Drupal Console DB  exception

 

API comparison

After the general functionality and performance comparison, I’d like to compare these tools from the development point of view. I think the best way to compare APIs of these tools is to build relatively same functionality for both tools.
Below, I will create a Drush emulator (wrapper) for Drupal Console and vice versa - Drupal Console emulator for Drush. The strongest will survive!

 

Drupal Console custom command

To create a simple Drupal Console command you need to extend Symfony\Component\Console\Command\Command class and implement at least two methods: configure and execute. 
It looks quite straightforward and does not leave any space for mistake.
However, such kind of strict process introduces a bunch of limitations. One of them, we can introduce only one command in one class. I will describe this limitation in the next section.


Running Drush in Drupal Console

In this section, I want to provide a brief description of Drush emulation in Drupal Console.
Prerequisite:
Before the Drupal Console Command development, I created an empty module “drem” with a single drem.info.yml
Both console and drush commands will be placed inside of this module.


To create a command we need to extend BaseCommand class in file [module_folder]/src/Command/[ClassName].php file. The architecture is pretty usual for Drupal 8 and should look familiar for Symfony developers. Symfony console commands are being created in exact the same way.
We need to implement configure() function and provide basic settings for the command.

 
$this
      ->setName("d")
      ->setDescription('Wrapper to run drush commands from the Console')
      //@todo use translatables instead of strings.
      ->addArgument('drush_command', InputArgument::REQUIRED, "Drush Command")
      ->addArgument('drush_argument', InputArgument::OPTIONAL, "Check");

Here we see the first huge limitation of such approach. I can create only one command per "Command" class. Or at least I did not find any adequate way to introduce multiple commands in the same class.


And finally, we need to implement execute() function which, as you can understand from its name, contains the logic. Here the code of the whole Command file:

 
<?php

namespace Drupal\drem\Command;

use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command as BaseCommand;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
use Drupal\Console\Command\Shared\CommandTrait;
use Drupal\Console\Style\DrupalStyle;

/**
 * Class DrushCommand.
 *
 * @package Drupal\drem
 */
class DrushCommand extends BaseCommand {

  use CommandTrait;

  /**
   * {@inheritdoc}
   */
  protected function configure() {
    $this
      ->setName("d")
      ->setDescription('Wrapper to run drush commands from the Console')
      //@todo use translatables
      ->addArgument('drush_command', InputArgument::REQUIRED, "Drush Command")
      ->addArgument('drush_argument', InputArgument::OPTIONAL, "Check");
  }

  /**
   * {@inheritdoc}
   */
  protected function execute(InputInterface $input, OutputInterface $output) {
    $io = new DrupalStyle($input, $output);

    $command = "drush {$input->getArgument('drush_command')}";
    if ($argument = $input->getArgument('drush_argument')) {
      $command .= ' ' . $argument;
    }
    $process = new Process($command);

    // Need this to run interactive command
    $process->setTty(TRUE);
    $process->run(function($type, $buffer) {
      echo $buffer;
    });

    if (!$process->isSuccessful()) {
      throw new ProcessFailedException($process);
    }

    $io->write($process->getOutput());
  }
}


We have pretty simple logic which runs a “'drush_argument'” as a Drush Command. It’s pretty straightforward. The only stuff I wanted to describe is a Process class. Here is an ascetically short description from Symfony documentation:


The Process component executes commands in sub-processes.


In few words - it’s a wrapper for PHP’s “exec()” function. Even though it’s simple enough it has one pretty useful feature. We can enable TTY mode by running $process->setTty(TRUE); method. We need this in order to run drush wizards, commands which require an answer from the user.
Now, our command is listed when you type drupal list.

Custom Drupal Console command

You should be able to trigger any drush command from the console. To clear a cache using drush you need to execute "drupal d cc" (drupal d [drush command name]). Even though it looks pretty useless, we can update our logic to make it independent from drush. We can invoke drush hooks implemented in modules directly from the console.

 

Drupal 8 custom drush command

Old buddy drush is pretty straightforward for Drupal-seven-guys and contains 3 steps.

  1. Create file [module_name].drush.inc
  2. Implement hook_drush_command(). (Nice description is here)
  3. Implement drush_[module_name]_[command_name] or introduce a function provided as a “callback” option at step #2.
  4. Actually, as I see now, 3rd step is the main reason why Drush wins our test. Sorry for the spoiler.

Running Console Commands in Drush

To finish our experiment and a comparison we need to develop the opposite thing. Drupal Console wrapper for drush. I will put it in the same "drem" module. So prerequisites are the same as for the previous step.

According to the Drupal 8 custom drush command section, we need to create a dremu.drush.inc file.After that, we need to implement required “hook_drush_command”:

 
<?php

use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;


/**
 * Implements hook_drush_command().
 */
function drem_drush_command() {
  $prefix = 'd:';
  $items = array();

  $process = new Process('drupal list --format=json');
  $process->run();
  if (!$process->isSuccessful()) {
    // Silently stop commands introduction.
    return $items;
  }

  $commands = json_decode($process->getOutput(), TRUE);

  foreach ($commands['commands'] as $command) {
    $items[$prefix . $command['name']] = array(
      'description' => $command['description'],
      'callback' => 'drem_general_callback',
      'callback arguments' => array('console_command' => $command['name']),
      'arguments' => array(
        'test_arg' => "Test Arg",
      ),
    );
  }

  return $items;
}

/**
 * Simple callback for all commands introduced in a drem_drush_command() above.
 *
 * @return string
 *   Process execution output.
 */
function drem_general_callback() {
  $args = func_get_args();

  $args = implode(' ', $args);
  $process = new Process("drupal {$args}");
  $process->setTty(TRUE);
  $process->run(function($type, $buffer) {
    echo $buffer;
  });
  if (!$process->isSuccessful()) {
    throw new ProcessFailedException($process);
  }

  return $process->getOutput();
}

To set up an interactive process, we need to set TTY mode and add a callback which will be triggered whenever the output is ready. We need this snippet for both Drush command and Console one.  

$process->setTty(TRUE);
$process->run(function($type, $buffer) {
  echo $buffer;
});


Process component appeared to be useful for this functionality too. We use it to make initial Drupal Console call to fetch all available commands. We run a “drupal list” command to get all commands. Fortunately, we can specify output format for this purpose, so final command looks like “drupal list --format=json”. This way retrieving the list of commands becomes a piece of cake.


Instead of implementation hook-based callback, I set a callback option explicitly because I need to trigger the same function no matter what command was fired. Callback arguments allow us to “hardcode” an argument received by command callback. In our case, it is a Drupal Console command name.


After these few steps, we can see all Drupal Console commands appear when we call “drush help”.

Custom Drush Cmmand (drem module)

To trigger any console command we need to type “drush d:[command_name]”. For instance, to generate a module, we need to type “drush d:generate:module” and follow all steps, as usual.

 

Kind of conclusion

I would be happy to provide you a good conclusion here. Unfortunately, I don’t have one.
It was a simple experiment just to understand the difference between the tools. I liked the Drupal Console, probably, for it’s “freshness”. Though, Drush is much more convenient for me as for developer. Commands are shorter, API is more flexible (remember, we wrote a Console wrapper in ~20 lines of code). Drush is still my tool of choice.


For those who want to try the wrapper here is a link to the GitHub repo.