* Affected components: None immediately, but PageIdentity is designed to replace many uses of the WikiPage and Title classsses. So eventually: much of MediaWiki.
* Engineer(s) or team for initial implementation: @daniel / #core_platform_team
* Code steward: #core_platform_team
### 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}, and to for serialized representation of deferred events, see e.g. {T212482} and {T208764}.
##### 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, There is two approachebut it may not be trivial to determin if the PageIdentity belongs to this:another wiki.
##### Option 1: compositionProposal
Make PageIdentity a value object,n interface and let thave Title contain an instance of PageIdentity,implement that interface. accessible via an asPageIdentity() method.Provide a pure value implementation, Title would also have a static castFromPageIdentity() method that would instantiate a Title from a PageIdentity.Value, For optimization, PageIdentity could have a reference to a Title "glued" to itas an alternative.
Title would again have a static castFromPageIdentity() method that would instantiate a Title from a PageIdentity, which can used by castFromPageIdentity() to avoid instantiating a new twould return the object unchanged if that object already was a Title on every callinstance.
This approach allows the definition of PageIdentity to be entirely independent of Title.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, The new semantics and signatures can be defined without much though how they relate to the semantics of TitleProperPageIdentity will become an alias for PageIdentity in the future.
However, any method that takes a Title as a parameter will need to be deprecated and replaced and later removed, and every caller of such a method will have to be updated to use the new method. While this change is incomplete, there will be a lot of asPageIdentity() and castFromPageIdentity() calls, cluttering the code and causing overhead.
Draft:
```lang=php
interface PageIdentity {
function getId(): int; // see Title::getArticleID(). Throws if it's not a "proper" page!
function getWikiIdcanExist(): string;bool; // see RevisionRecord::getWikiId()Title::canExist. False if not a proper page.
function exists(): bool;l; // see Title::exists. False if not a proper page.
function getWikiId(): string; // see Title::existsRevisionRecord::getWikiId()
function getNamespace(): int; // see Title::getNamespace
function getNameDBkey(): string; // see Title::getDBkey
}
```
##### Option 2: subtype
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 castFromPageIdentity() 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. This is not very pretty, but no worse than the present situation, which has different parts of code use Title objects with different assumptions about what that Title objects represents.
For transitioning to the new type, we can now simply change type hints of paramters from Title to PageIdentity. This widens the type, and thus doesn't break calling code, and doesn't require deprecation (except for methods that are considered //stable for overriding//). Only methods that return a Title will need to be replaced. This makes for a much smoother transition, with less need to deprecate and remove methods.interface ProperPageIdentity extends PageIdentity {
}
Draft:class PageIdentityValue implements ProperPageIdentity {
```lang=php...
interface}
class Title implements PageIdentity {
function getId(): int; // see Title::getArticleID()...
public static function getWikiId(): string; // see RevisionRecord::getWikiId()castFromPageIdentity( ?PageIdentity $pageIdentity ): ?Title;
public function exists() bool;getWikiId(); // always false, // sesince Title::exists() only supports the local wiki
public function getNamespace(): int;Id(); //like getArticleID(), // see Title::getNamespacebut throws if canExist() returns false.
public function getDBkeasPageIdentity(): string;ProperPageIdentity; // see// throws if not a proper page
}
class WikiPage implements PageIdentity {
...
}
class Title::getDBkeyleValue implements LinkTarget {
function assertProperPage(): bool; // Throws if canExist() returns false. Taint,public static function newFromPage( PageIdentity $page ): TitleValue; to be deprecated.// throws if not local
}
```
##### PageIdentity and LinkTarget
The link target aspect of Title has long ago been factored out into the LinkTarget interface and TitleValue class. PageIdentity would now be introduced to represent the other aspect of Title. But how do the two relate?
To get a PageIdentity for a given LinkTarget, it will have to be looked up in the database - for now, by converting to a Title object via Title::castFromLinkTarget, and in the future using the PageStore service (T195069).
To make a LinkTarget from a given PageIdentity, TitleValue could have a static newFromPage() method. Or PageIdentity could have an asLinkTarget() method.
Alternatively, PageIdentity could extend the LinkTarget interface. In that case, PageIdentity would require getFragement() and getInterwiki() to always return null, getDBkey() to always return a non-empty string, and isExternal() to always return false. This would satisfy LSP, though it seems to reduce the LinkTarget interface significantly. A more narrow LocalLinkTarget interface sounds tempting, but a general LinkTarget cannot extend a more narrow LocalLinkTarget, since that would violate LSP (because "every LinkTarget is a LocalLinkTarget" is incorrect).
Conceptually, the question is how tightly we want to tie the ideas of LinkTarget and PageIdentity together. The represent rather different things, though saying that "every page is a link target" does make sense. Combining them closely is easy enough for local pages, but what about cross-wiki PageIdentities, that return something from getWikiId()? Should these also return the equivalent interwiki prefix from getInterwiki()? In what context would that interwiki prefix be valid - the local database, or the database indicated by getWIkiId()? Also, it seems odd for a PageIdentity to have a getFragment() method that is required to always return an empty string.
##### 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).
##### Bike shed
* What terminology should be used to represent the idea that a PageIdentity refers to a "real" page, rather than a "special" page or foreign link target? "article" is ambiguous...
* What terminology should be used to identify PageIdentities that identify pages that belong to a different wiki, but one to which we still have direct database access via PageStore and RevisionStore, know the namespaces, etc? How does that relate to isExternal() and getInterwiki() defined in LinkTarget?