26

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?

1
  • To me it always seems like hooks are the old procedural way and events are more oop. Also I'm not a fan of using the languages function registration as a part of my application's state. I don't know if hooks always do this but they generally check the existence of a function definition to know what to run. The language will be like "I gave you arrays, lists, dictionaries, sets, tuples and you decided to keep track of a list of your hooks on my function registry? I'll remember this when machines take over". Yeah, I'm not even a small fan.
    – timuçin
    Mar 14, 2022 at 7:55

3 Answers 3

20
+100

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.

2
  • 2
    +1 for the alternatives, -1 for the definitions and the coupling argument (which does exist but coupling is a consequence of design choices, whichever name you give to your plugin system)
    – user44761
    May 7, 2014 at 5:58
  • 8
    I think you are also making assumptions about how an event travels from the generator to the observer/listener. As a matter of fact it is the reverse, hooks are tightly coupled whereas events are not. May 12, 2014 at 15:55
3

Definitely events, it allows for the necessary abstraction already on the architectural level.

Do not expect that anyone writing a plugin actually does so as documented or in any way correct. I have been maintaining a well-documented API with millions of users and I can tell you from a very painful experience that basically no one ever reads the documentation and almost no one uses the API correctly.

Take the following example with hooks: You have a system where 20 plugins are running. One of those plugins calls the file_copy method in the way it is documented and expects a result as documented. But some other plugin has hooked that function and therefore one of the following issues causes a crash or malfunction:

  • The hook function simply crashes. All other plugins are screwed now, because they can no longer file_copy or the function works differently from what is expected.
  • The input is correct based on the documentation, but the other plugin does not expect it and delivers strange results or crashes.
  • The call runs fine, but the result is no longer what is expected according to the documentation so the plugin fails or crashes.

If you do the same as above with events with the same issues inside those plugins the following happens:

  • The event function of plugin X crashes, but all others work fine. But since those plugins are not related, you can simply disable that crashing plugin while the others continue to work fine.
  • Strange input can be properly handled by your function and you can properly check for all possible things for each plugin individually. The plugin developer now has a stable and reliable way of actually testing his plugin, which allows him to be sure that if it works for him it will work for everyone. If one plugin provides incorrect input, it can be isolated to that one plugin.
  • Equally the result can be checked and defined properly under all circumstances, so the plugin developer has a stable and reliable answer from that function that he/she can test.
0

Definitely events. It allows your architecture be more wide-scalable.

Imagine what will happen if you'll need to place your plugin to a separate machine for example. Using events - you'll need just to modify a small peace of code to make your events network based.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.