Porting Commands to Drush 9

Drush 9 features a deep rewrite of our app, both user facing and internals. We created and open sourced AnnotatedCommand (example), OutputFormatters, and Config. We leveraged Symfony Console for our CLI fundamentals. For details on Drush9, see the video or slides from our Drupalcon Vienna presentation.

Annotated Command example

Unfortunately, old commandfiles such as example.drush.inc no longer load in Drush 9 (since beta5). We’ve made it relatively painless to port this code to Drush 9. The video below shows the drush generate command porting the migrate commands. Detailed instructions are below the video.

  1. Using Drush 9 on a working site, run drush generate drush-command-file. generate is a wrapper for the Drupal Code Generator library.
  2. You will be prompted for 2 pieces of information:
    1. Module name: example
    2. Absolute path to legacy Drush command file: /path/to/example.drush.inc
  3. Drush writes 2 files to the Example module:
    1. drush.services.yml. No edits are needed unless you want to inject Drupal dependencies into your class (e.g. yml, class.
    2. ExampleCommands.php: Each item in example_drush_command() in your old commandfile has been transformed into an Annotated method in a new ExampleCommands file. Copy the body of your command callback functions into the corresponding method in ExampleCommands. Then modernize the code as below. Compare the Drush9 commands versus the Drush8 commands for guidance.
      1. Replace drush_log() with $this->logger()->info() or $this->logger()->warning() or similar.
      2. Replace drush_get_option('foo') with $options['foo'']
      3. Replace drush_set_error() with throw new \Exception()
      4. Replace drush_print() with $this->output()->writeln()
      5. Replace drush_sitealias_get_record() as with $this->siteAliasManager()->getSelf(). From there you can call getRoot(), getUri(), legacyRecord(), and other handy methods. In order for this to work, edit your class to implement SiteAliasManagerAwareInterface (LoginCommands is an example).
      6. Optional - move user interaction to a @hook interact. Also, note the new $this-io()->confirm() and $this->io()->ask() methods.
  4. Run drush cr to add your commandfile to the Drupal container.
  5. Congrats - your commands are now runnable in Drush9!
    1. Please post a patch if Example is a Contrib module.
    2. Please leave example.drush.inc in your module so that Drush 8 users may still use it.

I’m in #drush on IRC and Slack in case anyone wants help with porting.

Note that drush generate knows how to generate controllers, plugins, config, services, libraries, etc. A blog post about generate is coming soon.

Moshe is available for hire as a consultant. Moshe has worked on Drupal core since 2001, and loves mentoring Drupal teams. Moshe loves to set up CI and developer workflows for PHP/JS teams.


Pedro October 05, 2017

Many thanks for the guide Moshe! very useful when porting commands.

A couple of questions:

  • Is there any particular way to move the dt() function to a $this->t() or something like that?
  • Do the $options array replace drush_get_option?
  • Has drush_print_table() been replaced by something else?

Moshe Weitzman October 05, 2017

Hi Pedro. Thanks for porting the Migrate commands!

  1. Lets discuss at https://github.com/drush-ops/drush/issues/3015
  2. Yes. I’ve updated the blog post.
  3. Yes. See the screenshot at top of this post for an example of how commands print a table. You can also grep for @field-labels.

Eric Mulder November 02, 2017

Would this also still work for drush command provided by themes? I am currently trying to port the zurb_foundation subtheme command but it simply doesn’t show up in my list of available drush commands. I have done multiple drush cr / drush cc drush command to make sure caching is not the culprit here.

Any thoughts are welcome.

Mark Dorison February 15, 2018

While attempting to port an older global drush command, I followed the porting instructions which generated the necessary Drush code, but I was getting a CommandNotFoundException when attempting to run the command. The command was not discovered by drush because it wasn’t part of an enabled module.

If this is your issue, either create a module_name.info.yml file manually, or run drush generate module, then enable the module in question.

MPP April 03, 2018

These hooks are no longer available:

  • hook_drush_help_alter
  • hook_drush_command_alter

Can they be replaced by extending the existing commands?

Moshe Weitzman April 03, 2018

Yes. See https://github.com/drush-ops/drush/pull/3447 for altering the annotations and https://github.com/consolidation/annotated-command/#replace-command-hook for altering the command method itself.