Most complex custom scripts become hard to maintain unless you have a team-member who is well-versed in shell commands and syntax. Despite having such knowledge, such custom scripts often end up needing some tooling like logging, formatting, accepting user input, arguments, parameters, and flags.
Using site-wide Drush commands allows the use of cool features that come with the Symfony Console component instead of reinventing the wheel in all custom scripts.
Commandfiles that are installed in a Drupal site and are not bundled inside a Drupal module are called “site-wide” commandfiles.
In this article, we’ll take a look at how to write such a site-wide Drush command to replace a fairly simple Bash script.
Two-minute version
- As Drupal developers, maintaining PHP scripts is easier.
- Site-wide Drush commands are not bundled inside a module.
- Create command files at
drush/Commands/*Commands.php
- Define commands as PHP class methods using annotations.
- Use Symfony Console features in your commands.
- Run shell commands using
passthru()
or equivalent. - See the source code for the examples used in this article.
Why?
Not everybody on a Drupal project is used to Shell scripting. Also, as Drupal developers, one finds it easier to work with PHP. Here’s a simple script:
#!/usr/bin/env php
<?php
echo "Hello world" . PHP_EOL;
This file can then be run from the terminal like a shell script. However, it is challenging if you want to do some fancy stuff like, for example:
- Define arguments
- Define options
- Format messages, e.g. showing colorful output.
- Handle logging and verbosity
Using sitewide Drush commands, you have access to the feature-rich Symfony Console library which makes it easier to write and maintain custom scripts.
You can run shell commands using PHP’s
passthru()
or its equivalents.
Additionally, these commands can easily be discovered with drush list
and their documentation can easily be accessed with the --help
flag.
$ drush list | grep custom
custom:
custom:hello-human (chh) Display "Hello {name}".
custom:hello-world (chw) Display "Hello world!".
Objectives
To serve as an example, we’ll implement a custom script using Drush that does the following:
- Displays
Hello world!
by default. - Displays
Hello {name}!
when a name is specified. - Displays an informal greeting if an
--informal
flag is passed. - Requires at least one name.
- Displays additional names, if any.
- Displays log messages when a
--verbose
flag is present.
Requirements
The only thing you need is a standard Drupal 9+ project with Drush 10 or higher. You can install this all using Composer.
Basic commands
Site-wide Drush commands are defined as PHP classes in one of the following locations:
drush/Commands/*Commands.php
drush/Commands/Foo/*Commands.php
, whereFoo
is a namespace.
For a basic example, here’s how we define a custom:hello-world
command.
# File: PROJECT/drush/Commands/CustomCommands.php
<?php
namespace Drush\Commands;
class CustomHelloCommands extends DrushCommands {
/**
* Display "Hello world!".
*
* @command custom:hello-world
* @aliases chw
*/
public function helloWorld(): void {
$this->output()->writeln('Hello world!');
}
}
If you run drush custom:hello-world
from within your project, you should get a nice Hello world! Let’s take a look at some important parts of the code:
- The
namespace
must be set to:Drush\Commands
. - The class must extend
DrushCommands
. - The
@command
annotation makes Drush discover the command. - The
@aliases
allows your command to be run asdrush chw
instead of having to typedrush custom:hello-world
.
$ drush custom:hello-world
Hello world!
Advanced commands
In this section, we’ll create a command that takes some required arguments, optional arguments, and an option to modify the command’s behavior. Here’s a preview of the command in action.
$ drush custom:hello-human --informal Jigarius Jerry Fabiola Cartman
What up Jigarius.
Looks like you're not alone!
What up Jerry, Fabiola, and Cartman.
We implement this command by adding a method in the same class defined in the Basic Example above.
/**
* Display "Hello {name}".
*
* @param string $name
* Name of the primary person to greet.
* @param string[] $others
* Names of other people to greet.
*
* @command custom:hello-human
* @aliases chh
* @option $informal Say "What up" instead of "Hello".
* @usage custom:hello-human Anya
* @usage custom:hello-human --informal Jerry
* @usage custom:hello-human --verbose John Jane Gene
*/
public function helloHuman(string $name, array $others): void {
$names = array_merge([$name], $others);
$greeting = $this->input()->getOption('informal') ? 'What up' : 'Hello';
// ...
}
Let’s analyze what’s going on in this command.
Arguments
The drush custom:hello-human
command needs at least one argument to work. This has been defined with string $name
. Additionally, it takes a variadic argument array $others
. It can be any number of values. Variadic arguments are defined as an array after all other arguments.
Options
Options are used for passing optional instructions or flags which usually alter the behavior of a command. In our example, an --informal
option has been defined with the @option
annotation. If it is set, then an informal greeting is used instead of a formal Hello.
There are some options that Symfony Console adds by default. For example, --verbose
displays some log messages that would otherwise be hidden.
What’s next
- See the source code for the examples used in this article.
- Read Symfony Console documentation.
- Read Drush documentation, especially, the section on Command Authoring.
- Create a sitewide Drush command on one of your projects.
- Leave a comment to share your thoughts or experiences.