Move WatchedItem::duplicateEntries to WatchedItemStore
authoraddshore <addshorewiki@gmail.com>
Tue, 26 Jan 2016 16:36:05 +0000 (17:36 +0100)
committeraddshore <addshorewiki@gmail.com>
Wed, 17 Feb 2016 15:22:32 +0000 (15:22 +0000)
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

autoload.php
includes/WatchedItem.php
includes/WatchedItemStore.php [new file with mode: 0644]
tests/phpunit/includes/WatchedItemStoreTest.php [new file with mode: 0644]

index 83ca3b3..87d0c7c 100644 (file)
@@ -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',
index 2c400d0..b597f99 100644 (file)
@@ -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 (file)
index 0000000..83a5856
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * Storage layer class for WatchedItems.
+ * Database interaction
+ *
+ * @author Addshore
+ *
+ * @since 1.27
+ */
+class WatchedItemStore {
+
+       /**
+        * @var LoadBalancer
+        */
+       private $loadBalancer;
+
+       public function __construct( LoadBalancer $loadBalancer ) {
+               $this->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 (file)
index 0000000..fc132b0
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * @author Addshore
+ *
+ * @covers WatchedItemStore
+ */
+class WatchedItemStoreTest extends PHPUnit_Framework_TestCase {
+
+       /**
+        * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
+        */
+       private function getMockDb() {
+               return $this->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' )
+               );
+       }
+
+}