Author image
Developer

Drupal 8 Event Subscribers - the successor to alter hooks

In Drupal 7 if you wanted to tweak the functionality of an existing function then an alter hook was your best friend, but in Drupal 8 it's "all change!"

With the introduction of Symfony, your new BFF is an Event Subscriber. Alter hooks still exist, but it is recommended that we all move towards Events to fall in line with Symfony.

If you are interested in the comparison between alter hooks and events then I recommend this article from PreviousNext.

Introduction

In just 4 simple steps, this article will talk you through the process of writing a custom Event Subscriber linked to an existing event (N.B. writing an Event Dispatcher will not be covered here).

A lot of my Drupal 8 work so far has revolved around migrations so my code snippets will focus on subscribing to migration events.

Step 1: Create Subscriber file

Following the precedent set by core, create a meaningfully titled .php file in modules/custom/my_module/src/EventSubscriber. In my case the file is MigrationSubscriber.php.

In this new file create a class (with the same name as the file) which implements EventSubscriberInterface, e.g.:

<?php

/**
 * @file
 * Contains \Drupal\my_module\EventSubscriber\MigrationSubscriber.
 */

namespace Drupal\my_module\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Code to run in conjunction with migrations.
 */
class MigrationSubscriber implements EventSubscriberInterface {

  /**
   * [email protected]}
   */
  public static function getSubscribedEvents() {

  }

}

Remember to include the @file doxygen, namespace and a short, but descriptive comment before the class.

The getSubscribedEvents() is a requirement of EventSubscriberInterface.

Step 2: List subscribed events

The getSubscribedEvents() method needs to return an array of method names keyed by the event names, e.g.

public static function getSubscribedEvents() {
  $events[MigrateEvents::PRE_IMPORT] = 'onMigrationPreImport';
  $events[MigrateEvents::POST_IMPORT] = 'onMigrationPostImport';
  return $events;
}

In this example when the MigrateEvents::PRE_IMPORT (which is the string constant 'migrate.pre_import') event is dispatched the onMigrationPreImport()will run, etc.

If you are using the MigrateEvents constants (PRE_IMPORT, POST_IMPORT, etc.) then you will need to add the necessary use statement at the top of the file, outside the class:

use Drupal\migrate\Event\MigrateEvents;

Further details about alternative return values of the getSubscribedEvents() method can be found in the doxygen of the EventSubscriberInterface.

Step 3: Add a function for each subscribed event

Of course, now we have assigned methods to the event names we must write those methods, e.g.:

/**
 * Code to run before a migration has been imported.
 */
public function onMigrationPreImport(MigrateImportEvent $event) {

}

/**
 * Code to run after a migration has been imported.
 */
public function onMigrationPostImport(MigrateImportEvent $event) {

}

To find the variable type of the parameter(s) of these methods look for the code that dispatches the event, e.g. the following code dispatches the PRE_IMPORT event:

$this->getEventDispatcher()->dispatch(MigrateEvents::PRE_IMPORT, new MigrateImportEvent($this->migration));

Step 4: Register your new service

Your new service will need to be registered with the module. This is done by adding an entry to the module's .services.yml file which can be found at the root of the module. If you are creating a custom module then it is quite likely that this file does not yet exist, if not create my_module.services.yml (where my_module should be replaced with the name of your module). Your new entry should look something like this:

services:
  my_module.migration_subscriber:
    class: Drupal\my_module\EventSubscriber\MigrationSubscriber
    tags:
      - { name: event_subscriber }

Make sure your service has a meaningful (and unique) id, in our case it is migration_subscriber. Note that the service is tagged as an event_subscriber.

And that is all there is to it! As ever, remember to clear caches having written the code. Next time the appropriate event is dispatched your custom method will be run.