Hooks::run() was soft-deprecated in Nikki Nikkhoui's HookContainer
patch, merged on April 17. [1] And my patch to remove almost all
instances of it in MediaWiki Core was finally merged over the weekend.
[2] That means that everyone writing core code now needs to learn how
to use the new hook system.
HookContainer is a new service which replaces the functionality which
was previously in static methods in the Hooks class. HookContainer
contains a generic run() method which runs a specified hook, analogous
to Hooks::run(). However, unlike Hooks::run(), you generally should
not use HookContainer::run() directly. Instead, you call a proxy
method in a hook runner class.
Hook runner classes give hooks machine-readable parameter names and types.
How to call a hook
------------------
In MediaWiki Core, there are two hook runner classes: HookRunner and
ApiHookRunner. ApiHookRunner is used in the Action API, and HookRunner
is used everywhere else.
How you get an instance of HookRunner depends on where you are:
* In classes that use dependency injection, a HookContainer object is
passed in as a constructor parameter. Then the class creates a local
HookRunner instance:
$this->hookRunner = new HookRunner( $hookContainer );
* In big hierarchies like SpecialPage, there are always
getHookRunner() and getHookContainer() methods which you can use.
* Some classes use the ProtectedHookAccessor trait, which provides
getHookRunner() and getHookContainer() methods that get their
HookContainer from the global service locator. You can also call
MediaWikiServices::getHookContainer() in your own code, if dependency
injection is not feasible.
* There is a convenience method for static code called
Hooks::runner(), which returns a HookRunner instance using the global
HookContainer.
* Extensions should generally not use HookRunner, since the available
hooks may change without deprecation. Instead, extensions should have
their own HookRunner class which calls HookContainer::run().
Once you have a HookRunner object, you call the hook by simply calling
the relevant method.
How to add a hook
-----------------
* Create an interface for the hook. The interface name is always the
hook name with "Hook" appended. The interface should contain a single
method, which is the hook name with a prefix of "on". So for example,
for a hook called MovePage, there will be an interface called
MovePageHook with a method called onMovePage(). The interface will
typically be in a "Hook" subnamespace relative to the caller namespace.
* Add an "implements" clause to HookRunner.
* Implement the method in HookRunner.
Note that the name of the interface is currently not enforced by CI.
Alphabetical sorting of interface names and method names in HookRunner
is also not enforced. Please be careful to follow existing conventions.
How to deprecate a hook
-----------------------
Hooks were previously deprecated by passing options to Hook::run().
They are now deprecated globally by adding the hook to an array in the
DeprecatedHooks class.
Using the new system in extensions
----------------------------------
Extensions should create their own HookRunner classes and use them to
call hooks. HookContainer::run() should be used instead of Hooks::run().
As for handling hooks, I think it's too early for a mass migration of
extensions to the new registration system as described in the RFC.[3]
Extension authors who are keen to pilot the new system can give it a
go. Make sure you add Nikki and me as reviewers.
More information about the new system can be found in docs/Hooks.md
[4]. The patch to add it should soon be merged.
[1]
https://gerrit.wikimedia.org/r/c/mediawiki/core/+/571297
[2]
https://gerrit.wikimedia.org/r/c/mediawiki/core/+/581225
[3]
https://phabricator.wikimedia.org/T240307
[4]
<https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/core/+/323ac073d38ec30a97b73b4a25999079b3a125d3/docs/Hooks.md>
-- Tim Starling