MediaWiki master
MessageBlobStore.php
Go to the documentation of this file.
1<?php
24
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
134 private static function makeGlobalPurgeKey( WANObjectCache $cache ) {
135 return $cache->makeGlobalKey( 'resourceloader-messageblob' );
136 }
137
144 private function makeModulePurgeKey( $name ) {
145 return $this->wanCache->makeKey( 'resourceloader-messageblob', $name );
146 }
147
154 private function makeBlobCacheKey( $name, $lang, Module $module ) {
155 $messages = array_values( array_unique( $module->getMessages() ) );
156 sort( $messages );
157 return $this->wanCache->makeKey( 'resourceloader-messageblob',
158 $name,
159 $lang,
160 md5( json_encode( $messages ) )
161 );
162 }
163
171 protected function recacheMessageBlob( $cacheKey, Module $module, $lang ) {
172 $blob = $this->generateMessageBlob( $module, $lang );
173 $cache = $this->wanCache;
174 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
175 $cache->set( $cacheKey, $blob,
176 // Add part of a day to TTL to avoid all modules expiring at once
177 $cache::TTL_WEEK + mt_rand( 0, $cache::TTL_DAY ),
179 );
180 return $blob;
181 }
182
189 public function updateMessage( $key ): void {
190 $moduleNames = $this->resourceloader->getModulesByMessage( $key );
191 foreach ( $moduleNames as $moduleName ) {
192 // Use the default holdoff TTL to account for database replica DB lag
193 // which can affect MessageCache.
194 $this->wanCache->touchCheckKey( $this->makeModulePurgeKey( $moduleName ) );
195 }
196 }
197
204 public static function clearGlobalCacheEntry( WANObjectCache $cache ) {
205 // Disable holdoff TTL because:
206 // - LocalisationCache is populated by messages on-disk and don't have DB lag,
207 // thus there is no need for hold off. We only clear it after new localisation
208 // updates are known to be deployed to all servers.
209 // - This global check key invalidates message blobs for all modules for all wikis
210 // in cache contexts (e.g. languages, skins). Setting a hold-off on this key could
211 // cause a cache stampede since no values would be stored for several seconds.
212 $cache->touchCheckKey( self::makeGlobalPurgeKey( $cache ), $cache::HOLDOFF_TTL_NONE );
213 }
214
221 protected function fetchMessage( $key, $lang ) {
222 $message = wfMessage( $key )->inLanguage( $lang );
223 if ( !$message->exists() ) {
224 $this->logger->warning( 'Failed to find {messageKey} ({lang})', [
225 'messageKey' => $key,
226 'lang' => $lang,
227 ] );
228 $value = null;
229 } else {
230 $value = $message->plain();
231 }
232 return $value;
233 }
234
242 private function generateMessageBlob( Module $module, $lang ) {
243 $messages = [];
244 foreach ( $module->getMessages() as $key ) {
245 $value = $this->fetchMessage( $key, $lang );
246 // If the message does not exist, omit it from the blob so that
247 // client-side mw.message may do its own existence handling.
248 if ( $value !== null ) {
249 $messages[$key] = $value;
250 }
251 }
252
253 $json = FormatJson::encode( (object)$messages, false, FormatJson::UTF8_OK );
254 // @codeCoverageIgnoreStart
255 if ( $json === false ) {
256 $this->logger->warning( 'Failed to encode message blob for {module} ({lang})', [
257 'module' => $module->getName(),
258 'lang' => $lang,
259 ] );
260 $json = '{}';
261 }
262 // codeCoverageIgnoreEnd
263 return $json;
264 }
265}
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)
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
Definition Module.php:47
getMessages()
Get the messages needed for this module.
Definition Module.php:385
getName()
Get this module's name.
Definition Module.php:127
ResourceLoader is a loading system for JavaScript and CSS resources.
Multi-datacenter aware caching interface.
makeGlobalKey( $keygroup,... $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(?IReadableDatabase ... $dbs)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...