MediaWiki master
MessageBlobStore.php
Go to the documentation of this file.
1<?php
10
13use Psr\Log\LoggerAwareInterface;
14use Psr\Log\LoggerInterface;
15use Psr\Log\NullLogger;
18
28class MessageBlobStore implements LoggerAwareInterface {
30 private $resourceloader;
31
33 protected $logger;
34
36 protected $wanCache;
37
43 public function __construct(
45 ?LoggerInterface $logger,
46 ?WANObjectCache $wanObjectCache
47 ) {
48 $this->resourceloader = $rl;
49 $this->logger = $logger ?: new NullLogger();
50
51 // NOTE: when changing this assignment, make sure the code in the instantiator for
52 // LocalisationCache which calls MessageBlobStore::clearGlobalCacheEntry() uses the
53 // same cache object.
54 $this->wanCache = $wanObjectCache ?: MediaWikiServices::getInstance()
55 ->getMainWANObjectCache();
56 }
57
62 public function setLogger( LoggerInterface $logger ): void {
63 $this->logger = $logger;
64 }
65
74 public function getBlob( Module $module, $lang ) {
75 $blobs = $this->getBlobs( [ $module->getName() => $module ], $lang );
76 return $blobs[$module->getName()];
77 }
78
87 public function getBlobs( array $modules, $lang ) {
88 // Each cache key for a message blob by module name and language code also has a generic
89 // check key without language code. This is used to invalidate any and all language subkeys
90 // that exist for a module from the updateMessage() method.
91 $checkKeys = [
92 self::makeGlobalPurgeKey( $this->wanCache )
93 ];
94 $cacheKeys = [];
95 foreach ( $modules as $name => $module ) {
96 $cacheKey = $this->makeBlobCacheKey( $name, $lang, $module );
97 $cacheKeys[$name] = $cacheKey;
98 $checkKeys[$cacheKey][] = $this->makeModulePurgeKey( $name );
99 }
100 $curTTLs = [];
101 $result = $this->wanCache->getMulti( array_values( $cacheKeys ), $curTTLs, $checkKeys );
102
103 $blobs = [];
104 foreach ( $modules as $name => $module ) {
105 $key = $cacheKeys[$name];
106 if ( !isset( $result[$key] ) || $curTTLs[$key] === null || $curTTLs[$key] < 0 ) {
107 $blobs[$name] = $this->recacheMessageBlob( $key, $module, $lang );
108 } else {
109 // Use unexpired cache
110 $blobs[$name] = $result[$key];
111 }
112 }
113 return $blobs;
114 }
115
120 private static function makeGlobalPurgeKey( WANObjectCache $cache ) {
121 return $cache->makeGlobalKey( 'resourceloader-messageblob' );
122 }
123
130 private function makeModulePurgeKey( $name ) {
131 return $this->wanCache->makeKey( 'resourceloader-messageblob', $name );
132 }
133
140 private function makeBlobCacheKey( $name, $lang, Module $module ) {
141 $messages = array_values( array_unique( $module->getMessages() ) );
142 sort( $messages );
143 return $this->wanCache->makeKey( 'resourceloader-messageblob',
144 $name,
145 $lang,
146 md5( json_encode( $messages ) )
147 );
148 }
149
157 protected function recacheMessageBlob( $cacheKey, Module $module, $lang ) {
158 $blob = $this->generateMessageBlob( $module, $lang );
159 $cache = $this->wanCache;
160 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
161 $cache->set( $cacheKey, $blob,
162 // Add part of a day to TTL to avoid all modules expiring at once
163 $cache::TTL_WEEK + mt_rand( 0, $cache::TTL_DAY ),
165 );
166 return $blob;
167 }
168
175 public function updateMessage( $key ): void {
176 $moduleNames = $this->resourceloader->getModulesByMessage( $key );
177 foreach ( $moduleNames as $moduleName ) {
178 // Use the default holdoff TTL to account for database replica DB lag
179 // which can affect MessageCache.
180 $this->wanCache->touchCheckKey( $this->makeModulePurgeKey( $moduleName ) );
181 }
182 }
183
190 public static function clearGlobalCacheEntry( WANObjectCache $cache ) {
191 // Disable holdoff TTL because:
192 // - LocalisationCache is populated by messages on-disk and don't have DB lag,
193 // thus there is no need for hold off. We only clear it after new localisation
194 // updates are known to be deployed to all servers.
195 // - This global check key invalidates message blobs for all modules for all wikis
196 // in cache contexts (e.g. languages, skins). Setting a hold-off on this key could
197 // cause a cache stampede since no values would be stored for several seconds.
198 $cache->touchCheckKey( self::makeGlobalPurgeKey( $cache ), $cache::HOLDOFF_TTL_NONE );
199 }
200
207 protected function fetchMessage( $key, $lang ) {
208 $message = wfMessage( $key )->inLanguage( $lang );
209 if ( !$message->exists() ) {
210 $this->logger->warning( 'Failed to find {messageKey} ({lang})', [
211 'messageKey' => $key,
212 'lang' => $lang,
213 ] );
214 $value = null;
215 } else {
216 $value = $message->plain();
217 }
218 return $value;
219 }
220
228 private function generateMessageBlob( Module $module, $lang ) {
229 $messages = [];
230 foreach ( $module->getMessages() as $key ) {
231 $value = $this->fetchMessage( $key, $lang );
232 // If the message does not exist, omit it from the blob so that
233 // client-side mw.message may do its own existence handling.
234 if ( $value !== null ) {
235 $messages[$key] = $value;
236 }
237 }
238
239 $json = FormatJson::encode( (object)$messages, false, FormatJson::UTF8_OK );
240 // @codeCoverageIgnoreStart
241 if ( $json === false ) {
242 $this->logger->warning( 'Failed to encode message blob for {module} ({lang})', [
243 'module' => $module->getName(),
244 'lang' => $lang,
245 ] );
246 $json = '{}';
247 }
248 // codeCoverageIgnoreEnd
249 return $json;
250 }
251}
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:34
getMessages()
Get the messages needed for this module.
Definition Module.php:357
getName()
Get this module's name.
Definition Module.php:116
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...