Manual:Revision.php/Migration
For extensions that make widespread use of Revision objects, switching to RevisionRecord may be challenging. Here are some suggestions:
Constructors
[edit]To replace uses of the static constructors (Revision::newFrom*
) while still having Revision
objects, as a stop-gap measure use "new Revision( $revisionRecord )" with the RevisionRecord
constructed using the RevisionLookup
service.
The RevisionLookup
methods either return a RevisionRecord
or null, and the corresponding Revision
methods returned either a Revision
or null:
Instead of
$revision = Revision::newFromId( $id, $flags );
use
$revisionRecord = MediaWikiServices::getInstance()->getRevisionLookup()->getRevisionById( $ids, $flags );
$revision = $revisionRecord ? new Revision( $revisionRecord ) : null;
For the other static constructors, simply replace `getRevisionById` with the corresponding RevisionLookup method (note that the loadFrom*
methods accept different parameters than the newFrom*
):
Revision::newFromTitle -> RevisionLookup::getRevisionByTitle Revision::newFromPageId -> RevisionLookup::getRevisionByPageId Revision::loadFromPageId -> RevisionLookup::loadRevisionFromPageId Revision::loadFromTitle -> RevisionLookup::loadRevisionFromTitle Revision::loadFromTimestamp -> RevisionLookup::loadRevisionFromTimestamp
The RevisionFactory service is used to replace Revision::newFromArchiveRow and Revision::newFromRow; use RevisionFactory::newRevisionFromArchiveRow, and ::newRevisionFromRow (if you were calling ::newFromRow with an array, the replacement would be RevisionFactory::newMutableRevisionFromArray, but that method is also hard deprecated - you should construct a MutableRevisionRecord instead).
For direct uses of `new Revision`, either existing ones or those added now, see the notes below regarding the final removal of constructing Revision objects.
Fetching relative revisions
[edit]Revision::getNext was replaced by RevisionLookup::getNextRevision, which returns a RevisionRecord object. Likewise, Revision::getPrevious was replaced by RevisionLookup::getPreviousRevision.
Revision text
[edit]Revision::getRevisionText
returns a string of the text content for a specific revision (based on a row from the database).
To replace it, get the RevisionRecord
object corresponding to the row, and then use RevisionRecord::getContent( SlotRecord::MAIN )
to get the relevant content object, and if there is such an object, use Content::serialize
to get the text.
Revision::compressRevisionText
can be replaced with SqlBlobStore::compressData
.
Revision::decompressRevisionText
can be replaced with SqlBlobStore::decompressData
.
Other static methods
[edit]The following static methods are replaced by non-static methods:
Revision::getQueryInfo -> RevisionStore::getQueryInfo Revision::getArchiveQueryInfo -> RevisionStore::getArchiveQueryInfo
Revision::getParentLengths -> RevisionStore::getRevisionSizes Revision::getTimestampFromId -> RevisionStore::getTimestampFromId
Revision
method's first parameter was a Title
object that was ignored, the RevisionStore
method does not accept a Title
, and only needs the relevant id and any query flags.Revision::countByPageId -> RevisionStore::countRevisionsByPageId Revision::countByTitle -> RevisionStore::countRevisionsByTitle
Revision::userWasLastToEdit -> RevisionStore::userWasLastToEdit
Revision
method's first parameter was an int or an IDatabase
object, the RevisionStore
method requires an IDatabase
object. ADDITIONALLY, the RevisionStore
method is itself soft deprecated.userCan
[edit]Revision::userCanBitfield
can be replaced with RevisionRecord::userCanBitfield
, and Revision::userCan
can be replaced with RevisionRecord::userCanBitfield
with the bitfield being the int returned from RevisionRecord::getVisibility
.
Revision
methods fell back to $wgUser
if no User
object was passed; RevisionRecord::userCanBitfield
requires that a User
be provided.Corresponding Revision and RevisionRecord methods
[edit]The following Revision methods can be replaced with identical RevisionRecord methods (though some have different names).
Once a Revision
object is available in a relevant class or function, I suggest immediately retrieving the corresponding RevisionRecord
via Revision::getRevisionRecord
and slowly making use of the RevisionRecord
instead of the Revision
:
Revision::getId -> RevisionRecord::getId Revision::getParentId -> RevisionRecord::getParentId Revision::getPage -> RevisionRecord::getPageId Revision::isMinor -> RevisionRecord::isMinor Revision::isDeleted -> RevisionRecord::isDeleted Revision::getVisibility -> RevisionRecord::getVisibility Revision::getTimestamp -> RevisionRecord::getTimestamp Revision::isCurrent -> RevisionRecord::isCurrent
The following Revision
methods can be replaced with identical RevisionRecord
methods, BUT, while the Revision
methods returned null if the value was unknown, the RevisionRecord
methods throw RevisionAccessException
exceptions:
Revision::getSize -> RevisionRecord::getSize Revision::getSha1 -> RevisionRecord::getSha1
Revision::getTitle returned the relevant Title object for the revision.
Its replacement, RevisionRecord::getPageAsLinkTarget
, returns a LinkTarget
instead of a Title
.
If a full Title
object is needed, use Title::newFromLinkTarget
.
Audience-based information
[edit]The Revision
class had multiple methods that accepted a specific audience for which a value (the editing user's id and username, the edit summary used, or the revision content) was based on whether the audience could view the information (since it may have been revision deleted).
The constants used for specifying an audience are identical in the Revision
and RevisionRecord
classes:
Revision::FOR_PUBLIC -> RevisionRecord::FOR_PUBLIC Revision::FOR_THIS_USER -> RevisionRecord::FOR_THIS_USER Revision::RAW -> RevisionRecord::RAW
Replacements:
Revision::getUser
returned the editing user's id, or 0, andRevision::getUserText
returned the editing user's username, or an empty string. These were replaced withRevisionRecord::getUser
, which returns aUserIdentity
object if the audience specified can view the information, or null otherwise. To get the user's id, useUserIdentity::getId
, and for the username, useUserIdentity::getName
.Revision::getComment
returned the revision's edit summary (as a string), or null. It was replaced withRevisionRecord::getComment
, HOWEVER instead of a string,RevisionRecord::getComment
returns a CommentStoreComment.Revision::getContent
returned the content of the revision's main slot, or null. It was replaced withRevisionRecord::getContent
, HOWEVER theRevisionRecord
method requires that the slot for which the content should be retrieved be specified. UseSlotRecord::MAIN
to match the behaviour of theRevision
method. FURTHERMORE, theRevisionRecord
method can throw aRevisionAccessException
, which theRevision
method silently converted to null.
::getUser
, ::getUserText
, ::getComment
, or ::getContent
was FOR_THIS_USER, and no second parameter was passed with the user, the Revision
methods fell back to using $wgUser
. The RevisionRecord
methods have no such fallback, and will throw an InvalidArgumentException
if attempting to use FOR_THIS_USER without passing a User.Content handling
[edit]As part of the migration to multi-content revisions, there is no longer a single content model or format for a revision, but rather there are content models and formats for each slot.
- Revision::getContentModel returned a string for the content model of the revision's main slot (SlotRecord::MAIN), either the model set or the default model. To replace it, get the RevisionRecord's main slot, and use SlotRecord::getModel. If the revision has no main slot, fall back to the default model for the title. Use the SlotRoleRegistry service to get the SlotRoleHandler for the relevant role (in this case SlotRecord::MAIN), and then use SlotRoleHandler::getDefaultModel.
- Revision::getContentFormat returned a string for the content format of the revision's main slot, or falls back to the default format for the content model. To replace it, get the RevisionRecord's main slot, and use SlotRecord::getFormat. If the revision has no main slot, or if SlotRecord::getFormat returns null, fall back to the default format for the content model using ContentHandler::getDefaultFormat with the relevant ContentHandler.
- Revision::getContentHandler returned the relevant ContentHandler object. Use ContentHandlerFactory::getContentHandler with the relevant content model as a replacement.
Setting revision information
[edit]- Revision::setId was only supported if the Revision object corresponded to a MutableRevisionRecord. It can be replaced with MutableRevisionRecord::setId.
- Revision::setUserIdAndName was only supported if the Revision object corresponded to a MutableRevisionRecord. It can be replaced with MutableRevisionRecord::setUser.
- The MutableRevisionRecord method requires a UserIdentity, rather than a user id and name.
- Revision::setTitle was not supported, and threw an exception if it was called with a different title than the one already set for the revision.
Misc
[edit]Revision::getTextId -> use SlotRecord::getContentAddress for retrieving an actual content address, or RevisionRecord::hasSameContent to compare content Revision::isUnpatrolled -> RevisionStore::getRcIdIfUnpatrolled Revision::getRecentChange -> RevisionStore::getRecentChange Revision::getSerializedData -> use SlotRecord::getContent for retrieving a content object, and Content::serialize for the serialized form Revision::insertOn -> RevisionStore::insertRevisionOn Revision::base36Sha1 -> SlotRecord::base36Sha1 Revision::newNullRevision -> RevisionStore::newNullRevision Revision::newKnownCurrent -> RevisionLookup::getKnownCurrentRevision
Constructing
[edit]For uses of `new Revision`, there are multiple relevant replacements:
- If the Revision was constructed with a RevisionRecord, just use that RevisionRecord directly
- If the Revision was constructed with an array, use a MutableRevisionRecord - construct it manually and set the various fields.
- If neither the `user` nor `user_text` fields were set, the Revision class fell back to using $wgUser; MutableRevisionRecord includes no such fallback, and if no user is provided the object simply won't have a user set.
- If the Revision was constructed with an object, use RevisionFactory::newRevisionFromRow
Hooks
[edit]A number of hooks that included Revision objects as parameters were hard deprecated in 1.35 and then removed in 1.37.
The following is a basic guide to replacing the use of such hooks, with a focus on handling RevisionRecord objects instead of Revision objects.
Note that the replacement hooks can include other changes as well as the conversion from Revision to RevisionRecord, and the code examples below simply convert back everything else where possible (eg casting a UserIdentity back to a User object) - this will add technical debt and should be properly addressed by actually updating the code in the hook handler to use the new objects.
For the sake of simplicity, the code below assumes that all relevant use
statements are already included.
The hook replacement snippets do not include actually updating using the Revision objects to use RevisionRecord, see the notes above for how to do that.
The hooks UndeleteShowRevision and UserRetrieveNewTalks were removed without replacement.
Old code | New code |
---|---|
/**
* @param Title $title
* @param Revision $revision
* @param int|null $oldPageID
* @return bool|void
*/
public function onArticleRevisionUndeleted(
$title,
$revision,
$oldPageID
) { ... }
|
/**
* @param RevisionRecord $revisionRecord
* @param ?int $oldPageID
* @return bool|void
*/
public function onRevisionUndeleted(
$revisionRecord,
$oldPageID
) {
$title = Title::newFromLinkTarget( $revisionRecord->getPageAsLinkTarget() );
// And now update to handling RevisionRecord instead of Revision
...
}
|
/**
* @param WikiPage $wikiPage
* @param User $user
* @param Revision $revision Revision the page was reverted back to
* @param Revision $current Reverted revision
* @return bool|void
*/
public function onArticleRollbackComplete(
$wikiPage,
$user,
$revision,
$current
) { ... }
|
/**
* @param WikiPage $wikiPage
* @param UserIdentity $userIdentity
* @param RevisionRecord $revisionRecord RevisionRecord for the revision
* the page was reverted back to
* @param RevisionRecord $currentRevRecord RevisionRecord for the reverted revision
* @return bool|void
*/
public function onRollbackComplete(
$wikiPage,
$userIdentity,
$revisionRecord,
$currentRevRecord
) {
$user = User::newFromIdentity( $userIdentity );
// And now update to handling RevisionRecord instead of Revision
...
}
|
/**
* @param Revision $newRev
* @param string[] &$links
* @param Revision|null $oldRev
* @param User $user
* @return bool|void
*/
public function onDiffRevisionTools(
$newRev,
&$links,
$oldRev,
$user
) { ... }
|
/**
* @param RevisionRecord $newRevRecord
* @param string[] &$links
* @param RevisionRecord|null $oldRevRecord
* @param UserIdentity $userIdentity
* @return bool|void
*/
public function onDiffTools(
$newRevRecord,
&$links,
$oldRevRecord,
$userIdentity
) {
$user = User::newFromIdentity( $userIdentity );
// And now update to handling RevisionRecord instead of Revision
...
}
|
/**
* @param DifferenceEngine $diff
* @param Revision|null $oldRev Old revision (may be null/invalid)
* @param Revision $newRev New revision
* @return bool|void
*/
public function onDiffViewHeader(
$diff,
$oldRev,
$newRev
) { ... }
|
/**
* @param DifferenceEngine $differenceEngine
* @return bool|void
*/
public function onDifferenceEngineViewHeader( $differenceEngine ) {
$oldRevRecord = $differenceEngine->getOldRevision();
$newRevRecord = $differenceEngine->getNewRevision();
// And now update to handling RevisionRecord instead of Revision
...
}
|
/**
* @param Revision $rev
* @param string[] &$links
* @param Revision|null $prevRev
* @param User $user
* @return bool|void
*/
public function onHistoryRevisionTools(
$rev,
&$links,
$prevRev,
$user
) { ... }
|
/**
* @param RevisionRecord $revRecord
* @param string[] &$links
* @param RevisionRecord|null $prevRevRecord
* @param UserIdentity $userIdentity
* @return bool|void
*/
public function onHistoryTools(
$revRecord,
&$links,
$prevRevRecord,
$userIdentity
) {
$user = User::newFromIdentity( $userIdentity );
// And now update to handling RevisionRecord instead of Revision
...
}
|
/**
* @param WikiPage $wikiPage
* @param Revision $revision
* @param int|bool $originalRevId
* @param User $user
* @param string[] &$tags
* @return bool|void
*/
public function onNewRevisionFromEditComplete(
$wikiPage,
$revision,
$originalRevId,
$user,
&$tags
) { ... }
|
/**
* @param WikiPage $wikiPage
* @param RevisionRecord $revisionRecord
* @param int|bool $originalRevId
* @param UserIdentity $userIdentity
* @param string[] &$tags
* @return bool|void
*/
public function onRevisionFromEditComplete(
$wikiPage,
$revisionRecord,
$originalRevId,
$userIdentity,
&$tags
) {
$user = User::newFromIdentity( $userIdentity );
// And now update to handling RevisionRecord instead of Revision
...
}
|
/**
* Hook called for new pages created
* @param WikiPage $wikiPage
* @param User $user
* @param Content $content
* @param string $summary
* @param bool $isMinor
* @param null $isWatch (No longer used)
* @param null $section (No longer used)
* @param int $flags Flags passed to WikiPage::doEditContent()
* @param Revision $revision New Revision of the article
* @return bool|void
*/
public function onPageContentInsertComplete(
$wikiPage,
$user,
$content,
$summary,
$isMinor,
$isWatch,
$section,
$flags,
$revision
) { ... }
|
/**
* @param WikiPage $wikiPage
* @param UserIdentity $userIdentity
* @param string $summary
* @param int $flags Flags passed to WikiPage::doEditContent()
* @param RevisionRecord $revisionRecord New RevisionRecord of the article
* @param EditResult $editResult
* @return bool|void
*/
public function onPageSaveComplete(
$wikiPage,
$userIdentity,
$summary,
$flags,
$revisionRecord,
$editResult
) {
// The old hook was only called when new pages were created,
// but the replacement hook is also called for edits to existing pages
if ( !( $flags & EDIT_NEW ) ) {
// Not a new page
return;
}
$user = User::newFromIdentity( $userIdentity );
$content = $revisionRecord->getContent( SlotRecord::MAIN, RevisionRecord::RAW );
$isMinor = $flags & EDIT_MINOR;
// No support for recreating $isWatch and $section, those were unused
// And now update to handling RevisionRecord instead of Revision
...
}
|
/**
* Hook called for new pages created and existing pages updated
* @param WikiPage $wikiPage
* @param User $user
* @param Content $content
* @param string $summary
* @param bool $isMinor
* @param null $isWatch (No longer used)
* @param null $section (No longer used)
* @param int $flags Flags passed to WikiPage::doEditContent()
* @param Revision $revision New Revision of the article
* @param Status $status Status object about to be returned by doEditContent().
* @param int|bool $originalRevId
* @param int $undidRevId Rev ID (or 0) this edit undid
* @return bool|void
*/
public function onPageContentSaveComplete(
$wikiPage,
$user,
$content,
$summary,
$isMinor,
$isWatch,
$section,
$flags,
$revision,
$status,
$originalRevId,
$undidRevId
) { ... }
|
/**
* @param WikiPage $wikiPage
* @param UserIdentity $userIdentity
* @param string $summary
* @param int $flags Flags passed to WikiPage::doEditContent()
* @param RevisionRecord $revisionRecord New RevisionRecord of the article
* @param EditResult $editResult
* @return bool|void
*/
public function onPageSaveComplete(
$wikiPage,
$userIdentity,
$summary,
$flags,
$revisionRecord,
$editResult
) {
$user = User::newFromIdentity( $userIdentity );
$content = $revisionRecord->getContent( SlotRecord::MAIN, RevisionRecord::RAW );
$isMinor = $flags & EDIT_MINOR;
// No support for recreating $isWatch and $section, those were unused
// FIXME No idea how to recreate $status or if its needed
$originalRevId = $editResult->getOriginalRevisionId();
$undidRevId = $editResult->getUndidRevId();
// And now update to handling RevisionRecord instead of Revision
...
}
|
/**
* @param Parser|bool $parser Parser object or false
* @param Title $title Title object of the template to be fetched
* @param Revision $rev Revision object of the template
* @param string|bool|null &$text Transclusion text of the template or false or null
* @param array &$deps Array of template dependencies with 'title',
* 'page_id', and 'rev_id' keys
* @return bool|void
*/
public function onParserFetchTemplate(
$parser,
$title,
$rev,
&$text,
&$deps
) { ... }
|
/**
* @param ?LinkTarget $contextTitle The top-level page title, if any
* @param LinkTarget $title The template link (from the literal wikitext)
* @param bool &$skip Skip this template and link it?
* @param ?RevisionRecord &$revRecord The desired revision record
* @return bool|void True or no return value to continue or false to abort
*/
public function onBeforeParserFetchTemplateRevisionRecord(
?LinkTarget $contextTitle,
LinkTarget $title,
bool &$skip,
?RevisionRecord &$revRecord
) {
// FIXME I have no idea how to handle the differences between these hooks :(
// And now update to handling RevisionRecord instead of Revision
...
}
|
/**
* @param Revision $revision
* @param null $data DEPRECATED! Always null!
* @param null $flags DEPRECATED! Always null!
* @return bool|void
*/
public function onRevisionInsertComplete(
$revision,
$data,
$flags
) { ... }
|
/**
* @param RevisionRecord $revisionRecord
* @return bool|void
*/
public function onRevisionRecordInserted( $revisionRecord ) {
// No support for recreating $data and $flags, those were unused
// And now update to handling RevisionRecord instead of Revision
...
}
|
/**
* Page move post-commit
* @param Title $oldTitle
* @param Title $newTitle
* @param User $user
* @param int $pageid
* @param int $redirid
* @param string $reason
* @param Revision $revision
* @return bool|void
*/
public function onTitleMoveComplete(
$oldTitle,
$newTitle,
$user,
$pageid,
$redirid,
$reason,
$revision
) { ... }
|
/**
* Page move post-commit
* @param LinkTarget $oldLinkTarget
* @param LinkTarget $newLlinkTarget
* @param UserIdentity $userIdentity
* @param int $pageid
* @param int $redirid
* @param string $reason
* @param RevisionRecord $revisionRecord
* @return bool|void
*/
public function onPageMoveComplete(
$oldLinkTarget,
$newLinkTarget,
$userIdentity,
$pageid,
$redirid,
$reason,
$revisionRecord
) {
$oldTitle = Title::newFromLinkTarget( $oldLinkTarget );
$newTitle = Title::newFromLinkTarget( $newLinkTarget );
$user = User::newFromIdentity( $userIdentity );
// And now update to handling RevisionRecord instead of Revision
...
}
|
/**
* Page move pre-commit
* @param Title $oldTitle
* @param Title $newTitle
* @param User $user
* @param int $pageid
* @param int $redirid
* @param string $reason
* @param Revision $revision
* @return bool|void
*/
public function onTitleMoveCompleting(
$oldTitle,
$newTitle,
$user,
$pageid,
$redirid,
$reason,
$revision
) { ... }
|
/**
* Page move pre-commit
* @param LinkTarget $oldLinkTarget
* @param LinkTarget $newLinkTarget
* @param UserIdentity $userIdentiy
* @param int $pageid
* @param int $redirid
* @param string $reason
* @param RevisionRecord $revisionRecord
* @return bool|void
*/
public function onPageMoveCompleting(
$oldLinkTarget,
$newLinkTarget,
$userIdentity,
$pageid,
$redirid,
$reason,
$revisionRecord
) {
$oldTitle = Title::newFromLinkTarget( $oldLinkTarget );
$newTitle = Title::newFromLinkTarget( $newLinkTarget );
$user = User::newFromIdentity( $userIdentity );
// And now update to handling RevisionRecord instead of Revision
...
}
|