From: addshore Date: Tue, 26 Jan 2016 16:36:05 +0000 (+0100) Subject: Move WatchedItem::duplicateEntries to WatchedItemStore X-Git-Tag: 1.31.0-rc.0~7837^2 X-Git-Url: http://git.cyclocoop.org/%24image?a=commitdiff_plain;h=54d4315be6cb3b3c9a2f25f40a27e3ca6cafe188;p=lhc%2Fweb%2Fwiklou.git Move WatchedItem::duplicateEntries to WatchedItemStore This removes static logic from WatchedItem into a class that we can slowly fill with watchlist database and storage things. This also removes the dual namespace handling in the new implementation. Change-Id: Ia67ab1d200ac393c65013b2091e61acefcb3defb --- diff --git a/autoload.php b/autoload.php index 83ca3b35ff..87d0c7cb8e 100644 --- a/autoload.php +++ b/autoload.php @@ -1401,6 +1401,7 @@ $wgAutoloadLocalClasses = array( 'WantedTemplatesPage' => __DIR__ . '/includes/specials/SpecialWantedtemplates.php', 'WatchAction' => __DIR__ . '/includes/actions/WatchAction.php', 'WatchedItem' => __DIR__ . '/includes/WatchedItem.php', + 'WatchedItemStore' => __DIR__ . '/includes/WatchedItemStore.php', 'WatchlistCleanup' => __DIR__ . '/maintenance/cleanupWatchlist.php', 'WebInstaller' => __DIR__ . '/includes/installer/WebInstaller.php', 'WebInstallerComplete' => __DIR__ . '/includes/installer/WebInstallerComplete.php', diff --git a/includes/WatchedItem.php b/includes/WatchedItem.php index 2c400d0707..b597f99d93 100644 --- a/includes/WatchedItem.php +++ b/includes/WatchedItem.php @@ -379,63 +379,15 @@ class WatchedItem { } /** - * Check if the given title already is watched by the user, and if so - * add watches on a new title. To be used for page renames and such. + * @deprecated since 1.27. See WatchedItemStore::duplicateEntry * - * @param Title $ot Page title to duplicate entries from, if present - * @param Title $nt Page title to add watches on + * @param Title $oldTitle + * @param Title $newTitle */ - public static function duplicateEntries( $ot, $nt ) { - WatchedItem::doDuplicateEntries( $ot->getSubjectPage(), $nt->getSubjectPage() ); - WatchedItem::doDuplicateEntries( $ot->getTalkPage(), $nt->getTalkPage() ); + public static function duplicateEntries( Title $oldTitle, Title $newTitle ) { + $store = WatchedItemStore::getDefaultInstance(); + $store->duplicateEntry( $oldTitle->getSubjectPage(), $newTitle->getSubjectPage() ); + $store->duplicateEntry( $oldTitle->getTalkPage(), $newTitle->getTalkPage() ); } - /** - * Handle duplicate entries. Backend for duplicateEntries(). - * - * @param Title $ot - * @param Title $nt - * - * @return bool - */ - private static function doDuplicateEntries( $ot, $nt ) { - $oldnamespace = $ot->getNamespace(); - $newnamespace = $nt->getNamespace(); - $oldtitle = $ot->getDBkey(); - $newtitle = $nt->getDBkey(); - - $dbw = wfGetDB( DB_MASTER ); - $res = $dbw->select( 'watchlist', - [ 'wl_user', 'wl_notificationtimestamp' ], - [ 'wl_namespace' => $oldnamespace, 'wl_title' => $oldtitle ], - __METHOD__, 'FOR UPDATE' - ); - # Construct array to replace into the watchlist - $values = []; - foreach ( $res as $s ) { - $values[] = [ - 'wl_user' => $s->wl_user, - 'wl_namespace' => $newnamespace, - 'wl_title' => $newtitle, - 'wl_notificationtimestamp' => $s->wl_notificationtimestamp, - ]; - } - - if ( empty( $values ) ) { - // Nothing to do - return true; - } - - # Perform replace - # Note that multi-row replace is very efficient for MySQL but may be inefficient for - # some other DBMSes, mostly due to poor simulation by us - $dbw->replace( - 'watchlist', - [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ], - $values, - __METHOD__ - ); - - return true; - } } diff --git a/includes/WatchedItemStore.php b/includes/WatchedItemStore.php new file mode 100644 index 0000000000..83a5856869 --- /dev/null +++ b/includes/WatchedItemStore.php @@ -0,0 +1,86 @@ +loadBalancer = $loadBalancer; + } + + /** + * @return self + */ + public static function getDefaultInstance() { + static $instance; + if ( !$instance ) { + $instance = new self( wfGetLB() ); + } + return $instance; + } + + /** + * Check if the given title already is watched by the user, and if so + * add a watch for the new title. + * + * To be used for page renames and such. + * This must be called separately for Subject and Talk pages + * + * @param LinkTarget $oldTarget + * @param LinkTarget $newTarget + */ + public function duplicateEntry( LinkTarget $oldTarget, LinkTarget $newTarget ) { + $dbw = $this->loadBalancer->getConnection( DB_MASTER, [ 'watchlist' ] ); + + $result = $dbw->select( + 'watchlist', + [ 'wl_user', 'wl_notificationtimestamp' ], + [ + 'wl_namespace' => $oldTarget->getNamespace(), + 'wl_title' => $oldTarget->getDBkey(), + ], + __METHOD__, + [ 'FOR UPDATE' ] + ); + + $newNamespace = $newTarget->getNamespace(); + $newDBkey = $newTarget->getDBkey(); + + # Construct array to replace into the watchlist + $values = []; + foreach ( $result as $row ) { + $values[] = [ + 'wl_user' => $row->wl_user, + 'wl_namespace' => $newNamespace, + 'wl_title' => $newDBkey, + 'wl_notificationtimestamp' => $row->wl_notificationtimestamp, + ]; + } + + if ( !empty( $values ) ) { + # Perform replace + # Note that multi-row replace is very efficient for MySQL but may be inefficient for + # some other DBMSes, mostly due to poor simulation by us + $dbw->replace( + 'watchlist', + [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ], + $values, + __METHOD__ + ); + } + + $this->loadBalancer->reuseConnection( $dbw ); + } + +} diff --git a/tests/phpunit/includes/WatchedItemStoreTest.php b/tests/phpunit/includes/WatchedItemStoreTest.php new file mode 100644 index 0000000000..fc132b06e1 --- /dev/null +++ b/tests/phpunit/includes/WatchedItemStoreTest.php @@ -0,0 +1,91 @@ +getMock( 'IDatabase' ); + } + + /** + * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer + */ + private function getMockLoadBalancer( $mockDb ) { + $mock = $this->getMockBuilder( 'LoadBalancer' ) + ->disableOriginalConstructor() + ->getMock(); + $mock->expects( $this->any() ) + ->method( 'getConnection' ) + ->will( $this->returnValue( $mockDb ) ); + return $mock; + } + + private function getFakeRow( $userId, $timestamp ) { + $fakeRow = new stdClass(); + $fakeRow->wl_user = $userId; + $fakeRow->wl_notificationtimestamp = $timestamp; + return $fakeRow; + } + + public function testDuplicateEntry_nothingToDuplicate() { + $mockDb = $this->getMockDb(); + $mockDb->expects( $this->exactly( 1 ) ) + ->method( 'select' ) + ->will( $this->returnValue( new FakeResultWrapper( [] ) ) ); + + $store = new WatchedItemStore( $this->getMockLoadBalancer( $mockDb ) ); + + $store->duplicateEntry( + Title::newFromText( 'Old_Title' ), + Title::newFromText( 'New_Title' ) + ); + } + + public function testDuplicateEntry_somethingToDuplicate() { + $fakeRows = [ + $this->getFakeRow( 1, '20151212010101' ), + $this->getFakeRow( 2, null ), + ]; + + $mockDb = $this->getMockDb(); + $mockDb->expects( $this->at( 0 ) ) + ->method( 'select' ) + ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) ); + $mockDb->expects( $this->at( 1 ) ) + ->method( 'replace' ) + ->with( + 'watchlist', + [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ], + [ + [ + 'wl_user' => 1, + 'wl_namespace' => 0, + 'wl_title' => 'New_Title', + 'wl_notificationtimestamp' => '20151212010101', + ], + [ + 'wl_user' => 2, + 'wl_namespace' => 0, + 'wl_title' => 'New_Title', + 'wl_notificationtimestamp' => null, + ], + ], + $this->isType( 'string' ) + ); + + $store = new WatchedItemStore( $this->getMockLoadBalancer( $mockDb ) ); + + $store->duplicateEntry( + Title::newFromText( 'Old_Title' ), + Title::newFromText( 'New_Title' ) + ); + } + +}