Porting a module to Drupal 8

Tags: Tech Blog Published:

Following on from DrupalCon Amsterdam and the launch of Drupal 8 beta, we're starting to put Drupal 8 through its paces. Part of that process will be taking contributed modules that we maintain and moving them across to work with Drupal 8. This blog post looks at the first module we ported - login tracker. Login tracker is a fairly straightforward module that tracks user logins to a site and makes the information available to views for reporting. It's a fairly simple module with no real user-interface, or configuration, so makes for an easy first module to port. That does mean though that this post doesn't talk about Twig, or the Configuration Management system in Drupal 8 - two big areas of change. Note: This post isn't intended as an exhaustive guide - it's just a record of our experience porting a module.

Where to start?

Drupal 8

There's a pretty good guide on porting a module from 7 to 8 available on drupal.org and you'll find the change records invaluable as documentation on specific changes.

For this article, we'll step through the changes we made to get the Drupal 8 version of the login_tracker module up and running, referencing the commit history from Drupal.org where applicable, and the relevant change records as well so you can find out more about the changes, and how they might apply for your modules.

1. Making an installable module

The first steps are to actually get Drupal to recognise your module. In Drupal 8 modules are stored under the root modules/ folder rather than under sites/all/modules, so you'll need to place your module in the right place.

To get your module to show up, your module's .info file also needs to be updated (commitchange record):

  • It'll need converting to YAML format
  • To reflect the format change, it should also be renamed mymodule.info.yml rather than mymodule.info
  • The core version should be updated from 7.x to 8.x
  • A new entry type: module needs to be added

So, for login tracker, we went from:

name = Login tracker description = Track user logins core = 7.x

to

name: Login tracker description: Track user logins type: module core: 8.x

At this point, your module should show up on Drupal's module screen - yay!

You may find that activating your module doesn't work because you're using functions that no longer exist - or you may find your module activates, but doesn't do anything.

In our case it was definitely the former - so, our next step was to go through the module code and check whether there was an alternative, or better way to achieve the feature in Drupal 8.

2. Moving permissions to YAML, and removing hook_permission() implementation

We worked through our code (mostly) top-to-bottom - depending on the size or complexity of your module you may need to work through errors as they're thrown.

The first code we looked at was an implementation of hook_permission() - used to define a permission that can be granted to users. In Drupal 8, permissions can now be defined in YAML files. This has the advantage that the code for them isn't being processed on every single page load, so it's definitely a good thing.

So - we removed our hook_permission() implementation, and replaced it with a login_tracker.permissions.yml file (commitchange record). Our permissions file looks like this:

excluded_from_login_tracking: title: 'Excluded from login tracking' description: 'Assign this permission to roles, and any logins by users of that role will not be tracked.'

3. user_access() is no more

The user_access() function to check whether a user has a particular permission has been replaced in Drupal 8 as part of moving to a more object-oriented model (change record). So - we updated our module to use $account->hasPermission instead of user_access() (commit).

4. hook_user_login() signature changes

The main functionality of the login tracker modules happens on hook_user_login().

This hook still exists in Drupal 8, but instead of taking two arguments, it now only accepts one argument - the account object that is logging in. This also flowed through to some other places in the module which also needed updating to only take the single argument. (main commitfollow on).

5. drupal_alter() is dead, long live \Drupal::moduleHandler()->alter()

drupal_alter() is one of my favourite Drupal functions.

It's also something I always encourage people to think about whether adding to their code will make it more extensible. It's unsurprising then that the login_tracker module has a few well placed calls to drupal_alter() so that it can be extended by people who want it to work slightly differently.

In Drupal 8, this has been replaced by \Drupal::moduleHandler()->alter() (change record). This actually affects all of the hook/alter functions, e.g. module_invoke_all(), module_implements() etc.

Aside from changing the function you're calling though, there aren't any big changes to how this actually works, our changes were pretty simple, so instead of:

drupal_alter('login_tracker_track_login', $track_login, $edit, $account);

the module now calls:

\Drupal::moduleHandler()->alter('login_tracker_track_login', $track_login, $account);

(commit)

6. drupal_write_record() is no more

drupal_write_record() used to be a simple way to insert a record into a database table.

However, it was by no means the only way, and it's been removed in Drupal 8 in favour of using other existing APIs. In Drupal 8 you'll probably want to use the merge() method of the main database service (although there are other options depending what you're doing). Our commits here probably aren't that helpful since we did this in a couple of steps and changed the table structure along the way. The change record gives a nice clear example though - especially if you were familiar with merge queries in Drupal 7.

7. Views integration

One of the main reasons for our login_tracker module was to have the data made available to views. In Drupal 7 this required us to implementhook_views_api() and hook_views_data().

In Drupal 8, the hook_views_api() call is no longer required (commitchange record). Your views.inc file which traditionally stores yourhook_views_data() implementation can no longer live in a sub-directory of your module, it must live in the main module folder.

There are also quite a few changes to the format of the data that you need to return from hook_views_data(). Our changes are were in this commit. Some of these were cosmetic string changes, but some are definitely required to get things working. This particularly affects how handlers are defined for various data types.

There's no change record for this (presumably since views wasn't part of core in Drupal 7), however hook_views_data is already documented for drupal 8, with a well-commented example.

Conclusion

That was all we needed to do to get a working d8 release, although it really is a simple module. My overall impression of the process is that the code and APIs are more consistent, and predictable, especially if you've worked with OOP before. The Change Records are a great resource for finding out how to modify certain functions or approaches - a huge thanks to everyone who's worked on pulling them together and publishing.