MediaWiki REL1_40
MessageBlobStore.php
Go to the documentation of this file.
1<?php
24
25use FormatJson;
27use Psr\Log\LoggerAwareInterface;
28use Psr\Log\LoggerInterface;
29use Psr\Log\NullLogger;
32
42class MessageBlobStore implements LoggerAwareInterface {
44 private $resourceloader;
45
47 protected $logger;
48
50 protected $wanCache;
51
57 public function __construct(
59 ?LoggerInterface $logger,
60 ?WANObjectCache $wanObjectCache
61 ) {
62 $this->resourceloader = $rl;
63 $this->logger = $logger ?: new NullLogger();
64
65 // NOTE: when changing this assignment, make sure the code in the instantiator for
66 // LocalisationCache which calls MessageBlobStore::clearGlobalCacheEntry() uses the
67 // same cache object.
68 $this->wanCache = $wanObjectCache ?: MediaWikiServices::getInstance()
69 ->getMainWANObjectCache();
70 }
71
76 public function setLogger( LoggerInterface $logger ) {
77 $this->logger = $logger;
78 }
79
88 public function getBlob( Module $module, $lang ) {
89 $blobs = $this->getBlobs( [ $module->getName() => $module ], $lang );
90 return $blobs[$module->getName()];
91 }
92
101 public function getBlobs( array $modules, $lang ) {
102 // Each cache key for a message blob by module name and language code also has a generic
103 // check key without language code. This is used to invalidate any and all language subkeys
104 // that exist for a module from the updateMessage() method.
105 $checkKeys = [
106 self::makeGlobalPurgeKey( $this->wanCache )
107 ];
108 $cacheKeys = [];
109 foreach ( $modules as $name => $module ) {
110 $cacheKey = $this->makeBlobCacheKey( $name, $lang, $module );
111 $cacheKeys[$name] = $cacheKey;
112 $checkKeys[$cacheKey][] = $this->makeModulePurgeKey( $name );
113 }
114 $curTTLs = [];
115 $result = $this->wanCache->getMulti( array_values( $cacheKeys ), $curTTLs, $checkKeys );
116
117 $blobs = [];
118 foreach ( $modules as $name => $module ) {
119 $key = $cacheKeys[$name];
120 if ( !isset( $result[$key] ) || $curTTLs[$key] === null || $curTTLs[$key] < 0 ) {
121 $blobs[$name] = $this->recacheMessageBlob( $key, $module, $lang );
122 } else {
123 // Use unexpired cache
124 $blobs[$name] = $result[$key];
125 }
126 }
127 return $blobs;
128 }
129
136 private static function makeGlobalPurgeKey( WANObjectCache $cache ) {
137 return $cache->makeGlobalKey( 'resourceloader-messageblob' );
138 }
139
146 private function makeModulePurgeKey( $name ) {
147 return $this->wanCache->makeKey( 'resourceloader-messageblob', $name );
148 }
149
156 private function makeBlobCacheKey( $name, $lang, Module $module ) {
157 $messages = array_values( array_unique( $module->getMessages() ) );
158 sort( $messages );
159 return $this->wanCache->makeKey( 'resourceloader-messageblob',
160 $name,
161 $lang,
162 md5( json_encode( $messages ) )
163 );
164 }
165
173 protected function recacheMessageBlob( $cacheKey, Module $module, $lang ) {
174 $blob = $this->generateMessageBlob( $module, $lang );
175 $cache = $this->wanCache;
176 $cache->set( $cacheKey, $blob,
177 // Add part of a day to TTL to avoid all modules expiring at once
178 $cache::TTL_WEEK + mt_rand( 0, $cache::TTL_DAY ),
180 );
181 return $blob;
182 }
183
190 public function updateMessage( $key ): void {
191 $moduleNames = $this->resourceloader->getModulesByMessage( $key );
192 foreach ( $moduleNames as $moduleName ) {
193 // Use the default holdoff TTL to account for database replica DB lag
194 // which can affect MessageCache.
195 $this->wanCache->touchCheckKey( $this->makeModulePurgeKey( $moduleName ) );
196 }
197 }
198
204 public function clear() {
205 self::clearGlobalCacheEntry( $this->wanCache );
206 }
207
215 public static function clearGlobalCacheEntry( WANObjectCache $cache ) {
216 // Disable holdoff TTL because:
217 // - LocalisationCache is populated by messages on-disk and don't have DB lag,
218 // thus there is no need for hold off. We only clear it after new localisation
219 // updates are known to be deployed to all servers.
220 // - This global check key invalidates message blobs for all modules for all wikis
221 // in cache contexts (e.g. languages, skins). Setting a hold-off on this key could
222 // cause a cache stampede since no values would be stored for several seconds.
223 $cache->touchCheckKey( self::makeGlobalPurgeKey( $cache ), $cache::HOLDOFF_TTL_NONE );
224 }
225
232 protected function fetchMessage( $key, $lang ) {
233 $message = wfMessage( $key )->inLanguage( $lang );
234 if ( !$message->exists() ) {
235 $this->logger->warning( 'Failed to find {messageKey} ({lang})', [
236 'messageKey' => $key,
237 'lang' => $lang,
238 ] );
239 $value = null;
240 } else {
241 $value = $message->plain();
242 }
243 return $value;
244 }
245
253 private function generateMessageBlob( Module $module, $lang ) {
254 $messages = [];
255 foreach ( $module->getMessages() as $key ) {
256 $value = $this->fetchMessage( $key, $lang );
257 // If the message does not exist, omit it from the blob so that
258 // client-side mw.message may do its own existence handling.
259 if ( $value !== null ) {
260 $messages[$key] = $value;
261 }
262 }
263
264 $json = FormatJson::encode( (object)$messages, false, FormatJson::UTF8_OK );
265 // @codeCoverageIgnoreStart
266 if ( $json === false ) {
267 $this->logger->warning( 'Failed to encode message blob for {module} ({lang})', [
268 'module' => $module->getName(),
269 'lang' => $lang,
270 ] );
271 $json = '{}';
272 }
273 // codeCoverageIgnoreEnd
274 return $json;
275 }
276}
277
279class_alias( MessageBlobStore::class, 'MessageBlobStore' );
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
JSON formatter wrapper class.
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
This class generates message blobs for use by ResourceLoader.
static clearGlobalCacheEntry(WANObjectCache $cache)
Invalidate cache keys for all known modules.
getBlob(Module $module, $lang)
Get the message blob for a module.
recacheMessageBlob( $cacheKey, Module $module, $lang)
getBlobs(array $modules, $lang)
Get the message blobs for a set of modules.
updateMessage( $key)
Invalidate cache keys for modules using this message key.
__construct(ResourceLoader $rl, ?LoggerInterface $logger, ?WANObjectCache $wanObjectCache)
clear()
Invalidate cache keys for all known modules.
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
Definition Module.php:48
getMessages()
Get the messages needed for this module.
Definition Module.php:417
getName()
Get this module's name.
Definition Module.php:135
ResourceLoader is a loading system for JavaScript and CSS resources.
Multi-datacenter aware caching interface.
makeGlobalKey( $collection,... $components)
Make a cache key for the global keyspace and given components.
touchCheckKey( $key, $holdoff=self::HOLDOFF_TTL)
Increase the last-purge timestamp of a "check" key in all datacenters.
set( $key, $value, $ttl=self::TTL_INDEFINITE, array $opts=[])
Set the value of a key in cache.
static getCacheSetOptions(?IDatabase ... $dbs)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...
const DB_REPLICA
Definition defines.php:26
if(!isset( $args[0])) $lang