Stable interface policy

The stable interface policy for MediaWiki PHP code defines what parts of the software are considered stable and safe for use outside of a given component. Code that is considered part of the "stable interface" is subject to the deprecation process.

Quick guide

Using code

Writing code

When changing existing code:

  • Keep public methods and hook signatures compatible for callers. Follow the deprecation process for breaking changes.
  • Keep constructor signatures compatible, if it is marked @stable to call.
  • Keep method signatures compatible for subclasses, if the method is marked @stable to override.

When creating new code:

  • When defining hooks, keep the signature minimal. Expose narrow interfaces, ideally only pure value objects, as parameters.
  • Avoid using interfaces as extension points. It is recommended to use an abstract base class instead. See Stable to extend.

Terminology

  • Authors: You are working on something that others will use. For example, a class in MediaWiki core that extensions can use.
  • Users: You are working on something that uses a stable interface. For example, a class in an extension that interacts with MediaWiki core.

Definition of the stable interface

Stable to call

It's generally stable to call public methods and access public class fields – unless these are marked otherwise.
It's generally not stable to directly instantiate classes using the new operator – unless these are marked as @newable.

Stable to call can apply to methods and functions. It means they stay backwards-compatibility between releases. This stability applies to both the behavior (its contract), and the signature. Breaking changes that would impact callers must follow the deprecation policy.

Note that methods are not stable to override by default.

Included:

  • Global functions of which the name starts with the "wf" prefix.
  • Public methods on any class instance.
  • Protected methods of a class that is stable to extend.
  • Constructor methods that are marked @stable to call. This means their class will be considered "newable" and thus may be instantiated using the new operator in any code.
  • Constructor methods of classes marked @newable.

Not included:

  • Any constructor method, unless marked @stable to call.
  • Any method or function marked @deprecated, @internal or @unstable.
  • Legacy class methods that do not have an explicit visibility modifier. These are technically public, but considered unstable.

For authors:

  • It is recommended to only mark constructors as stable to call if they are for value objects or for extendable classes.
  • When making a constructor method @stable to call, consider marking the class it belongs to as @newable. This technically provides the stability guruantee, and is used to in discoverability of the stable constructor, and as self-documenting way to encourage a usage pattern through the new operator. It is at the author's discretion to decide whether or not to mark a class with a stable constructor as @newable. For example, if the class is generally only constructed through an intermediary utility method or subclass, then it may benefit users to not draw attention to the constructor.
  • For complex classes that may involve dependency injection, you should avoid making the constructor stable to call, as this means adding or changing dependencies would constitute a breaking change that requires following the deprecation policy.

Stable to type

It's generally stable to mention interfaces and classes in type hints for parameters and return values.

Stable to type can apply to interfaces and classes. It means the type will continue to exist between releases and provide at least the same public methods that are stable to call. You can type against these interfaces and classes from various contexts; such as argument type declarations ("type hints"), return types, catch statements, and instanceof assertions.

Remember that by default interfaces are not stable to implement, and thus methods may be widened or added without notice. As PHP requires implementations to define all methods and use the same or narrower signatures, these would normally be breaking changes, but are backwards-compatible for the purpose of typehints and calling methods. For the same reason, an interface may become a class, and a class may become an interface without notice.

Included:

  • All classes and interfaces.

Not included:

  • Any class or interface marked @deprecated, @internal or @unstable.

For authors:

  • Avoid using interfaces as extension points. It is recommended to use an abstract base class instead. See Stable to extend.
  • When you do create interfaces, it is recommended that you explicitly mark them as @stable to type. This is intended to aid the discovery of limited guarantees around interfaces.

Stable to extend

It's generally not stable to extend classes – unless these are marked @stable to extend. This means constructor signatures may break, protected methods are unstable, and new abstract methods may be added without notice.

Stable to extend can apply to classes. It means the class and its methods will stay backward-compatible between releases and may be subclassed anywhere. Changes that affect subclasses will follow the deprecation policy. Protected methods of extendable classes are automatically stable to call, unless they are marked @deprecated, @internal or @unstable. Remember that by default methods remain not stable to override.

