Stable interface policy

From MediaWiki.org
Jump to navigation Jump to search

This stable interface policy for PHP code of MediaWiki defines what parts of the software can be considered stable and safe for use by extensions and other code outside of core. More generally, it is intended to provide best practices for using PHP code across module boundaries in general.

All code considered part of the "stable interface" by this policy is subject to the Deprecation policy. Any code not part of this stable interface may change without notice.

Motivation[edit]

The motivation for this policy is two-fold: On the one hand, it offers guarantees to extension developers, providing guidance on what aspects of MediaWiki core they can safely rely upon. On the other hand, it provides 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.

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

This proposed policy explicitly defines the stable interfaces for use by extensions, to accompany Deprecation policy and the Best practices for extensions. The new policy is intended to apply from MediaWiki 1.35 onward.

Definition of the Stable Interface[edit]

The following is considered part of the stable interface, and therefore subject to this deprecation policy, after having been present in at least one stable release of MediaWiki:

  • Global functions with the "wf" prefix (unless declared otherwise through annotations).
  • Public methods and fields (unless declared otherwise). Legacy functions that are implicitly public due to not having visibility modifiers are not considered public by this policy.
  • Documented hooks (unless declared otherwise).
  • Any other code explicitly opting in to the stable interface, through annotations like @stable, @fixed, @extensible or @newable tags, or by other explicitly documented means.

In contrast, the following things are not part of the stable interface, and are therefore subject to change without notice:

  • Protected methods and fields are not stable, unless declared as an extension point using the @extensible annotation.
  • Method signatures are not stable (as observed through subclassing), unless annotated with @fixed.
  • Constructor signatures are not stable, unless annotated with @stable. Classes annotated with @extensible or @newable are also considered stable.
  • Interface signatures are not stable, unless annotated with @fixed or @extensible. This means that extensions should generally not directly implement interfaces from core. Instead, an extensible base class should be provided for supported extension use cases.
  • Global variables are not stable (including those with the "wg" prefix). MediaWikiServices should be used as an entry point that provides access to service objects and configuration instead.
  • Implicitly public methods and fields (those lacking explicit visibility modifiers) are not stable.
  • Any other code annotated with @deprecated, @internal, @unstable, or @experimental, or explicitly documented by other means as being unstable or internal.

Annotations[edit]

As described above, the following defaults apply:

  • Public methods of classes and interfaces are considered stable unless marked with @deprecated, @internal, or @unstable. They are however considered not fixed, meaning they are not safe to override.
  • Protected methods are considered unstable unless marked with @stable or @fixed.
  • Constructor signatures are considered unstable unless marked with @stable or @fixed.
  • Signatures of interfaces are considered not fixed unless the interface is marked with @fixed. Methods and optional arguments may be added without notice.
  • Similarly, the set of required abstract methods is considered unstable unless the class is marked with @extensible (@stable does not suffice). Methods and optional arguments may be added without notice.

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

to remove guarantees:

  • @deprecated: do not use outside the module, may be removed following the procedure defined by the deprecation policy. Should always be accompanied by instructions of what to use instead.
  • @internal: do not use outside the module, subject to change without notice. Same as @unstable, except that internal typically stays internal.
  • @unstable: do not use outside the module, subject to change without notice. Same as @internal, except that unstable things are intended to become stable in the future.
  • @experimental: same as @unstable.

