During our recent work on the GatherContent module, we received a feature request to allow other modules to modify the data we were saving. As this is not a very well known topic for non-contrib and non-core development, we decide to write a short blogpost about the different approaches in Drupal 7 and Drupal 8.

Drupal 7

Drupal 7 is based on a hook system. This basically means that your module can define some hooks which can be implemented by other modules. Before you define your hook, you have to think about whether someone is going to change your data before it is saved or if they just want to perform some actions on it after saving.

If you want to let a user change some data before saving, you need to call the drupal_alter function in your module. This function is defined in the includes/module.inc file as:
function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL, &$context3 = NULL)
The first parameter is the name of your hook, you can pass some data as reference in the other parameters. The type of parameter can be a string or an array of possible implementations, which is used by Form API in implementation of hook_form_alter and hook_form_FORM_ID_alter. The type in this case is array(‘form’,’form_’.$form_id).

In GatherContent, we implemented it this way: hook_gathercontent_pre_node_save_alter hook. In the code it looks this way:

  1. $node = entity_create('node', ['type' => $node_type_id]));
  2. ...
  3. drupal_alter('gathercontent_pre_node_save', $node, $content, $files);
  4. $node->save();

The other way of implementing the hook is by calling the module_invoke_all function. The difference between using drupal_alter and module_invoke_all is that you can pass values into module_invoke_all only as values, so you need to take care when saving your data, etc. In GatherContent we use this approach for hooks invoked after the node is saved, e.g. hook_gathercontent_post_node_save. In code it looks this way:

  1. $node = entity_create('node', ['type' => $node_type_id]));
  2. ...
  3. drupal_alter('gathercontent_pre_node_save', $node, $content, $files);
  4. $node->save();
  5. module_invoke_all('gathercontent_post_node_save', $node, $content, $files);

If your module exposes a lot of hooks, you should consider implementing hook_hook_info() as well. This hook allows you to define groups of hooks and a module implementing these hooks can place them into the $module_name.$group.name.inc file, making the whole module better structured.

Drupal 8

Related to Drupal 8 architectural changes, we can see a certain divergence from the usage of the hook system. Instead of hooks there is a new Event system adopted from Symfony.
The Event system basically consists of two parts, EventListener and EventDispatcher. EventDispatcher takes care of triggering all EventListeners.

For implementing your new event, you should create a class extending the \Symfony\Component\EventDispatcher\Event class. In this class you should implement a constructor, so you can pass values into your Event, and getters, so you are able to reach them in your EventListener. All parameters are passed as reference to Event.

In your module you can dispatch this Event with the core service event_dispatcher. This service implements the dispatch method, which takes the machine name of your event in the first parameter and the object of your event in the second parameter. In the GatherContent module it looks like this:

  1. \Drupal::service('event_dispatcher')
  2.           ->dispatch(GatherContentEvents::POST_NODE_SAVE, new PostNodeSaveEvent($node, $content, $files));

We store all our event identifiers in constants in the GatherContentEvents class with additional documentation of these events. The second argument is an instance of our event, code for this event can be found in the gathercontent/src/Event/PostNodeSaveEvent.php file.

If you want your module to have a reaction for this event, you need to subscribe for it. You do this by implementing the service in your module and in $your_module.services.yml you need to tag this service with the event_subscriber tag. Your class should implement \Symfony\Component\EventDispatcher\EventSubscriberInterface and you have to implement the getSubscribedEvents() function. In this function you simply return an array with events and methods, which will be called as a reaction to dispatching this events.

In Drupal 8 you can use still use the module_invoke_all approach by calling \Drupal::moduleHandler()->invokeAll(), but it’s recommended to use the Event system.

Generally, we think that the event approach is simpler to understand and very common outside the Drupal world. The only problem with events is that they’re more resource consuming, which might have an impact on performance.