Stable interface policy/T255803 Draft

This stable interface policy for MediaWiki PHP code defines what parts of the software can be considered stable and safe for use by outside of MediaWiki core, e.g. by extensions and skins. It is intended to provide best practices for how to reuse PHP code across module boundaries.

Code that is considered part of the "stable interface" is subject to the Deprecation process. Any code not part of the stable interface may change without notice.

Quick guide
For extension authors:


 * It's generally safe to call public methods, and to access public fields in classes defined by MediaWiki core, unless these methods are documented to be unsafe (e.g. annotated as,  , or  ).
 * It's generally unsafe to extend (subclass) classes or implement interfaces defined by MediaWiki core, unless that class or interface was marked as  or , respectively. In particular, the constructor signature may change without notice, and abstract methods may be added to interfaces.
 * It's generally unsafe to directly instantiate (using new) classes defined by MediaWiki core, unless that class is marked as.
 * It's generally unsafe to rely on global variables from MediaWiki core. Use methods such as  or   instead.

When changing existing code:
 * Keep public methods and hook signatures backwards compatible for callers. Follow the deprecation process when removing them.
 * Keep constructor signatures backwards compatible if the constructor was marked.
 * Ensure compatibility of method signatures for code that overrides them if they are marked.
 * Do not add abstract methods to classes or interfaces marked as  or.

When defining extension points:
 * When defining hooks, keep the signature minimal, and expose narrow interfaces, ideally only pure value objects.
 * When defining an interface to be implemented by extensions, provide a base class, and mark it as.
 * Discourage extensions from directly implementing interfaces by marking them as . If direct implementation is to be allowed, mark the interface.

Notable changes from the 1.34 policy:
 * Public methods are per default considered stable only for calling, not for overriding.
 * Constructors are considered unstable per default.
 * Classes and interfaces are considered unstable for subclassing and implementation, unless documented otherwise.
 * Code not used in a public repository that is part of the Wikimedia ecosystem may be changed or removed without deprecation.

Motivation
The motivation for this policy is two-fold:


 * Offer guarantees to extension developers, providing guidance on what aspects of MediaWiki core they can safely rely upon.
 * Provide guarantees to developers working on MediaWiki core, telling them what aspects of the code they can safely change without having to worry about breaking extensions.

This policy is designed to make extensions more robust against changes in MediaWiki core, and provide more freedom for MediaWiki core code to evolve.

This policy explicitly defines the stable interfaces for use by extensions, to accompany the best practices for extensions. The new policy applies from MediaWiki 1.35 onward. The policy for MediaWiki 1.34 and earlier is given in the section Stable Interfaces up to MediaWiki 1.34 below.

Terminology
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

Scope
This policy applies to the following:


 * PHP code of MediaWiki core (mediawiki/core.git) as published in official releases. It does not apply to unreleased code, or to other aspects of MediaWiki core like the HTTP interface of api.php, or client-side JavaScript, HTML output, or database tables. Those may have their own policies and practices for maintaining stable interfaces. As with all policies, developers should apply their best judgement when following it.
 * Libraries and extensions maintained as part of the Wikimedia ecosystem.

This policy is mainly written to define a contract between MediaWiki core and MediaWiki extensions, but it also applies to the relationship between MediaWiki and libraries it uses. In this case, it is the libraries that expose parts of their code as a stable interface, and MediaWiki core binds to that stable interface, following the rules set out by this policy. A "library" in this context is any separately released code that is maintained as part of the Wikimedia ecosystem, as well as any directory inside the repository that is identified as a separate library by convention, such as the directories under.

These rules also govern dependencies between libraries, or between extensions, or between extensions and libraries.

Note that the deprecation process may be bypassed for code that is unused within the Wikimedia ecosystem, which is defined to consist of all actively maintained code published in repositories operated by Wikimedia. This means that code maintained by third parties only benefits from the stability guarantees made by this policy as far as it is contributed back to the ecosystem.

Stable Interfaces up to MediaWiki 1.34
''The following definition of stable interfaces, as previously given on the deprecation policy page, is applicable to MediaWiki up to version 1.34. With the release of MediaWiki 1.35, this section becomes obsolete and should be removed.''

The stable part of the PHP API is comprised of all code in MediaWiki core that is explicitly marked public, and has been included in at least one stable release. In addition, some classes expect to be subclassed in extensions; in those cases protected functions also are included in the API. These classes should have a note in their documentation comment that they expect subclassing. If no note is present, it SHOULD be assumed that the class is not expected to be subclassed. Hooks are considered part of the PHP API.

Note: Typically PHP code would use private or final, however those are not supported by PHPUnit's mocking features, and as a result not really used in MediaWiki code.

Classes and/or functions with public visibility MAY also have,   or   annotations to indicate they are not stable interfaces that SHOULD NOT be depended upon.

