MediaWiki master
ObjectCacheFactory.php
Go to the documentation of this file.
1<?php
28
72 public const CONSTRUCTOR_OPTIONS = [
73 MainConfigNames::SQLiteDataDir,
74 MainConfigNames::UpdateRowsPerQuery,
75 MainConfigNames::MemCachedServers,
76 MainConfigNames::MemCachedPersistent,
77 MainConfigNames::MemCachedTimeout,
78 MainConfigNames::CachePrefix,
79 MainConfigNames::ObjectCaches,
80 MainConfigNames::MainCacheType,
81 MainConfigNames::MessageCacheType,
82 MainConfigNames::ParserCacheType,
83 ];
84
85 private ServiceOptions $options;
86 private StatsFactory $stats;
87 private Spi $logger;
89 private $instances = [];
90 private string $domainId;
92 private $dbLoadBalancerFactory;
98
99 public function __construct(
100 ServiceOptions $options,
101 StatsFactory $stats,
102 Spi $loggerSpi,
103 callable $dbLoadBalancerFactory,
104 string $domainId
105 ) {
106 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
107 $this->options = $options;
108 $this->stats = $stats;
109 $this->logger = $loggerSpi;
110 $this->dbLoadBalancerFactory = $dbLoadBalancerFactory;
111 $this->domainId = $domainId;
112 }
113
123 private function getDefaultKeyspace(): string {
124 $cachePrefix = $this->options->get( MainConfigNames::CachePrefix );
125 if ( is_string( $cachePrefix ) && $cachePrefix !== '' ) {
126 return $cachePrefix;
127 }
128
129 return $this->domainId;
130 }
131
138 private function newFromId( $id ): BagOStuff {
139 if ( $id === CACHE_ANYTHING ) {
140 $id = $this->getAnythingId();
141 }
142
143 if ( !isset( $this->options->get( MainConfigNames::ObjectCaches )[$id] ) ) {
144 // Always recognize these
145 if ( $id === CACHE_NONE ) {
146 return new EmptyBagOStuff();
147 } elseif ( $id === CACHE_HASH ) {
148 return new HashBagOStuff();
149 } elseif ( $id === CACHE_ACCEL ) {
150 return self::makeLocalServerCache( $this->getDefaultKeyspace() );
151 }
152
153 throw new InvalidArgumentException( "Invalid object cache type \"$id\" requested. " .
154 "It is not present in \$wgObjectCaches." );
155 }
156
157 return $this->newFromParams( $this->options->get( MainConfigNames::ObjectCaches )[$id] );
158 }
159
166 public function getInstance( $id ): BagOStuff {
167 if ( !isset( $this->instances[$id] ) ) {
168 $this->instances[$id] = $this->newFromId( $id );
169 }
170
171 return $this->instances[$id];
172 }
173
189 public function newFromParams( array $params ): BagOStuff {
190 $logger = $this->logger->getLogger( $params['loggroup'] ?? 'objectcache' );
191 // Apply default parameters and resolve the logger instance
192 $params += [
193 'logger' => $logger,
194 'keyspace' => $this->getDefaultKeyspace(),
195 'asyncHandler' => [ DeferredUpdates::class, 'addCallableUpdate' ],
196 'reportDupes' => true,
197 'stats' => $this->stats,
198 ];
199
200 if ( isset( $params['factory'] ) ) {
201 $args = $params['args'] ?? [ $params ];
202
203 return call_user_func( $params['factory'], ...$args );
204 }
205
206 if ( !isset( $params['class'] ) ) {
207 throw new InvalidArgumentException(
208 'No "factory" nor "class" provided; got "' . print_r( $params, true ) . '"'
209 );
210 }
211
212 $class = $params['class'];
213
214 // Normalization and DI for SqlBagOStuff
215 if ( is_a( $class, SqlBagOStuff::class, true ) ) {
216 $this->prepareSqlBagOStuffFromParams( $params );
217 }
218
219 // Normalization and DI for MemcachedBagOStuff
220 if ( is_subclass_of( $class, MemcachedBagOStuff::class ) ) {
221 $this->prepareMemcachedBagOStuffFromParams( $params );
222 }
223
224 // Normalization and DI for MultiWriteBagOStuff
225 if ( is_a( $class, MultiWriteBagOStuff::class, true ) ) {
226 $this->prepareMultiWriteBagOStuffFromParams( $params );
227 }
228 if ( is_a( $class, RESTBagOStuff::class, true ) ) {
229 $this->prepareRESTBagOStuffFromParams( $params );
230 }
231
232 return new $class( $params );
233 }
234
235 private function prepareSqlBagOStuffFromParams( array &$params ): void {
236 if ( isset( $params['globalKeyLB'] ) ) {
237 throw new InvalidArgumentException(
238 'globalKeyLB in $wgObjectCaches is no longer supported' );
239 }
240 if ( isset( $params['server'] ) && !isset( $params['servers'] ) ) {
241 $params['servers'] = [ $params['server'] ];
242 unset( $params['server'] );
243 }
244 if ( isset( $params['servers'] ) ) {
245 // In the past it was not required to set 'dbDirectory' in $wgObjectCaches
246 foreach ( $params['servers'] as &$server ) {
247 if ( $server['type'] === 'sqlite' && !isset( $server['dbDirectory'] ) ) {
248 $server['dbDirectory'] = $this->options->get( MainConfigNames::SQLiteDataDir );
249 }
250 }
251 } elseif ( isset( $params['cluster'] ) ) {
252 $cluster = $params['cluster'];
253 $dbLbFactory = $this->dbLoadBalancerFactory;
254 $params['loadBalancerCallback'] = static function () use ( $cluster, $dbLbFactory ) {
255 return $dbLbFactory()->getExternalLB( $cluster );
256 };
257 $params += [ 'dbDomain' => false ];
258 } else {
259 $dbLbFactory = $this->dbLoadBalancerFactory;
260 $params['loadBalancerCallback'] = static function () use ( $dbLbFactory ) {
261 return $dbLbFactory()->getMainLb();
262 };
263 $params += [ 'dbDomain' => false ];
264 }
265 $params += [ 'writeBatchSize' => $this->options->get( MainConfigNames::UpdateRowsPerQuery ) ];
266 }
267
268 private function prepareMemcachedBagOStuffFromParams( array &$params ): void {
269 $params += [
270 'servers' => $this->options->get( MainConfigNames::MemCachedServers ),
271 'persistent' => $this->options->get( MainConfigNames::MemCachedPersistent ),
272 'timeout' => $this->options->get( MainConfigNames::MemCachedTimeout ),
273 ];
274 }
275
276 private function prepareMultiWriteBagOStuffFromParams( array &$params ): void {
277 // Phan warns about foreach with non-array because it
278 // thinks any key can be Closure|IBufferingStatsdDataFactory
279 '@phan-var array{caches:array[]} $params';
280 foreach ( $params['caches'] ?? [] as $i => $cacheInfo ) {
281 // Ensure logger, keyspace, asyncHandler, etc are injected just as if
282 // one of these was configured without MultiWriteBagOStuff.
283 $params['caches'][$i] = $this->newFromParams( $cacheInfo );
284 }
285 }
286
287 private function prepareRESTBagOStuffFromParams( array &$params ): void {
288 $params['telemetry'] = Telemetry::getInstance();
289 }
290
308 $cache = $this->getInstance( CACHE_ACCEL );
309 if ( $cache instanceof EmptyBagOStuff ) {
310 if ( is_array( $fallback ) ) {
311 $fallback = $fallback['fallback'] ?? CACHE_NONE;
312 }
313 $cache = $this->getInstance( $fallback );
314 }
315
316 return $cache;
317 }
318
322 public function clear(): void {
323 $this->instances = [];
324 }
325
330 private static function getLocalServerCacheClass() {
331 if ( self::$localServerCacheClass !== null ) {
332 return self::$localServerCacheClass;
333 }
334 if ( function_exists( 'apcu_fetch' ) ) {
335 // Make sure the APCu methods actually store anything
336 if ( PHP_SAPI !== 'cli' || ini_get( 'apc.enable_cli' ) ) {
337 return APCUBagOStuff::class;
338
339 }
340 } elseif ( function_exists( 'wincache_ucache_get' ) ) {
341 return WinCacheBagOStuff::class;
342 }
343
344 return EmptyBagOStuff::class;
345 }
346
353 public function getAnythingId() {
354 $candidates = [
355 $this->options->get( MainConfigNames::MainCacheType ),
356 $this->options->get( MainConfigNames::MessageCacheType ),
357 $this->options->get( MainConfigNames::ParserCacheType )
358 ];
359 foreach ( $candidates as $candidate ) {
360 if ( $candidate === CACHE_ACCEL ) {
361 // CACHE_ACCEL might default to nothing if no APCu
362 // See includes/ServiceWiring.php
363 $class = self::getLocalServerCacheClass();
364 if ( $class !== EmptyBagOStuff::class ) {
365 return $candidate;
366 }
367 } elseif ( $candidate !== CACHE_NONE && $candidate !== CACHE_ANYTHING ) {
368 return $candidate;
369 }
370 }
371
372 $services = MediaWikiServices::getInstance();
373
374 if ( $services->isServiceDisabled( 'DBLoadBalancer' ) ) {
375 // The DBLoadBalancer service is disabled, so we can't use the database!
376 $candidate = CACHE_NONE;
377 } elseif ( $services->isStorageDisabled() ) {
378 // Storage services are disabled because MediaWikiServices::disableStorage()
379 // was called. This is typically the case during installation.
380 $candidate = CACHE_NONE;
381 } else {
382 $candidate = CACHE_DB;
383 }
384 return $candidate;
385 }
386
406 public static function makeLocalServerCache( string $keyspace ) {
407 $params = [
408 'reportDupes' => false,
409 // Even simple caches must use a keyspace (T247562)
410 'keyspace' => $keyspace,
411 ];
412 $class = self::getLocalServerCacheClass();
413 return new $class( $params );
414 }
415
423 public function isDatabaseId( $id ) {
424 // NOTE: Sanity check if $id is set to CACHE_ANYTHING and
425 // everything is going through service wiring. CACHE_ANYTHING
426 // would default to CACHE_DB, let's handle that early for cases
427 // where all cache configs are set to CACHE_ANYTHING (T362686).
428 if ( $id === CACHE_ANYTHING ) {
429 $id = $this->getAnythingId();
430 return $this->isDatabaseId( $id );
431 }
432
433 if ( !isset( $this->options->get( MainConfigNames::ObjectCaches )[$id] ) ) {
434 return false;
435 }
436 $cache = $this->options->get( MainConfigNames::ObjectCaches )[$id];
437 if ( ( $cache['class'] ?? '' ) === SqlBagOStuff::class ) {
438 return true;
439 }
440
441 return false;
442 }
443
450 public function getLocalClusterInstance() {
451 return $this->getInstance(
452 $this->options->get( MainConfigNames::MainCacheType )
453 );
454 }
455}
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
$fallback
array $params
The job parameters.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:81
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.
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.
StatsFactory Implementation.
Service provider interface to create \Psr\Log\LoggerInterface objects.
Definition Spi.php:64