Introduce StatsdAwareInterface
[lhc/web/wiklou.git] / includes / WatchedItemStore.php
index 20ec592..8ae7932 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 
+use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
 use Wikimedia\Assert\Assert;
 
 /**
@@ -10,7 +11,10 @@ use Wikimedia\Assert\Assert;
  *
  * @since 1.27
  */
-class WatchedItemStore {
+class WatchedItemStore implements StatsdAwareInterface {
+
+       const SORT_DESC = 'DESC';
+       const SORT_ASC = 'ASC';
 
        /**
         * @var LoadBalancer
@@ -40,6 +44,11 @@ class WatchedItemStore {
         */
        private $revisionGetTimestampFromIdCallback;
 
+       /**
+        * @var StatsdDataFactoryInterface
+        */
+       private $stats;
+
        /**
         * @var self|null
         */
@@ -55,10 +64,15 @@ class WatchedItemStore {
        ) {
                $this->loadBalancer = $loadBalancer;
                $this->cache = $cache;
+               $this->stats = new NullStatsdDataFactory();
                $this->deferredUpdatesAddCallableUpdateCallback = [ 'DeferredUpdates', 'addCallableUpdate' ];
                $this->revisionGetTimestampFromIdCallback = [ 'Revision', 'getTimestampFromId' ];
        }
 
+       public function setStatsdDataFactory( StatsdDataFactoryInterface $stats ) {
+               $this->stats = $stats;
+       }
+
        /**
         * Overrides the DeferredUpdates::addCallableUpdate callback
         * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined.
@@ -145,6 +159,7 @@ class WatchedItemStore {
                                wfGetLB(),
                                new HashBagOStuff( [ 'maxKeys' => 100 ] )
                        );
+                       self::$instance->setStatsdDataFactory( RequestContext::getMain()->getStats() );
                }
                return self::$instance;
        }
@@ -163,18 +178,22 @@ class WatchedItemStore {
                $key = $this->getCacheKey( $user, $target );
                $this->cache->set( $key, $item );
                $this->cacheIndex[$target->getNamespace()][$target->getDBkey()][$user->getId()] = $key;
+               $this->stats->increment( 'WatchedItemStore.cache' );
        }
 
        private function uncache( User $user, LinkTarget $target ) {
                $this->cache->delete( $this->getCacheKey( $user, $target ) );
                unset( $this->cacheIndex[$target->getNamespace()][$target->getDBkey()][$user->getId()] );
+               $this->stats->increment( 'WatchedItemStore.uncache' );
        }
 
        private function uncacheLinkTarget( LinkTarget $target ) {
                if ( !isset( $this->cacheIndex[$target->getNamespace()][$target->getDBkey()] ) ) {
                        return;
                }
+               $this->stats->increment( 'WatchedItemStore.uncacheLinkTarget' );
                foreach ( $this->cacheIndex[$target->getNamespace()][$target->getDBkey()] as $key ) {
+                       $this->stats->increment( 'WatchedItemStore.uncacheLinkTarget.items' );
                        $this->cache->delete( $key );
                }
        }
@@ -448,8 +467,10 @@ class WatchedItemStore {
 
                $cached = $this->getCached( $user, $target );
                if ( $cached ) {
+                       $this->stats->increment( 'WatchedItemStore.getWatchedItem.cached' );
                        return $cached;
                }
+               $this->stats->increment( 'WatchedItemStore.getWatchedItem.load' );
                return $this->loadWatchedItem( $user, $target );
        }
 
@@ -494,18 +515,34 @@ class WatchedItemStore {
         * @param User $user
         * @param array $options Allowed keys:
         *        'forWrite' => bool defaults to false
+        *        'sort' => string optional sorting by namespace ID and title
+        *                     one of the self::SORT_* constants
         *
         * @return WatchedItem[]
         */
        public function getWatchedItemsForUser( User $user, array $options = [] ) {
                $options += [ 'forWrite' => false ];
 
+               $dbOptions = [];
+               if ( array_key_exists( 'sort', $options ) ) {
+                       Assert::parameter(
+                               ( in_array( $options['sort'], [ self::SORT_ASC, self::SORT_DESC ] ) ),
+                               '$options[\'sort\']',
+                               'must be SORT_ASC or SORT_DESC'
+                       );
+                       $dbOptions['ORDER BY'] = [
+                               "wl_namespace {$options['sort']}",
+                               "wl_title {$options['sort']}"
+                       ];
+               }
                $db = $this->getConnection( $options['forWrite'] ? DB_MASTER : DB_SLAVE );
+
                $res = $db->select(
                        'watchlist',
                        [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
                        [ 'wl_user' => $user->getId() ],
-                       __METHOD__
+                       __METHOD__,
+                       $dbOptions
                );
                $this->reuseConnection( $db );
 
@@ -596,30 +633,30 @@ class WatchedItemStore {
         * @param LinkTarget $target
         */
        public function addWatch( User $user, LinkTarget $target ) {
-               $this->addWatchBatch( [ [ $user, $target ] ] );
+               $this->addWatchBatchForUser( $user, [ $target ] );
        }
 
        /**
-        * @param array[] $userTargetCombinations array of arrays containing [0] => User [1] => LinkTarget
+        * @param User $user
+        * @param LinkTarget[] $targets
         *
         * @return bool success
         */
-       public function addWatchBatch( array $userTargetCombinations ) {
+       public function addWatchBatchForUser( User $user, array $targets ) {
                if ( $this->loadBalancer->getReadOnlyReason() !== false ) {
                        return false;
                }
+               // Only loggedin user can have a watchlist
+               if ( $user->isAnon() ) {
+                       return false;
+               }
+
+               if ( !$targets ) {
+                       return true;
+               }
 
                $rows = [];
-               foreach ( $userTargetCombinations as list( $user, $target ) ) {
-                       /**
-                        * @var User $user
-                        * @var LinkTarget $target
-                        */
-
-                       // Only loggedin user can have a watchlist
-                       if ( $user->isAnon() ) {
-                               continue;
-                       }
+               foreach ( $targets as $target ) {
                        $rows[] = [
                                'wl_user' => $user->getId(),
                                'wl_namespace' => $target->getNamespace(),
@@ -629,10 +666,6 @@ class WatchedItemStore {
                        $this->uncache( $user, $target );
                }
 
-               if ( !$rows ) {
-                       return false;
-               }
-
                $dbw = $this->getConnection( DB_MASTER );
                foreach ( array_chunk( $rows, 100 ) as $toInsert ) {
                        // Use INSERT IGNORE to avoid overwriting the notification timestamp