MediaWiki  master
ObjectCache.php
Go to the documentation of this file.
1 <?php
28 
66 class ObjectCache {
68  public static $instances = [];
69 
74  public static $localServerCacheClass;
75 
82  public static function getInstance( $id ) {
83  if ( !isset( self::$instances[$id] ) ) {
84  self::$instances[$id] = self::newFromId( $id );
85  }
86 
87  return self::$instances[$id];
88  }
89 
97  private static function newFromId( $id ) {
98  global $wgObjectCaches;
99 
100  if ( !isset( $wgObjectCaches[$id] ) ) {
101  // Always recognize these ones
102  if ( $id === CACHE_NONE ) {
103  return new EmptyBagOStuff();
104  } elseif ( $id === CACHE_HASH ) {
105  return new HashBagOStuff();
106  }
107 
108  throw new InvalidArgumentException( "Invalid object cache type \"$id\" requested. " .
109  "It is not present in \$wgObjectCaches." );
110  }
111 
112  return self::newFromParams( $wgObjectCaches[$id] );
113  }
114 
124  private static function getDefaultKeyspace() {
125  global $wgCachePrefix;
126 
127  $keyspace = $wgCachePrefix;
128  if ( is_string( $keyspace ) && $keyspace !== '' ) {
129  return $keyspace;
130  }
131 
132  return WikiMap::getCurrentWikiDbDomain()->getId();
133  }
134 
146  public static function newFromParams( array $params, MediaWikiServices $services = null ) {
147  $services ??= MediaWikiServices::getInstance();
148  $conf = $services->getMainConfig();
149 
150  // Apply default parameters and resolve the logger instance
151  $params += [
152  'logger' => LoggerFactory::getInstance( $params['loggroup'] ?? 'objectcache' ),
153  'keyspace' => self::getDefaultKeyspace(),
154  'asyncHandler' => [ DeferredUpdates::class, 'addCallableUpdate' ],
155  'reportDupes' => true,
156  'stats' => $services->getStatsdDataFactory(),
157  ];
158 
159  if ( isset( $params['factory'] ) ) {
160  $args = $params['args'] ?? [ $params ];
161 
162  return call_user_func( $params['factory'], ...$args );
163  }
164 
165  if ( !isset( $params['class'] ) ) {
166  throw new InvalidArgumentException(
167  'No "factory" nor "class" provided; got "' . print_r( $params, true ) . '"'
168  );
169  }
170 
171  $class = $params['class'];
172 
173  // Normalization and DI for SqlBagOStuff
174  if ( is_a( $class, SqlBagOStuff::class, true ) ) {
175  if ( isset( $params['globalKeyLB'] ) ) {
176  throw new InvalidArgumentException(
177  'globalKeyLB in $wgObjectCaches is no longer supported' );
178  }
179  if ( isset( $params['server'] ) && !isset( $params['servers'] ) ) {
180  $params['servers'] = [ $params['server'] ];
181  unset( $params['server'] );
182  }
183  if ( isset( $params['servers'] ) ) {
184  // In the past it was not required to set 'dbDirectory' in $wgObjectCaches
185  foreach ( $params['servers'] as &$server ) {
186  if ( $server['type'] === 'sqlite' && !isset( $server['dbDirectory'] ) ) {
187  $server['dbDirectory'] = $conf->get( MainConfigNames::SQLiteDataDir );
188  }
189  }
190  } elseif ( isset( $params['cluster'] ) ) {
191  $cluster = $params['cluster'];
192  $params['loadBalancerCallback'] = static function () use ( $services, $cluster ) {
193  return $services->getDBLoadBalancerFactory()->getExternalLB( $cluster );
194  };
195  $params += [ 'dbDomain' => false ];
196  } else {
197  $params['loadBalancerCallback'] = static function () use ( $services ) {
198  return $services->getDBLoadBalancer();
199  };
200  $params += [ 'dbDomain' => false ];
201  }
202  $params += [ 'writeBatchSize' => $conf->get( MainConfigNames::UpdateRowsPerQuery ) ];
203  }
204 
205  // Normalization and DI for MemcachedBagOStuff
206  if ( is_subclass_of( $class, MemcachedBagOStuff::class ) ) {
207  $params += [
208  'servers' => $conf->get( MainConfigNames::MemCachedServers ),
209  'persistent' => $conf->get( MainConfigNames::MemCachedPersistent ),
210  'timeout' => $conf->get( MainConfigNames::MemCachedTimeout ),
211  ];
212  }
213 
214  // Normalization and DI for MultiWriteBagOStuff
215  if ( is_a( $class, MultiWriteBagOStuff::class, true ) ) {
216  // Phan warns about foreach with non-array because it
217  // thinks any key can be Closure|IBufferingStatsdDataFactory
218  '@phan-var array{caches:array[]} $params';
219  foreach ( $params['caches'] ?? [] as $i => $cacheInfo ) {
220  // Ensure logger, keyspace, asyncHandler, etc are injected just as if
221  // one of these was configured without MultiWriteBagOStuff.
222  $params['caches'][$i] = self::newFromParams( $cacheInfo, $services );
223  }
224  }
225 
226  return new $class( $params );
227  }
228 
242  public static function newAnything( $params ) {
243  return self::getInstance( self::getAnythingId() );
244  }
245 
250  private static function getAnythingId() {
253  foreach ( $candidates as $candidate ) {
254  if ( $candidate === CACHE_ACCEL ) {
255  // CACHE_ACCEL might default to nothing if no APCu
256  // See includes/ServiceWiring.php
257  $class = self::getLocalServerCacheClass();
258  if ( $class !== EmptyBagOStuff::class ) {
259  return $candidate;
260  }
261  } elseif ( $candidate !== CACHE_NONE && $candidate !== CACHE_ANYTHING ) {
262  return $candidate;
263  }
264  }
265 
266  $services = MediaWikiServices::getInstance();
267 
268  if ( $services->isServiceDisabled( 'DBLoadBalancer' ) ) {
269  // The DBLoadBalancer service is disabled, so we can't use the database!
270  $candidate = CACHE_NONE;
271  } elseif ( $services->isStorageDisabled() ) {
272  // Storage services are disabled because MediaWikiServices::disableStorage()
273  // was called. This is typically the case during installation.
274  $candidate = CACHE_NONE;
275  } else {
276  $candidate = CACHE_DB;
277  }
278  return $candidate;
279  }
280 
298  public static function getLocalServerInstance( $fallback = CACHE_NONE ) {
299  $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
300  if ( $cache instanceof EmptyBagOStuff ) {
301  if ( is_array( $fallback ) ) {
302  $fallback = $fallback['fallback'] ?? CACHE_NONE;
303  }
304  $cache = self::getInstance( $fallback );
305  }
306 
307  return $cache;
308  }
309 
316  public static function getLocalClusterInstance() {
317  return MediaWikiServices::getInstance()->get( '_LocalClusterCache' );
318  }
319 
326  public static function isDatabaseId( $id ) {
327  global $wgObjectCaches;
328  if ( !isset( $wgObjectCaches[$id] ) ) {
329  return false;
330  }
331  $cache = $wgObjectCaches[$id];
332  if ( ( $cache['class'] ?? '' ) === SqlBagOStuff::class ) {
333  return true;
334  }
335  // Ideally we would inspect the config, but it's complicated. The ID is suggestive.
336  if ( $id === 'db-replicated' ) {
337  return true;
338  }
339  if ( ( $cache['factory'] ?? '' ) === 'ObjectCache::newAnything' ) {
340  $id = self::getAnythingId();
341  return self::isDatabaseId( $id );
342  }
343  return false;
344  }
345 
349  public static function clear() {
350  self::$instances = [];
351  }
352 
367  public static function makeLocalServerCache(): BagOStuff {
368  $params = [
369  'reportDupes' => false,
370  // Even simple caches must use a keyspace (T247562)
371  'keyspace' => self::getDefaultKeyspace(),
372  ];
373  $class = self::getLocalServerCacheClass();
374  return new $class( $params );
375  }
376 
381  private static function getLocalServerCacheClass() {
382  if ( self::$localServerCacheClass !== null ) {
383  return self::$localServerCacheClass;
384  }
385  if ( function_exists( 'apcu_fetch' ) ) {
386  // Make sure the APCu methods actually store anything
387  if ( PHP_SAPI !== 'cli' || ini_get( 'apc.enable_cli' ) ) {
388  return APCUBagOStuff::class;
389 
390  }
391  } elseif ( function_exists( 'wincache_ucache_get' ) ) {
392  return WinCacheBagOStuff::class;
393  }
394 
395  return EmptyBagOStuff::class;
396  }
397 }
const CACHE_NONE
Definition: Defines.php:86
const CACHE_ANYTHING
Definition: Defines.php:85
const CACHE_ACCEL
Definition: Defines.php:89
const CACHE_HASH
Definition: Defines.php:90
const CACHE_DB
Definition: Defines.php:87
$fallback
Definition: MessagesAb.php:8
Class representing a cache/ephemeral data store.
Definition: BagOStuff.php:85
A BagOStuff object with no objects in it.
Simple store for keeping values in an associative array for the current process.
PSR-3 logger instance factory.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
Helper tools for dealing with other locally-hosted wikis.
Definition: WikiMap.php:33
Functions to get cache objects.
Definition: ObjectCache.php:66
static getLocalServerInstance( $fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from configuration)
static makeLocalServerCache()
Create a new BagOStuff instance for local-server caching.
static isDatabaseId( $id)
Determine whether a config ID would access the database.
static newAnything( $params)
Factory function for CACHE_ANYTHING (referenced by configuration)
static clear()
Clear all the cached instances.
static newFromParams(array $params, MediaWikiServices $services=null)
Create a new cache object from parameters.
static BagOStuff[] $instances
Map of (id => BagOStuff)
Definition: ObjectCache.php:68
static getInstance( $id)
Get a cached instance of the specified type of cache object.
Definition: ObjectCache.php:82
static string $localServerCacheClass
Definition: ObjectCache.php:74
static getLocalClusterInstance()
Get the main cluster-local cache object.
$wgObjectCaches
Config variable stub for the ObjectCaches setting, for use by phpdoc and IDEs.
$wgParserCacheType
Config variable stub for the ParserCacheType setting, for use by phpdoc and IDEs.
$wgMainCacheType
Config variable stub for the MainCacheType setting, for use by phpdoc and IDEs.
$wgCachePrefix
Config variable stub for the CachePrefix setting, for use by phpdoc and IDEs.
$wgMessageCacheType
Config variable stub for the MessageCacheType setting, for use by phpdoc and IDEs.