[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/ -> MessageBlobStore.php (source)

   1  <?php
   2  /**
   3   * Resource message blobs storage used by the resource loader.
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   * @author Roan Kattouw
  22   * @author Trevor Parscal
  23   */
  24  
  25  /**
  26   * This class provides access to the resource message blobs storage used by
  27   * the ResourceLoader.
  28   *
  29   * A message blob is a JSON object containing the interface messages for a
  30   * certain resource in a certain language. These message blobs are cached
  31   * in the msg_resource table and automatically invalidated when one of their
  32   * constituent messages or the resource itself is changed.
  33   */
  34  class MessageBlobStore {
  35      /**
  36       * Get the singleton instance
  37       *
  38       * @since 1.24
  39       * @return MessageBlobStore
  40       */
  41  	public static function getInstance() {
  42          static $instance = null;
  43          if ( $instance === null ) {
  44              $instance = new self;
  45          }
  46  
  47          return $instance;
  48      }
  49  
  50      /**
  51       * Get the message blobs for a set of modules
  52       *
  53       * @param ResourceLoader $resourceLoader
  54       * @param array $modules Array of module objects keyed by module name
  55       * @param string $lang Language code
  56       * @return array An array mapping module names to message blobs
  57       */
  58  	public function get( ResourceLoader $resourceLoader, $modules, $lang ) {
  59          wfProfileIn( __METHOD__ );
  60          if ( !count( $modules ) ) {
  61              wfProfileOut( __METHOD__ );
  62              return array();
  63          }
  64          // Try getting from the DB first
  65          $blobs = $this->getFromDB( $resourceLoader, array_keys( $modules ), $lang );
  66  
  67          // Generate blobs for any missing modules and store them in the DB
  68          $missing = array_diff( array_keys( $modules ), array_keys( $blobs ) );
  69          foreach ( $missing as $name ) {
  70              $blob = $this->insertMessageBlob( $name, $modules[$name], $lang );
  71              if ( $blob ) {
  72                  $blobs[$name] = $blob;
  73              }
  74          }
  75  
  76          wfProfileOut( __METHOD__ );
  77          return $blobs;
  78      }
  79  
  80      /**
  81       * Generate and insert a new message blob. If the blob was already
  82       * present, it is not regenerated; instead, the preexisting blob
  83       * is fetched and returned.
  84       *
  85       * @param string $name Module name
  86       * @param ResourceLoaderModule $module
  87       * @param string $lang Language code
  88       * @return mixed Message blob or false if the module has no messages
  89       */
  90  	public function insertMessageBlob( $name, ResourceLoaderModule $module, $lang ) {
  91          $blob = $this->generateMessageBlob( $module, $lang );
  92  
  93          if ( !$blob ) {
  94              return false;
  95          }
  96  
  97          try {
  98              $dbw = wfGetDB( DB_MASTER );
  99              $success = $dbw->insert( 'msg_resource', array(
 100                      'mr_lang' => $lang,
 101                      'mr_resource' => $name,
 102                      'mr_blob' => $blob,
 103                      'mr_timestamp' => $dbw->timestamp()
 104                  ),
 105                  __METHOD__,
 106                  array( 'IGNORE' )
 107              );
 108  
 109              if ( $success ) {
 110                  if ( $dbw->affectedRows() == 0 ) {
 111                      // Blob was already present, fetch it
 112                      $blob = $dbw->selectField( 'msg_resource', 'mr_blob', array(
 113                              'mr_resource' => $name,
 114                              'mr_lang' => $lang,
 115                          ),
 116                          __METHOD__
 117                      );
 118                  } else {
 119                      // Update msg_resource_links
 120                      $rows = array();
 121  
 122                      foreach ( $module->getMessages() as $key ) {
 123                          $rows[] = array(
 124                              'mrl_resource' => $name,
 125                              'mrl_message' => $key
 126                          );
 127                      }
 128                      $dbw->insert( 'msg_resource_links', $rows,
 129                          __METHOD__, array( 'IGNORE' )
 130                      );
 131                  }
 132              }
 133          } catch ( Exception $e ) {
 134              wfDebug( __METHOD__ . " failed to update DB: $e\n" );
 135          }
 136          return $blob;
 137      }
 138  
 139      /**
 140       * Update the message blob for a given module in a given language
 141       *
 142       * @param string $name Module name
 143       * @param ResourceLoaderModule $module
 144       * @param string $lang Language code
 145       * @return string Regenerated message blob, or null if there was no blob for
 146       *   the given module/language pair.
 147       */
 148  	public function updateModule( $name, ResourceLoaderModule $module, $lang ) {
 149          $dbw = wfGetDB( DB_MASTER );
 150          $row = $dbw->selectRow( 'msg_resource', 'mr_blob',
 151              array( 'mr_resource' => $name, 'mr_lang' => $lang ),
 152              __METHOD__
 153          );
 154          if ( !$row ) {
 155              return null;
 156          }
 157  
 158          // Save the old and new blobs for later
 159          $oldBlob = $row->mr_blob;
 160          $newBlob = $this->generateMessageBlob( $module, $lang );
 161  
 162          try {
 163              $newRow = array(
 164                  'mr_resource' => $name,
 165                  'mr_lang' => $lang,
 166                  'mr_blob' => $newBlob,
 167                  'mr_timestamp' => $dbw->timestamp()
 168              );
 169  
 170              $dbw->replace( 'msg_resource',
 171                  array( array( 'mr_resource', 'mr_lang' ) ),
 172                  $newRow, __METHOD__
 173              );
 174  
 175              // Figure out which messages were added and removed
 176              $oldMessages = array_keys( FormatJson::decode( $oldBlob, true ) );
 177              $newMessages = array_keys( FormatJson::decode( $newBlob, true ) );
 178              $added = array_diff( $newMessages, $oldMessages );
 179              $removed = array_diff( $oldMessages, $newMessages );
 180  
 181              // Delete removed messages, insert added ones
 182              if ( $removed ) {
 183                  $dbw->delete( 'msg_resource_links', array(
 184                          'mrl_resource' => $name,
 185                          'mrl_message' => $removed
 186                      ), __METHOD__
 187                  );
 188              }
 189  
 190              $newLinksRows = array();
 191  
 192              foreach ( $added as $message ) {
 193                  $newLinksRows[] = array(
 194                      'mrl_resource' => $name,
 195                      'mrl_message' => $message
 196                  );
 197              }
 198  
 199              if ( $newLinksRows ) {
 200                  $dbw->insert( 'msg_resource_links', $newLinksRows, __METHOD__,
 201                      array( 'IGNORE' ) // just in case
 202                  );
 203              }
 204          } catch ( Exception $e ) {
 205              wfDebug( __METHOD__ . " failed to update DB: $e\n" );
 206          }
 207          return $newBlob;
 208      }
 209  
 210      /**
 211       * Update a single message in all message blobs it occurs in.
 212       *
 213       * @param string $key Message key
 214       */
 215  	public function updateMessage( $key ) {
 216          try {
 217              $dbw = wfGetDB( DB_MASTER );
 218  
 219              // Keep running until the updates queue is empty.
 220              // Due to update conflicts, the queue might not be emptied
 221              // in one iteration.
 222              $updates = null;
 223              do {
 224                  $updates = $this->getUpdatesForMessage( $key, $updates );
 225  
 226                  foreach ( $updates as $k => $update ) {
 227                      // Update the row on the condition that it
 228                      // didn't change since we fetched it by putting
 229                      // the timestamp in the WHERE clause.
 230                      $success = $dbw->update( 'msg_resource',
 231                          array(
 232                              'mr_blob' => $update['newBlob'],
 233                              'mr_timestamp' => $dbw->timestamp() ),
 234                          array(
 235                              'mr_resource' => $update['resource'],
 236                              'mr_lang' => $update['lang'],
 237                              'mr_timestamp' => $update['timestamp'] ),
 238                          __METHOD__
 239                      );
 240  
 241                      // Only requeue conflicted updates.
 242                      // If update() returned false, don't retry, for
 243                      // fear of getting into an infinite loop
 244                      if ( !( $success && $dbw->affectedRows() == 0 ) ) {
 245                          // Not conflicted
 246                          unset( $updates[$k] );
 247                      }
 248                  }
 249              } while ( count( $updates ) );
 250  
 251              // No need to update msg_resource_links because we didn't add
 252              // or remove any messages, we just changed their contents.
 253          } catch ( Exception $e ) {
 254              wfDebug( __METHOD__ . " failed to update DB: $e\n" );
 255          }
 256      }
 257  
 258  	public function clear() {
 259          // TODO: Give this some more thought
 260          try {
 261              // Not using TRUNCATE, because that needs extra permissions,
 262              // which maybe not granted to the database user.
 263              $dbw = wfGetDB( DB_MASTER );
 264              $dbw->delete( 'msg_resource', '*', __METHOD__ );
 265              $dbw->delete( 'msg_resource_links', '*', __METHOD__ );
 266          } catch ( Exception $e ) {
 267              wfDebug( __METHOD__ . " failed to update DB: $e\n" );
 268          }
 269      }
 270  
 271      /**
 272       * Create an update queue for updateMessage()
 273       *
 274       * @param string $key Message key
 275       * @param array $prevUpdates Updates queue to refresh or null to build a fresh update queue
 276       * @return array Updates queue
 277       */
 278  	private function getUpdatesForMessage( $key, $prevUpdates = null ) {
 279          $dbw = wfGetDB( DB_MASTER );
 280  
 281          if ( is_null( $prevUpdates ) ) {
 282              // Fetch all blobs referencing $key
 283              $res = $dbw->select(
 284                  array( 'msg_resource', 'msg_resource_links' ),
 285                  array( 'mr_resource', 'mr_lang', 'mr_blob', 'mr_timestamp' ),
 286                  array( 'mrl_message' => $key, 'mr_resource=mrl_resource' ),
 287                  __METHOD__
 288              );
 289          } else {
 290              // Refetch the blobs referenced by $prevUpdates
 291  
 292              // Reorganize the (resource, lang) pairs in the format
 293              // expected by makeWhereFrom2d()
 294              $twoD = array();
 295  
 296              foreach ( $prevUpdates as $update ) {
 297                  $twoD[$update['resource']][$update['lang']] = true;
 298              }
 299  
 300              $res = $dbw->select( 'msg_resource',
 301                  array( 'mr_resource', 'mr_lang', 'mr_blob', 'mr_timestamp' ),
 302                  $dbw->makeWhereFrom2d( $twoD, 'mr_resource', 'mr_lang' ),
 303                  __METHOD__
 304              );
 305          }
 306  
 307          // Build the new updates queue
 308          $updates = array();
 309  
 310          foreach ( $res as $row ) {
 311              $updates[] = array(
 312                  'resource' => $row->mr_resource,
 313                  'lang' => $row->mr_lang,
 314                  'timestamp' => $row->mr_timestamp,
 315                  'newBlob' => $this->reencodeBlob( $row->mr_blob, $key, $row->mr_lang )
 316              );
 317          }
 318  
 319          return $updates;
 320      }
 321  
 322      /**
 323       * Reencode a message blob with the updated value for a message
 324       *
 325       * @param string $blob Message blob (JSON object)
 326       * @param string $key Message key
 327       * @param string $lang Language code
 328       * @return string Message blob with $key replaced with its new value
 329       */
 330  	private function reencodeBlob( $blob, $key, $lang ) {
 331          $decoded = FormatJson::decode( $blob, true );
 332          $decoded[$key] = wfMessage( $key )->inLanguage( $lang )->plain();
 333  
 334          return FormatJson::encode( (object)$decoded );
 335      }
 336  
 337      /**
 338       * Get the message blobs for a set of modules from the database.
 339       * Modules whose blobs are not in the database are silently dropped.
 340       *
 341       * @param ResourceLoader $resourceLoader
 342       * @param array $modules Array of module names
 343       * @param string $lang Language code
 344       * @throws MWException
 345       * @return array Array mapping module names to blobs
 346       */
 347  	private function getFromDB( ResourceLoader $resourceLoader, $modules, $lang ) {
 348          $config = $resourceLoader->getConfig();
 349          $retval = array();
 350          $dbr = wfGetDB( DB_SLAVE );
 351          $res = $dbr->select( 'msg_resource',
 352              array( 'mr_blob', 'mr_resource', 'mr_timestamp' ),
 353              array( 'mr_resource' => $modules, 'mr_lang' => $lang ),
 354              __METHOD__
 355          );
 356  
 357          foreach ( $res as $row ) {
 358              $module = $resourceLoader->getModule( $row->mr_resource );
 359              if ( !$module ) {
 360                  // This shouldn't be possible
 361                  throw new MWException( __METHOD__ . ' passed an invalid module name' );
 362              }
 363  
 364              // Update the module's blobs if the set of messages changed or if the blob is
 365              // older than the CacheEpoch setting
 366              $keys = array_keys( FormatJson::decode( $row->mr_blob, true ) );
 367              $values = array_values( array_unique( $module->getMessages() ) );
 368              if ( $keys !== $values
 369                  || wfTimestamp( TS_MW, $row->mr_timestamp ) <= $config->get( 'CacheEpoch' )
 370              ) {
 371                  $retval[$row->mr_resource] = $this->updateModule( $row->mr_resource, $module, $lang );
 372              } else {
 373                  $retval[$row->mr_resource] = $row->mr_blob;
 374              }
 375          }
 376  
 377          return $retval;
 378      }
 379  
 380      /**
 381       * Generate the message blob for a given module in a given language.
 382       *
 383       * @param ResourceLoaderModule $module
 384       * @param string $lang Language code
 385       * @return string JSON object
 386       */
 387  	private function generateMessageBlob( ResourceLoaderModule $module, $lang ) {
 388          $messages = array();
 389  
 390          foreach ( $module->getMessages() as $key ) {
 391              $messages[$key] = wfMessage( $key )->inLanguage( $lang )->plain();
 392          }
 393  
 394          return FormatJson::encode( (object)$messages );
 395      }
 396  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1