fromSeptember 2013
Feature:

Drupal 8 Modules

A New World in Module Writing
1

This article will be more about the patterns you need to use during Drupal 8 development than how to fit the various pieces together.

Implements of MathematicsThere’s good reason for this approach: fitting the pieces together has plenty of examples, change records, and whatnot – but many pieces of the puzzle are entirely new to Drupal developers.

The Background

The first half of this article provides general PHP information which uses Drupal as an example, but is not Drupal specific. The idea behind this is that the knowledge can be reused well (indeed, this was also a design goal for Drupal 8).

Classes, Objects, Interfaces

In Drupal 7, stdClass was used at a lot of places; ergo, classes (like stdClass) and objects (instances of a class, like node, user, etc.) should be familiar. stdClass is a class without methods, and the properties are not defined ahead of time. In PHP, it’s valid to set any property on an object, even if it’s not defined on its class. So, stdClass worked much like an associated array, except that it used arrows instead of brackets. Another important distinction between arrays and objects is passing them to a function: in PHP5, if a function/method gets an object and then changes the object, it will affect the object everywhere – objects are not copied every time, while arrays are.

In Drupal 8, we tried to use specific classes for specific things instead of the generic stdClass. Unlike stdClass, these can have protected (and private) properties and methods, which can only be accessed within the object, but not from outside. This helps developers see what they are supposed to change – and what they aren’t. To help further, these classes usually implement an interface. An interface is like the blueprint of a class: it lists the methods and the arguments of those methods. Thus, a developer need not bother with the actual implementation, and can focus on the interface and its documentation when interacting with a class.

You will find most Drupal 8 classes are relatively short because of the “single responsibility principle”: one class should do one narrow thing. Of course, this leads to having a large number of classes; we then need good interaction between them.

Let’s suppose your class has to work with a cache bin. In Drupal 7, you’d call cache_get(). But that sort of code really makes testing difficult. For performance reasons, you’d want to use an in-memory cache backend, in which case you need to set up some global setting which tells cache_get() to use that backend. As you can see, this can quickly become very complicated.

Injection

Drupal 8 uses constructor injection which can make this sort of code much easier to write. With constructor injection, the class constructor gets passed objects that it will need later. For example:

class SomeClass {
  protected $cache;
  function __construct(CacheBackendInterface $cache) {
    $this->cache = $cache;
  }
}

And then anywhere in the class you can just do $this->cache->get(). Now the testing framework can instantiate the in-memory cache backend and pass it to this class for testing.

That’s not too bad, but now everyone who wants to interact with SomeClass needs to create an instance of a cache backend class, which is not ideal. Instead, we say that SomeClass is a service that depends on other services like the cache, and then we create a container containing these services and their dependencies and call it a service container or dependency injection container. This container is described in a file called mymodule.services.yml. As the extension suggests, it’s a YAML file. YAML is a file format readable and writeable both by humans and machines, and used widely in Drupal 8. Here’s a typical services YAML file:

services:
  mymodule.some_service:
    class: Drupal\mymodule\SomeClass
    arguments: ['@cache.path’']

That’s quite a mouthful, isn’t it? Remember, in YAML, whitespace is important – it is used to mark structured data (like arrays) belonging together. The mymodule.services.yaml file contains an array of services where each definition is another array. The classname contains backslashes, which isn’t a YAML weirdness; it’s used for a namespace delimiter for PHP. We will get back to that. Now, the arguments are where the fun is – the @ means that what follows is the name of another service. When mymodule.some_service is used, then the container will run new Drupal\mymodule\SomeClass($container->get(‘cache.path’));. Here, cache.path is just the service name for the ‘path’ bin. All you need to know about this service is that it implements the CacheBackendInterface: whether it implements it by using SQL tables or just by discarding every write is immaterial.

Recommended reading: http://wdog.it/3/2/inject (although the bundles mentioned here are gone by now).

Namespaces

Back to namespaces, which are a PHP 5.3 feature. It's best to think of namespaces as folders for classes, with some very confusing notation. Let’s see why it’s so confusing:

namespace Drupal\mymodule;

use Drupal\Core\Cache\CacheBackendInterface;
…
class SomeClass {
  function __construct(CacheBackendInterface $cache) {
    $x = new Foo\Bar;
    $y = new \Foo\Bar;
  }
}

