This RFC proposes policies and strategies to mitigate problems arising from the need to change PHP interfaces that are implemented by classes defined by extensions. The core of the proposal is to recommend the use of base classes instead, and be more explicit about what constitutes MediaWiki's stable interface, and what guarantees apply to which part of it.
Problem
Historically, the main contact surface between MediaWiki and extensions were base classes (to be subclassed by extensions) and hook signatures. This made non-breaking changes (with support for outdated code during some deprecation period) relatively easy: it was possible to add more parameters to a hook signature, add more parameters (with default values) to a base class method, and add new methods to a base class, without breaking anything, since all of these are valid in PHP:
function hook1( $a ) {} call_user_func( 'hook1', 1, 2 );
class Core1 { public function f( $a, $b = null ) {} } class Extension1 extends Core1 { public function f( $a ) {} } // will raise a warning but still work
class Core2 { public function f( $a ) {} public function f2( $b ) {} } class Extension2 extends Core2 { public function f( $a ) {} }
With dependency injection and generic composition over inheritance practices, we are increasingly moving towards asking extensions to implement an interface instead of subclassing a base class (and there are plans to change the hook system in similar ways). The above change mitigation strategies fail for interfaces:
interface Core3 { public function f( $a, $b = null ); } class Extension3 implements Core3 { public function f( $a ) {} } // fatal error
interface Core4 { public function f( $a ); public function f2( $b ); } class Extension4 implements Core4 { public function f( $a ) {} } // fatal error
Replacing the old interface with a new one is not much better as it will break type hints. If we want to avoid inflicting a lot of pain on wiki owners with in-house or not fully up to date extensions, we need to come up with new change mechanisms.
Proposal (Daniel, September 2019)
The problem outlined above is best addressed by using (abstract) base classes instead of "proper" PHP interfaces for extension points, and by providing more explicit guarantees to extension authors. The following steps are proposed:
- Adopt the Stable Interface Policy as drafted at https://www.mediawiki.org/wiki/Stable_Interface_Policy. The policy will take effect with the release of MW 1.35, giving extension authors time to adapt. This new policy will replace the existing Deprecation policy.
- Before the release of MW 1.35, add the annotations defined by the Stable Interface Policy to the relevant classes in MediaWiki core. Use existing extensions as a guide for what needs the new annotations.
Summary of the new policy
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 @deprecated, @unstable, or @internal).
- It's generally unsafe to extend (subclass) or implement interfaces defined by MediaWiki core, unless that class or interface was marked as safe for that purpose. 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 @newable.
- It's generally unsafe to rely on global variables from MediaWiki core. Use methods such as MediaWikiServices::getInstance() or MediaWikiServices::getMainConfig() 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 @stable for calling.
- Ensure compatibility of method signatures for code that overrides them if they are marked @stable for overriding.
- Do not add abstract methods to classes or interfaces marked as @stable for subclassing or @stable for implementation.
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 @stable for subclassing.
- Discourage extensions from directly implementing interfaces by marking them as @unstable for implementation. If direct implementation is to be allowed, mark the interface @stable for implementation.
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