MediaWiki 1.43.1
ObjectCacheFactory.php
Go to the documentation of this file.
1<?php
35
78 public const CONSTRUCTOR_OPTIONS = [
79 MainConfigNames::SQLiteDataDir,
80 MainConfigNames::UpdateRowsPerQuery,
81 MainConfigNames::MemCachedServers,
82 MainConfigNames::MemCachedPersistent,
83 MainConfigNames::MemCachedTimeout,
84 MainConfigNames::CachePrefix,
85 MainConfigNames::ObjectCaches,
86 MainConfigNames::MainCacheType,
87 MainConfigNames::MessageCacheType,
88 MainConfigNames::ParserCacheType,
89 ];
90
91 private ServiceOptions $options;
92 private StatsFactory $stats;
93 private Spi $logger;
95 private $instances = [];
96 private string $domainId;
98 private $dbLoadBalancerFactory;
104
105 public function __construct(
106 ServiceOptions $options,
107 StatsFactory $stats,
108 Spi $loggerSpi,
109 callable $dbLoadBalancerFactory,
110 string $domainId
111 ) {
112 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
113 $this->options = $options;
114 $this->stats = $stats;
115 $this->logger = $loggerSpi;
116 $this->dbLoadBalancerFactory = $dbLoadBalancerFactory;
117 $this->domainId = $domainId;
118 }
119
129 private function getDefaultKeyspace(): string {
130 $cachePrefix = $this->options->get( MainConfigNames::CachePrefix );
131 if ( is_string( $cachePrefix ) && $cachePrefix !== '' ) {
132 return $cachePrefix;
133 }
134
135 return $this->domainId;
136 }
137
144 private function newFromId( $id ): BagOStuff {
145 if ( $id === CACHE_ANYTHING ) {
146 $id = $this->getAnythingId();
147 }
148
149 if ( !isset( $this->options->get( MainConfigNames::ObjectCaches )[$id] ) ) {
150 // Always recognize these
151 if ( $id === CACHE_NONE ) {
152 return new EmptyBagOStuff();
153 } elseif ( $id === CACHE_HASH ) {
154 return new HashBagOStuff();
155 } elseif ( $id === CACHE_ACCEL ) {
156 return self::makeLocalServerCache( $this->getDefaultKeyspace() );
157 } elseif ( $id === 'wincache' ) {
158 wfDeprecated( __METHOD__ . ' with cache ID "wincache"', '1.43' );
159 return self::makeLocalServerCache( $this->getDefaultKeyspace() );
160 }
161
162 throw new InvalidArgumentException( "Invalid object cache type \"$id\" requested. " .
163 "It is not present in \$wgObjectCaches." );
164 }
165
166 return $this->newFromParams( $this->options->get( MainConfigNames::ObjectCaches )[$id] );
167 }
168
175 public function getInstance( $id ): BagOStuff {
176 if ( !isset( $this->instances[$id] ) ) {
177 $this->instances[$id] = $this->newFromId( $id );
178 }
179
180 return $this->instances[$id];
181 }
182
198 public function newFromParams( array $params ): BagOStuff {
199 $logger = $this->logger->getLogger( $params['loggroup'] ?? 'objectcache' );
200 // Apply default parameters and resolve the logger instance
201 $params += [
202 'logger' => $logger,
203 'keyspace' => $this->getDefaultKeyspace(),
204 'asyncHandler' => [ DeferredUpdates::class, 'addCallableUpdate' ],
205 'reportDupes' => true,
206 'stats' => $this->stats,
207 ];
208
209 if ( isset( $params['factory'] ) ) {
210 $args = $params['args'] ?? [ $params ];
211
212 return call_user_func( $params['factory'], ...$args );
213 }
214
215 if ( !isset( $params['class'] ) ) {
216 throw new InvalidArgumentException(
217 'No "factory" nor "class" provided; got "' . print_r( $params, true ) . '"'
218 );
219 }
220
221 $class = $params['class'];
222
223 // Normalization and DI for SqlBagOStuff
224 if ( is_a( $class, SqlBagOStuff::class, true ) ) {
225 $this->prepareSqlBagOStuffFromParams( $params );
226 }
227
228 // Normalization and DI for MemcachedBagOStuff
229 if ( is_subclass_of( $class, MemcachedBagOStuff::class ) ) {
230 $this->prepareMemcachedBagOStuffFromParams( $params );
231 }
232
233 // Normalization and DI for MultiWriteBagOStuff
234 if ( is_a( $class, MultiWriteBagOStuff::class, true ) ) {
235 $this->prepareMultiWriteBagOStuffFromParams( $params );
236 }
237 if ( is_a( $class, RESTBagOStuff::class, true ) ) {
238 $this->prepareRESTBagOStuffFromParams( $params );
239 }
240
241 return new $class( $params );
242 }
243
244 private function prepareSqlBagOStuffFromParams( array &$params ): void {
245 if ( isset( $params['globalKeyLB'] ) ) {
246 throw new InvalidArgumentException(
247 'globalKeyLB in $wgObjectCaches is no longer supported' );
248 }
249 if ( isset( $params['server'] ) && !isset( $params['servers'] ) ) {
250 $params['servers'] = [ $params['server'] ];
251 unset( $params['server'] );
252 }
253 if ( isset( $params['servers'] ) ) {
254 // In the past it was not required to set 'dbDirectory' in $wgObjectCaches
255 foreach ( $params['servers'] as &$server ) {
256 if ( $server['type'] === 'sqlite' && !isset( $server['dbDirectory'] ) ) {
257 $server['dbDirectory'] = $this->options->get( MainConfigNames::SQLiteDataDir );
258 }
259 }
260 } elseif ( isset( $params['cluster'] ) ) {
261 $cluster = $params['cluster'];
262 $dbLbFactory = $this->dbLoadBalancerFactory;
263 $params['loadBalancerCallback'] = static function () use ( $cluster, $dbLbFactory ) {
264 return $dbLbFactory()->getExternalLB( $cluster );
265 };
266 $params += [ 'dbDomain' => false ];
267 } else {
268 $dbLbFactory = $this->dbLoadBalancerFactory;
269 $params['loadBalancerCallback'] = static function () use ( $dbLbFactory ) {
270 return $dbLbFactory()->getMainLb();
271 };
272 $params += [ 'dbDomain' => false ];
273 }
274 $params += [ 'writeBatchSize' => $this->options->get( MainConfigNames::UpdateRowsPerQuery ) ];
275 }
276
277 private function prepareMemcachedBagOStuffFromParams( array &$params ): void {
278 $params += [
279 'servers' => $this->options->get( MainConfigNames::MemCachedServers ),
280 'persistent' => $this->options->get( MainConfigNames::MemCachedPersistent ),
281 'timeout' => $this->options->get( MainConfigNames::MemCachedTimeout ),
282 ];
283 }
284
285 private function prepareMultiWriteBagOStuffFromParams( array &$params ): void {
286 // Phan warns about foreach with non-array because it
287 // thinks any key can be Closure|IBufferingStatsdDataFactory
288 '@phan-var array{caches:array[]} $params';
289 foreach ( $params['caches'] ?? [] as $i => $cacheInfo ) {
290 // Ensure logger, keyspace, asyncHandler, etc are injected just as if
291 // one of these was configured without MultiWriteBagOStuff (T318272)
292 $params['caches'][$i] = $this->newFromParams( $cacheInfo );
293 }
294 }
295
296 private function prepareRESTBagOStuffFromParams( array &$params ): void {
297 $params['telemetry'] = Telemetry::getInstance();
298 }
299
317 $cache = $this->getInstance( CACHE_ACCEL );
318 if ( $cache instanceof EmptyBagOStuff ) {
319 if ( is_array( $fallback ) ) {
320 $fallback = $fallback['fallback'] ?? CACHE_NONE;
321 }
322 $cache = $this->getInstance( $fallback );
323 }
324
325 return $cache;
326 }
327
331 public function clear(): void {
332 $this->instances = [];
333 }
334
339 private static function getLocalServerCacheClass() {
340 if ( self::$localServerCacheClass !== null ) {
341 return self::$localServerCacheClass;
342 }
343 if ( function_exists( 'apcu_fetch' ) ) {
344 // Make sure the APCu methods actually store anything
345 if ( PHP_SAPI !== 'cli' || ini_get( 'apc.enable_cli' ) ) {
346 return APCUBagOStuff::class;
347
348 }
349 }
350
351 return EmptyBagOStuff::class;
352 }
353
360 public function getAnythingId() {
361 $candidates = [
362 $this->options->get( MainConfigNames::MainCacheType ),
363 $this->options->get( MainConfigNames::MessageCacheType ),
364 $this->options->get( MainConfigNames::ParserCacheType )
365 ];
366 foreach ( $candidates as $candidate ) {
367 if ( $candidate === CACHE_ACCEL ) {
368 // CACHE_ACCEL might default to nothing if no APCu
369 // See includes/ServiceWiring.php
370 $class = self::getLocalServerCacheClass();
371 if ( $class !== EmptyBagOStuff::class ) {
372 return $candidate;
373 }
374 } elseif ( $candidate !== CACHE_NONE && $candidate !== CACHE_ANYTHING ) {
375 return $candidate;
376 }
377 }
378
379 $services = MediaWikiServices::getInstance();
380
381 if ( $services->isServiceDisabled( 'DBLoadBalancer' ) ) {
382 // The DBLoadBalancer service is disabled, so we can't use the database!
383 $candidate = CACHE_NONE;
384 } elseif ( $services->isStorageDisabled() ) {
385 // Storage services are disabled because MediaWikiServices::disableStorage()
386 // was called. This is typically the case during installation.
387 $candidate = CACHE_NONE;
388 } else {
389 $candidate = CACHE_DB;
390 }
391 return $candidate;
392 }
393
413 public static function makeLocalServerCache( string $keyspace ) {
414 $params = [
415 'reportDupes' => false,
416 // Even simple caches must use a keyspace (T247562)
417 'keyspace' => $keyspace,
418 ];
419 $class = self::getLocalServerCacheClass();
420 return new $class( $params );
421 }
422
430 public function isDatabaseId( $id ) {
431 // NOTE: Sanity check if $id is set to CACHE_ANYTHING and
432 // everything is going through service wiring. CACHE_ANYTHING
433 // would default to CACHE_DB, let's handle that early for cases
434 // where all cache configs are set to CACHE_ANYTHING (T362686).
435 if ( $id === CACHE_ANYTHING ) {
436 $id = $this->getAnythingId();
437 return $this->isDatabaseId( $id );
438 }
439
440 if ( !isset( $this->options->get( MainConfigNames::ObjectCaches )[$id] ) ) {
441 return false;
442 }
443 $cache = $this->options->get( MainConfigNames::ObjectCaches )[$id];
444 if ( ( $cache['class'] ?? '' ) === SqlBagOStuff::class ) {
445 return true;
446 }
447
448 return false;
449 }
450
457 public function getLocalClusterInstance() {
458 return $this->getInstance(
459 $this->options->get( MainConfigNames::MainCacheType )
460 );
461 }
462}
const CACHE_NONE
Definition Defines.php:87
const CACHE_ANYTHING
Definition Defines.php:86
const CACHE_ACCEL
Definition Defines.php:90
const CACHE_HASH
Definition Defines.php:91
const CACHE_DB
Definition Defines.php:88
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
$fallback
array $params
The job parameters.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:81
A class for passing options to services.
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
Defer callable updates to run later in the PHP process.
Service for handling telemetry data.
Definition Telemetry.php:29
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
Factory for cache objects as configured in the ObjectCaches setting.
isDatabaseId( $id)
Determine whether a config ID would access the database.
getLocalClusterInstance()
Get the main cluster-local cache object.
getInstance( $id)
Get a cached instance of the specified type of cache object.
__construct(ServiceOptions $options, StatsFactory $stats, Spi $loggerSpi, callable $dbLoadBalancerFactory, string $domainId)
getLocalServerInstance( $fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from configuration)
static string $localServerCacheClass
getAnythingId()
Get the ID that will be used for CACHE_ANYTHING.
newFromParams(array $params)
Create a new cache object from parameters specification supplied.
static makeLocalServerCache(string $keyspace)
Create a new BagOStuff instance for local-server caching.
clear()
Clear all the cached instances.
Store data in the local server memory via APCu (php-apcu)
Abstract class for any ephemeral data store.
Definition BagOStuff.php:89
No-op implementation that stores nothing.
Store data in a memory for the current request/process only.
Store data in a memcached server or memcached cluster.
Wrap multiple BagOStuff objects, to implement different caching tiers.
Store key-value data via an HTTP service.
StatsFactory Implementation.
Service provider interface to create \Psr\Log\LoggerInterface objects.
Definition Spi.php:64