to add guarantees:

  • @stable: Backwards compatibility will be maintained for calls; changes that impact callers are subject to the deprecation policy. This applies to the function's or method's contract as well as its signature. Does not mean the method can safely be overridden, even if the class is marked @extensible.
    • On protected methods: safe to call for subclasses outside of core. Only useful if the class is @extensible.
    • On public methods, fields, and functions: discouraged since redundant. Public methods are considered stable by default.
    • On constructors: safe to call directly. Should only be used on @newable and @extensible classes. Technically redundant, but encouraged for clarity.
  • @fixed: Backwards compatibility will be maintained for overriding and implementing. Only very limited changes to signatures are possible. All such changes are subject to the deprecation policy.
    • On methods: safe to override. Technically redundant on abstract methods of extensible classes, but still encouraged for clarity.
    • In interfaces: safe to implement. Interfaces not marked as @fixed are not safe to implement directly, a base class should be used instead.
  • @extensible: Backwards compatibility will be maintained for subclassing; changes affecting subclasses will be done in a backwards compatible way. This implies that the constructor is stable, and no abstract methods can be added. Note that methods can only be safely overridden if marked fixed, and protected methods are safe to call only if declared stable.
    • On classes: safe to subclass. This implies that the constructor is stable, and no abstract methods can be added.
    • On interfaces: discouraged, should be interpreted to mean the same as @fixed.
  • @newable: The class is safe to be instantiated using the new operator. This implies that the constructor is stable. Generally, only plain value objects should be newable, since any other kind of object may require dependency injection.

Implications and Recommendations[edit]

This section provides an overview of the practical implications this policy has for authors of extensions as well as authors of core code. By following the guidance of the principles given here, developers on both sides of the module boundary can benefit from the guarantees provided by a stable interface.

Authors of code outside of MediaWiki core, in particular of extensions, should consider the following principles and recommendations to avoid incompatibility with future versions of MediaWiki:

  • It's generally safe for extensions to call public methods and access public fields in classes defined by MediaWiki core, unless these methods are documented to be unsafe (e.g. annotated with @deprecated or @unstable or @internal).
  • It's generally unsafe for extensions to extend (subclass) a class or implement an interface defined by MediaWiki core, unless that class or interface was designated to be safe for that purpose. In particular, the constructor signature may change, and abstract methods may be added. Thus, extensions should not implement interfaces, extend classes, or override methods, unless annotated with @fixed or @extensible.
  • It's generally unsafe for extensions to directly instantiate (using new) a class defined by MediaWiki core, unless that class is designated to be safe for this purpose. Thus, extensions should not directly create instances of classes that do not have the @newable annotation.
  • It's generally unsafe for extensions to rely on global variables. Static methods such as MediaWikiServices::getInstance() should be used instead.

Authors of code in MediaWiki core, in particular code that defines extension points, should consider the following principles and recommendations, in order to provide extensions with a stable interface without removing flexibility from the code in core:

  • MediaWiki extension points should use base classes marked with the @extensible annotation. Any methods to be implemented or overwritten should be explicitly marked as @fixed to indicate that their signature will not change in an incompatible way.
  • Base classes are preferred over interfaces as extension points. MediaWiki extension points may also use PHP interfaces if these are marked with the @fixed annotation. This however means that no method can ever be added to them, since that would break any extension implementing the interface.
  • Pure value objects defined by MediaWiki should be marked with the @newable annotation if extensions would benefit from being able to construct such objects directly. The constructor of a @newable class should be marked as @stable.
  • For objects that extensions may need to instantiate, but which for which a stable constructor cannot be guaranteed, a factory service should be provided . This is especially true for objects that may in the future need to have a service instance injected.

Application to Libraries Used by Core[edit]

While this policy is written to define a contract between MediaWiki core and MediaWiki extensions, it can by extension be applied to the relationship between core and libraries used by core. In this case, it is the libraries that expose parts of their code as a stable interface, and MediaWiki core that bind to that stable interface, following the rules set out by this policy.

A "library" in this context would be any separately released code that is used by MediaWiki core, and also all code under includes/libs which is still awaiting extraction into a separate repository.

The same rules also govern dependencies between libraries, or between extensions, or of extensions on libraries: this policy is intended to provide general best practices for accessing code across module boundaries.

Stable Interfaces up to MediaWiki 1.34[edit]

The following definition of stable interfaces, as given on the deprecation policy page, is applicable to MediaWiki up to version 1.34. Staring with version 1.35, the more restrictive policy above applies. 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 @internal, @private or @unstable 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 visiblity set on all properties and functions.