Auto discovery of global commands in Drush
--
This blog post is about the auto-discovery of global Drush commands. To be honest, I have no idea what a global command is or means and how it differentiates between a site command. However, I find the global-command discovery feature fascinating and a streamlined developer experience when writing commands for your Drupal site. Note: this feature is available in Drush 10.5+ or 11.0+.
Some Drush commands can already be auto-discovered. Those are site-wide commands. Site-wide Drush commands exist in your Drupal codebase’s drush/Commands directory and use the Drush\Commands
namespace.
I am working on a new Drupal book and dove into a section that discussed writing Drush commands for your Drupal module. While Drush provides a command to generate Drush commands, I wanted to brush up on the manual steps and help explain the generated code. That’s when I saw the section on auto-discovered commands.
Drush global commands can be auto-discovered if they meet the following conditions:
- The command is in a class that is PSR-4 auto-loadable
- The namespace for the command class contains
Drush\Commands
. SoApp\Drush\Commands
would be a viable namespace. - The command class ends with
DrushCommands
. So the filename would beAppDrushCommands
orFooDrushCommands
, etc.
Personal note: I find the file naming requirement a bit redundant since we already have a specific namespace, and the class is inspected to ensure it isn’t abstract, an interface, and is a subclass of Drush\Commands\DrushCommands
.
This is accomplished with Robo\ClassDiscovery\RelativeNamespaceDiscovery
. The relative namespace discovery class inspects the known namespaces in the class autoload. Given an expected relative namespace (Drush\Commands
) and a file matching pattern (*DrushCommands.php
), it will return available classes that have been discovered.
Here is a copy of the code from Drush (Application.php#L393-L407):
/**
* Discovers commands that are PSR4 auto-loaded.
*/
protected function discoverPsr4Commands(ClassLoader $classLoader): array
{
$classes = (new RelativeNamespaceDiscovery($classLoader))
->setRelativeNamespace('Drush\Commands')
->setSearchPattern('/.*DrushCommands\.php$/')
->getClasses();
return array_filter($classes, function (string $class): bool {…