Development icon

Lessons for Module Development in Drupal 8

Mark Boyd, Senior Developer
#Drupal 8 | Posted

I maintain a small module called Number Double, which is used for creating fields that store floating point values as a SQL DOUBLE type. Last week, I decided to update this module for D8. Here are a few things I learned.

Namespaces

Namespaces are a useful feature of object oriented programming (OOP) that allow you to isolate identical class names within different contexts using a syntax that looks a lot like folder hierarchy.

A totally contrived example is if I wanted to have two classes both named “Form,” one for login and one for content, I can define different namespaces for each class like so:

namespace \System\Login class Form { ... } namespace \System\Content class Form { ... }

  1. namespace \System\Login
  2. class Form {
  3. ...
  4. }
  5.  
  6. namespace \System\Content
  7. class Form {
  8. ...
  9. }

Each of these classes is now scoped to a namespace, meaning we can easily target whichever one we want in other contexts. One way to instantiate the class you want is to use the full namespace:

$form = new \System\Login\Form();

In reality, this is a bit of an anti-pattern because it requires you to specify the full namespace anywhere in your current context where that class is referenced. The solution to that problem and recommended way of instantiating a class from a namespace is to provide a “use” statement to give PHP a context for where to find the class, which will be discussed and shown in more detail below. Regardless, if you instantiate a class this way, you must include the initial backslash in order to tell PHP to look for your class from the namespace root, otherwise PHP will assume that you are specifying a known context.

For Drupal 8 core modules and all custom modules, the core namespace is “Drupal”.  For custom modules, the next element should be your module name. Beyond that, the rest of the namespace depends on which element from Drupal core you’re extending.

To provide a field type plugin:

namespace Drupal\number_double\Plugin\Field\FieldType

To provide a field type formatter:

namespace Drupal\number_double\Plugin\Field\FieldFormatter

Dependencies and “use”

Drupal 8 core makes heavy use of dependency injection to specify what type of parameters class constructors and methods should expect. The benefit of this pattern is that it allows classes to be strict about what types of parameters they accept, but the types themselves can be extended to accommodate different use cases.

Since you will almost definitely be extending core Drupal classes for your custom modules, you’ll have to provide your code a way of loading the definition for these dependencies, otherwise PHP won’t be able to evaluate those dependencies properly at runtime and you’ll get fatal errors.

The technique for providing access to other classes/interfaces in Drupal 8 (and PHP in general) is the “use” statement. It allows you to specify what namespace should be used to load a specific class.

You will need to add “use” statements for:

  • Any dependencies registered in methods that you are extending/defining
  • Any class that you are extending
  • Any interface that you are implementing
  • Any class you are instantiating directly in code

For example, let’s consider the “schema” method, which is used in field type plugins (which was implemented as hook_field_schema in Drupal 7). It has the following function signature:

public static function schema(FieldStorageDefinitionInterface $field_definition)

Even though your field type plugin extends another class which can already access “FieldStorageDefinitionInterface”, your new module class does not know how or where to find this interface, so you have to tell it how with “use:”

use Drupal\Core\Field\FieldStorageDefinitionInterface;

Tip: You can find and determine the namespace for most Drupal core classes by looking in the “core/lib” directory from the docroot. For example, the definition for “FieldStorageDefinitionInterface” can be found inside the “core/lib/Drupal/Core/Field/FieldStorageDefinitionInterface.php” file. The namespace for the class/interface should always be declared at the top of the file. You can learn a lot about OOP patterns just by browsing the code in this directory.

Folder structure is important

Drupal uses a standard called PSR-4 (it was going to be PSR-0, but the community changed its mind) to autoload your PHP class files.  As a module developer, you must follow PSR-4 conventions strictly or else your class files will not get autoloaded by Drupal properly and your module won’t work.

The first convention is that all of your class files will be nested in folders underneath a “src” folder in your module directory. Inside that “src” folder, for each class you are editing, each element of the class namespace corresponds directly to the folder hierarchy for that file.

To register a field type plugin for my module, I needed the following namespace:

Drupal\number_double\Plugin\Field\FieldType

Consequently, the “DoubleItem.php” class file that defines the field type plugin had to be stored in the following folder structure:

number_double/src/Plugin/Field/FieldType

Another important convention to be aware of is that you can only have one class per file.

Learn more about PSR-4 autoloading and how it works in Drupal 8.

Annotations

Following the PSR-4 standards, plugins are now registered and described using “annotations”, which are basically specially formatted comments. These annotations allow Drupal to discover your new plugins and execute them.

For Number Double, the annotation for the field type plugin looks like this:

  1. /**
  2.  * Defines the 'double' field type.
  3.  *
  4.  * @FieldType(
  5.  * id = "double",
  6.  * label = @Translation("Number (double)"),
  7.  * description = @Translation("This field stores a number in the database as a DOUBLE type."),
  8.  * category = @Translation("Number"),
  9.  * default_widget = "number_double",
  10.  * default_formatter = "number_double"
  11.  * )
  12.  */

Most of these properties are pretty self-explanatory, but the “id” property is especially important. This is the value you will reference in other plugins which relate to  this one.

For example, when defining a custom field formatter that should apply only to “double” type fields, this is the annotation for the field formatter plugin:

  1. /**
  2.  * Plugin implementation of the 'number_double' formatter.
  3.  *
  4.  * @FieldFormatter(
  5.  *   id = "number_double",
  6.  *   label = @Translation("Default"),
  7.  *   field_types = {
  8.  *     "double"
  9.  *   }
  10.  * )
  11.  */

The “double” value in the “field_types” property is a reference to the field type plugin defined above. Also, the id of this formatter, “number_double”, is referenced in the field type plugin annotation as the “default_formatter”.

The properties for annotations vary according to what type of plugin you are defining, so consult the relevant plugin base class from Drupal core. Here is some helpful documentation on annotations from drupal.org.

Your .module file may be less important

In many cases, the use of hooks (such as hook_field_schema or event-based ones like hook_url_inbound_alter) has  been replaced by class extension patterns. As a result, most of your module code may end up in class files within your “src” directory.  For the Number Double module, there is actually no .module file because all functionality is provided through class extension.

Final Thoughts

Overall, I have found Drupal 8 to be a pleasure to work with so far and I think it was even easier to create Number Double in Drupal 8 than in Drupal 7. If I hadn’t cared about creating a field widget and field formatters for my field type, I only would have needed to extend one class.

I also find the pattern of extending classes directly to be a lot more intuitive than hooks with custom if/switch statements to isolate your module’s scope. Class inheritance allows your functionality to be a lot more self-contained and yet also more easily extensible.

If this is your first exposure to OOP principles and concepts, there will be a learning curve with Drupal 8, but my advice to anyone in that situation is don’t give up, because if you invest the time, I think you’ll reap the benefits of cleaner, more well-structured code very quickly.

If you’re looking for more good resources and reference material on OOP principles and patterns, head on over to Drupal.org where they have a great starter page set up: https://www.drupal.org/getting-started-d8-bkg-prereq.

Mark Boyd

Senior Developer