Lazy coding: exploring ‘drush generate’
Generate boilerplate code for modules, plugins, services and much more.
For Drupal site builders drush is the Swiss army knife that can be used for a wide range of purposes, from simply clearing the cache to updating translations or even install a complete new site.
In the more recent versions of drush a command was added that makes drush also interesting for Drupal developers. This command, generate, makes it easy to generate boilerplate code for modules, plugins, services and much more. And it is even possible to extend existing custom (or contrib) modules with a generator.
The generate
command uses the chi-teck/drupal-code-generator-library to generate the code and if you are looking for examples how to write your own generator, or are just curious about how this works, the vendor/chi-teck/drupal-code-generator/templates and vendor/chi-teck/drupal-code-generator/src/Command directories
are a good place to start.
The great strength of the generate command is the way it creates code according to the answers to a number of questions asked when run. These questions usually include the machine name of the module you want to add the code to, the machine name of the plugin that is created etcetera, but where applicable also questions about which services you want to inject or which routes you want to add. The inner workings of this will be discussed later when we write our own generate-command, but first let’s take a look at what is included by default.
Default generate
To get a list of all the generators included by default simply issue the generate command without an argument:
$ drush generate
This will show a list of all the generators, divided into a number of categories. I will not discuss the full list here, you can check it out for yourself, but will highlight a number of categories and generators.
The _global category
In the global category there are a number of generators unrelated to specific Drupal entities like plugins or modules (although it also seems to be the “I did not know where to put it”-category). The most interesting generators (at least in my daily work) here are:
Controller
This will generate a custom controller and all the files needed to get it functioning. The best way to see how this works is by using the command yourself, but to give you an idea, this is the output as shown on the command line:
$ drush generate controller
Welcome to controller generator!
––––––––––––––––––––––––––––––––––
Module machine name:
➤ dclg_base
Class [DclgBaseController]:
➤
Would you like to inject dependencies? [No]:
➤ yes
Type the service name or use arrows up/down. Press enter to continue:
➤ entity_type.manager
Type the service name or use arrows up/down. Press enter to continue:
➤
Would you like to create a route for this controller? [Yes]:
➤
Route name [dclg_base.example]:
➤
Route path [/dclg-base/example]:
➤
Route title [Example]:
➤
Route permission [access content]:
➤
The following directories and files have been created or updated:
–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
• modules/custom/dclg_base/dclg_base.routing.yml
• modules/custom/dclg_base/src/Controller/DclgBaseController.php
If you are curious about why these specific questions are asked in this case, take a look at the Controller class
in vendor/chi-teck/drupal-code-generator/src/Command/Drupal_8/Controller.php
.
When you run the command yourself you wil notice that a number of questions, like Module machine name and Type the service name provide auto completion to make it easy to find the correct name.
Of course the code generated is boilerplate code so you still have to write the code that actually does something useful (the fun bit!), but it handles a lot of the nitpicking required to, for example, inject dependencies. And even the comments required by the Drupal coding standards are added.
Javascript
This generates an “empty” javascript file but with all the different brackets (I’m much more of a backend developer…) in place and with an example behavior, like this:
/**
* @file
* Drupal Coding LG Base module behaviors.
*/
(function ($, Drupal) {
'use strict';
/**
* Behavior description.
*/
Drupal.behaviors.dclgBase = {
attach: function (context, settings) {
console.log('It works!');
}
};
} (jQuery, Drupal));
Hook
When you use an IDE like PHPStorm, adding hooks is also possible from within the editor. drush generate hook
has the advantage that it also generates any extra files like [module_name].views_execution.inc
file for views hooks and adds code examples.
But, and this is a rather large drawback, unlike PHPStorm for example, it does not include the hooks defined by contrib and custom modules. Only the hooks defined in vendor/chi-teck/drupal-code-generator/templates/d8/hook
are available.
The form category
form-config
No one likes hard-coded settings, but creating a settings form can be a bit of a hassle. drush generate form-config
takes away the hassle and not only creates the settings form but also adds (or alters if it already exists) the routing.yml and links.menu.yml you need to make the settings form accessible.
form-simple
The drush generate form-simple
commands adds a complete (with submit and validation functions) custom form and, when requested, even adds a route and injects dependencies.
The module category
module-standard
This generates a complete Drupal module, with optionally the basic files for a custom module:
- An install file.
- A libraries file.
- Permissions.
- Event subscribers.
- A block-plugin.
- A controller.
- A settings form.
This generator shows another important feature of drush generate
, you can have one generator call other ones and so can reduce the amount of work necessary to create complex code structures.
The plugin category
Since Drupal 8 plugins are the backbone for a large part of the functionality in Drupal. With drush generate
you can quickly create the main structure for simple plugins like a queueworker
, but also complex plugins, like a block (with optionally a settings form, dependency injection and access control) and even a ckeditor
-plugin including the necessary Javascript files and a (standard) icon, can be generated.
In the current version 20 types of plugins are supported by default.
In the section about how to create your own generate-command we will add a plugin-generator and take a more in depth look at how this works.
The service category
Another important concept since Drupal 8 are services. drush generate
by default includes 16 generators for services, including service-event-subscriber
, service-route-subscriber
and service-access-checker
and also:
service-custom
The drush generate service-custom
-command will create the class file in the src
directory and also will add the services.yml
to the module or alter the existing one. It also gives you the option to inject any existing services and adds these to the constructor and the services.yml
as well.
The yml category
In the Yml-section 12 generators are listed that you can use to add yaml-files for things like permissions, routing or services to custom modules or add breakpoints and libraries to existing themes.
The output of the generators is rather simple, but still, it is a better way then adding them by copy/paste from your latest project…
Creating a custom generator
As mentioned above the list of available generators is quite extensive, so why add your own? A possible reason is that you maintain or extensively use a Drupal module and want to add a generator to save time.
In this example we will use the Extra Field -module as base for our generator. The main reason for this is that I am using this module quite frequently, but it is also a good candidate because the code that needs to be generated is quite straightforward.
The process of adding a generator (alas there is no drush generate generator
-command) requires a number of steps. First of all we need to tell drush
our module has its own drush commands, secondly we have to implement the command and define the template(s) for the code we want to generate.
Drush commands
To generate the boiler-template for the drush-commands we use ddrush generate drush-command-file.
This will generate a drush.services.yml
file and a class in src/Commands
that extends the DrushCommands
-class.
In our case we need the command-class because it must be present in de drush.services.yml
) but we do not need the methods the drush-command-file
-generator adds because we are extending the existing generate
-command. So we can simply delete the methods, but keep the class file.
The generator class
To add our command we have to add a class that extends the BaseGenerator
-class in the src/Generators
-directory inside our module.
Of course we have to define the name space and we add a number of helper-classes we need, so we start with:
namespace Drupal\extra_field\Generators;
use DrupalCodeGenerator\Command\BaseGenerator;
use DrupalCodeGenerator\Utils;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
In the class we have to set a number of properties to define our command:
class ExtraFieldGenerator extends BaseGenerator{
protected $name = 'plugin:extra-field-generator';
protected $description = 'Generates an extra field plugin.';
protected $alias = 'extra-field';
protected $templatePath = __DIR__;
The $name
, $alias
and $description
is what we see if we run drush generate
, and because the $name
is prefixed with plugin:
the command is shown in the 'plugin'-section as:
plugin-extra-field-generator (extra-field) Generates an extra field plugin.
The $templatePath
defines the directory in which the template (see The generator template) resides. The default is the template directory of the library in the vendor directory:
vendor/chi-teck/drupal-code-generator/templates
But in our case we want to include the template inside the src/Generators directory and we use the PHP magic constant ‘__DIR__’ which will point to that directory.
The only method we are required to implement is the interact
-method in which is determined which questions are asked when the command runs and which files are generated.
First we define a group of default questions:
// Determine the module the plugin must be added to.
$questions = Utils::moduleQuestions();
// Determine the label, id and class name for the plugin.
$questions += Utils::pluginQuestions('');
// Ask the questions.
$vars = &$this->collectVars($input, $output, $questions);
Optionally we want the possibility to inject one or more services, which is handled by:
// Check if user want to adde services.
$di_question = new ConfirmationQuestion('Would you like to inject dependencies?', FALSE);
if ($this->ask($input, $output, $di_question)) {
// Add services.
$this->collectServices($input, $output);
}
The above code sets the variables and after that we only have to determine where the code is added and which template is used with:
$this->addFile()
->path('src/Plugin/ExtraField/Display/{class}.php')
->template('extra-field-generator.twig');
This also creates the necessary directories if they are not yet present.
Complete generator
So, the complete generator class looks like this:
<?php
namespace Drupal\extra_field\Generators;
use DrupalCodeGenerator\Command\BaseGenerator;
use DrupalCodeGenerator\Utils;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class ExtraFieldGenerator extends BaseGenerator{
protected $name = 'plugin:extra-field-generator';
protected $description = 'Generates an extra field plugin.';
protected $alias = 'extra-field';
protected $templatePath = __DIR__;
/**
* {@inheritdoc}
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
$questions = Utils::moduleQuestions();
$questions += Utils::pluginQuestions('');
$vars = &$this->collectVars($input, $output, $questions);
$di_question = new ConfirmationQuestion('Would you like to inject dependencies?', FALSE);
if ($this->ask($input, $output, $di_question)) {
$this->collectServices($input, $output);
}
$this->addFile()
->path('src/Plugin/ExtraField/Display/{class}.php')
->template('extra-field-generator.twig');
}
}
Generator template, the basics
The generator template is a twig-file that is rendered in the location defined by the path
in the addFile
-call in the generator-class above.
In this template we define the basic code we need for our generated plugin and we can use the variables set by the questions in the command file. Because Twig is used the code will closely resemble the code used in templates within a theme in html.twig
templates.
If for example we need to define the namespace we use the machine name set in Utils::moduleQuestions()
with:
namespace Drupal\{{ machine_name }}\Plugin\ExtraField\Display;
In the annotation and the class name we use the variables set in Utils::pluginQuestions('')
/**
* Displays {{ plugin_label }}.
*
* @ExtraFieldDisplay(
* id = "{{ plugin_id }}",
* label = @Translation("{{ plugin_label }}"),
* bundles = {
* "node.*",
* }
* )
*/
class {{ class }} extends ExtraFieldDisplayFormattedBase implements ContainerFactoryPluginInterface {
And we define the boiler-template code we want to add with some static text:
/**
* {@inheritDoc}
*/
public function viewElements(ContentEntityInterface $entity) {
$build =[];
// TODO: build render array to return.
return $build;
}
Besides simply using variables other twig-constructs like:
{% if variable %}
{% for variable in variables %}
also can be used and even macros can be defined. See the Twig-documentation for a complete list.
Generator template, including other twigs
In the basics described above we left out the part about the dependency injection. The variables for this part are collected in the collectServices
-call in the command class and to render the code-parts for the services we include the di.twig with
{% import '/lib/di.twig' as di %}
By combining static text with variables and macros defined in the library /lib/di.twig
we can create exactly the code we want to.
The full template looks like this:
{% import '/lib/di.twig' as di %}
<?php
namespace Drupal\{{ machine_name }}\Plugin\ExtraField\Display;
{% sort %}
use Drupal\Core\Controller\ControllerBase;
{% if services %}
use Symfony\Component\DependencyInjection\ContainerInterface;
{{ di.use(services) }}
{% endif %}
{% endsort %}
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\extra_field\Plugin\ExtraFieldDisplayFormattedBase;
/**
* Displays {{ plugin_label }}.
*
* @ExtraFieldDisplay(
* id = "{{ plugin_id }}",
* label = @Translation("{{ plugin_label }}"),
* bundles = {
* "node.*",
* }
* )
*/
class {{ class }} extends ExtraFieldDisplayFormattedBase implements ContainerFactoryPluginInterface {
{% if services %}
{{ di.properties(services) }}
/**
* The constructor.
*
{{ di.annotation(services) }}
*/
public function __construct({{ di.signature(services) }}) {
{{ di.assignment(services) }}
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
{{ di.container(services) }}
);
}
{% endif %}
/**
* {@inheritDoc}
*/
public function viewElements(ContentEntityInterface $entity) {
$build =[];
// TODO: build render array to return.
return $build;
}
}
Conclusion
Of course being ‘lazy’ is not per se a good quality for a developer. But just like using a proper IDE can make your life so much easier compared to using vi
, using a tool like drush generate
can make your life much easier. You don’t have to concern yourself with a large part of the bookkeeping normally necessary when creating complex code with, for example, dependency injection or routes and, as a bonus, often get sample code too.
In short, it makes it possible to concentrate on the fun bits of Drupal-programing.