Some legacy code may not have any visibility modifiers, in which case it is not considered to be part of the stable, public interface. Developers SHOULD add visibility modifiers as soon as possible, and use judgement when making this code protected or private. Where known users exist, developers SHOULD still consider following the full deprecation process. New code MUST have explicit visibility set on all properties and functions.

Definition of the stable interface
This policy distinguishes several kinds of stability:


 * stable for calling: Backwards compatibility will be maintained for calling code. This applies to the contract as well as its signature. This does not imply stable for overriding.
 * stable for overriding: Backwards compatibility will be maintained for code overriding the method (or implementing an abstract method).
 * stable for subclassing: For classes marked this way, backwards compatibility will be maintained for subclassing across module boundaries.
 * stable for implementation: Interface marked this way are safe to be implemented by classes in another module.
 * stable for instantiation (or newable): A class marked this way is safe to be instantiated by code in another module using the  operator.

The stable interface of the code in scope of this policy is defined as follows:


 * Public methods of classes and interfaces are considered stable for calling unless marked otherwise. They are however considered not stable for overriding unless marked as such.
 * Protected methods are considered stable for calling if the class they belong to is stable for subclassing (unless the method is marked otherwise). They are however considered not stable for overriding unless marked as such.
 * Constructor signatures are generally considered unstable unless explicitly declared stable for calling. Constructors of classes that are stable for instantiation or stable for subclassing should always be marked as stable for calling.
 * Interfaces are considered unsafe for implementation in another module, unless the interface is marked as stable for implementation. To avoid confusion, interfaces that are not intended to be implemented by other modules SHOULD be annotated to be unstable for implementation. The preferred way to enable polymorphism across module boundaries is to provide an abstract base class is marked as stable for subclassing.
 * Classes are considered unsafe for subclassing unless explicitly marked as stable for subclassing. In particular, abstract methods may be added and protected methods may be removed or modified without notice.


 * Global functions with the "wf" prefix are considered stable for calling (unless declared otherwise).
 * Documented hooks are considered stable for overriding, unless declared otherwise. Hook handler callbacks are treated like implementations of abstract methods for the sake of this policy.
 * Global variables are not stable (including those with the "wg" prefix). When access to global state cannot be avoided, static methods SHOULD be used.
 * Legacy functions that are implicitly public due to not having visibility modifiers are not considered public by this policy, and are thus not treated as part of the stable interface.

Stability annotations
The following annotations can be used to depart from the defaults above.

To remove guarantees:


 *  : do not use outside the module, may be removed following the procedure defined by the deprecation process. SHOULD always be accompanied by instructions of what to use instead.
 *  : do not use outside the module, subject to change without notice. Similar to, except that internal typically stays internal.
 *  : do not use outside the module, subject to change without notice. Similar to, except that unstable things are intended to become stable in the future.
 *  : do not implement outside the module. Useful for interfaces that are stable for calling methods on and to use through typehinting, but may change in ways that would break classes that implement them directly. In other words, the interface does not act as an extension point.

To add guarantees:


 *  : Backwards compatibility will be maintained for code calling this constructor, method, or function. Changes that impact callers are subject to the deprecation policy. This applies to both the behaviour (or "contract") and its call signature. This annotation does not mean a method can be safely overridden. The explicit annotation  is only meaningful for constructors, and SHOULD be applied to constructors of classes that are stable for instantiation, or table for subclassing. Being   is implied for all public methods, as well as for protected methods of classes that are marked.
 *  : Backwards compatibility will be maintained for code overriding the method (or implementing an abstract method) in another module. Only methods explicitly marked this way (or declared abstract) are safe to override. This annotations should be applied to all abstract methods of classes marked as.
 *  : Backwards compatibility will be maintained for subclassing across module boundaries; changes affecting subclasses will be done in a backwards compatible way. Note that methods can only be safely overridden if they are declared abstract or specifically marked as . Protected methods can safely be called. The constructor SHOULD be marked as.
 *  : The interface is safe to be implemented by classes in another module. Interfaces not marked as such are not safe to implement directly, a base class SHOULD be used instead. On (abstract) methods, this means the same as.
 *  : The class is stable for instantiation, that is, another module can create instances of it using the  operator. This implies that the constructor is stable for calling. Generally, only plain value objects  SHOULD be newable, since any other kind of object may require dependency injection.

Annotations such as  or   can be followed by    or a   to indicate that a particular use of the class or method is only supported until or since a specific version. Such restrictions can e.g. be used in cases where extensions should continue to be able to call methods on a class, but should no longer subclass it. Similarly, they can be used to indicate that a protected method on an extensible class may still be called, but should no longer be overwritten. Similarly,  MUST be followed by   to specify the version at which the deprecation occurred, and SHOULD further be followed by information of what calling code should do instead.

