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
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 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
177 $cache->set( $cacheKey, $blob,
178 // Add part of a day to TTL to avoid all modules expiring at once
179 $cache::TTL_WEEK + mt_rand( 0, $cache::TTL_DAY ),
181 );
182 return $blob;
183 }
184
191 public function updateMessage( $key ): void {
192 $moduleNames = $this->resourceloader->getModulesByMessage( $key );
193 foreach ( $moduleNames as $moduleName ) {
194 // Use the default holdoff TTL to account for database replica DB lag
195 // which can affect MessageCache.
196 $this->wanCache->touchCheckKey( $this->makeModulePurgeKey( $moduleName ) );
197 }
198 }
199
205 public function clear() {
206 self::clearGlobalCacheEntry( $this->wanCache );
207 }
208
216 public static function clearGlobalCacheEntry( WANObjectCache $cache ) {
217 // Disable holdoff TTL because:
218 // - LocalisationCache is populated by messages on-disk and don't have DB lag,
219 // thus there is no need for hold off. We only clear it after new localisation
220 // updates are known to be deployed to all servers.
221 // - This global check key invalidates message blobs for all modules for all wikis
222 // in cache contexts (e.g. languages, skins). Setting a hold-off on this key could
223 // cause a cache stampede since no values would be stored for several seconds.
224 $cache->touchCheckKey( self::makeGlobalPurgeKey( $cache ), $cache::HOLDOFF_TTL_NONE );
225 }
226
233 protected function fetchMessage( $key, $lang ) {
234 $message = wfMessage( $key )->inLanguage( $lang );
235 if ( !$message->exists() ) {
236 $this->logger->warning( 'Failed to find {messageKey} ({lang})', [
237 'messageKey' => $key,
238 'lang' => $lang,
239 ] );
240 $value = null;
241 } else {
242 $value = $message->plain();
243 }
244 return $value;
245 }
246
254 private function generateMessageBlob( Module $module, $lang ) {
255 $messages = [];
256 foreach ( $module->getMessages() as $key ) {
257 $value = $this->fetchMessage( $key, $lang );
258 // If the message does not exist, omit it from the blob so that
259 // client-side mw.message may do its own existence handling.
260 if ( $value !== null ) {
261 $messages[$key] = $value;
262 }
263 }
264
265 $json = FormatJson::encode( (object)$messages, false, FormatJson::UTF8_OK );
266 // @codeCoverageIgnoreStart
267 if ( $json === false ) {
268 $this->logger->warning( 'Failed to encode message blob for {module} ({lang})', [
269 'module' => $module->getName(),
270 'lang' => $lang,
271 ] );
272 $json = '{}';
273 }
274 // codeCoverageIgnoreEnd
275 return $json;
276 }
277}
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:47
getMessages()
Get the messages needed for this module.
Definition Module.php:429
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...