Drupal 8: Creating and Subscribing to Events

Drupal 8: Creating and Subscribing to Events image

The introduction of the EventDispatcher Component has brought an interesting approach to how components of an application talk to each other in a clean and organized manner. Interestingly, this is what the hook system, which is as old as the Drupal project, was designed to do. From the documentation of Drupal 1.0:

The idea is to be able to run random code at given places in the engine. This random code should then be able to do whatever needed to enhance the functionality. The places where code can be executed are called "hooks" and are defined by a fixed interface.

In places where hooks are made available, the engine calls each module's exported functions. This is done by iterating through the modules directory where all modules must reside. Say your module is named foo (i.e. modules/foo.module) and if there was a hook called bar, the engine will call foo_bar() if this was exported by your module.

The arrival of the EventDispatcher component in Drupal 8 does not do away with the hook system. Instead, they work alongside each other. The component is based on

  • an event that will be dispatched by the application
  • an event listener that will execute a callable when the event occurs

In this tutorial, we are going to create an event and a listener for when the event is dispatched. Here is the problem we are going to solve. When a node is created, we want to notify the EventDispatcher system and log the title and author of the node.

In order to do this, we will create a custom module, an event, dispatch it in a hook_ENTITY_TYPE_insert implementation and subscribe to it so we can log the data we are interested in. It is possible to do the logging in our hook_ENTITY_TYPE_insert, but we want to demonstrate the co-existence of the old and new system as well as learn a couple of things about the EventDispatcher system. In addition, other modules can subscribe to the new event and listen for the event without implementing hook_ENTITY_TYPE_insert directly.

We assume that we have a custom module called event_subscriber_demo. See our article on Drupal 8 modules for details on how to create a Drupal module. Here is our event_subscriber_demo.info.yml file:

name: Event subscriber demo
type: module
description: Demo of creating and subscribing to events
core: 8.x
package: X-Team

Create an Event

We need a class that extends Symfony\Component\EventDispatcher\Event. Create src\Event\NodeInsertDemoEvent.php and add the following:

<?php

namespace Drupal\event_subscriber_demo\Event;

use Symfony\Component\EventDispatcher\Event;
use Drupal\Core\Entity\EntityInterface;

/** * Wraps a node insertion demo event for event listeners. */
class NodeInsertDemoEvent extends Event {

  const DEMO_NODE_INSERT = 'event_subscriber_demo.node.insert';

  /** * Node entity. * * @var \Drupal\Core\Entity\EntityInterface */
  protected $entity;

  /** * Constructs a node insertion demo event object. * * @param \Drupal\Core\Entity\EntityInterface $entity */
  public function __construct(EntityInterface $entity) {
    $this->entity = $entity;
  }

  /** * Get the inserted entity. * * @return \Drupal\Core\Entity\EntityInterface */
  public function getEntity() {
    return $this->entity;
  }
}

What this means is that we can only create an instance of NodeInsertDemoEvent with an entity object. And we can access the entity with the accessor getEntity() method. We also have a nice constant DEMO_NODE_INSERT when we want to refer to this event later.

Create a Listener

An event listener class must implement the EventSubscriberInterface with 2 mandatory methods:

  • getSubscribedEvents() which returns an array keyed by the event to listen to (NodeInsertDemoEvent::DEMO_NODE_INSERT) and a value of the name of a method to call (onDemoNodeInsert). We can leave the priority out and it will default to 0.
  • onDemoNodeInsert() which will take an instance of the NodeInsertDemoEvent class we created when the event is dispatched.

Let us go ahead and create the NodeInsertDemoSubscriber class and namespace it properly under src\EventSubscriber:

<?php

namespace Drupal\event_subscriber_demo\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\event_subscriber_demo\Event\NodeInsertDemoEvent;

/** * Logs the creation of a new node. */
class NodeInsertDemoSubscriber implements EventSubscriberInterface {

  /** * Log the creation of a new node. * * @param \Drupal\event_subscriber_demo\Event\NodeInsertDemoEvent $event */
  public function onDemoNodeInsert(NodeInsertDemoEvent $event) {
    $entity = $event->getEntity();
    \Drupal::logger('event_subscriber_demo')->notice('New @type: @title. Created by: @owner',
      array(
        '@type' => $entity->getType(),
        '@title' => $entity->label(),
        '@owner' => $entity->getOwner()->getDisplayName()
        ));
  }

  /** * {@inheritdoc} */
  public static function getSubscribedEvents() {
    $events[NodeInsertDemoEvent::DEMO_NODE_INSERT][] = ['onDemoNodeInsert'];
    return $events;
  }
}

Inside the onDemoNodeInsert() method, we get our entity and log the desired information.

Define a Service

This is where we inform Drupal (and the EventDispatcher) about the event we have subscribed to. Only an appropriately-named YAML configuration file, event_subscriber_demo.services.yml, is required and it is as follows:

services:
  event_subscriber_demo.node.insert:
    class: Drupal\event_subscriber_demo\EventSubscriber\NodeInsertDemoSubscriber
    tags:
    - { name: 'event_subscriber' }

The key is event_subscriber_demo.node.insert and apart from specifying the subscriber class, we tag it with event_subscriber for the service container.

Dispatch the Event

The event will be dispatched in the event_subscriber_demo_node_insert() function in the event_subscriber_demo.module file. Add this to it:

function event_subscriber_demo_node_insert(Drupal\Core\Entity\EntityInterface $entity) {
  // Dispatch the node insert demo event so that subscribers can act accordingly.
  \Drupal::service('event_dispatcher')->dispatch(NodeInsertDemoEvent::DEMO_NODE_INSERT, new NodeInsertDemoEvent($entity));
}

When a new node is created, the hook is fired and we dispatch it as an event. Our listener picks up the broadcast and runs the code to log the data as required.

Run it

Enable the event_subscriber_demo module. If you have already done so before writing the new classes, do a cache rebuild. Create a node. Check the logs at admin/reports/dblog.

Conclusion

The EventDispatcher component is not difficult to understand. We have explored it by creating a custom event, listener and dispatched the event. Hope this will inspire you to dig deeper into the subject and enable you to write more awesome code.

Code

The demo module for this article is in the X-Team Drupal 8 Examples Github repository.

KEEP MOVING FORWARD

Deji Akala / code