MediaWiki REL1_39
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
41
51class MessageBlobStore implements LoggerAwareInterface {
53 private $resourceloader;
54
56 protected $logger;
57
59 protected $wanCache;
60
66 public function __construct(
68 ?LoggerInterface $logger,
69 ?WANObjectCache $wanObjectCache
70 ) {
71 $this->resourceloader = $rl;
72 $this->logger = $logger ?: new NullLogger();
73
74 // NOTE: when changing this assignment, make sure the code in the instantiator for
75 // LocalisationCache which calls MessageBlobStore::clearGlobalCacheEntry() uses the
76 // same cache object.
77 $this->wanCache = $wanObjectCache ?: MediaWikiServices::getInstance()
78 ->getMainWANObjectCache();
79 }
80
85 public function setLogger( LoggerInterface $logger ) {
86 $this->logger = $logger;
87 }
88
97 public function getBlob( Module $module, $lang ) {
98 $blobs = $this->getBlobs( [ $module->getName() => $module ], $lang );
99 return $blobs[$module->getName()];
100 }
101
110 public function getBlobs( array $modules, $lang ) {
111 // Each cache key for a message blob by module name and language code also has a generic
112 // check key without language code. This is used to invalidate any and all language subkeys
113 // that exist for a module from the updateMessage() method.
115 $checkKeys = [
116 // Global check key, see clear()
117 $cache->makeGlobalKey( __CLASS__ )
118 ];
119 $cacheKeys = [];
120 foreach ( $modules as $name => $module ) {
121 $cacheKey = $this->makeCacheKey( $module, $lang );
122 $cacheKeys[$name] = $cacheKey;
123 // Per-module check key, see updateMessage()
124 $checkKeys[$cacheKey][] = $cache->makeKey( __CLASS__, $name );
125 }
126 $curTTLs = [];
127 $result = $cache->getMulti( array_values( $cacheKeys ), $curTTLs, $checkKeys );
128
129 $blobs = [];
130 foreach ( $modules as $name => $module ) {
131 $key = $cacheKeys[$name];
132 if ( !isset( $result[$key] ) || $curTTLs[$key] === null || $curTTLs[$key] < 0 ) {
133 $blobs[$name] = $this->recacheMessageBlob( $key, $module, $lang );
134 } else {
135 // Use unexpired cache
136 $blobs[$name] = $result[$key];
137 }
138 }
139 return $blobs;
140 }
141
148 private function makeCacheKey( Module $module, $lang ) {
149 $messages = array_values( array_unique( $module->getMessages() ) );
150 sort( $messages );
151 return $this->wanCache->makeKey( __CLASS__, $module->getName(), $lang,
152 md5( json_encode( $messages ) )
153 );
154 }
155
163 protected function recacheMessageBlob( $cacheKey, Module $module, $lang ) {
164 $blob = $this->generateMessageBlob( $module, $lang );
166 $cache->set( $cacheKey, $blob,
167 // Add part of a day to TTL to avoid all modules expiring at once
168 $cache::TTL_WEEK + mt_rand( 0, $cache::TTL_DAY ),
170 );
171 return $blob;
172 }
173
180 public function updateMessage( $key ): void {
181 $moduleNames = $this->resourceloader->getModulesByMessage( $key );
182 foreach ( $moduleNames as $moduleName ) {
183 // Uses a holdoff to account for database replica DB lag (for MessageCache)
184 $this->wanCache->touchCheckKey( $this->wanCache->makeKey( __CLASS__, $moduleName ) );
185 }
186 }
187
191 public function clear() {
192 self::clearGlobalCacheEntry( $this->wanCache );
193 }
194
202 public static function clearGlobalCacheEntry( WANObjectCache $cache ) {
203 // Disable hold-off because:
204 // - LocalisationCache is populated by messages on-disk and don't have DB lag,
205 // thus there is no need for hold off. We only clear it after new localisation
206 // updates are known to be deployed to all servers.
207 // - This global check key invalidates message blobs for all modules for all wikis
208 // in cache contexts (e.g. languages, skins). Setting a hold-off on this key could
209 // cause a cache stampede since no values would be stored for several seconds.
210 $cache->touchCheckKey( $cache->makeGlobalKey( __CLASS__ ), $cache::HOLDOFF_TTL_NONE );
211 }
212
219 protected function fetchMessage( $key, $lang ) {
220 $message = wfMessage( $key )->inLanguage( $lang );
221 if ( !$message->exists() ) {
222 $this->logger->warning( 'Failed to find {messageKey} ({lang})', [
223 'messageKey' => $key,
224 'lang' => $lang,
225 ] );
226 $value = null;
227 } else {
228 $value = $message->plain();
229 }
230 return $value;
231 }
232
240 private function generateMessageBlob( Module $module, $lang ) {
241 $messages = [];
242 foreach ( $module->getMessages() as $key ) {
243 $value = $this->fetchMessage( $key, $lang );
244 // If the message does not exist, omit it from the blob so that
245 // client-side mw.message may do its own existence handling.
246 if ( $value !== null ) {
247 $messages[$key] = $value;
248 }
249 }
250
251 $json = FormatJson::encode( (object)$messages, false, FormatJson::UTF8_OK );
252 // @codeCoverageIgnoreStart
253 if ( $json === false ) {
254 $this->logger->warning( 'Failed to encode message blob for {module} ({lang})', [
255 'module' => $module->getName(),
256 'lang' => $lang,
257 ] );
258 $json = '{}';
259 }
260 // codeCoverageIgnoreEnd
261 return $json;
262 }
263}
264
266class_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
PHP 7.2 hack to work around the issue described at https://phabricator.wikimedia.org/T166010#5962098 ...
ResourceLoader is a loading system for JavaScript and CSS resources.
Multi-datacenter aware caching interface.
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...
$cache
Definition mcc.php:33
const DB_REPLICA
Definition defines.php:26
if(!isset( $args[0])) $lang