Deprecation process
All code that falls within the scope of this policy and defines a stable interface is subject to the deprecation process defined in this section.

Prior to deprecation
Deprecation is typically considered when code needs to be refactored in order to add new functionality, improve general architecture, or fix bugs. Developers SHOULD consider the impact of their proposed changes by doing basic greps on extensions or using tools like MediaWiki code search.

Extension developers are encouraged to mirror their code into Wikimedia's Gerrit/Phabricator/Github to make it easier for core developers to identify usage patterns. Extensions that are open source will be given more consideration than those that core developers cannot see.

Deprecations MUST first take place on the master branch. It is NOT RECOMMENDED to backport deprecations to stable branches.

Developers SHOULD consider deprecating similar parts of code together so affected code can be updated all at once.

Deprecation
There are two steps to deprecation: a soft deprecation, and then a hard deprecation.

A soft deprecation occurs when a developer adds a  annotation to the documentation comment for a method, function, or class.
 * The documentation comment MUST mention what the alternative method or migration path is. If there is no alternative, it should state that.
 * The documentation comment MUST state what MediaWiki core version the deprecation occurred in.
 * If code is only soft deprecated, it SHOULD function the same as prior to deprecation. (This is only a SHOULD not MUST as sometimes it is only possible to provide similar functionality, which is enough for practical purposes, but technically not equal functionality.)
 * Any relevant documentation in the git repository or on mediawiki.org MUST be updated once the change is approved.
 * The deprecation MUST also be mentioned in the relevant RELEASE-NOTES file, and MAY also be mentioned in the "Upgrade notices for MediaWiki administrators" section of the wiki release page depending upon severity. Deprecation of hooks MUST be mentioned in docs/hooks.txt.
 * Developers SHOULD update MediaWiki core to no longer use the deprecated functionality.
 * Developers SHOULD update any extension or skin bundled with the MediaWiki tarball when soft deprecating, and MAY update popular extensions (WikiApiary.com and ExtensionDistributor can be used as indicators of extension popularity).
 * If they don't submit patches, developers MUST file bugs about bundled extensions/skins using deprecated functions so their maintainers can work on updating them.

A hard deprecation occurs, when a  call is added to the function or method in question, or by supplying the $deprecatedVersion parameter to Hooks::run. This emits deprecation warnings and causes things like unit tests to fail.
 * Code that is hard deprecated MUST also be soft deprecated.
 * The version number in the  call MUST match the one in the @deprecated annotation, even if the hard deprecation occurs in a different release.
 * Often code is soft deprecated first, and hard deprecated at a later date, but they MAY occur at the same time.
 * Hard deprecated code MAY act as no-ops instead of actually functioning, though this is not recommended.
 * MediaWiki core code that triggers hard deprecation warnings MUST NOT be reachable from non-deprecated core code using non-deprecated configuration settings
 * Extensions and skins bundled with the MediaWiki tarball MUST NOT trigger hard deprecation warnings and MUST be updated to use the new code.


 * Developers MAY email wikitech-l or mediawiki-l about the soft or hard deprecation depending about severity.
 * When a bug report or a task is related to a deprecation, it is RECOMMENDED to tag it specifically in the bug tracker; for instance with the "Technical Debt" tag, column "Deprecate / Remove" in Phabricator.

Removal

 * Code MUST emit hard deprecation notices for at least one major MediaWiki version before being removed. It is RECOMMENDED to emit hard deprecation notices for at least two major MediaWiki versions. EXCEPTIONS to this are listed in the section "Removal without deprecation" below.
 * Developers SHOULD consider how difficult it is to support and maintain the deprecated code when determining how urgent removal is. In addition, developers SHOULD consider usage statistics in extensions.
 * Developers MAY consider the LTS cycle in removing deprecated code (removals may be accelerated to avoid deprecated code being included in LTS versions, requiring an extended support period).
 * The removal MUST also be mentioned in the relevant RELEASE-NOTES file, and MAY also be mentioned in the "Upgrade notices for MediaWiki administrators" section of the wiki release page depending upon severity.

Caveat: As one of the principles of MediaWiki, developers should ensure any removals will not cause issues in the Wikimedia setup and extensions deployed there. If they do, developers should expect to be reverted by Wikimedia system administrators.

Removal without deprecation
The deprecation process may be bypassed for code that is unused within the MediaWiki ecosystem. The ecosystem is defined to consist of all actively maintained code residing in repositories owned by the Wikimedia foundation, and can be searched using the code search tool.

In some cases rare, it may be necessary to remove code without deprecating it in a major MediaWiki version beforehand, because the old behavior cannot reasonably be emulated. In such a case, developers MUST email wikitech-l ahead of time, explaining why deprecation is not possible or not reasonable, and providing an opportunity for affected parties to raise concerns and propose alternatives.

In any case, all steps about documenting deprecations and removals MUST still be followed, as applicable.