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