In the above code, $x will try to instantiate Drupal\mymodule\Foo\Bar, because that’s the namespace inside, and $y will try to instantiate \Foo\Bar, because it is fully qualified. The confusing part is when the use statement is not using a starting backslash, and yet it is fully qualified, so it refers to Drupal\Core\Cache\CacheBackendInterface. After this statement, as it is visible in the type hint of the constructor, CacheBackendInterface can be used in place of Drupal\Core\Cache\CacheBackendInterface. That makes working with namespaces easy, and the best practice calls for every class from other namespaces to be use’d on top of the file, so that the dependencies are clearly visible. In the example above, $y violates this.

Namespaces are folders in another way – there is a standard which maps the namespaced classes to directories, so that interface resides in core/lib/Drupal/Core/Cache/CacheBackendInterface.php. As you can see, there is a root (core/lib), and after it you can find the fully qualified name, one directory per part. For module code, right now we use core/modules/field/lib/Drupal/field/Annotation/FieldFormatter.php, but there is a serious initiative to shorten that to core/modules/field/src/Annotation/FieldFormatter.php.

Annotation

Speaking of annotations, this is another big, big change to Drupal 8. Drupal 7 has already used special constructors in comments to make it easier for sites like api.drupal.org to parse the documentation. Annotations are similar-looking, but they are processed runtime instead of an external parser, and are designed more for machines than for humans. Here’s an (abbreviated) example:

use Drupal\field\Annotation\FieldFormatter;
use Drupal\Core\Annotation\Translation;

/**
 * Plugin implementation of the 'text_plain' formatter.
 *
 * @FieldFormatter(
 *   id = "text_plain",
 *   provider = "text",
 *   label = @Translation("Plain text"),
 *   edit = {
 *     "editor" = "direct"
 *   }
 * )
 */
