Creating Drush 9 commands and porting legacy commands

14 Okt. 2017
Drush 9

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.  
  3. use Drush\Commands\DrushCommands;
  4.  
  5. /**
  6.  *
  7.  * In addition to a commandfile like this one, you need a drush.services.yml
  8.  * in root of your module.
  9.  *
  10.  * See these files for an example of injecting Drupal services:
  11.  * - http://cgit.drupalcode.org/devel/tree/src/Commands/DevelCommands.php
  12.  * - http://cgit.drupalcode.org/devel/tree/drush.services.yml
  13.  */
  14. class MyModuleCommands extends DrushCommands {
  15.  
  16. /**
  17.   * @command mymodule:do-something
  18.   * @param array $options An associative array of options whose values come from cli, aliases, config, etc.
  19.   * @validate-module-enabled mymodule
  20.   * @aliases mm:do-something, mm:ds, mymodule-do-something
  21.   */
  22. public function generate()
  23. {
  24. // See bottom of https://weitzman.github.io/blog/port-to-drush9 for details on what to change when porting a
  25. // legacy command.
  26. }
  27.  
  28.  
  29. }

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. /**
  7.  * Implements hook_drush_command().
  8.  */
  9. function simple_sitemap_drush_command() {
  10. $items['simple-sitemap-generate'] = [
  11. 'description' => 'Regenerate the XML sitemaps according to the module settings.',
  12. 'callback' => 'drush_simple_sitemap_generate',
  13. 'drupal dependencies' => ['simple_sitemap'],
  14. 'aliases' => ['ssg'],
  15. ];
  16.  
  17. $items['simple-sitemap-rebuild-queue'] = [
  18. 'description' => 'Rebuild the sitemap queue for all sitemap variants.',
  19. 'callback' => 'drush_simple_sitemap_rebuild_queue',
  20. 'drupal dependencies' => ['simple_sitemap'],
  21. 'aliases' => ['ssr'],
  22. ];
  23.  
  24. return $items;
  25. }
  26.  
  27. /**
  28.  * Callback function for hook_drush_command().
  29.  *
  30.  * Regenerate the XML sitemaps according to the module settings.
  31.  */
  32. function drush_simple_sitemap_generate() {
  33. \Drupal::service('simple_sitemap.generator')->generateSitemap('drush');
  34. }
  35.  
  36. /**
  37.  * Callback function for hook_drush_command().
  38.  *
  39.  * Rebuild the sitemap queue for all sitemap variants.
  40.  */
  41. function drush_simple_sitemap_rebuild_queue() {
  42. \Drupal::service('simple_sitemap.generator')->rebuildQueue();
  43. }

SimplesitemapCommands.php (Drush 9)

  1. namespace Drupal\simple_sitemap\Commands;
  2.  
  3. use Drupal\simple_sitemap\Simplesitemap;
  4. use Drush\Commands\DrushCommands;
  5.  
  6. /**
  7.  * Class SimplesitemapCommands
  8.  * @package Drupal\simple_sitemap\Commands
  9.  */
  10. class SimplesitemapCommands extends DrushCommands {
  11.  
  12. /**
  13.   * @var \Drupal\simple_sitemap\Simplesitemap
  14.   */
  15. protected $generator;
  16.  
  17. /**
  18.   * SimplesitemapCommands constructor.
  19.   * @param \Drupal\simple_sitemap\Simplesitemap $generator
  20.   */
  21. public function __construct(Simplesitemap $generator) {
  22. $this->generator = $generator;
  23. }
  24.  
  25. /**
  26.   * Regenerate the XML sitemaps according to the module settings.
  27.   *
  28.   * @command simple-sitemap:generate
  29.   *
  30.   * @usage drush simple-sitemap:generate
  31.   * Regenerate the XML sitemaps according to the module settings.
  32.   *
  33.   * @validate-module-enabled simple_sitemap
  34.   *
  35.   * @aliases ssg, simple-sitemap-generate
  36.   */
  37. public function generate() {
  38. $this->generator->generateSitemap('drush');
  39. }
  40.  
  41. /**
  42.   * Rebuild the sitemap queue for all sitemap variants.
  43.   *
  44.   * @command simple-sitemap:rebuild-queue
  45.   *
  46.   * @usage drush simple-sitemap:rebuild-queue
  47.   * Rebuild the sitemap queue for all sitemap variants.
  48.   *
  49.   * @validate-module-enabled simple_sitemap
  50.   *
  51.   * @aliases ssr, simple-sitemap-rebuild-queue
  52.   */
  53. public function rebuildQueue() {
  54. $this->generator->rebuildQueue();
  55. }
  56.  
  57. }

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.

Comments

Hi thanks for the article. By the way you miss to include the @simple_sitemap.generator as an argument in your drush.services while it is present in the command controller (and in the repo :)).

Neuen Kommentar hinzufügen

Der Inhalt dieses Feldes wird nicht öffentlich zugänglich angezeigt.

Restricted HTML

  • Erlaubte HTML-Tags: <a href hreflang target> <em> <strong> <cite> <blockquote cite> <pre> <ul type> <ol start type> <li> <dl> <dt> <dd> <h4 id> <h5 id> <h6 id>
  • Zeilenumbrüche und Absätze werden automatisch erzeugt.
  • Website- und E-Mail-Adressen werden automatisch in Links umgewandelt.

Angebot innerhalb von 24 Stunden

Ob ein großes kommerzielles System, oder eine kleine Business Seite, wir schicken ein Angebot ab innerhalb von 24 Stunden nachdem Sie diese Taste drücken: Angebot anfordern