Upgrading Drupal’s Viewport module to Drupal 8 (Part 2)

Drupal 8 logo

In the last blog post, I started to talk about the process of upgrading the Viewport module from Drupal 7 to Drupal 8. While the module is now fully ported and has a stable version for Drupal 8, I didn’t cover all the steps and new things that I came across while working on it.

This time I’ll go through the notes I took while working on the settings page, the creation of a proper schema for configuration management, and the conversion of procedural code into PHP classes. Let’s get into it:

The Viewport settings page

Most of the work I had done a year ago was still valid for the settings page, yet I had to change some details to get it fully working. Defining a path for it was trivial, I just needed a viewport.routing.yml file, most of which was already complete from the last time I worked on the module:

One of the first issues I came across was the site not recognising my settings form class under “lib/Drupal“. How annoying! Fortunately, on this one I already knew that class autoloading had changed months ago from PSR-0, to use the PSR-4 standard. The fix was trivial, as described in the change record, as it only involved moving the class to the “src” folder.

Another small change was in the class constructor. My old implementation was making use of it to set up a property to hold the viewport settings. Since my old settings class extended core’s ConfigFormBase class, the construct method was using a ContextInterface object, but this wasn’t available anymore because ConfigFormBase constructor changed some time ago as well, and now it only receives a ConfigFactoryInterface instead.

That led to some fatal errors when accessing the settings page, so I decided not to override the __construct() method at all, as I didn’t really need it. However, due to Drupal 8 using the Factory pattern and late static binding to instantiate this class, it’d be quite simple to override this by simply implementing a create() method, which will receive a ContainerInterface object, which can be used to pass all the dependencies to the form constructor, like this:

Once the form was working again, I needed to get hold of the viewport settings in order to read their current values, and to write the new ones when saving the form. In order to access a settings object from a configuration form, a getEditableConfigNames method has to be implemented. In my case, it was as simple as this:

“viewport.settings” is the machine name used to keep the configuration settings of the module. With this in place, I was able to read the configured values by simply doing this:

$this->configFactory()->get(‘viewport.settings’).

To change any of the stored values, though, the syntax is different:

$stored_settings = $this->configFactory()->getEditable(‘viewport.settings’)

That can be used to retrieve a settings object that can be written to, and a setting can be changed by simply doing this:

$stored_settings->set(‘some_setting’, ‘my_value’);

Some other findings and notes taken while working on the settings form:

  • $form_state now is an object, which implements

    FormStateInterface.

  • element_children() has been moved to Element::children().

  • The ability to get the editable settings object, is provided by the ConfigFormBaseTrait trait, which is used by ConfigFormBase.

  • form_set_error() has become $form_state->setErrorByName().

  • The main element that links the route to the form class in viewport.routing.yml, is the _form key.

Providing menu links for the settings page

In Drupal 8, the menu system has been completely overhauled, and now it works as different systems. Hook_menu is no more! Luckily for me, most of the work required was already done for the settings page, as I had already declared a route for it.

For the Drupal 8 version I decided to place the settings page under “Appearance”, which means my menu link would be a local task. To do that, I only needed to define a viewport.links.task.yml file, like this:

Note the “base_route” key. This is needed for local tasks. However, for menu links a “parent” key is needed instead. This confused me a bit at first sight.

Configuration Management

There wasn’t much that I had to do in order to make the module expose its settings to the Drupal 8 Configuration API. A simple viewport.settings.yml file would have been enough, and that’s what I was using on the earlier version. However, I wanted to define a proper configuration schema for it, so the first step was to learn what the heck was a configuration schema in Drupal 8.

After some simple changes, I ended up with two files. One to provide default settings when installing the module, and the other (the configuration schema), to tell Drupal the type of data stored by each one of those settings.

For default settings, I just had to create config/install/viewport.settings.yml:

And for the configuration schema, config/install/viewport.schema.yml:

As with most aspects of Drupal 8,  there’s also a coding standard for configuration files, which is well worth a read.

Refactoring procedural code into PHP Classes

With most of the module finished (except for tests), there was still some old procedural code in place. While there’s nothing wrong with that, the Drupal class suggests to move that kind of code to an object that implements the actual logic:

This class exists only to support legacy code that cannot be dependency
injected. If your code needs it, consider refactoring it to be object
oriented, if possible. When this is not possible, for instance in the case of
hook implementations, and your code is more than a few non-reusable lines, it
is recommended to instantiate an object implementing the actual logic.

So, while not strictly necessary, I decided to define a ViewportResolverInterface interface, to specify what is expected of any class to be considered a ViewportResolver. Essentially, the key methods are two:

  • isPathSelected: To decide whether a given path is meant to have a custom viewport tag.
  • generateViewportTagArray: To generate the array that would be used as a page attachment.

With the interface in place, I decided to make the viewport resolver an actual service, so that anyone interested in replacing the default class, can do it easily. For that, I just needed a viewport.services.yml file in the module root:

With this in place, and a default ViewportResolver class implementing the logic that was written as procedural code, all I needeed to do was implement hook_page_attachments_alter(), and get Drupal to return the right service class for the viewport.resolver service, like this:

And then call the relevant methods to generate (if needed) the custom viewport tag array and include it in the page attachments. This simple refactoring was also done so that the custom logic to figure out if a page requires a custom viewport and to generate the tag array, could be unit tested. But that will be covered in the next (and final) post of the series.

Some of the links I went through while working on the sections above:

Form API in Drupal 8: https://www.drupal.org/node/2117411.

Providing module-defined menu links: https://www.drupal.org/node/2122241.

Configuration file coding standards: https://www.drupal.org/coding-standards/config.

Provide default config in a Drupal 8 module: https://www.drupal.org/node/2087879.

State API in Drupal 8: https://www.drupal.org/developing/api/8/state.

Config Storage change record: https://www.drupal.org/node/2241059 (File-based to DB).

Services and DI in Drupal 8: https://www.drupal.org/node/2133171.

Service Tags: https://api.drupal.org/api/drupal/core%21core.api.php/group/service_tag/8.2.x.

Issue on documenting service definition tags: https://www.drupal.org/node/2264047, and https://www.drupal.org/node/2745947.

Service Container definition change record (PHP Storage to DB Storage): https://www.drupal.org/node/2540430.

Drupal’s Service Container and PHP array dumper (compatible with Symfony): https://www.drupal.org/node/2540408.

PSR-4 Namespaces and autoloading in Drupal: https://www.drupal.org/node/2156625.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">