Included:

  • Only classes that are marked @stable to extend.

For authors:

  • When allowing extensions to create additional classes of a certain type, it is recommended you provide an abstract base class (marked stable to extend) instead of an interface. This is because is not possible to use deprecation in an interface. If you mark an interface as stable to implement, you commit to never changing its method signatures, and never adding new methods – unless the interface as a whole is deprecated first.

Stable to implement

It's generally not stable to implement interfaces – unless these are marked @stable to implement. This means existing signatures may change and new required methods may be added without notice.

Stable to implement can apply to interfaces. It means they will stay backward-compatible between releases and may be implemented anywhere. Changes that affect implementations will follow the deprecation policy.

Included:

  • Only interfaces that are marked @stable to implement.

For authors:

  • Any hook interface that is documented should be marked @stable to implement.
  • Avoid using interfaces as extension points. It is recommended to use an abstract base class instead. See Stable to extend.

Stable to override

It's generally not stable to override methods in subclasses unless the method is marked @stable to override.

Stable to override can apply to class methods and hooks. It means the subclass method or hook handler will continue to be called in relevant circumstances to let you influence the caller's behaviour. Changes to that contract must follow the deprecation policy.

Included:

  • Any hook that is documented. For the sake of this policy, hook handlers are treated as implementations of abstract methods.
  • Methods that are declared as abstract in classes that are stable to extend.
  • Any method marked @stable to override.

Not included:

  • Any method marked @deprecated, @internal or @unstable.

Global variables

It's generally not stable to use global variables.

There is no official way to mark global variables as stable for any purpose. Global variables are not stable, not even those with the "wg" prefix.

For users:

  • To access site configuration, use MediaWikiServices::getMainConfig() instead.
  • To access service objects, use MediaWikiServices::get* methods instead.

For authors:

  • When access to global state cannot be avoided, static methods SHOULD be used.

Stability annotations

Add guarantees

The @stable annotations can be followed by a Since segment to indicate that a particular use of the class or method is only supported since a specific version. For example:

/**
 * @since 1.17
 * @stable to extend Since 1.35
 */
class Foo {
    /* … */
}

The @stable annotations can be followed by a Deprecated since segment to indicate that a particular use of the class or method is currently deprecated. This can be used to indicate that extensions should no longer subclass, but may still call public methods. This guruantee may then be removed in the next release. Note that there is currently no hard-deprecation for the removal of stability guarantees.

/**
 * @stable to extend Deprecated since 1.35
 */
class Foo {
    /* … */
}

Remove guarantees

  • @internal: Do not use outside the original module. It may change without notice.
  • @unstable: It may change without notice. Similar to @internal, except that unstable things are aimed at external use and intended to become stable in the future.
  • @deprecated: This means something should not be used as this may be removed in a future release, per the deprecation process. This must include a since segment, and must include instructions for what to use instead (or state that there is no alternative). For example:
/**
 * @deprecated Since 1.35, use expandFoo() instead.
 */
public function getSomething( Foo $foo );

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.

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 searching for existing usage in extensions using tools such as Grep, Ack, or Codesearch.

Extension developers are encouraged to develop their code in Wikimedia Gerrit, to mirror it to Wikimedia's Gerrit or 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 @deprecated annotation to the documentation comment of a method, function, class, or interface.

  • 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 wfDeprecated( __METHOD__, '1.xx' ); 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 wfDeprecated() 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.
  • Hard deprecation of code MUST NOT happen if it is still used in Wikimedia production, or used by extensions and skins bundled with MediaWiki, or reachable from parts of MediaWiki core that are not deprecated or used via non-deprecated configuration settings. Such usage MUST be updated first.
  • Developers MAY email wikitech-l or mediawiki-l about the soft or hard deprecation depending on 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 "Deprecation process" tag 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 "Changes 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.

Changes 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.

Additionally, in some rare cases, it may be necessary to make breaking changes 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.

Meta

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.

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 includes/libs.

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