Wikimedia Technical Committee/Extension point guidelines

This guideline governs the creation of new extension points in MediaWiki. An extension point is an interface that allows an Extension to modify and extend the functionality of MediaWiki core. There are several distinct types of extension points, among which the most important ones are Hooks, Services, and Handlers.

Since extension points act as an interface between software components that are released separately and may be maintained by different entities, they need to be stable. In order to be stable, they need to be narrow and well defined, so they isolate extensions from implementation details, and help to avoid breakage when these implementation details change. This reflects the software engineering principle idea of information hiding and the Open–closed principle.

In the past, extension points have often be created without much consideration for the need of stability and isolation. The purpose of this guideline is to improve that situation. This improvement benefits both, extension authors and maintainers of MediaWiki core: extension authors get clear guarantees about how MediaWiki behaves and interacts with the extension. And MediaWiki maintainers gain flexibility to change the software's internals without having to watch out for breaking extensions.

The sections below provide guidance for the creation of different types of extension points.

Hooks
NOTE: This section is based on a rough summary of the RFC for evolving the hook system, which is still under discussion as of August 2019.

Hook handlers are simple callback functions that get triggered by hook points in the MediaWiki code. Extensions can register hook handlers to modify the behavior of the code that contains the hook point. The hook itself is just a contract about what a handler for that hook can expect and achieve.

New hook points should follow the following principles:


 * The hook's name and contract should reflect what it is intended to to, not when and where it is called. It is preferable to trigger hooks immediately one after the other, each with a distinct purpose, rather than creating a single hook that covers all needs. It's also admissible to trigger the same hook from multiple hook points, though this should be done with caution.
 * Hooks should either notify extensions about events ("actions"), or allow extensions to modify data ("filters"), bit never both. Handlers for action hooks cannot influence the ongoing execution path, and may even be called asynchronously long after the actual event has happened. Handler for filters should only modify data it gets as a parameter, in the way allowed by the hook's contract.
 * Hooks should receive a minimal set of parameters, with each parameter exposing the most narrow interface possible. All parameters not explicitly declared to be modifiable by filters should be treated as immutable. One important consequence is that hook points should almost never be passing $this to hooks. Doing so makes it impossible to refactor the code in a way that would move the hook point to a different class.
 * Hook handlers should generally not use type hinting. This allows them to be called using compatibility shunts instead of real objects, providing additional flexibility for modifications to MediaWiki core.

TBD: create and reference best practices for using dependency injection with hook handlers.

Handlers
Handlers are the primary way to add support for new specializations to MediaWiki: new API modules, new content models, new special pages, etc. A handler can provide complex functionality specialized for a purpose in a more concise than what would be possible with hooks alone.

Technically speaking, handlers are objects implementing a specific interface (by subclassing, see below) in a way specialized for some purpose identified by a name. Examples include ApiModule (by module name), SpecialPage (by page name), ContentHandler (by content model), SlotRoleHandler (by slot role), REST route handlers (by route), etc.

Handlers are typically managed by a registry, which should be managed as a service in MediaWikiServices. A registry is a type of factory that can be used to obtain the specialized implementation of a given type of handler for a given name. For instance, the SpecialPageFactory provides the correct SpecialPage object for a given page title, the SlotRoleRegistry provides the correct SlotRoleHandler implementation for a given slot role, etc.

Handler objects should behave idempotent in isolation: calling the same methods with the same parameters should always yield the same result, unless the system's state has been modified by other code in the meantime.

Each registry is responsible for one type of handler, and all handlers of a type implement a common interface (see below). There are two possible patterns for allowing extensions to register their own handler implementations with the registry:


 * The registry has a method that allows new handlers to be specified (by providing an instance, an instantiator callback, or an object spec for use with ObjectFactory). The extension uses the  hook to call    with a callback that calls the appropriate method on the registry.
 * The registry takes a list of wiring files as a parameter, with each wiring file containing instantiators or object specifications for the handlers, to be used with a ServiceContainer or ObjectFactory. The wiring for the registry triggers a filter hook that extensions can use to add to the list of wiring files. Instead of files, the wiring may also be passed as an array or object.

Handlers are extension points by nature. All types of handlers should be based on an abstract base class that defines their shared interface -- a proper PHP interface may also be defined, but should never be used directly by extensions when defining their own implementations. The extension should subclass the abstract base class instead. This approach allows the interface of the Handler to change over time, new methods to be added and old methods to be deprecated, while the base class provides compatibility stubs.

The reason for using abstract base classes is to make them more future proof. If extensions were implementing PHP interfaces directly, there would be no way to change that interface. Instead, a new interface would have to be created, and all callers would need to be able to handle both, or handler objects would need to be wrapped.

Services
Service objects are (pseudo)singletons managed by a ServiceContainer (typically MediaWikiServices). They provide abstractions for all essential functionality, encoding storage logic, application logic, etc.

Service objects should be (pseudo)stateless, or more specifically, behave idempotent in isolation: calling the same methods with the same parameters should always yield the same result, unless the system's state has been modified by other code in the meantime.

Some services may be declared to be extension points that can be replaced or wrapped by extensions. Extensions will typically do this by registering a handler to the  hook which gets triggered when all wiring has been defined. In the hook handler, the extension can then manipulate, wrap, or replace services using the  method on the ServiceContainer. Extensions may also define their own services using the  method.

In any case, only services that are explicitly declared as extension points should be wrapped or replaced. Services that are defined as extension points should have an abstract base class for use by extensions that want to provide their own implementation. A proper PHP interface should not be used for this purpose, because they would make the extension brittle against future modifications. See the explanation in the section on Handler classes for more details.

Other extension points
Other extension points should follow the same ideas outlined above: narrow interfaces exposing only what is necessary, and isolating extensions from implementation details. This document may be amended in the future to provide more specific guidance for additional types of extension points, such as skins, resource loader modules, and maintenance scripts.