Hooks spec

Jump to: navigation, search

Moodle hooks/features (Proposal)

Discussion is welcome on the issue MDL-44078 and in forum: https://moodle.org/mod/forum/discuss.php?d=254508

Summary

Allow plugins to substitute or extend some core code where change is not big enough to create a new plugin type but we want to allow plugins interactions. Create management UI where admin can see the list of all available hooks/features definitions and available implementations, enable/prioritise implementations.

Current solutions

  • Defining functions in /plugindir/lib.php with the name <pluginname>_<hookname> and querying all plugins if somebody implements function and/or list of plugins implementing it. *This is the current implementation of hooks.* Disadvantage: functions must be defined in lib.php therefore they are always included even when function is not there (performance loss); no UI to configure if only one plugin can implement feature; difficult for developers because they don't know if they can or how they can implement a hook (no separate place for documentation).
  • Events system. The core notifies whoever wants to listen to it about something that happens. Disadvantage: one-way communication, no feedback. Advantage: plugins cannot break important core functionality.
  • $CFG variables. Disadvantage: they should be used for settings and not for including the 3rd party code
  • Creating a new plugin type. Disadvantage: very long process, involves lots of documentation, creating lots of strings and UI, may be available in major releases only. Also not applicable for minor features.
  • Renderers. These can be used to override a lot of standard Moodle behaviour, particularly on the view side. Only can be overriden by themes

Implementation

Very similar to Events API but with much less restrictions and more flexibility

  • Hook definition is a class stored in /classes/hook/ folder (of core component or plugin). They have a function execute() that calls all registered callbacks and passes the actual instance of the hook definition class to them.
  • Plugins that want to register hook callbacks do so in /db/hooks.php the same way events do
  • Hook manager, very similar to event manager, is responsible for caching of the list of existing hooks and their callbacks. It also has an interface with list of all hooks and callbacks.
  • Hook callbacks are called recursively in a way similar to legacy events.

Example

Typical example is a hook that allows plugins to add fields to the existing form. Note that actual hook classes may be much more complicated, with lots of methods that can be called from callbacks and also with properties/methods that can be called by the component who executed the hook to access the collected data from plugins.

0. Base class does almost nothing, it's just a suggestion for implementation, methods are not final (unlike events) /lib/classes/hook/base.php

namespace core\hook;
abstract class base {
    private $isexecuting = false;
    public function execute() {
        if ($this->isexecuting) {
            throw new \moodle_exception('Already executing');
        }
        $this->isexecuting = true;
        \core\hook\manager::dispatch($this);
        $this->isexecuting = false;
        return $this;
    }
}

1. /lib/classes/hook/courseresetform.php

namespace core\hook;
public class courseresetform extends \core\hook\base {
    private $mform;
    public function __get($name) {
        if ($name === 'mform') {
            // Mform is a read-only property.
            return $this->mform;
        }
    }
    public static function create($mform) {
        $hook = new self();
        $hook->mform = $mform;
        return $hook;
    }
}

2. /course/reset_form.php, function course_reset_form::definition() at the moment this function looks for function xxx_reset_course_form_definition() in all mod plugins and call this function passing $mform. It will continue doing so for backward compatibility but also it can call:

\core\hook\courseresetform::create($mform)->execute();


3. Now any plugin type and not only module but also blocks (existing issue MDL-24359) can register their callbacks in /blocks/myblock/db/hooks.php.

$callbacks = array(
    array(
        'hookname'    => '\core\hook\courseresetform',
        'callback'    => array('block_myblock_hook_callbacks', 'courseresetform'),
        //'includefile' => '/optional/file/path.php',
    ),
);


4. Actual callback implementation from /blocks/myblock/classes/hook_callbacks.php:

class block_myblock_hook_callbacks {
    public static function courseresetform(\core\hook\courseresetform $courseresetformhook) {
        $courseresetformhook->mform->addElement('checkbox', 'Reset contents of block Myblock');
    }
}

Use cases

  1. Navigation: we want to allow any plugin to put own items anywhere
  2. Recent activity in course
  3. Course search: we have quite primitive course search algorithm in core. Some other plugin may implement search with sorting by relevance, analysing tags, going through attached pdf files, etc. Hook implementation returns the list of courses and core goes back to the core/theme renderers to display this list
  4. Global search: similar to above but result will be list of title+url+abstract
  5. Front page: We have $CFG->customfrontpageinclude for including a file displaying frontpage contents. It could be also hook/feature
  6. Extending mimetypes list
  7. Extending licenses list
  8. Adding fields to the standard forms. This may be a comprehensive feature that includes several functions - adding fields, validation, processing.

Comparison to new events

Differences:

  • events are one way communication (from one to many) - hooks are two way communication (usually many to one)
  • event data must not be modified - hook callbacks may alter the hook instance data
  • events are triggered after actions - hooks are executed usually before actions
  • event structure is fixed - hooks can be arbitrary classes that extend the base
  • event observers receive the event in chronological order - hook callbacks are called recursively
  • database transaction may cause event buffering - hook manager does does a simple look when executing callbacks
  • all events are logged - hooks are not logged

Similarities:

  • the definition of event and hook is similar - one class per item with fixed namespace rules
  • the structure of db/events.php and db/hooks.php is similar
  • the creation of hook and event instance may be similar