Plugins – Should Plugins Use Hooks, Events, or Alternatives?

event-programminghooksmaintainabilitypluginsreadability

Consider an app that allows plugins to react to its program flow.

I know 2 ways to achieve this: hooks and events

1. Hooks

Use calls to empty functions inside the main program flow. These functions can be overriden by plugins.

For example, Drupal CMS implements hooks which are available to modules and themes. Here's an example of how hook is implemented in a file_copy function.

function file_copy(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
    // ... [File copying routine]

    // Inform modules that the file has been copied.
    module_invoke_all('file_copy', $file, $source);

    return $file;
    // ...
}

A module can implement a modulename_file_copy($file, $source) function which will be called by the module_invoke_all in file_copy. After this function finishes, the file_copy will resume execution.

2. Events

Have the app dispatch events, which can be listened to by the plugins. After receiving an event that it's been subscribed to, a plugin will intercept the program flow and perform neccessary operations.

For example, a jQuery gallery plugin Fotorama implements several events. As an example, here's a part of its show method that fires the fotorama:show event.

  that.show = function (options) {
    // ... [show the new frame]

    // [fire the event]
    options.reset || triggerEvent('show', {
      user: options.user,
      time: time
    });

    // ... [do lots of other stuff with navigation bars, etc.]
  };

A script can listen to this event and do something when it fires:

$('.fotorama').on(
  'fotorama:show',
  function (e, fotorama, extra) {
    console.log(e.type + (extra.user ? ' after user’s touch' : ''));
    console.log('transition duration: ' + extra.time);
  }
);

QUESTION

  1. Are there other mainstream ways to implement such plugin behaviour?

  2. If not, when should one use hooks, and when should one use events? Considering the ultimate goal is to make the code more maintanable and readable, from both the app and the plugin developer's perspective?

Best Answer

The main difference between a hook and event is loose coupling versus tight coupling.

A hook is a generic way to broadcast that something has happened. You can add new hooks without having to recompile plugins, and all hooks follow a generic design pattern. Once the hook API is defined it doesn't change so the coupling between the app and plugin isn't likely to break.

Events are more tightly coupled to the app. Events can define parameters that are attached to the event, and if you change those parameters you break the API with existing plugins.

They both achieve the same results. It just depends upon how you want to couple the plugin to the app.

Hooks can offer you a more dynamic coupling that isn't likely to break as new versions of your app are released, but the disadvantage is you don't get any compile time warnings that the plugins no longer are compatible.

Events offer you the ability to get compile time errors that the plugin needs to be modified, because some of the event signatures have changed.

You asked for alternative approaches.

Commands:

Instead of plugins responding to triggered events. Plugins push command objects to the application. Each command object implements an interface used by commands. When the application needs to execute a feature it runs all the commands for that feature. This is very much like events, except that it's implemented as objects instead of callback functions.

Macros:

Instead of plugins responding to when things happen. Plugins proactively cause things to happen. A macro is a small high-level language that runs above the application telling it what to do.

State Change Listeners:

Events are triggered by the application with forethought by the developer. The developer has to knowingly write code that issues the event. Instead, an alternative approach is to make objects automatically broadcast when their internal state has changed. Either the changing of a property or other indicators. Plugins can then listen for these specific state changes and react accordingly. The advantage of this approach is that the programmer does not have to remember to broadcast events. For example, there might be an Document object and the programmer sets a flag to mark that the document needs to be saved. This state change is broadcasted to listening plugins, and there could be a plugin that changes the document title to include an asterisk.

Related Topic