- Affected components: None immediately, but PageIdentity is designed to replace many uses of the WikiPage and Title classes. So eventually: much of MediaWiki.
- Engineer(s) or team for initial implementation: @daniel / Platform Engineering
- Code steward: Platform Engineering
Motivation
Create a light weight replacement for WikiPage and Title to be used to represent editable wiki pages.
The Title class is not only large and has many dependencies, it's also conceptually overburdened: it is used to represent link targets on the one hand, and editable pages on the other. It's also an "active record" that uses global state to interact with the storage backend, which is something we want to move away from.
Similarly, the WikiPage class contains database logic and business logic, and relies on global state.
A light-weight value object to represent wiki pages is generally useful for decoupling, e.g. T208764: Remove cyclic dependency between Title and User classes, and to for serialized representation of deferred events, see e.g. T212482: RFC: Evolve hook system to support "filters" and "actions" only .
Requirements
- PageIdentity must make available to following: the page's internal ID, the namespace number, the title (db key), and the ID of the wiki it belongs to, for cross-wiki operations.
- PageIdentity (or a subclass or implementation) should be newable
- PageIdentity (or a subclass or implementation) should be serializable
- PageIdentity (or a subclass or implementation) should be a immutable pure value object
Exploration
One major aspect of the design of PageIdentity is interoperability with and transition from the Title class.
Considerations
- To easy transition, it would be useful if Title could implement PageIdentity. This allows parameters of existing methods to be narrows from Title to PageIdentity.
- To easy transition, it would be useful if WikiPage could implement PageIdentity. This allows parameters of existing methods to be narrows from WikiPage to PageIdentity.
- It would be desirable of any instance of PageIdentity was guaranteed to represent a "real" page (not a special page, not an interwiki link, not a section). However, this desire conflicts with the wish to have Title implement PageIdentity.
- PageIdentity and LinkTarget are distinct. Not all LinkTargets correspond to a PageIdentity (e.g. SpecialPages, interwiki linkls, section links, etc). Every PageIdentity has a corresponding LinkTarget, but it may not be trivial to determin if the PageIdentity belongs to another wiki.
Proposal
Make PageIdentity an interface and have Title implement that interface. Provide a pure value implementation, PageIdentityValue, as an alternative.
Title would again have a static newFromPage() method that would instantiate a Title from a PageIdentity, which would return the object unchanged if that object already was a Title instance.
We have been successfully using this approach with UserIdentity and LinkTarget.
The signature and semantics of the PageIdentity interface will have to make some concessions to be compatible with Title. In particular, since a Title object can represent things that are not editable wiki pages but merely link targets (Special pages, interwiki links, relative section links), PageIdentity will have to allow for such "improper" instances, and will need to offer a method for calling code to check whether it's a "proper" PageIdentity.
To provide a patch towards providing stronger guarantees, provide an additional interface, ProperPageIdentity, that represents the stricter semantics that PageIdentity should eventually represent (specifically, that it is always a page that exists or can be created). Ideally, ProperPageIdentity will become an alias for PageIdentity in the future.
Draft:
interface PageIdentity { function getId(): int; // see Title::getArticleID(). Throws if it's not a "proper" page! function canExist(): bool; // see Title::canExist. False if not a proper page. Taint, should eventually go away. function exists(): bool; // see Title::exists. False if not a proper page. function getWikiId(): string; // see RevisionRecord::getWikiId() function getNamespace(): int; // see Title::getNamespace function getDBkey(): string; // see Title::getDBkey } interface ProperPageIdentity extends PageIdentity { function getId(): int; // see Title::getArticleID(). Never throws. function canExist(): bool; // Always true. Should eventually go away. } class PageIdentityValue implements ProperPageIdentity { ... } class Title implements PageIdentity { ... public static function newFromPage( ?PageIdentity $pageIdentity ): ?Title; public function getWikiId(); // always false, since Title only supports the local wiki public function getId( $wikiId ); // Throws if canExist() returns false. Also throws if $wikiId mismatches. public function toPageIdentity(): ProperPageIdentity; // throws if not a proper page } class WikiPage implements PageIdentity { ... function canExist(): bool; // see Title::canExist. Always true (needs fixing of some edge cases) function getWikiId( $wikiId ): string; // see RevisionRecord::getWikiId(); throws if $wikiId mismatches. function getNamespace(): int; // see Title::getNamespace function getDBkey(): string; // see Title::getDBkey } class TitleValue implements LinkTarget { public static function newFromPage( PageIdentity $page ): TitleValue; // throws if not local }
PageIdentity and PageRecord
With the proposal of PageStore (T195069) also comes the concept of a PageRecord object, representing page level meta data such as the page's latest revision, touch date, etc. PageRecord should by a subtype of PageIdentity (extending interface of implementing class).
Notes
- The idea to require a wiki ID in getId() to ensure the ID refers to the correct wiki in the context of cross-wiki enabled code allows us to safely introduce the idea of non-local page identities to existing code. If it does not cause unexpected problems, the intent is to use the same pattern in classes representing other entities, such as revisions or users.
- PageIdentity instances may be defined relative to the local wiki, using a special value for the wiki ID. While this works nicely with the way MediaWiki handles database connections to different wikis, it may prove problematic when constructing cache keys or serializing instances of PageIdentity for consumption in the context of another wiki.