Make LocalisationCache a service
This removes Language::$dataCache without deprecation, because 1) I
don't know of a way to properly simulate it in the new paradigm, and 2)
I found no direct access to the member outside of the Language and
LanguageTest classes.
An earlier version of this patch (e4468a1d6b) had to be reverted
because of a massive slowdown on test runs. Based on some local testing,
this should fix the problem. Running all tests in languages is slowed
down by only around 20% instead of a factor of five, and memory usage is
actually reduced greatly (~350 MB -> ~200 MB). The slowdown is still not
great, but I assume it's par for the course for converting things to
services and is acceptable. If not, I can try to optimize further.
Bug: T231220
Bug: T231198
Bug: T231200
Bug: T201405
Change-Id: Ieadbd820379a006d8ad2d2e4a1e96241e172ec5a
This commit is contained in:
parent
48fa084a86
commit
043d88f680
16 changed files with 311 additions and 142 deletions
|
|
@ -458,6 +458,8 @@ because of Phabricator reports.
|
|||
* User::setNewpassword(), deprecated in 1.27 has been removed.
|
||||
* The ObjectCache::getMainWANInstance and ObjectCache::getMainStashInstance
|
||||
functions, deprecated since 1.28, have been removed.
|
||||
* Language::$dataCache has been removed (without prior deprecation, for
|
||||
practical reasons). Use MediaWikiServices instead to get a LocalisationCache.
|
||||
|
||||
=== Deprecations in 1.34 ===
|
||||
* The MWNamespace class is deprecated. Use NamespaceInfo.
|
||||
|
|
@ -607,6 +609,8 @@ because of Phabricator reports.
|
|||
remove HHVM support from MediaWiki, which started in MediaWiki 1.31.
|
||||
* RESTBagOStuff users should specify either "JSON" or "PHP" serialization type.
|
||||
* Hard deprecate Revision::getRevisionText() method.
|
||||
* Language::getLocalisationCache() is deprecated. Use MediaWikiServices
|
||||
instead.
|
||||
|
||||
=== Other changes in 1.34 ===
|
||||
* Added option to specify "Various authors" as author in extension credits using
|
||||
|
|
|
|||
|
|
@ -2624,6 +2624,8 @@ $wgLocalisationCacheConf = [
|
|||
'store' => 'detect',
|
||||
'storeClass' => false,
|
||||
'storeDirectory' => false,
|
||||
'storeServer' => [],
|
||||
'forceRecache' => false,
|
||||
'manualRecache' => false,
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use GlobalVarConfig;
|
|||
use Hooks;
|
||||
use IBufferingStatsdDataFactory;
|
||||
use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
|
||||
use LocalisationCache;
|
||||
use MediaWiki\Block\BlockManager;
|
||||
use MediaWiki\Block\BlockRestrictionStore;
|
||||
use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
|
||||
|
|
@ -653,6 +654,14 @@ class MediaWikiServices extends ServiceContainer {
|
|||
return $this->getService( 'LinkRendererFactory' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.34
|
||||
* @return LocalisationCache
|
||||
*/
|
||||
public function getLocalisationCache() : LocalisationCache {
|
||||
return $this->getService( 'LocalisationCache' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.28
|
||||
* @return \BagOStuff
|
||||
|
|
|
|||
|
|
@ -286,6 +286,36 @@ return [
|
|||
);
|
||||
},
|
||||
|
||||
'LocalisationCache' => function ( MediaWikiServices $services ) : LocalisationCache {
|
||||
$conf = $services->getMainConfig()->get( 'LocalisationCacheConf' );
|
||||
|
||||
$logger = LoggerFactory::getInstance( 'localisation' );
|
||||
|
||||
$store = LocalisationCache::getStoreFromConf(
|
||||
$conf, $services->getMainConfig()->get( 'CacheDirectory' ) );
|
||||
$logger->debug( 'LocalisationCache: using store ' . get_class( $store ) );
|
||||
|
||||
return new $conf['class'](
|
||||
new ServiceOptions(
|
||||
LocalisationCache::$constructorOptions,
|
||||
// Two of the options are stored in $wgLocalisationCacheConf
|
||||
$conf,
|
||||
// In case someone set that config variable and didn't reset all keys, set defaults.
|
||||
[
|
||||
'forceRecache' => false,
|
||||
'manualRecache' => false,
|
||||
],
|
||||
// Some other options come from config itself
|
||||
$services->getMainConfig()
|
||||
),
|
||||
$store,
|
||||
$logger,
|
||||
[ function () use ( $services ) {
|
||||
$services->getResourceLoader()->getMessageBlobStore()->clear();
|
||||
} ]
|
||||
);
|
||||
},
|
||||
|
||||
'LocalServerObjectCache' => function ( MediaWikiServices $services ) : BagOStuff {
|
||||
$config = $services->getMainConfig();
|
||||
$cacheId = ObjectCache::detectLocalServerCache();
|
||||
|
|
|
|||
147
includes/cache/localisation/LocalisationCache.php
vendored
147
includes/cache/localisation/LocalisationCache.php
vendored
|
|
@ -22,14 +22,14 @@
|
|||
|
||||
use CLDRPluralRuleParser\Evaluator;
|
||||
use CLDRPluralRuleParser\Error as CLDRPluralRuleError;
|
||||
use MediaWiki\Logger\LoggerFactory;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use MediaWiki\Config\ServiceOptions;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Class for caching the contents of localisation files, Messages*.php
|
||||
* and *.i18n.php.
|
||||
*
|
||||
* An instance of this class is available using Language::getLocalisationCache().
|
||||
* An instance of this class is available using MediaWikiServices.
|
||||
*
|
||||
* The values retrieved from here are merged, containing items from extension
|
||||
* files, core messages files and the language fallback sequence (e.g. zh-cn ->
|
||||
|
|
@ -40,8 +40,8 @@ use MediaWiki\MediaWikiServices;
|
|||
class LocalisationCache {
|
||||
const VERSION = 4;
|
||||
|
||||
/** Configuration associative array */
|
||||
private $conf;
|
||||
/** @var ServiceOptions */
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* True if recaching should only be done on an explicit call to recache().
|
||||
|
|
@ -50,11 +50,6 @@ class LocalisationCache {
|
|||
*/
|
||||
private $manualRecache = false;
|
||||
|
||||
/**
|
||||
* True to treat all files as expired until they are regenerated by this object.
|
||||
*/
|
||||
private $forceRecache = false;
|
||||
|
||||
/**
|
||||
* The cache data. 3-d array, where the first key is the language code,
|
||||
* the second key is the item key e.g. 'messages', and the third key is
|
||||
|
|
@ -71,16 +66,20 @@ class LocalisationCache {
|
|||
private $store;
|
||||
|
||||
/**
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/** @var callable[] See comment for parameter in constructor */
|
||||
private $clearStoreCallbacks;
|
||||
|
||||
/**
|
||||
* A 2-d associative array, code/key, where presence indicates that the item
|
||||
* is loaded. Value arbitrary.
|
||||
*
|
||||
* For split items, if set, this indicates that all of the subitems have been
|
||||
* loaded.
|
||||
*
|
||||
*/
|
||||
private $loadedItems = [];
|
||||
|
||||
|
|
@ -189,59 +188,81 @@ class LocalisationCache {
|
|||
private $mergeableKeys = null;
|
||||
|
||||
/**
|
||||
* For constructor parameters, see the documentation in DefaultSettings.php
|
||||
* for $wgLocalisationCacheConf.
|
||||
* Return a suitable LCStore as specified by the given configuration.
|
||||
*
|
||||
* @param array $conf
|
||||
* @throws MWException
|
||||
* @since 1.34
|
||||
* @param array $conf In the format of $wgLocalisationCacheConf
|
||||
* @param string|false|null $fallbackCacheDir In case 'storeDirectory' isn't specified
|
||||
* @return LCStore
|
||||
*/
|
||||
function __construct( $conf ) {
|
||||
global $wgCacheDirectory;
|
||||
|
||||
$this->conf = $conf;
|
||||
$this->logger = LoggerFactory::getInstance( 'localisation' );
|
||||
|
||||
$directory = !empty( $conf['storeDirectory'] ) ? $conf['storeDirectory'] : $wgCacheDirectory;
|
||||
public static function getStoreFromConf( array $conf, $fallbackCacheDir ) : LCStore {
|
||||
$storeArg = [];
|
||||
$storeArg['directory'] = $directory;
|
||||
$storeArg['directory'] =
|
||||
$conf['storeDirectory'] ?: $fallbackCacheDir;
|
||||
|
||||
if ( !empty( $conf['storeClass'] ) ) {
|
||||
$storeClass = $conf['storeClass'];
|
||||
} elseif ( $conf['store'] === 'files' || $conf['store'] === 'file' ||
|
||||
( $conf['store'] === 'detect' && $storeArg['directory'] )
|
||||
) {
|
||||
$storeClass = LCStoreCDB::class;
|
||||
} elseif ( $conf['store'] === 'db' || $conf['store'] === 'detect' ) {
|
||||
$storeClass = LCStoreDB::class;
|
||||
$storeArg['server'] = $conf['storeServer'] ?? [];
|
||||
} elseif ( $conf['store'] === 'array' ) {
|
||||
$storeClass = LCStoreStaticArray::class;
|
||||
} else {
|
||||
switch ( $conf['store'] ) {
|
||||
case 'files':
|
||||
case 'file':
|
||||
$storeClass = LCStoreCDB::class;
|
||||
break;
|
||||
case 'db':
|
||||
$storeClass = LCStoreDB::class;
|
||||
$storeArg['server'] = $conf['storeServer'] ?? [];
|
||||
break;
|
||||
case 'array':
|
||||
$storeClass = LCStoreStaticArray::class;
|
||||
break;
|
||||
case 'detect':
|
||||
if ( $directory ) {
|
||||
$storeClass = LCStoreCDB::class;
|
||||
} else {
|
||||
$storeClass = LCStoreDB::class;
|
||||
$storeArg['server'] = $conf['storeServer'] ?? [];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new MWException(
|
||||
'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
|
||||
);
|
||||
}
|
||||
throw new MWException(
|
||||
'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
|
||||
);
|
||||
}
|
||||
$this->logger->debug( static::class . ": using store $storeClass" );
|
||||
|
||||
$this->store = new $storeClass( $storeArg );
|
||||
foreach ( [ 'manualRecache', 'forceRecache' ] as $var ) {
|
||||
if ( isset( $conf[$var] ) ) {
|
||||
$this->$var = $conf[$var];
|
||||
}
|
||||
}
|
||||
return new $storeClass( $storeArg );
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Make this a const when HHVM support is dropped (T192166)
|
||||
*
|
||||
* @var array
|
||||
* @since 1.34
|
||||
*/
|
||||
public static $constructorOptions = [
|
||||
// True to treat all files as expired until they are regenerated by this object.
|
||||
'forceRecache',
|
||||
'manualRecache',
|
||||
'ExtensionMessagesFiles',
|
||||
'MessagesDirs',
|
||||
];
|
||||
|
||||
/**
|
||||
* For constructor parameters, see the documentation in DefaultSettings.php
|
||||
* for $wgLocalisationCacheConf.
|
||||
*
|
||||
* Do not construct this directly. Use MediaWikiServices.
|
||||
*
|
||||
* @param ServiceOptions $options
|
||||
* @param LCStore $store What backend to use for storage
|
||||
* @param LoggerInterface $logger
|
||||
* @param callable[] $clearStoreCallbacks To be called whenever the cache is cleared. Can be
|
||||
* used to clear other caches that depend on this one, such as ResourceLoader's
|
||||
* MessageBlobStore.
|
||||
* @throws MWException
|
||||
*/
|
||||
function __construct(
|
||||
ServiceOptions $options,
|
||||
LCStore $store,
|
||||
LoggerInterface $logger,
|
||||
array $clearStoreCallbacks = []
|
||||
) {
|
||||
$options->assertRequiredOptions( self::$constructorOptions );
|
||||
|
||||
$this->options = $options;
|
||||
$this->store = $store;
|
||||
$this->logger = $logger;
|
||||
$this->clearStoreCallbacks = $clearStoreCallbacks;
|
||||
|
||||
// Keep this separate from $this->options so it can be mutable
|
||||
$this->manualRecache = $options->get( 'manualRecache' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -406,7 +427,7 @@ class LocalisationCache {
|
|||
* @return bool
|
||||
*/
|
||||
public function isExpired( $code ) {
|
||||
if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) {
|
||||
if ( $this->options->get( 'forceRecache' ) && !isset( $this->recachedLangs[$code] ) ) {
|
||||
$this->logger->debug( __METHOD__ . "($code): forced reload" );
|
||||
|
||||
return true;
|
||||
|
|
@ -796,14 +817,12 @@ class LocalisationCache {
|
|||
public function getMessagesDirs() {
|
||||
global $IP;
|
||||
|
||||
$config = MediaWikiServices::getInstance()->getMainConfig();
|
||||
$messagesDirs = $config->get( 'MessagesDirs' );
|
||||
return [
|
||||
'core' => "$IP/languages/i18n",
|
||||
'exif' => "$IP/languages/i18n/exif",
|
||||
'api' => "$IP/includes/api/i18n",
|
||||
'oojs-ui' => "$IP/resources/lib/ooui/i18n",
|
||||
] + $messagesDirs;
|
||||
] + $this->options->get( 'MessagesDirs' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -813,8 +832,6 @@ class LocalisationCache {
|
|||
* @throws MWException
|
||||
*/
|
||||
public function recache( $code ) {
|
||||
global $wgExtensionMessagesFiles;
|
||||
|
||||
if ( !$code ) {
|
||||
throw new MWException( "Invalid language code requested" );
|
||||
}
|
||||
|
|
@ -861,7 +878,7 @@ class LocalisationCache {
|
|||
|
||||
# Load non-JSON localisation data for extensions
|
||||
$extensionData = array_fill_keys( $codeSequence, $initialData );
|
||||
foreach ( $wgExtensionMessagesFiles as $extension => $fileName ) {
|
||||
foreach ( $this->options->get( 'ExtensionMessagesFiles' ) as $extension => $fileName ) {
|
||||
if ( isset( $messageDirs[$extension] ) ) {
|
||||
# This extension has JSON message data; skip the PHP shim
|
||||
continue;
|
||||
|
|
@ -1023,8 +1040,9 @@ class LocalisationCache {
|
|||
# HACK: If using a null (i.e. disabled) storage backend, we
|
||||
# can't write to the MessageBlobStore either
|
||||
if ( !$this->store instanceof LCStoreNull ) {
|
||||
$blobStore = MediaWikiServices::getInstance()->getResourceLoader()->getMessageBlobStore();
|
||||
$blobStore->clear();
|
||||
foreach ( $this->clearStoreCallbacks as $callback ) {
|
||||
$callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1085,5 +1103,4 @@ class LocalisationCache {
|
|||
$this->store = new LCStoreNull;
|
||||
$this->manualRecache = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -412,14 +412,17 @@ abstract class Installer {
|
|||
// This will be overridden in the web installer with the user-specified language
|
||||
RequestContext::getMain()->setLanguage( 'en' );
|
||||
|
||||
// Disable the i18n cache
|
||||
// TODO: manage LocalisationCache singleton in MediaWikiServices
|
||||
Language::getLocalisationCache()->disableBackend();
|
||||
|
||||
// Disable all global services, since we don't have any configuration yet!
|
||||
MediaWikiServices::disableStorageBackend();
|
||||
|
||||
$mwServices = MediaWikiServices::getInstance();
|
||||
|
||||
// Disable i18n cache
|
||||
$mwServices->getLocalisationCache()->disableBackend();
|
||||
|
||||
// Clear language cache so the old i18n cache doesn't sneak back in
|
||||
Language::clearCaches();
|
||||
|
||||
// Disable object cache (otherwise CACHE_ANYTHING will try CACHE_DB and
|
||||
// SqlBagOStuff will then throw since we just disabled wfGetDB)
|
||||
$wgObjectCaches = $mwServices->getMainConfig()->get( 'ObjectCaches' );
|
||||
|
|
|
|||
|
|
@ -77,10 +77,8 @@ class Language {
|
|||
*/
|
||||
public $transformData = [];
|
||||
|
||||
/**
|
||||
* @var LocalisationCache
|
||||
*/
|
||||
public static $dataCache;
|
||||
/** @var LocalisationCache */
|
||||
private $localisationCache;
|
||||
|
||||
public static $mLangObjCache = [];
|
||||
|
||||
|
|
@ -285,12 +283,12 @@ class Language {
|
|||
* @since 1.32
|
||||
*/
|
||||
public static function clearCaches() {
|
||||
if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
|
||||
throw new MWException( __METHOD__ . ' must not be used outside tests' );
|
||||
if ( !defined( 'MW_PHPUNIT_TEST' ) && !defined( 'MEDIAWIKI_INSTALL' ) ) {
|
||||
throw new MWException( __METHOD__ . ' must not be used outside tests/installer' );
|
||||
}
|
||||
if ( defined( 'MW_PHPUNIT_TEST' ) ) {
|
||||
MediaWikiServices::getInstance()->resetServiceForTesting( 'LocalisationCache' );
|
||||
}
|
||||
self::$dataCache = null;
|
||||
// Reinitialize $dataCache, since it's expected to always be available
|
||||
self::getLocalisationCache();
|
||||
self::$mLangObjCache = [];
|
||||
self::$fallbackLanguageCache = [];
|
||||
self::$grammarTransformations = null;
|
||||
|
|
@ -445,15 +443,11 @@ class Language {
|
|||
/**
|
||||
* Get the LocalisationCache instance
|
||||
*
|
||||
* @deprecated since 1.34, use MediaWikiServices
|
||||
* @return LocalisationCache
|
||||
*/
|
||||
public static function getLocalisationCache() {
|
||||
if ( is_null( self::$dataCache ) ) {
|
||||
global $wgLocalisationCacheConf;
|
||||
$class = $wgLocalisationCacheConf['class'];
|
||||
self::$dataCache = new $class( $wgLocalisationCacheConf );
|
||||
}
|
||||
return self::$dataCache;
|
||||
return MediaWikiServices::getInstance()->getLocalisationCache();
|
||||
}
|
||||
|
||||
function __construct() {
|
||||
|
|
@ -464,7 +458,7 @@ class Language {
|
|||
} else {
|
||||
$this->mCode = str_replace( '_', '-', strtolower( substr( static::class, 8 ) ) );
|
||||
}
|
||||
self::getLocalisationCache();
|
||||
$this->localisationCache = MediaWikiServices::getInstance()->getLocalisationCache();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -497,7 +491,7 @@ class Language {
|
|||
* @return array
|
||||
*/
|
||||
public function getBookstoreList() {
|
||||
return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
|
||||
return $this->localisationCache->getItem( $this->mCode, 'bookstoreList' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -514,7 +508,7 @@ class Language {
|
|||
getCanonicalNamespaces();
|
||||
|
||||
$this->namespaceNames = $wgExtraNamespaces +
|
||||
self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
|
||||
$this->localisationCache->getItem( $this->mCode, 'namespaceNames' );
|
||||
$this->namespaceNames += $validNamespaces;
|
||||
|
||||
$this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
|
||||
|
|
@ -621,7 +615,7 @@ class Language {
|
|||
global $wgExtraGenderNamespaces;
|
||||
|
||||
$ns = $wgExtraGenderNamespaces +
|
||||
(array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
|
||||
(array)$this->localisationCache->getItem( $this->mCode, 'namespaceGenderAliases' );
|
||||
|
||||
return $ns[$index][$gender] ?? $this->getNsText( $index );
|
||||
}
|
||||
|
|
@ -643,7 +637,7 @@ class Language {
|
|||
return false;
|
||||
} else {
|
||||
// Check what is in i18n files
|
||||
$aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
|
||||
$aliases = $this->localisationCache->getItem( $this->mCode, 'namespaceGenderAliases' );
|
||||
return count( $aliases ) > 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -667,7 +661,7 @@ class Language {
|
|||
*/
|
||||
public function getNamespaceAliases() {
|
||||
if ( is_null( $this->namespaceAliases ) ) {
|
||||
$aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
|
||||
$aliases = $this->localisationCache->getItem( $this->mCode, 'namespaceAliases' );
|
||||
if ( !$aliases ) {
|
||||
$aliases = [];
|
||||
} else {
|
||||
|
|
@ -681,8 +675,8 @@ class Language {
|
|||
}
|
||||
|
||||
global $wgExtraGenderNamespaces;
|
||||
$genders = $wgExtraGenderNamespaces +
|
||||
(array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
|
||||
$genders = $wgExtraGenderNamespaces + (array)$this->localisationCache
|
||||
->getItem( $this->mCode, 'namespaceGenderAliases' );
|
||||
foreach ( $genders as $index => $forms ) {
|
||||
foreach ( $forms as $alias ) {
|
||||
$aliases[$alias] = $index;
|
||||
|
|
@ -783,21 +777,21 @@ class Language {
|
|||
* @return string[]|bool List of date format preference keys, or false if disabled.
|
||||
*/
|
||||
public function getDatePreferences() {
|
||||
return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
|
||||
return $this->localisationCache->getItem( $this->mCode, 'datePreferences' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
function getDateFormats() {
|
||||
return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
|
||||
return $this->localisationCache->getItem( $this->mCode, 'dateFormats' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|string
|
||||
*/
|
||||
public function getDefaultDateFormat() {
|
||||
$df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
|
||||
$df = $this->localisationCache->getItem( $this->mCode, 'defaultDateFormat' );
|
||||
if ( $df === 'dmy or mdy' ) {
|
||||
global $wgAmericanDates;
|
||||
return $wgAmericanDates ? 'mdy' : 'dmy';
|
||||
|
|
@ -810,7 +804,7 @@ class Language {
|
|||
* @return array
|
||||
*/
|
||||
public function getDatePreferenceMigrationMap() {
|
||||
return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
|
||||
return $this->localisationCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2277,7 +2271,8 @@ class Language {
|
|||
}
|
||||
|
||||
if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
|
||||
$df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
|
||||
$df =
|
||||
$this->localisationCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
|
||||
|
||||
if ( $type === 'pretty' && $df === null ) {
|
||||
$df = $this->getDateFormatString( 'date', $pref );
|
||||
|
|
@ -2285,7 +2280,8 @@ class Language {
|
|||
|
||||
if ( !$wasDefault && $df === null ) {
|
||||
$pref = $this->getDefaultDateFormat();
|
||||
$df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
|
||||
$df = $this->getLocalisationCache()
|
||||
->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
|
||||
}
|
||||
|
||||
$this->dateFormatStrings[$type][$pref] = $df;
|
||||
|
|
@ -2649,14 +2645,14 @@ class Language {
|
|||
* @return string|null
|
||||
*/
|
||||
public function getMessage( $key ) {
|
||||
return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
|
||||
return $this->localisationCache->getSubitem( $this->mCode, 'messages', $key );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
function getAllMessages() {
|
||||
return self::$dataCache->getItem( $this->mCode, 'messages' );
|
||||
return $this->localisationCache->getItem( $this->mCode, 'messages' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2898,7 +2894,7 @@ class Language {
|
|||
* @return string
|
||||
*/
|
||||
function fallback8bitEncoding() {
|
||||
return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
|
||||
return $this->localisationCache->getItem( $this->mCode, 'fallback8bitEncoding' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -3088,7 +3084,7 @@ class Language {
|
|||
* @return bool
|
||||
*/
|
||||
function isRTL() {
|
||||
return self::$dataCache->getItem( $this->mCode, 'rtl' );
|
||||
return $this->localisationCache->getItem( $this->mCode, 'rtl' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -3164,7 +3160,7 @@ class Language {
|
|||
* @return array
|
||||
*/
|
||||
function capitalizeAllNouns() {
|
||||
return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
|
||||
return $this->localisationCache->getItem( $this->mCode, 'capitalizeAllNouns' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -3197,7 +3193,7 @@ class Language {
|
|||
* @return bool
|
||||
*/
|
||||
function linkPrefixExtension() {
|
||||
return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
|
||||
return $this->localisationCache->getItem( $this->mCode, 'linkPrefixExtension' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -3205,7 +3201,7 @@ class Language {
|
|||
* @return array
|
||||
*/
|
||||
function getMagicWords() {
|
||||
return self::$dataCache->getItem( $this->mCode, 'magicWords' );
|
||||
return $this->localisationCache->getItem( $this->mCode, 'magicWords' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -3215,7 +3211,7 @@ class Language {
|
|||
*/
|
||||
function getMagic( $mw ) {
|
||||
$rawEntry = $this->mMagicExtensions[$mw->mId] ??
|
||||
self::$dataCache->getSubitem( $this->mCode, 'magicWords', $mw->mId );
|
||||
$this->localisationCache->getSubitem( $this->mCode, 'magicWords', $mw->mId );
|
||||
|
||||
if ( !is_array( $rawEntry ) ) {
|
||||
wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
|
||||
|
|
@ -3250,7 +3246,7 @@ class Language {
|
|||
if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
|
||||
// Initialise array
|
||||
$this->mExtendedSpecialPageAliases =
|
||||
self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
|
||||
$this->localisationCache->getItem( $this->mCode, 'specialPageAliases' );
|
||||
}
|
||||
|
||||
return $this->mExtendedSpecialPageAliases;
|
||||
|
|
@ -3415,28 +3411,28 @@ class Language {
|
|||
* @return string
|
||||
*/
|
||||
function digitGroupingPattern() {
|
||||
return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
|
||||
return $this->localisationCache->getItem( $this->mCode, 'digitGroupingPattern' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
function digitTransformTable() {
|
||||
return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
|
||||
return $this->localisationCache->getItem( $this->mCode, 'digitTransformTable' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
function separatorTransformTable() {
|
||||
return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
|
||||
return $this->localisationCache->getItem( $this->mCode, 'separatorTransformTable' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
function minimumGroupingDigits() {
|
||||
return self::$dataCache->getItem( $this->mCode, 'minimumGroupingDigits' );
|
||||
return $this->localisationCache->getItem( $this->mCode, 'minimumGroupingDigits' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -4336,7 +4332,7 @@ class Language {
|
|||
* @return string
|
||||
*/
|
||||
public function linkTrail() {
|
||||
return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
|
||||
return $this->localisationCache->getItem( $this->mCode, 'linkTrail' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -4346,7 +4342,7 @@ class Language {
|
|||
* @return string
|
||||
*/
|
||||
public function linkPrefixCharset() {
|
||||
return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
|
||||
return $this->localisationCache->getItem( $this->mCode, 'linkPrefixCharset' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -4936,11 +4932,13 @@ class Language {
|
|||
* @return array Associative array with plural form, and plural rule as key-value pairs
|
||||
*/
|
||||
public function getCompiledPluralRules() {
|
||||
$pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
|
||||
$pluralRules =
|
||||
$this->localisationCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
|
||||
$fallbacks = self::getFallbacksFor( $this->mCode );
|
||||
if ( !$pluralRules ) {
|
||||
foreach ( $fallbacks as $fallbackCode ) {
|
||||
$pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
|
||||
$pluralRules = $this->localisationCache
|
||||
->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
|
||||
if ( $pluralRules ) {
|
||||
break;
|
||||
}
|
||||
|
|
@ -4955,11 +4953,13 @@ class Language {
|
|||
* @return array Associative array with plural form number and plural rule as key-value pairs
|
||||
*/
|
||||
public function getPluralRules() {
|
||||
$pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
|
||||
$pluralRules =
|
||||
$this->localisationCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
|
||||
$fallbacks = self::getFallbacksFor( $this->mCode );
|
||||
if ( !$pluralRules ) {
|
||||
foreach ( $fallbacks as $fallbackCode ) {
|
||||
$pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
|
||||
$pluralRules = $this->localisationCache
|
||||
->getItem( strtolower( $fallbackCode ), 'pluralRules' );
|
||||
if ( $pluralRules ) {
|
||||
break;
|
||||
}
|
||||
|
|
@ -4974,11 +4974,13 @@ class Language {
|
|||
* @return array Associative array with plural form number and plural rule type as key-value pairs
|
||||
*/
|
||||
public function getPluralRuleTypes() {
|
||||
$pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
|
||||
$pluralRuleTypes =
|
||||
$this->localisationCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
|
||||
$fallbacks = self::getFallbacksFor( $this->mCode );
|
||||
if ( !$pluralRuleTypes ) {
|
||||
foreach ( $fallbacks as $fallbackCode ) {
|
||||
$pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
|
||||
$pluralRuleTypes = $this->localisationCache
|
||||
->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
|
||||
if ( $pluralRuleTypes ) {
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@
|
|||
* @ingroup Maintenance
|
||||
*/
|
||||
|
||||
use MediaWiki\Config\ServiceOptions;
|
||||
use MediaWiki\Logger\LoggerFactory;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
|
||||
require_once __DIR__ . '/Maintenance.php';
|
||||
|
||||
/**
|
||||
|
|
@ -58,7 +62,7 @@ class RebuildLocalisationCache extends Maintenance {
|
|||
}
|
||||
|
||||
public function execute() {
|
||||
global $wgLocalisationCacheConf;
|
||||
global $wgLocalisationCacheConf, $wgCacheDirectory;
|
||||
|
||||
$force = $this->hasOption( 'force' );
|
||||
$threads = $this->getOption( 'threads', 1 );
|
||||
|
|
@ -77,13 +81,24 @@ class RebuildLocalisationCache extends Maintenance {
|
|||
|
||||
$conf = $wgLocalisationCacheConf;
|
||||
$conf['manualRecache'] = false; // Allow fallbacks to create CDB files
|
||||
if ( $force ) {
|
||||
$conf['forceRecache'] = true;
|
||||
}
|
||||
$conf['forceRecache'] = $force || !empty( $conf['forceRecache'] );
|
||||
if ( $this->hasOption( 'outdir' ) ) {
|
||||
$conf['storeDirectory'] = $this->getOption( 'outdir' );
|
||||
}
|
||||
$lc = new LocalisationCacheBulkLoad( $conf );
|
||||
// XXX Copy-pasted from ServiceWiring.php. Do we need a factory for this one caller?
|
||||
$lc = new LocalisationCacheBulkLoad(
|
||||
new ServiceOptions(
|
||||
LocalisationCache::$constructorOptions,
|
||||
$conf,
|
||||
MediaWikiServices::getInstance()->getMainConfig()
|
||||
),
|
||||
LocalisationCache::getStoreFromConf( $conf, $wgCacheDirectory ),
|
||||
LoggerFactory::getInstance( 'localisation' ),
|
||||
[ function () {
|
||||
MediaWikiServices::getInstance()->getResourceLoader()
|
||||
->getMessageBlobStore()->clear();
|
||||
} ]
|
||||
);
|
||||
|
||||
$allCodes = array_keys( Language::fetchLanguageNames( null, 'mwfile' ) );
|
||||
if ( $this->hasOption( 'lang' ) ) {
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ class TestSetup {
|
|||
// Assume UTC for testing purposes
|
||||
$wgLocaltimezone = 'UTC';
|
||||
|
||||
$wgLocalisationCacheConf['class'] = TestLocalisationCache::class;
|
||||
$wgLocalisationCacheConf['storeClass'] = LCStoreNull::class;
|
||||
|
||||
// Do not bother updating search tables
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ $wgAutoloadClasses += [
|
|||
'ResourceLoaderFileTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
|
||||
'ResourceLoaderTestCase' => "$testDir/phpunit/ResourceLoaderTestCase.php",
|
||||
'ResourceLoaderTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
|
||||
'TestLocalisationCache' => "$testDir/phpunit/includes/TestLocalisationCache.php",
|
||||
'TestUser' => "$testDir/phpunit/includes/TestUser.php",
|
||||
'TestUserRegistry' => "$testDir/phpunit/includes/TestUserRegistry.php",
|
||||
|
||||
|
|
|
|||
|
|
@ -408,6 +408,7 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
|
|||
|
||||
$wgRequest = new FauxRequest();
|
||||
MediaWiki\Session\SessionManager::resetCache();
|
||||
Language::clearCaches();
|
||||
}
|
||||
|
||||
public function run( TestResult $result = null ) {
|
||||
|
|
|
|||
82
tests/phpunit/includes/TestLocalisationCache.php
Normal file
82
tests/phpunit/includes/TestLocalisationCache.php
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
use Wikimedia\TestingAccessWrapper;
|
||||
|
||||
/**
|
||||
* A test-only LocalisationCache that caches all data in memory for test speed.
|
||||
*/
|
||||
class TestLocalisationCache extends LocalisationCache {
|
||||
|
||||
/**
|
||||
* A cache of the parsed data for tests. Services are reset between every test, which forces
|
||||
* localization to be recached between every test, which is unreasonably slow. As an
|
||||
* optimization, we cache our data in a static member for tests.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $testingCache = [];
|
||||
|
||||
private $selfAccess;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct( ...func_get_args() );
|
||||
$this->selfAccess = TestingAccessWrapper::newFromObject( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Recurse through the given array and replace every object by a scalar value that can be
|
||||
* serialized as JSON to use as a hash key.
|
||||
*
|
||||
* @param array $arr
|
||||
* @return array
|
||||
*/
|
||||
private static function hashiblifyArray( array $arr ) : array {
|
||||
foreach ( $arr as $key => $val ) {
|
||||
if ( is_array( $val ) ) {
|
||||
$arr[$key] = self::hashiblifyArray( $val );
|
||||
} elseif ( is_object( $val ) ) {
|
||||
// spl_object_hash() may return duplicate values if an object is destroyed and a new
|
||||
// one gets its hash and happens to be registered in the same hook in the same
|
||||
// location. This seems unlikely, but let's be safe and maintain a reference so it
|
||||
// can't happen. (In practice, there are probably no objects in the hooks at all.)
|
||||
static $objects = [];
|
||||
if ( !in_array( $val, $objects, true ) ) {
|
||||
$objects[] = $val;
|
||||
}
|
||||
$arr[$key] = spl_object_hash( $val );
|
||||
}
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
|
||||
public function recache( $code ) {
|
||||
// Test run performance is killed if we have to regenerate l10n for every test
|
||||
$cacheKey = sha1( json_encode( [
|
||||
$code,
|
||||
$this->selfAccess->options->get( 'ExtensionMessagesFiles' ),
|
||||
$this->selfAccess->options->get( 'MessagesDirs' ),
|
||||
// json_encode doesn't handle objects well
|
||||
self::hashiblifyArray( Hooks::getHandlers( 'LocalisationCacheRecacheFallback' ) ),
|
||||
self::hashiblifyArray( Hooks::getHandlers( 'LocalisationCacheRecache' ) ),
|
||||
] ) );
|
||||
if ( isset( self::$testingCache[$cacheKey] ) ) {
|
||||
$this->data[$code] = self::$testingCache[$cacheKey];
|
||||
foreach ( self::$testingCache[$cacheKey] as $key => $item ) {
|
||||
$loadedItems = $this->selfAccess->loadedItems;
|
||||
$loadedItems[$code][$key] = true;
|
||||
$this->selfAccess->loadedItems = $loadedItems;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
parent::recache( $code );
|
||||
|
||||
if ( count( self::$testingCache ) > 4 ) {
|
||||
// Don't store more than a few $data's, they can add up to a lot of memory if
|
||||
// they're kept around for the whole test duration
|
||||
array_pop( self::$testingCache );
|
||||
}
|
||||
// Put the new one in front
|
||||
self::$testingCache = array_merge( [ $cacheKey => $this->data[$code] ], self::$testingCache );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,8 @@
|
|||
<?php
|
||||
|
||||
use MediaWiki\Config\ServiceOptions;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* @group Database
|
||||
* @group Cache
|
||||
|
|
@ -19,8 +23,18 @@ class LocalisationCacheTest extends MediaWikiTestCase {
|
|||
*/
|
||||
protected function getMockLocalisationCache() {
|
||||
global $IP;
|
||||
$lc = $this->getMockBuilder( \LocalisationCache::class )
|
||||
->setConstructorArgs( [ [ 'store' => 'detect' ] ] )
|
||||
|
||||
$lc = $this->getMockBuilder( LocalisationCache::class )
|
||||
->setConstructorArgs( [
|
||||
new ServiceOptions( LocalisationCache::$constructorOptions, [
|
||||
'forceRecache' => false,
|
||||
'manualRecache' => false,
|
||||
'ExtensionMessagesFiles' => [],
|
||||
'MessagesDirs' => [],
|
||||
] ),
|
||||
new LCStoreDB( [] ),
|
||||
new NullLogger
|
||||
] )
|
||||
->setMethods( [ 'getMessagesDirs' ] )
|
||||
->getMock();
|
||||
$lc->expects( $this->any() )->method( 'getMessagesDirs' )
|
||||
|
|
@ -31,7 +45,7 @@ class LocalisationCacheTest extends MediaWikiTestCase {
|
|||
return $lc;
|
||||
}
|
||||
|
||||
public function testPuralRulesFallback() {
|
||||
public function testPluralRulesFallback() {
|
||||
$cache = $this->getMockLocalisationCache();
|
||||
|
||||
$this->assertEquals(
|
||||
|
|
|
|||
|
|
@ -39,13 +39,13 @@ class LogFormatterTest extends MediaWikiLangTestCase {
|
|||
global $wgExtensionMessagesFiles;
|
||||
self::$oldExtMsgFiles = $wgExtensionMessagesFiles;
|
||||
$wgExtensionMessagesFiles['LogTests'] = __DIR__ . '/LogTests.i18n.php';
|
||||
Language::getLocalisationCache()->recache( 'en' );
|
||||
Language::clearCaches();
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass() {
|
||||
global $wgExtensionMessagesFiles;
|
||||
$wgExtensionMessagesFiles = self::$oldExtMsgFiles;
|
||||
Language::getLocalisationCache()->recache( 'en' );
|
||||
Language::clearCaches();
|
||||
|
||||
parent::tearDownAfterClass();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1812,12 +1812,6 @@ class LanguageTest extends LanguageClassesTestCase {
|
|||
public function testClearCaches() {
|
||||
$languageClass = TestingAccessWrapper::newFromClass( Language::class );
|
||||
|
||||
// Populate $dataCache
|
||||
Language::getLocalisationCache()->getItem( 'zh', 'mainpage' );
|
||||
$oldCacheObj = Language::$dataCache;
|
||||
$this->assertNotCount( 0,
|
||||
TestingAccessWrapper::newFromObject( Language::$dataCache )->loadedItems );
|
||||
|
||||
// Populate $mLangObjCache
|
||||
$lang = Language::factory( 'en' );
|
||||
$this->assertNotCount( 0, Language::$mLangObjCache );
|
||||
|
|
@ -1836,9 +1830,6 @@ class LanguageTest extends LanguageClassesTestCase {
|
|||
|
||||
Language::clearCaches();
|
||||
|
||||
$this->assertNotSame( $oldCacheObj, Language::$dataCache );
|
||||
$this->assertCount( 0,
|
||||
TestingAccessWrapper::newFromObject( Language::$dataCache )->loadedItems );
|
||||
$this->assertCount( 0, Language::$mLangObjCache );
|
||||
$this->assertCount( 0, $languageClass->fallbackLanguageCache );
|
||||
$this->assertNull( $languageClass->grammarTransformations );
|
||||
|
|
|
|||
|
|
@ -13,10 +13,6 @@ class ResourceLoaderImageTest extends MediaWikiUnitTestCase {
|
|||
$this->imagesPath = __DIR__ . '/../../../data/resourceloader';
|
||||
}
|
||||
|
||||
protected function tearDown() {
|
||||
Language::$dataCache = null;
|
||||
}
|
||||
|
||||
protected function getTestImage( $name ) {
|
||||
$options = ResourceLoaderImageModuleTest::$commonImageData[$name];
|
||||
$fileDescriptor = is_string( $options ) ? $options : $options['file'];
|
||||
|
|
@ -57,6 +53,7 @@ class ResourceLoaderImageTest extends MediaWikiUnitTestCase {
|
|||
* @dataProvider provideGetPath
|
||||
*/
|
||||
public function testGetPath( $imageName, $languageCode, $path ) {
|
||||
$this->markTestSkipped( 'Depends on overriding LanguageFallback/LocalisationCache' );
|
||||
static $dirMap = [
|
||||
'en' => 'ltr',
|
||||
'en-gb' => 'ltr',
|
||||
|
|
|
|||
Loading…
Reference in a new issue