Creating Drush 9 commands and porting legacy commands

Parent Feed: 

Upgrading to Drush 9

Drush should be installed and updated through composer. There is no stable Drush 9 version yet, so the development version must be used. Updating to the development version of Drush 9 is a simple as typing:

$ composer require drush/drush:dev-master

Porting your Drush commands to Drush 9

Porting the commands is a semi-automatic process: There is a command that will generate the required files and class structure for you. To start the wizard, just type:

$ drush generate drush-command-file -l dev

Drush will ask you for the module's machine name and for the optional path to the legacy Drush command file (the one that has your commands, ending with .drush.inc). You will have to provide the absolute path.

drush.services.yml

This is the file your Drush command definition goes into. Do not use your module's regular services.yml as you might have done in Drush 8 or else you will confuse the legacy Drush which will lead to a PHP error like this:

Fatal error: Class 'Drush\Commands\DrushCommands' not found in MyModuleCommands.

Use the dedicated drush.services.yml file in your module's root directory instead.

The file should look like this:


  1. services:

  2. mymodule.commands:

  3. class: \Drupal\mymodule\Commands\MyModuleCommands

  4. tags:

  5. - { name: drush.command }

As in other symfony service definitions, you can (and should) provide other services as arguments DI style and do all the other crazy stuff.

The most recent Drush 9 version recommends to explicitly declare the location of the drush command file for each version of drush by adding the extra.drush.services section to the composer.json file of the implementing module. This is now optional, but will be required for Drush 10.

To comply, let us declare the above file in composer.json for Drush 9:


  1. "extra": {

  2. "drush": {

  3. "services": {

  4. "drush.services.yml": "^9"

  5. }

  6. }

  7. }

Refusing to alter composer.json will result in the following message while running drush commands:

module_name should have an extra.drush.services section. In the future, this will be required in order to use this Drush extension.

MyModuleCommands.php


  1. namespace Drupal\mymodule\Commands;

  2. use Drush\Commands\DrushCommands;

  3. /**

  4.  *

  5.  * In addition to a commandfile like this one, you need a drush.services.yml

  6.  * in root of your module.

  7.  *

  8.  * See these files for an example of injecting Drupal services:

  9.  * - http://cgit.drupalcode.org/devel/tree/src/Commands/DevelCommands.php

  10.  * - http://cgit.drupalcode.org/devel/tree/drush.services.yml

  11.  */

  12. class MyModuleCommands extends DrushCommands {

  13. /**

  14.   * @command mymodule:do-something

  15.   * @param array $options An associative array of options whose values come from cli, aliases, config, etc.

  16.   * @validate-module-enabled mymodule

  17.   * @aliases mm:do-something, mm:ds, mymodule-do-something

  18.   */

  19. public function generate()

  20. {

  21. // See bottom of https://weitzman.github.io/blog/port-to-drush9 for details on what to change when porting a

  22. // legacy command.

  23. }

  24. }

As seen above, the generate() method needs to be implemented manually. Other manual changes may include creating a constructor in case other services are injected.

Drush 9 mimics symfony's style module:command naming structure and this should be respected. I don't see any reson not to include the legacy command as an alias however: If your command used to be my_module:do-something, use my-module:do-something in @command, but also the old my_module-do-something as @alias as presented in the example above. This way scripts calling the old Drush will continue working.

Maintaining Drush 8, Drush 9 and Drupal Console commands side by side

The new three standards of managing Drupal through a shell should not be an excuse for bad practice. To avoid code duplication, make sure your module defines a service which holds all the business logic that can be run by any of the above tools.

Simple XML Sitemap (project page) now supports Drush 9 and is a good example of this principle:

simple_sitemap.drush.inc (Drush 8)


  1. /**

  2.  * @file

  3.  * Drush (< 9) integration.

  4.  */

  5. /**

  6.  * Implements hook_drush_command().

  7.  */

  8. function simple_sitemap_drush_command() {

  9. $items['simple-sitemap-generate'] = [

  10. 'description' => 'Regenerate the XML sitemaps according to the module settings.',

  11. 'callback' => 'drush_simple_sitemap_generate',

  12. 'drupal dependencies' => ['simple_sitemap'],

  13. 'aliases' => ['ssg'],

  14. ];

  15. $items['simple-sitemap-rebuild-queue'] = [

  16. 'description' => 'Rebuild the sitemap queue for all sitemap variants.',

  17. 'callback' => 'drush_simple_sitemap_rebuild_queue',

  18. 'drupal dependencies' => ['simple_sitemap'],

  19. 'aliases' => ['ssr'],

  20. ];

  21. return $items;

  22. }

  23. /**

  24.  * Callback function for hook_drush_command().

  25.  *

  26.  * Regenerate the XML sitemaps according to the module settings.

  27.  */

  28. function drush_simple_sitemap_generate() {

  29. \Drupal::service('simple_sitemap.generator')->generateSitemap('drush');

  30. }

  31. /**

  32.  * Callback function for hook_drush_command().

  33.  *

  34.  * Rebuild the sitemap queue for all sitemap variants.

  35.  */

  36. function drush_simple_sitemap_rebuild_queue() {

  37. \Drupal::service('simple_sitemap.generator')->rebuildQueue();

  38. }

SimplesitemapCommands.php (Drush 9)


  1. namespace Drupal\simple_sitemap\Commands;

  2. use Drupal\simple_sitemap\Simplesitemap;

  3. use Drush\Commands\DrushCommands;

  4. /**

  5.  * Class SimplesitemapCommands

  6.  * @package Drupal\simple_sitemap\Commands

  7.  */

  8. class SimplesitemapCommands extends DrushCommands {

  9. /**

  10.   * @var \Drupal\simple_sitemap\Simplesitemap

  11.   */

  12. protected $generator;

  13. /**

  14.   * SimplesitemapCommands constructor.

  15.   * @param \Drupal\simple_sitemap\Simplesitemap $generator

  16.   */

  17. public function __construct(Simplesitemap $generator) {

  18. $this->generator = $generator;

  19. }

  20. /**

  21.   * Regenerate the XML sitemaps according to the module settings.

  22.   *

  23.   * @command simple-sitemap:generate

  24.   *

  25.   * @usage drush simple-sitemap:generate

  26.   * Regenerate the XML sitemaps according to the module settings.

  27.   *

  28.   * @validate-module-enabled simple_sitemap

  29.   *

  30.   * @aliases ssg, simple-sitemap-generate

  31.   */

  32. public function generate() {

  33. $this->generator->generateSitemap('drush');

  34. }

  35. /**

  36.   * Rebuild the sitemap queue for all sitemap variants.

  37.   *

  38.   * @command simple-sitemap:rebuild-queue

  39.   *

  40.   * @usage drush simple-sitemap:rebuild-queue

  41.   * Rebuild the sitemap queue for all sitemap variants.

  42.   *

  43.   * @validate-module-enabled simple_sitemap

  44.   *

  45.   * @aliases ssr, simple-sitemap-rebuild-queue

  46.   */

  47. public function rebuildQueue() {

  48. $this->generator->rebuildQueue();

  49. }

  50. }

drush.services.yml (Drush 9)


  1. services:

  2. simple_sitemap.commands:

  3. class: \Drupal\simple_sitemap\Commands\SimplesitemapCommands

  4. arguments:

  5. - '@simple_sitemap.generator'

  6. tags:

  7. - { name: drush.command }

All of the business logic of this command is inside of the method generateSitemap() of the simple_sitemap service.

Downgrading back to Drush 8

Not a fan of changing APIs? Downgrading is a composer command away:

$ composer require drush/drush:^8.0

Conclusion

It is good to see the Drush project keeping up with time and pubishing Drush 9 parallely to the appearance of Drupal 8.4.0. The API changes are the necessary price we pay for a modern and continuously evolving framework like Drupal.


Feel free to leave a comment below in case of questions or new Drupal 8.4 / Drush 9 insights.

Original Post: 

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web