Introduce PageRecord interface

PageRecord is intended as a lightweight alternative to WikiPage,
similar to RevisionRecord.

Bug: T272421
Bug: T272422
Depends-On: Ia34cc8fa795b6b27832982fc22fa237ee36339b4
Depends-On: I39a984c9f3132f755f7d8984b789d05ad080d7b4
Change-Id: Ib3c9b22e0c0b7c97e6c230f2fecf0663b4c68cc6
This commit is contained in:
daniel 2020-04-16 18:39:15 +02:00 committed by Petr Pchelko
parent 749b25892b
commit f37ebf4478
8 changed files with 684 additions and 5 deletions

View file

@ -27,8 +27,10 @@ use MediaWiki\Interwiki\InterwikiLookup;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use MediaWiki\Page\ExistingPageRecord;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\PageIdentityValue;
use MediaWiki\Page\PageStoreRecord;
use MediaWiki\Page\ProperPageIdentity;
use Wikimedia\Assert\Assert;
use Wikimedia\Assert\PreconditionException;
@ -4682,4 +4684,44 @@ class Title implements LinkTarget, PageIdentity, IDBAccessObject {
);
}
/**
* Returns the page represented by this Title as a ProperPageRecord.
* The PageRecord returned by this method is guaranteed to be immutable,
* the page is guaranteed to exist.
*
* @note For now, this method queries the database on every call.
* @since 1.36
*
* @param int $flags Either a bitfield of class READ_* constants or GAID_FOR_UPDATE
*
* @return ExistingPageRecord
* @throws PreconditionException if the page does not exist, or is not a proper page,
* that is, if it is a section link, interwiki link, link to a special page, or such.
*/
public function toPageRecord( $flags = 0 ): ExistingPageRecord {
// TODO: Cache this? Construct is more efficiently?
$this->assertProperPage();
Assert::precondition(
$this->exists(),
'This Title instance does not represent an existing page: ' . $this
);
return new PageStoreRecord(
(object)[
'page_id' => $this->getArticleID( $flags ),
'page_namespace' => $this->getNamespace(),
'page_title' => $this->getDBkey(),
'page_wiki_id' => $this->getWikiId(),
'page_latest' => $this->getLatestRevID( $flags ),
'page_is_new' => $this->isNewPage(), // no flags?
'page_is_redirect' => $this->isRedirect( $flags ),
'page_touched' => $this->getTouched(), // no flags?
'page_lang' => $this->getPageLanguage()->getCode(),
],
PageIdentity::LOCAL
);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace MediaWiki\Page;
/**
* Data record representing a page that currently exists as
* an editable page on a wiki.
*
* @note This is intended to become an alias for PageRecord, once PageRecord is guaranteed
* to be immutable and to represent existing pages.
*
* @stable to type
*
* @since 1.36
*/
interface ExistingPageRecord extends PageRecord, ProperPageIdentity {
/**
* Always true.
*
* @return bool
*/
public function exists(): bool;
}

View file

@ -0,0 +1,70 @@
<?php
namespace MediaWiki\Page;
/**
* Data record representing a page that is (or used to be, or could be)
* an editable page on a wiki.
*
* @note For compatibility with the WikiPage class, PageIdentity instances may
* represent non-existing pages. In the future, the contract of this interface is intended
* to be changed to disallow this.
*
* @note For compatibility with the WikiPage class, PageIdentity instances may
* be mutable, and return different values from methods such as getLatest() or isRedirect()
* at different times. In the future, the contract of this interface is intended
* to be changed to disallow this.
*
* @note Only WikiPage should implement PageRecord directly, other implementations should use
* ExistingPageRecord instead. Once WikiPage is removed or guaranteed to be immutable and
* existing, ExistingPageRecord will become an alias of PageRecord.
*
* @todo In the future, PageRecord should extend ProperPageIdentity. This will only
* become possible when WikiPage can no longer represent non-proper pages.
*
* @stable to type
*
* @since 1.36
*/
interface PageRecord extends PageIdentity {
/**
* False if the page has had more than one edit.
*
* @warning this is not guaranteed to always return true for old pages that have only one edit.
*
* @return bool
*/
public function isNew();
/**
* True if the page is a redirect.
*
* @return bool
*/
public function isRedirect();
/**
* The ID of the page's latest revision.
*
* @param string|false $wikiId Must be provided when accessing the ID of the latest revision of
* a non-local PageRecord, to prevent data corruption when using a PageRecord belonging
* to one wiki in the context of another. Should be omitted if expecting the local wiki.
*
* @return int The revision ID of the page's latest revision, or 0 if the page does not exist.
*/
public function getLatest( $wikiId = self::LOCAL );
/**
* Timestamp at which the page was last flagged for rerendering.
*
* @return string
*/
public function getTouched();
/**
* The page's language.
*
* @return string
*/
public function getLanguage();
}

View file

@ -0,0 +1,122 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\Page;
use MWTimestamp;
use stdClass;
use Wikimedia\Assert\Assert;
/**
* Immutable data record representing an editable page on a wiki.
* Corresponds to a row in the page table.
*
* @since 1.36
*/
class PageStoreRecord extends PageIdentityValue implements ExistingPageRecord {
/**
* Fields from the page table.
*
* @var stdClass
*/
private $row;
/**
* The $row object must provide the following fields:
* - page_id: the page ID
* - page_namespace: the page's namespace
* - page_title: the page's title in normalized DB key form.
* - page_latest: the revision ID of the page's current revision
* - page_is_new: whether the page is new and only has one edit
* - page_is_redirect: whether the page is a redirect
* - page_touched: the time at which the page was last re-parsed
* - page_lang: the page's primary language (supply the content language if not known)
*
* @param stdClass $row A row from the page table
* @param string|bool $wikiId The Id of the wiki this page belongs to,
* or self::LOCAL for the local wiki.
*/
public function __construct( stdClass $row, $wikiId ) {
Assert::parameter( isset( $row->page_id ), '$row->page_id', 'is required' );
Assert::parameter( isset( $row->page_namespace ), '$row->page_namespace', 'is required' );
Assert::parameter( isset( $row->page_title ), '$row->page_title', 'is required' );
Assert::parameter( isset( $row->page_latest ), '$row->page_latest', 'is required' );
Assert::parameter( isset( $row->page_is_new ), '$row->page_is_new', 'is required' );
Assert::parameter( isset( $row->page_is_redirect ), '$row->page_is_redirect', 'is required' );
Assert::parameter( isset( $row->page_touched ), '$row->page_touched', 'is required' );
Assert::parameter( isset( $row->page_lang ), '$row->page_lang', 'is required' );
Assert::parameter( $row->page_id > 0, '$pageId', 'must be greater than zero (page must exist)' );
parent::__construct( $row->page_id, $row->page_namespace, $row->page_title, $wikiId );
$this->row = $row;
}
/**
* False if the page has had more than one edit.
*
* @return bool
*/
public function isNew(): bool {
return (bool)$this->row->page_is_new;
}
/**
* True if the page is a redirect.
*
* @return bool
*/
public function isRedirect(): bool {
return (bool)$this->row->page_is_redirect;
}
/**
* The ID of the page'S latest revision.
*
* @param bool $wikiId
*
* @return int
*/
public function getLatest( $wikiId = self::LOCAL ): int {
$this->assertWiki( $wikiId );
return (int)$this->row->page_latest;
}
/**
* Timestamp at which the page was last rerendered.
*
* @return string
*/
public function getTouched(): string {
return MWTimestamp::convert( TS_MW, $this->row->page_touched );
}
/**
* Language in which the page is written.
*
* @return string
*/
public function getLanguage(): string {
return (string)$this->row->page_lang;
}
}

View file

@ -29,7 +29,10 @@ use MediaWiki\Edit\PreparedEdit;
use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use MediaWiki\Page\ExistingPageRecord;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\PageRecord;
use MediaWiki\Page\PageStoreRecord;
use MediaWiki\Page\ParserOutputAccess;
use MediaWiki\Permissions\Authority;
use MediaWiki\Permissions\PermissionStatus;
@ -58,7 +61,7 @@ use Wikimedia\Rdbms\LoadBalancer;
* Some fields are public only for backwards-compatibility. Use accessors.
* In the past, this class was part of Article.php and everything was public.
*/
class WikiPage implements Page, IDBAccessObject, PageIdentity {
class WikiPage implements Page, IDBAccessObject, PageRecord {
use NonSerializableTrait;
use ProtectedHookAccessorTrait;
use WikiAwareEntityTrait;
@ -100,6 +103,16 @@ class WikiPage implements Page, IDBAccessObject, PageIdentity {
*/
protected $mRedirectTarget = null;
/**
* @var bool
*/
private $mIsNew = false;
/**
* @var bool
*/
private $mIsRedirect = false;
/**
* @var int|false False means "not loaded"
* @todo make protected
@ -139,6 +152,11 @@ class WikiPage implements Page, IDBAccessObject, PageIdentity {
*/
protected $mTouched = '19700101000000';
/**
* @var string|null
*/
protected $mLanguage = null;
/**
* @var string
*/
@ -340,8 +358,11 @@ class WikiPage implements Page, IDBAccessObject, PageIdentity {
$this->mPageIsRedirectField = false;
$this->mLastRevision = null; // Latest revision
$this->mTouched = '19700101000000';
$this->mLanguage = null;
$this->mLinksUpdated = '19700101000000';
$this->mTimestamp = '';
$this->mIsNew = false;
$this->mIsRedirect = false;
$this->mLatest = false;
// T59026: do not clear $this->derivedDataUpdater since getDerivedDataUpdater() already
// checks the requested rev ID and content against the cached one. For most
@ -561,12 +582,17 @@ class WikiPage implements Page, IDBAccessObject, PageIdentity {
// Old-fashioned restrictions
$this->mTitle->loadRestrictions( $data->page_restrictions );
$contLang = MediaWikiServices::getInstance()->getContentLanguage();
$this->mId = intval( $data->page_id );
$this->mTouched = MWTimestamp::convert( TS_MW, $data->page_touched );
$this->mLanguage = $data->page_lang ?? $contLang->getCode();
$this->mLinksUpdated = $data->page_links_updated === null
? null
: MWTimestamp::convert( TS_MW, $data->page_links_updated );
$this->mPageIsRedirectField = (bool)$data->page_is_redirect;
$this->mIsNew = intval( $data->page_is_new ?? 0 );
$this->mIsRedirect = intval( $data->page_is_redirect ?? 0 );
$this->mLatest = intval( $data->page_latest );
// T39225: $latest may no longer match the cached latest RevisionRecord object.
// Double-check the ID of any cached latest RevisionRecord object for consistency.
@ -650,7 +676,11 @@ class WikiPage implements Page, IDBAccessObject, PageIdentity {
* @return bool
*/
public function isRedirect() {
return $this->getRedirectTarget() !== null;
if ( !$this->mDataLoaded ) {
$this->loadPageData();
}
return (bool)$this->mIsRedirect;
}
/**
@ -669,6 +699,22 @@ class WikiPage implements Page, IDBAccessObject, PageIdentity {
return $this->mPageIsRedirectField;
}
/**
* Tests if the page is new (only has one revision).
* May produce false negatives for some old pages.
*
* @since 1.36
*
* @return bool
*/
public function isNew() {
if ( !$this->mDataLoaded ) {
$this->loadPageData();
}
return (bool)$this->mIsNew;
}
/**
* Returns the page's content model id (see the CONTENT_MODEL_XXX constants).
*
@ -733,6 +779,17 @@ class WikiPage implements Page, IDBAccessObject, PageIdentity {
return $this->mTouched;
}
/**
* @return string language code for the page
*/
public function getLanguage() {
if ( !$this->mDataLoaded ) {
$this->loadLastEdit();
}
return $this->mLanguage ?: MediaWikiServices::getInstance()->getContentLanguage()->getCode();
}
/**
* Get the page_links_updated field
* @return string|null Containing GMT timestamp
@ -746,9 +803,12 @@ class WikiPage implements Page, IDBAccessObject, PageIdentity {
/**
* Get the page_latest field
* @param bool $wikiId
* @return int The rev_id of current revision
*/
public function getLatest() {
public function getLatest( $wikiId = self::LOCAL ) {
$this->assertWiki( $wikiId );
if ( !$this->mDataLoaded ) {
$this->loadPageData();
}
@ -1454,6 +1514,8 @@ class WikiPage implements Page, IDBAccessObject, PageIdentity {
$content = $revision->getContent( SlotRecord::MAIN );
$len = $content ? $content->getSize() : 0;
$rt = $content ? $content->getUltimateRedirectTarget() : null;
$isNew = ( $lastRevision === 0 ) ? 1 : 0;
$isRedirect = $rt !== null ? 1 : 0;
$conditions = [ 'page_id' => $this->getId() ];
@ -1470,8 +1532,8 @@ class WikiPage implements Page, IDBAccessObject, PageIdentity {
$row = [ /* SET */
'page_latest' => $revId,
'page_touched' => $dbw->timestamp( $revision->getTimestamp() ),
'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
'page_is_redirect' => $rt !== null ? 1 : 0,
'page_is_new' => $isNew,
'page_is_redirect' => $isRedirect,
'page_len' => $len,
'page_content_model' => $model,
];
@ -1489,6 +1551,8 @@ class WikiPage implements Page, IDBAccessObject, PageIdentity {
$this->mRedirectTarget = null;
$this->mHasRedirectTarget = null;
$this->mPageIsRedirectField = (bool)$rt;
$this->mIsNew = (bool)$isNew;
$this->mIsRedirect = (bool)$isRedirect;
// Update the LinkCache.
$linkCache = MediaWikiServices::getInstance()->getLinkCache();
$linkCache->addGoodLinkObj(
@ -4223,4 +4287,42 @@ class WikiPage implements Page, IDBAccessObject, PageIdentity {
return true;
}
/**
* Returns the page represented by this WikiPage as a PageStoreRecord.
* The PageRecord returned by this method is guaranteed to be immutable.
*
* It is preferred to use this method rather than using the WikiPage as a PageIdentity directly.
* @since 1.36
*
* @throws PreconditionException if the page does not exist.
*
* @return ExistingPageRecord
*/
public function toPageRecord(): ExistingPageRecord {
// TODO: replace individual member fields with a PageRecord instance that is always present
if ( !$this->mDataLoaded ) {
$this->loadPageData();
}
Assert::precondition(
$this->exists(),
'This WikiPage instance does not represent an existing page: ' . $this->mTitle
);
return new PageStoreRecord(
(object)[
'page_id' => $this->getId(),
'page_namespace' => $this->mTitle->getNamespace(),
'page_title' => $this->mTitle->getDBkey(),
'page_latest' => $this->mLatest,
'page_is_new' => $this->mIsNew,
'page_is_redirect' => $this->mIsRedirect,
'page_touched' => $this->mTouched,
'page_lang' => $this->getLanguage()
],
PageIdentity::LOCAL
);
}
}

View file

@ -475,6 +475,41 @@ class TitleMethodsTest extends MediaWikiLangTestCase {
$this->assertSame( $title->getWikiId(), $page->getWikiId() );
}
/**
* @dataProvider provideProperPage
* @covers Title::toPageRecord
*/
public function testToPageRecord( $ns, $text ) {
$title = Title::makeTitle( $ns, $text );
$wikiPage = $this->getExistingTestPage( $title );
$record = $title->toPageRecord();
$this->assertNotSame( $title, $record );
$this->assertNotSame( $title, $wikiPage );
$this->assertSame( $title->getId(), $record->getId() );
$this->assertSame( $title->getNamespace(), $record->getNamespace() );
$this->assertSame( $title->getDBkey(), $record->getDBkey() );
$this->assertSame( $title->getWikiId(), $record->getWikiId() );
$this->assertSame( $title->getLatestRevID(), $record->getLatest() );
$this->assertSame( MWTimestamp::convert( TS_MW, $title->getTouched() ), $record->getTouched() );
$this->assertSame( $title->isNewPage(), $record->isNew() );
$this->assertSame( $title->isRedirect(), $record->isRedirect() );
}
/**
* @dataProvider provideImproperPage
* @covers Title::toPageRecord
*/
public function testToPageRecord_fail( $ns, $text, $fragment = '', $interwiki = '' ) {
$title = Title::makeTitle( $ns, $text, $fragment, $interwiki );
$this->expectException( PreconditionException::class );
$title->toPageRecord();
}
public function provideImproperPage() {
return [
[ NS_MAIN, '' ],

View file

@ -1931,12 +1931,14 @@ more stuff
'page_id' => '44',
'page_len' => '76',
'page_is_redirect' => '1',
'page_is_new' => '1',
'page_latest' => '99',
'page_namespace' => '3',
'page_title' => 'JaJaTitle',
'page_restrictions' => 'edit=autoconfirmed,sysop:move=sysop',
'page_touched' => '20120101020202',
'page_links_updated' => '20140101020202',
'page_lang' => 'it',
];
foreach ( $overrides as $key => $value ) {
$row[$key] = $value;
@ -1952,6 +1954,8 @@ more stuff
$test->assertSame( 76, $wikiPage->getTitle()->getLength() );
$test->assertTrue( $wikiPage->getPageIsRedirectField() );
$test->assertSame( 99, $wikiPage->getLatest() );
$test->assertSame( true, $wikiPage->isNew() );
$test->assertSame( 'it', $wikiPage->getLanguage() );
$test->assertSame( 3, $wikiPage->getTitle()->getNamespace() );
$test->assertSame( 'JaJaTitle', $wikiPage->getTitle()->getDBkey() );
$test->assertSame(
@ -1989,6 +1993,17 @@ more stuff
);
}
];
yield 'no language' => [
$this->getRow( [
'page_lang' => null,
] ),
function ( WikiPage $wikiPage, self $test ) {
$test->assertSame(
'en',
$wikiPage->getLanguage()
);
}
];
yield 'not redirect' => [
$this->getRow( [
'page_is_redirect' => '0',
@ -1997,6 +2012,14 @@ more stuff
$test->assertFalse( $wikiPage->isRedirect() );
}
];
yield 'not new' => [
$this->getRow( [
'page_is_new' => '0',
] ),
function ( WikiPage $wikiPage, self $test ) {
$test->assertFalse( $wikiPage->isNew() );
}
];
}
/**
@ -2699,4 +2722,48 @@ more stuff
$this->assertTrue( $title->isRedirect() );
}
/**
* @covers WikiPage::getTitle
* @covers WikiPage::getId
* @covers WikiPage::getNamespace
* @covers WikiPage::getDBkey
* @covers WikiPage::getWikiId
* @covers WikiPage::canExist
*/
public function testGetTitle() {
$page = $this->createPage( __METHOD__, 'whatever' );
$title = $page->getTitle();
$this->assertSame( __METHOD__, $title->getText() );
$this->assertSame( $page->getId(), $title->getId() );
$this->assertSame( $page->getNamespace(), $title->getNamespace() );
$this->assertSame( $page->getDBkey(), $title->getDBkey() );
$this->assertSame( $page->getWikiId(), $title->getWikiId() );
$this->assertSame( $page->canExist(), $title->canExist() );
}
/**
* @covers WikiPage::toPageRecord
* @covers WikiPage::getLatest
* @covers WikiPage::getTouched
* @covers WikiPage::isNew
* @covers WikiPage::isRedirect
*/
public function testToPageRecord() {
$page = $this->createPage( __METHOD__, 'whatever' );
$record = $page->toPageRecord();
$this->assertSame( $page->getId(), $record->getId() );
$this->assertSame( $page->getNamespace(), $record->getNamespace() );
$this->assertSame( $page->getDBkey(), $record->getDBkey() );
$this->assertSame( $page->getWikiId(), $record->getWikiId() );
$this->assertSame( $page->canExist(), $record->canExist() );
$this->assertSame( $page->getLatest(), $record->getLatest() );
$this->assertSame( $page->getTouched(), $record->getTouched() );
$this->assertSame( $page->isNew(), $record->isNew() );
$this->assertSame( $page->isRedirect(), $record->isRedirect() );
}
}

View file

@ -0,0 +1,218 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @author Daniel Kinzler
*/
namespace MediaWiki\Tests\Unit\Page;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\PageIdentityValue;
use MediaWiki\Page\PageRecord;
use MediaWiki\Page\PageStoreRecord;
use MediaWikiUnitTestCase;
use RuntimeException;
use Wikimedia\Assert\ParameterAssertionException;
/**
* @covers \MediaWiki\Page\PageStoreRecord
*
* @group Title
*/
class PageStoreRecordTest extends MediaWikiUnitTestCase {
public function goodConstructorProvider() {
return [
[
(object)[
'page_id' => 7,
'page_namespace' => NS_MAIN,
'page_title' => 'Test',
'page_touched' => '20200909001122',
'page_latest' => 1717,
'page_is_new' => true,
'page_is_redirect' => true,
'page_lang' => 'it',
],
PageIdentity::LOCAL
],
[
(object)[
'page_id' => 3,
'page_namespace' => NS_USER,
'page_title' => 'Test',
'page_touched' => '20200909001122',
'page_latest' => 1717,
'page_is_new' => false,
'page_is_redirect' => false,
'page_lang' => 'und',
],
'h2g2'
]
];
}
/**
* @dataProvider goodConstructorProvider
*/
public function testConstruction( $row, $wikiId ) {
$pageRecord = new PageStoreRecord( $row, $wikiId );
$this->assertSame( $row->page_id, $pageRecord->getId( $wikiId ) );
$this->assertSame( $row->page_id > 0, $pageRecord->exists() );
$this->assertSame( $row->page_namespace, $pageRecord->getNamespace() );
$this->assertSame( $row->page_title, $pageRecord->getDBkey() );
$this->assertTrue( $pageRecord->canExist() );
$this->assertSame( $wikiId, $pageRecord->getWikiId() );
$this->assertSame( $row->page_touched, $pageRecord->getTouched() );
$this->assertSame( $row->page_latest, $pageRecord->getLatest( $wikiId ) );
$this->assertSame( $row->page_is_new, $pageRecord->isNew() );
$this->assertSame( $row->page_is_redirect, $pageRecord->isRedirect() );
$this->assertSame( $row->page_lang, $pageRecord->getLanguage() );
}
public function badConstructorProvider() {
$row = [
'page_id' => 1,
'page_namespace' => NS_MAIN,
'page_title' => 'Test',
'page_touched' => '20200909001122',
'page_latest' => 1717,
'page_is_new' => true,
'page_is_redirect' => true,
];
return [
'nonexisting page' => [ (object)( [ 'page_id' => 0 ] + $row ) ],
'negative id' => [ (object)( [ 'page_id' => -1 ] + $row ) ],
'special page' => [ (object)( [ 'page_namespace' => NS_SPECIAL ] + $row ) ],
'empty title' => [ (object)( [ 'page_title' => '' ] + $row ) ],
'section link' => [ (object)( [ 'page_title' => 'Foo#Bar' ] + $row ) ],
'pipe in title' => [ (object)( [ 'page_title' => 'Foo|Bar' ] + $row ) ],
'tab in title' => [ (object)( [ 'page_title' => "Foo\tBar" ] + $row ) ],
// missing data
'missing touched' => [ (object)array_diff_key( $row, [ 'touched' => 'foo' ] ) ],
'missing latest' => [ (object)array_diff_key( $row, [ 'latest' => 'foo' ] ) ],
'missing is_new' => [ (object)array_diff_key( $row, [ 'is_new' => 'foo' ] ) ],
'missing lang' => [ (object)array_diff_key( $row, [ 'lang' => 'foo' ] ) ],
'missing is_redirect' => [ (object)array_diff_key( $row, [ 'is_redirect' => 'foo' ] ) ],
];
}
/**
* @dataProvider badConstructorProvider
*/
public function testConstructionErrors( $row ) {
$this->expectException( ParameterAssertionException::class );
new PageStoreRecord( $row, PageStoreRecord::LOCAL );
}
public function testGetLatestRequiresForeignWikiId() {
$row = (object)[
'page_id' => 7,
'page_namespace' => NS_MAIN,
'page_title' => 'Test',
'page_touched' => '20200909001122',
'page_latest' => 1717,
'page_is_new' => true,
'page_is_redirect' => true,
'page_lang' => 'it',
];
$pageRecord = new PageStoreRecord( $row, 'acme' );
$this->expectException( RuntimeException::class );
$pageRecord->getLatest( 'xyzzy' );
}
public function provideToString() {
$row = [
'page_id' => 7,
'page_namespace' => NS_MAIN,
'page_title' => 'Test',
'page_touched' => '20200909001122',
'page_latest' => 1717,
'page_is_new' => true,
'page_is_redirect' => true,
'page_lang' => 'it',
];
yield [
new PageStoreRecord( (object)$row, PageIdentity::LOCAL ),
'#7 [0:Test]'
];
yield [
new PageStoreRecord( (object)( [ 'page_namespace' => 200 ] + $row ), 'codewiki' ),
'#7@codewiki [200:Test]'
];
}
/**
* @dataProvider provideToString
*/
public function testToString( PageStoreRecord $value, $expected ) {
$this->assertSame(
$expected,
$value->__toString()
);
}
public function provideIsSamePageAs() {
$row = [
'page_id' => 7,
'page_namespace' => NS_MAIN,
'page_title' => 'Test',
'page_touched' => '20200909001122',
'page_latest' => 1717,
'page_is_new' => true,
'page_is_redirect' => true,
'page_lang' => 'it',
];
yield [
new PageStoreRecord( (object)$row, PageRecord::LOCAL ),
new PageStoreRecord( (object)$row, PageRecord::LOCAL ),
true
];
yield [
new PageStoreRecord( (object)$row, PageRecord::LOCAL ),
new PageStoreRecord( (object)$row, 'acme' ),
false
];
yield [
new PageStoreRecord( (object)$row, 'acme' ),
new PageStoreRecord( (object)$row, 'acme' ),
true
];
yield [
new PageStoreRecord( (object)$row, 'acme' ),
new PageIdentityValue( 7, NS_MAIN, 'Test', 'acme' ),
true
];
}
/**
* @dataProvider provideIsSamePageAs
*/
public function testIsSamePageAs( PageIdentity $a, PageIdentity $b, $expected ) {
$this->assertSame( $expected, $a->isSamePageAs( $b ) );
$this->assertSame( $expected, $b->isSamePageAs( $a ) );
}
}