Creating Custom Commands¶
Tip
- Drush 13 expects commandfiles to use the AutowireTrait to inject Drupal and Drush dependencies. Prior versions used a drush.services.yml file which is now deprecated and will be removed in Drush 14.
- Drush 12 expects all commandfiles in the
<module-name>/src/Drush/<Commands|Generators>
directory. TheDrush
subdirectory is a new requirement.
Creating a new Drush command is easy. Follow the steps below.
- Run
drush generate drush:command-file
. - Drush will prompt for the machine name of the module that should own the file. The module selected must already exist and be enabled. Use
drush generate module
to create a new module. - Drush will then report that it created a commandfile. Edit as needed.
- Use the classes for the core Drush commands at /src/Commands as inspiration and documentation.
- You may inject dependencies into a command instance.
- Write PHPUnit tests based on Drush Test Traits.
Attributes or Annotations¶
The following are both valid ways to declare a command:
use Drush\Attributes as CLI;
/**
* Retrieve and display xkcd cartoons (attribute variant).
*/
#[CLI\Command(name: 'xkcd:fetch-attributes', aliases: ['xkcd-attributes'])]
#[CLI\Argument(name: 'search', description: 'Optional argument to retrieve the cartoons matching an index, keyword, or "random".')]
#[CLI\Option(name: 'image-viewer', description: 'Command to use to view images (e.g. xv, firefox).', suggestedValues: ['open', 'xv', 'firefox'])]
#[CLI\Option(name: 'google-custom-search-api-key', description: 'Google Custom Search API Key')]
#[CLI\Usage(name: 'drush xkcd', description: 'Retrieve and display the latest cartoon')]
#[CLI\Usage(name: 'drush xkcd sandwich', description: 'Retrieve and display cartoons about sandwiches.')]
public function fetch($search = null, $options = ['image-viewer' => 'open', 'google-custom-search-api-key' => 'AIza']) {
$this->doFetch($search, $options);
}
/**
* @command xkcd:fetch
* @param $search Optional argument to retrieve the cartoons matching an index number, keyword, or "random".
* @option image-viewer Command to use to view images (e.g. xv, firefox).
* @option google-custom-search-api-key Google Custom Search API Key.
* @usage drush xkcd
* Retrieve and display the latest cartoon.
* @usage drush xkcd sandwich
* Retrieve and display cartoons about sandwiches.
* @aliases xkcd
*/
public function fetch($search = null, $options = ['image-viewer' => 'open', 'google-custom-search-api-key' => 'AIza']) {
$this->doFetch($search, $options);
}
- A commandfile that will only be used on PHP8+ should use PHP Attributes instead of Annotations.
- See Attributes provided by Drush core. Custom code can supply additional Attribute classes, which may then be added to any command. For example see InteractConfigName which is used by ConfigCommands.
Altering Command Info¶
Drush command info (annotations/attributes) can be altered from other modules. This is done by creating and registering command info alterers. Alterers are classes that are able to intercept and manipulate an existing command annotation.
In the module that wants to alter a command info, add a class that:
- The class namespace, relative to base namespace, should be
Drupal\<module-name>\Drush\CommandInfoAlterers
and the class file should be located under thesrc/Drush/CommandInfoAlterers
directory. - The filename must have a name like FooCommandInfoAlterer.php. The prefix
Foo
can be whatever string you want. The file must end inCommandInfoAlterer.php
. - The class must implement the
\Consolidation\AnnotatedCommand\CommandInfoAltererInterface
. - Implement the alteration logic in the
alterCommandInfo()
method. - Along with the alter code, it's strongly recommended to log a debug message explaining what exactly was altered. This makes things easier on others who may need to debug the interaction of the alter code with other modules. Also it's a good practice to inject the the logger in the class constructor.
For an example, see WootCommandInfoAlterer provided by the testing 'woot' module.
Symfony Console Commands¶
Drush lists and runs Symfony Console commands, in addition to more typical annotated commands. See GreetCommands as an example. Note that these commands must conform to the usual class name and class namespace requirements. You might need to extend the Console class if you can't rename and move it.
Auto-discovered commands (PSR4)¶
Such commands are auto-discovered by their class PSR4 namespace and class/file name suffix. Drush will auto-discover commands if:
- The commands class is PSR4 auto-loadable.
- The commands class namespace, relative to base namespace, is
Drush\Commands
. For instance, if a Drush command provider third party library maps this PSR4 autoload entry:then the Drush global commands class namespace should be"autoload": { "psr-4": { "My\\Custom\\Library\\": "src" } }
My\Custom\Library\Drush\Commands
and the class file should be located under thesrc/Drush/Commands
directory. - The class and file name ends with
*Commands
, e.g.FooCommands
.
Auto-discovered commandfiles should declare their Drush version compatibility via a conflict
directive. For example, a Composer-managed site-wide command that works with both Drush 11 and Drush 12 might contain something similar to the following in its composer.json file:
"conflict": {
"drush/drush": "<11.0",
}
require
in place of conflict
is not recommended.
Symlinked packages
While it is good practice to make your custom commands into a Composer package, please beware that symlinked packages (by using the composer repository type Path) will not be discovered by Drush. When in development, it is recommended to specify your package's path in your drush.yml
to have quick access to your commands.
Site-wide Commands¶
Commandfiles that are installed in a Drupal site and are not bundled inside a Drupal module are called site-wide commandfiles. Site-wide commands may either be added directly to the Drupal site's repository (e.g. for site-specific policy files), or via composer require
. See the examples/Commands folder for examples. In general, it's preferable to use modules to carry your Drush commands.
Here are some examples of valid commandfile names and namespaces:
- Simple
- Filename: $PROJECT_ROOT/drush/Commands/ExampleCommands.php
- Namespace: Drush\Commands
- Nested in a subdirectory committed to the site's repository
- Filename: $PROJECT_ROOT/drush/Commands/example/ExampleCommands.php
- Namespace: Drush\Commands\example
- Nested in a subdirectory installed via a Composer package
- Filename: $PROJECT_ROOT/drush/Commands/contrib/dev_modules/ExampleCommands.php
- Namespace: Drush\Commands\dev_modules
Note: Make sure you do not include src
in the path to your command. Your command may not be discovered and have additional problems.
If a commandfile is added via a Composer package, then it may declare any dependencies that it may need in its composer.json file. Site-wide commandfiles that are committed directly to a site's repository only have access to the dependencies already available in the site.
A site-wide commandfile should have tests that run with each (major) version of Drush that is supported. You may model your test suite after the example drush extension project.
Global commands discovered by configuration¶
Deprecation
Configuration discovery has been deprecated and will be removed in a future version of Drush. It is recommended that you avoid global Drush commands, and favor site-wide or PSR4 discovered commandfiles instead. If you really need commands that are not part of any Drupal site, consider making a stand-alone script or custom .phar instead. See ahoy, Robo and g1a/starter as potential starting points.
Global commandfiles discoverable by configuration are not supported by default; in order to enable them, you must configure your drush.yml
configuration file to add an include
search location.
For example:
drush:
paths:
include:
- '${env.home}/.drush/commands'
Tip
- The filename must be have a name like Commands/ExampleCommands.php
- The prefix
Example
can be whatever string you want. - The file must end in
Commands.php
- The directory above
Commands
must be one of:- A Folder listed in the 'include' option. Include may be provided via config or via CLI.
- ../drush, /drush or /sites/all/drush. These paths are relative to Drupal root.
- The directory above
Xdebug¶
Drush disables Xdebug by default. This improves performance substantially, because developers are often debugging something other than Drush and they still need to clear caches, import config, etc. There are two equivalent ways to override Drush's disabling of Xdebug:
- Pass the
--xdebug
global option. - Set an environment variable:
DRUSH_ALLOW_XDEBUG=1 drush [command]