Manual:Extension registration/Architecture

From mediawiki.org

This page documents the architecture of the new extension registration system introduced in MediaWiki 1.25. Note that generally wherever the term "extension" is used, "skin" can also be used interchangeably.

ExtensionRegistry[edit]

The ExtensionRegistry is a singleton that maintains state of the current load queue and what extensions have already been loaded.

LocalSettings.php will call wfLoadExtension( 'FooBar' ); (or wfLoadSkin), adding the extension to the load queue. At the beginning of Setup.php, the load queue will be read, and the cache (APCu) will be checked to see if we can use it. If there is a cache miss, each extension.json file will be read individually, and the result will be combined into a single array. The array is then cached as one, so future reads will just need to read one key for any number of extensions. The cache is based on the modified time of all the individual files plus their file paths.

Based on what was read, the registry then sets various values into the global namespace ($GLOBALS).

Public functions:

  • getInstance() - static method to return the singleton instance
  • queue() - Add a file to the load queue
  • loadFromQueue() - Read the load queue and load those extensions
  • load() - Load a specific file and cache it on its own
  • readFromQueue() - Public function to get structured data out of extension.json files, for uses like the installer
  • isLoaded() - Check whether a specific extension has been loaded
  • getAttribute() - See Manual:Extension_registration#Attributes
  • getAllThings() - Bulk get credits information about all loaded extensions, useful for things like Special:Version

When setting global variables, we try for O(1) performance:

// Simplified code snippet
if ( !isset( $GLOBALS[$key] ) || !$GLOBALS[$key] ) {
	$GLOBALS[$key] = $val;
} elseif ( is_array( $GLOBALS[$key] ) && is_array( $val ) ) {
	$GLOBALS[$key] = array_merge( $val, $GLOBALS[$key] );
}

The first case is O(1) since we can just do a variable assignment, while the latter is more expensive due to requiring an array_merge. By making initial arrays empty like was done for $wgMessagesDirs in gerrit:177385 will provide a performance benefit.

ExtensionProcessor[edit]

The ExtensionProcessor is responsible for reading the extension.json files and returning structured data based on that. It does not modify global state in any way. It also should not depend upon any global state that the ExtensionRegistry cache does not vary upon. In most requests the ExtensionProcessor class should never be invoked because reads are done from the ExtensionRegistry cache.

extension.json[edit]

extension.json (and skin.json) is the file format that ExtensionRegistry reads and parses for ExtensionProcessor to interpret. It is formally documented as a JSON schema in docs/extension.schema.v1.json and docs/extension.schema.v2.json, and documented for humans at Manual:Extension.json/Schema.

JSON was picked as the file format because it is a non-executable (you don't execute code while reading it), and easily machine readable format that PHP has built-in support for. While it's human readability and editability is not the best (especially when compared to YAML for example), we had already seen it work rather well for composer (composer.json) and npm (package.json). Adding comments to JSON has been suggested a few times, but has been rejected due to deviating from the JSON standard. For now, keys that start with @ are ignored by the format, and are used to add comments.

One of the key features of extension.json is the manifest_version field that indicates what version of the file format is being used, and how some parts of it should be parsed. It allows for breaking changes to the file format without breaking all extensions. All differences between the different file format versions are taken care of by the ExtensionProcessor. Fields may be added and removed following the standard MediaWiki deprecation policy if those components are changing, but expectation is that the format itself is stable.

During discussions at the Lyon Hackathon, Tim Starling told me (Legoktm) that one of the design features of the PHP entry point for extensions was that the format was very stable, and rarely (if ever) had breaking changes. While specific settings might be introduced/deprecated/removed, the format itself (plain PHP variables that are loaded in LocalSettings.php) was incredibly stable. Similarly, the extension.json format too, aims to be as stable as possible. As of this writing (MediaWiki 1.32), there are no plans to ever remove the v1 format, despite v2 being recommended. New fields are expected to be supported in both formats, and due to the design of ExtensionProcessor, should be trivial to implement for most cases.