* Affected components: None immediately, but PageIdentity is designed to replace many uses of the Title class. So eventually: much of MediaWiki.
* Engineer(s) or team for initial implementation: #core_platform_team
* Code steward: @daniel
### Motivation
Create a light weight replacement for 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.
A light-weight value object to represent wiki pages is generally useful for decoupling, 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. There is two approaches to this:
##### Option 1: composition
Make PageIdentity a value object, and let title contain an instance of PageIdentity, accessible via an asPageIdentity() method. Title would also have a static castFromPageIdentity() method that would instantiate a Title from a PageIdentity. For optimization, PageIdentity could have a reference to a Title "glued" to it, which can used by castFromPageIdentity() to avoid instantiating a new title on every call.
This approach allows the definition of PageIdentity to be entirely independent of Title. The new semantics and signatures can be defined without much though how they relate to the semantics of Title.
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()
function getWikiId(): string; // see RevisionRecord::getWikiId()
function exists() bool; // see Title::exists()
function getNamespace(): int; // see Title::getNamespace
function getName(): 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.
Draft:
```lang=php
interface PageIdentity {
function getId(): int; // see Title::getArticleID()
function getWikiId(): string; // see RevisionRecord::getWikiId()
function exists() bool; // see Title::exists()
function getNamespace(): int; // see Title::getNamespace
function getDBkey(): string; // see Title::getDBkey
function assertProperPage(): bool; // Throws if canExist() returns false. Taint, to be deprecated.
}
```
##### PageIdentity, PageRecord, and LinkTarget, etcet
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
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, TitleValue should have a static newFromPage() method.or the database indicated by getWIkiId()?
##### 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 doe4s that relate to isExternal() and getInterwiki() defined in LinkTarget?