class TextPlainFormatter extends FormatterBase {

In Drupal 7, one would have used:

function text_field_formatter_info() {
  return array(
    'text_plain' => array(
      'label' => t('Plain text'),
      ‘edit' => array(editor’ => ‘direct’),
    ),
  );
}

Annotations have obvious disadvantages: they are not native code – so knowledge is much less – and integration support and error reporting are much worse. (Quick tips: use double quotes and never use a trailing comma.) However, it is necessary to provide metadata for classes, and the other ways are even worse: a YAML file could be used but that would be a separate file from the class. A method would make it impossible for static tools (like api.drupal.org) to reuse this information. In fact, Drupal core uses a static tool (from the Doctrine Commons project) for reading annotations – the source file is read as a file, the annotation is parsed, and then the file is gone. This would be impossible with a method because the file would need to be included as code and it’s impossible to “un-include” a file. This means a memory advantage for annotations. Thus, picking between various lesser evils, that one was chosen. Also, PHPUnit uses annotations, so it’s something people will need to learn anyway.

Drupal 8 Specifics

Configuration management

In Drupal 7, configuration management was an absolute mess. People were flinging data willy-nilly into the variables table, or defining their own tables, and so on. No wonder there was no working configuration deployment solution. (Nope, features does not fall under the “working” category.) In Drupal 8, all of this is completely gone. There’s a simple subsystem that handles all configuration, the service name is ‘config.factory’, and it can be used like this:

namespace  Drupal\my_module;

use Drupal\Core\Config\ConfigFactory;

class SomeClass {
  public function __construct(ConfigFactory $config_factory) {
    $this->configFactory = $config_factory;
  }
  public function foo() {
    $config = $this->configFactory->get('my_module.settings');
    $name = $config->get('foo.bar.name’);
  }
}

In this code, $config is a configuration object. The configuration object basically stores an array of configuration, the get method retrieves the value of a key, the set method sets the value of a key, and setData replaces the whole array. There’s a trick: get('foo.bar.name’) retrieves $config[‘foo’][‘bar’][‘name’], allowing easy manipulation of deep arrays.

The configuration system stores settings in YAML files – in this case in my_module.settings.yml. This file can be shipped by default in the my_module/config directory and when the module is installed it gets added to the active config directory; any consequent changes will be saved there. To edit configuration manually, or to deploy it across servers, copy the contents of this directory into the staging directory and run configuration import. Every module is using this subsystem so configuration deployment, manual editing, and translation become possible.

Plugins

In Drupal 7, info hooks were used to describe which function provided the plain text field formatter. Or the logic for unpacking ZIP archives. Or sorting Views by a timestamp. All of these and so much more are now provided by plugins. Basically, if you had a Drupal 7 hook that pointed to a callback with some additional metadata, then the callback would now be implemented as a plugin class and the metadata would be an annotation on that class. There are plugin types, for example: the TextPlainFormatter shown above is a FieldFormatter type plugin. Every plugin type has a plugin manager which is responsible for the discovery and the instantiating of the plugin. In Drupal 8, most plugin types use annotations for discovery. We have omitted the namespace for the TextPlainFormatter above; if we wanted to refer to it with its full namespace, it would be namespace Drupal\text\Plugin\field\formatter.

Breaking down this namespace into its individual components, you can see we are inside the text module and providing a plugin. This plugin is defined by the field module and the type is formatter. This flexible structure allows any module to define any number of plugin types (field also has field types and widgets) and to implement the plugin defined by any module – either our own or any other module. While previously a module implementing various formatters would need gigantic switches inside hook_field_formatter_prepare_view and hook_field_formatter_view, based on the formatter type, now it’s just a matter of providing a class per type. Much more tidy.

Recommended reading: http://wdog.it/3/2/plug

Routing

In Drupal 7, hook_menu provides routing, menu links, breadcrumbs, contextual links, local actions, and tasks. In Drupal 8, these have all been split out – mostly into plugins, except in the case of routing, where the metadata is provided by a YAML file:

views_ajax:
  pattern: '/views/ajax'
  defaults:
    _controller: '\Drupal\views\Controller\ViewAjaxController::ajaxView'
  requirements:
    _access: 'TRUE'

Previously, in Drupal 7, this was accomplished with:

   $items['views/ajax'] = array(
    'title' => 'Views',
    'page callback' => 'views_ajax',
    'access callback' => TRUE,
     'type' => MENU_CALLBACK,
    );

There is a lot more, of course, but this shows how routing changed. There’s a YAML file instead of hook_menu, and instead of a page callback, there is a method in a controller class to provide the page content.

For further information on converting a Drupal 7 hook_menu() implementation to the new routing system, read http://wdog.it/3/2/convert. Another set of handbook pages on the internals of the routing system are being prepared at http://wdog.it/3/2/router but at the time of publication it was very much a work in progress.

While there are a bewildering amount of changes in Drupal 8, hopefully this article will help by laying out the fundamentals. As the article was written before Drupal 8 has been released, there will be even more changes to come. You can find them at http://wdog.it/3/2/changes.

Image: ©IStockphoto.com/antishock

Comments

Great post! thanks, also I would add one recommendation, after to you understand how drupal 8 works, you can use the console scaffolding to generate all repetitive code.

https://www.drupal.org/project/console

Thanks

Advertisement

From our blog

Entity Storage, the Drupal 8 Way

In Drupal 7 the Field API introduced the concept of swappable field storage.

The Drupal 6 to 8 Upgrade Challenge - Part 2

Having concluded the readiness assessment, we turn next to migrating the content and configuration. In reality, there’s little chance that we would migrate anything but the blogs from our old site. For the sake of giving Migrate in Core a workout with real conditions, however, we’re going to upgrade with core’s Migrate Drupal module rather than rebuilding.

The Drupal 6 to 8 Upgrade Challenge - Part 1

Nathaniel Catchpole , the Drupal 8 release maintainer and Tag1 Senior Performance Engineer, suggested that Drupal shops everywhere could support the

DrupalCon Austin

The entertainment industry is not for the faint of heart.

Drupal Watchdog Joins the Linux New Media Family
Drupal Watchdog 6.01 is the first issue published by Linux New Media.

Drupal Watchdog 6.01 is the first issue published by Linux New Media. Come see the Drupal Watchdog team at DrupalCon 2016!

Drupal Watchdog was founded in 2011 by Tag1 Consulting as a resource for the Drupal community to share news and information. Now in its sixth year, Drupal Watchdog is ready to expand to meet the needs of this growing community.

Drupal Watchdog will now be published by Linux New Media, aptly described as the Pulse of Open Source.

Welcome to DrupalCon Barcelona - The Director's Cut

For all you schedule-challenged CEOs – and ADHD coders – this Abbreviated Official Director’s Cut is just what the doctor ordered.

Welcome to DrupalCon - The Barcelona Edition

Did we have fun in Barcelona?
OMG, yes!

Did we eat all the tapas on the menu and wash them down with pitchers of sangria?
Yes indeed!

Recursive Closures and How to Get Rid of Them

This came up while manipulating taxonomy children and their children recursively, so it’s as not far from Drupal as you’d think.