collation: Create CollationFactory service

Use ObjectFactory specs for collation classes
Avoid the language construction in the factory class,
make it a detail of the implementation of each class

Follow-Up of Ifc96f851e6091ce834dbaf0e91695c648a42169c

Bug: T286079
Change-Id: Ib581f64aec8619986fb8dd49ceee0524d59a1b84
This commit is contained in:
Umherirrender 2021-08-24 21:12:39 +02:00
parent 7c06047203
commit 5b3d3ef802
20 changed files with 289 additions and 108 deletions

View file

@ -715,6 +715,8 @@ because of Phabricator reports.
- loadRestrictions()
* User::isAllowUsertalk was hard deprecated. Use User::getBlock and
AbstractBlock::isUsertalkEditAllowed instead.
* Collation::singleton and ::factory were deprecated, obtain an CollationFactory
instance from MediaWikiServices instead.
* …
=== Other changes in 1.37 ===

View file

@ -852,6 +852,7 @@ $wgAutoloadLocalClasses = [
'MediaWikiSite' => __DIR__ . '/includes/site/MediaWikiSite.php',
'MediaWikiTitleCodec' => __DIR__ . '/includes/title/MediaWikiTitleCodec.php',
'MediaWikiVersionFetcher' => __DIR__ . '/includes/MediaWikiVersionFetcher.php',
'MediaWiki\\Collation\\CollationFactory' => __DIR__ . '/includes/collation/CollationFactory.php',
'MediaWiki\\DAO\\WikiAwareEntity' => __DIR__ . '/includes/dao/WikiAwareEntity.php',
'MediaWiki\\DAO\\WikiAwareEntityTrait' => __DIR__ . '/includes/dao/WikiAwareEntityTrait.php',
'MediaWiki\\Debug\\DeprecatablePropertyArray' => __DIR__ . '/includes/debug/DeprecatablePropertyArray.php',

View file

@ -105,7 +105,7 @@ class CategoryViewer extends ContextSource {
$this->limit = $context->getConfig()->get( 'CategoryPagingLimit' );
$this->cat = Category::newFromTitle( $title );
$this->query = $query;
$this->collation = Collation::singleton();
$this->collation = MediaWikiServices::getInstance()->getCollationFactory()->getCategoryCollation();
$this->languageConverter = MediaWikiServices::getInstance()
->getLanguageConverterFactory()->getLanguageConverter();
unset( $this->query['title'] );

View file

@ -39,6 +39,7 @@ use MediaWiki\Block\BlockUtils;
use MediaWiki\Block\DatabaseBlockStore;
use MediaWiki\Block\UnblockUserFactory;
use MediaWiki\Cache\LinkBatchFactory;
use MediaWiki\Collation\CollationFactory;
use MediaWiki\Config\ConfigRepository;
use MediaWiki\Content\IContentHandlerFactory;
use MediaWiki\Content\Transform\ContentTransformer;
@ -734,6 +735,14 @@ class MediaWikiServices extends ServiceContainer {
return $this->getService( 'ChangeTagDefStore' );
}
/**
* @since 1.37
* @return CollationFactory
*/
public function getCollationFactory(): CollationFactory {
return $this->getService( 'CollationFactory' );
}
/**
* @since 1.31
* @return CommentStore

View file

@ -19,6 +19,7 @@
* @file
*/
use MediaWiki\Collation\CollationFactory;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\Content\IContentHandlerFactory;
use MediaWiki\EditPage\SpamChecker;
@ -119,6 +120,9 @@ class MovePage {
/** @var MovePageFactory */
private $movePageFactory;
/** @var CollationFactory */
public $collationFactory;
/**
* @internal For use by PageCommandFactory
*/
@ -143,6 +147,7 @@ class MovePage {
* @param UserFactory|null $userFactory
* @param UserEditTracker|null $userEditTracker
* @param MovePageFactory|null $movePageFactory
* @param CollationFactory|null $collationFactory
* @deprecated since 1.34, hard deprecated since 1.37. Use MovePageFactory instead.
*/
public function __construct(
@ -160,7 +165,8 @@ class MovePage {
WikiPageFactory $wikiPageFactory = null,
UserFactory $userFactory = null,
UserEditTracker $userEditTracker = null,
MovePageFactory $movePageFactory = null
MovePageFactory $movePageFactory = null,
CollationFactory $collationFactory = null
) {
if ( !$options ) {
wfDeprecatedMsg(
@ -195,6 +201,7 @@ class MovePage {
$this->userFactory = $userFactory ?? $services()->getUserFactory();
$this->userEditTracker = $userEditTracker ?? $services()->getUserEditTracker();
$this->movePageFactory = $movePageFactory ?? $services()->getMovePageFactory();
$this->collationFactory = $collationFactory ?? $services()->getCollationFactory();
}
/**
@ -689,12 +696,13 @@ class MovePage {
__METHOD__
);
$type = $this->nsInfo->getCategoryLinkType( $this->newTitle->getNamespace() );
$collation = $this->collationFactory->getCategoryCollation();
foreach ( $prefixes as $prefixRow ) {
$prefix = $prefixRow->cl_sortkey_prefix;
$catTo = $prefixRow->cl_to;
$dbw->update( 'categorylinks',
[
'cl_sortkey' => Collation::singleton()->getSortKey(
'cl_sortkey' => $collation->getSortKey(
$this->newTitle->getCategorySortkey( $prefix ) ),
'cl_collation' => $this->options->get( 'CategoryCollation' ),
'cl_type' => $type,

View file

@ -59,6 +59,7 @@ use MediaWiki\Block\DatabaseBlockStore;
use MediaWiki\Block\UnblockUserFactory;
use MediaWiki\Block\UserBlockCommandFactory;
use MediaWiki\Cache\LinkBatchFactory;
use MediaWiki\Collation\CollationFactory;
use MediaWiki\Config\ConfigRepository;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\Content\ContentHandlerFactory;
@ -318,6 +319,15 @@ return [
return $services->getNameTableStoreFactory()->getChangeTagDef();
},
'CollationFactory' => static function ( MediaWikiServices $services ): CollationFactory {
return new CollationFactory(
new ServiceOptions(
CollationFactory::CONSTRUCTOR_OPTIONS, $services->getMainConfig() ),
$services->getObjectFactory(),
$services->getHookContainer()
);
},
'CommentStore' => static function ( MediaWikiServices $services ): CommentStore {
return new CommentStore(
$services->getContentLanguage(),
@ -1888,7 +1898,8 @@ return [
$services->getActorMigration(),
$services->getActorNormalization(),
$services->getTitleFactory(),
$services->getUserEditTracker()
$services->getUserEditTracker(),
$services->getCollationFactory()
);
},

View file

@ -266,6 +266,9 @@ class ApiQuery extends ApiBase {
],
'categorymembers' => [
'class' => ApiQueryCategoryMembers::class,
'services' => [
'CollationFactory',
]
],
'deletedrevs' => [
'class' => ApiQueryDeletedrevs::class,

View file

@ -20,6 +20,8 @@
* @file
*/
use MediaWiki\Collation\CollationFactory;
/**
* A query module to enumerate pages that belong to a category.
*
@ -27,12 +29,21 @@
*/
class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
/** @var Collation */
private $collation;
/**
* @param ApiQuery $query
* @param string $moduleName
* @param CollationFactory $collationFactory
*/
public function __construct( ApiQuery $query, $moduleName ) {
public function __construct(
ApiQuery $query,
$moduleName,
CollationFactory $collationFactory
) {
parent::__construct( $query, $moduleName, 'cm' );
$this->collation = $collationFactory->getCategoryCollation();
}
public function execute() {
@ -150,7 +161,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$this->addWhereRange( 'cl_from', $dir, null, null );
} else {
if ( $params['startsortkeyprefix'] !== null ) {
$startsortkey = Collation::singleton()->getSortKey( $params['startsortkeyprefix'] );
$startsortkey = $this->collation->getSortKey( $params['startsortkeyprefix'] );
} elseif ( $params['starthexsortkey'] !== null ) {
if ( !$this->validateHexSortkey( $params['starthexsortkey'] ) ) {
$encParamName = $this->encodeParamName( 'starthexsortkey' );
@ -161,7 +172,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$startsortkey = $params['startsortkey'];
}
if ( $params['endsortkeyprefix'] !== null ) {
$endsortkey = Collation::singleton()->getSortKey( $params['endsortkeyprefix'] );
$endsortkey = $this->collation->getSortKey( $params['endsortkeyprefix'] );
} elseif ( $params['endhexsortkey'] !== null ) {
if ( !$this->validateHexSortkey( $params['endhexsortkey'] ) ) {
$encParamName = $this->encodeParamName( 'endhexsortkey' );

View file

@ -20,17 +20,16 @@
* @file
*/
use MediaWiki\Languages\LanguageFactory;
class AbkhazUppercaseCollation extends CustomUppercaseCollation {
/**
* @param Language $abLanguage Language object for Abkhaz
* @param Language $enLanguage Language object for English
* @param LanguageFactory $languageFactory
*/
public function __construct(
Language $abLanguage,
Language $enLanguage
) {
public function __construct( LanguageFactory $languageFactory ) {
parent::__construct(
$languageFactory,
[
'А',
'Б',
@ -97,8 +96,7 @@ class AbkhazUppercaseCollation extends CustomUppercaseCollation {
'ь',
'ә',
],
$abLanguage,
$enLanguage
'ab'
);
}
}

View file

@ -20,17 +20,16 @@
* @file
*/
use MediaWiki\Languages\LanguageFactory;
class BashkirUppercaseCollation extends CustomUppercaseCollation {
/**
* @param Language $baLanguage Language object for Bashkir
* @param Language $enLanguage Language object for English
* @param LanguageFactory $languageFactory
*/
public function __construct(
Language $baLanguage,
Language $enLanguage
) {
public function __construct( LanguageFactory $languageFactory ) {
parent::__construct(
$languageFactory,
[
'А',
'Б',
@ -75,8 +74,7 @@ class BashkirUppercaseCollation extends CustomUppercaseCollation {
'Ю',
'Я',
],
$baLanguage,
$enLanguage
'ba'
);
}
}

View file

@ -20,7 +20,6 @@
* @file
*/
use MediaWiki\HookContainer\HookRunner;
use MediaWiki\MediaWikiServices;
/**
@ -32,6 +31,7 @@ abstract class Collation {
private static $instance;
/**
* @deprecated 1.37 Use MediaWikiServices::getCollationFactory()->getCategoryCollation()
* @since 1.16.3
* @return Collation
*/
@ -45,70 +45,13 @@ abstract class Collation {
/**
* @since 1.16.3
* @deprecated 1.37 Use MediaWikiServices::getCollationFactory()->makeCollation()
* @throws MWException
* @param string $collationName
* @return Collation
*/
public static function factory( $collationName ) {
$mwServices = MediaWikiServices::getInstance();
$languageFactory = $mwServices->getLanguageFactory();
switch ( $collationName ) {
case 'uppercase':
return new UppercaseCollation(
$languageFactory->getLanguage( 'en' )
);
case 'numeric':
return new NumericUppercaseCollation(
$mwServices->getContentLanguage(),
$languageFactory->getLanguage( 'en' )
);
case 'identity':
return new IdentityCollation(
$mwServices->getContentLanguage()
);
case 'uca-default':
return new IcuCollation(
$languageFactory,
'root'
);
case 'uca-default-u-kn':
return new IcuCollation(
$languageFactory,
'root-u-kn'
);
case 'xx-uca-ckb':
return new CollationCkb( $languageFactory );
case 'uppercase-ab':
return new AbkhazUppercaseCollation(
$languageFactory->getLanguage( 'ab' ),
$languageFactory->getLanguage( 'en' )
);
case 'uppercase-ba':
return new BashkirUppercaseCollation(
$languageFactory->getLanguage( 'ba' ),
$languageFactory->getLanguage( 'en' )
);
default:
$match = [];
if ( preg_match( '/^uca-([A-Za-z@=-]+)$/', $collationName, $match ) ) {
return new IcuCollation(
$languageFactory,
$match[1]
);
}
# Provide a mechanism for extensions to hook in.
$collationObject = null;
$hookRunner = new HookRunner( $mwServices->getHookContainer() );
$hookRunner->onCollation__factory( $collationName, $collationObject );
if ( $collationObject instanceof self ) {
return $collationObject;
}
// If all else fails...
throw new MWException( __METHOD__ . ": unknown collation type \"$collationName\"" );
}
return MediaWikiServices::getInstance()->getCollationFactory()->makeCollation( $collationName );
}
/**

View file

@ -0,0 +1,180 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\Collation;
use Collation;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\HookContainer\HookContainer;
use MediaWiki\HookContainer\HookRunner;
use MWException;
use Wikimedia\ObjectFactory;
/**
* Common factory to construct collation classes.
*
* @since 1.37
*/
class CollationFactory {
/**
* @internal For use by ServiceWiring
*/
public const CONSTRUCTOR_OPTIONS = [
'CategoryCollation',
];
private const CORE_COLLATIONS = [
'uppercase' => [
'class' => \UppercaseCollation::class,
'services' => [
'LanguageFactory',
]
],
'numeric' => [
'class' => \NumericUppercaseCollation::class,
'services' => [
'LanguageFactory',
'ContentLanguage',
]
],
'identity' => [
'class' => \IdentityCollation::class,
'services' => [
'ContentLanguage',
]
],
'uca-default' => [
'class' => \IcuCollation::class,
'services' => [
'LanguageFactory',
],
'args' => [
'root',
]
],
'uca-default-u-kn' => [
'class' => \IcuCollation::class,
'services' => [
'LanguageFactory',
],
'args' => [
'root-u-kn',
]
],
'xx-uca-ckb' => [
'class' => \CollationCkb::class,
'services' => [
'LanguageFactory',
]
],
'uppercase-ab' => [
'class' => \AbkhazUppercaseCollation::class,
'services' => [
'LanguageFactory',
]
],
'uppercase-ba' => [
'class' => \BashkirUppercaseCollation::class,
'services' => [
'LanguageFactory',
]
],
];
/** @var ServiceOptions */
private $options;
/** @var ObjectFactory */
private $objectFactory;
/** @var HookRunner */
private $hookRunner;
/**
* @param ServiceOptions $options
* @param ObjectFactory $objectFactory
* @param HookContainer $hookContainer
*/
public function __construct(
ServiceOptions $options,
ObjectFactory $objectFactory,
HookContainer $hookContainer
) {
$options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
$this->options = $options;
$this->objectFactory = $objectFactory;
$this->hookRunner = new HookRunner( $hookContainer );
}
/**
* @return Collation
*/
public function getCategoryCollation(): Collation {
return $this->makeCollation( $this->options->get( 'CategoryCollation' ) );
}
/**
* @throws MWException
* @param string $collationName
* @return Collation
*/
public function makeCollation( string $collationName ): Collation {
if ( isset( self::CORE_COLLATIONS[$collationName] ) ) {
return $this->instantiateCollation( self::CORE_COLLATIONS[$collationName] );
}
if ( preg_match( '/^uca-([A-Za-z@=-]+)$/', $collationName, $match ) ) {
return $this->instantiateCollation( [
'class' => \IcuCollation::class,
'services' => [
'LanguageFactory',
],
'args' => [
$match[1],
]
] );
}
// Provide a mechanism for extensions to hook in.
$collationObject = null;
$this->hookRunner->onCollation__factory( $collationName, $collationObject );
if ( !$collationObject instanceof Collation ) {
throw new MWException( __METHOD__ . ": unknown collation type \"$collationName\"" );
}
return $collationObject;
}
/**
* @param array $spec
* @return Collation
*/
private function instantiateCollation( $spec ): Collation {
return $this->objectFactory->createObject(
$spec,
[
'assertClass' => Collation::class
]
);
}
}

View file

@ -20,6 +20,8 @@
* @file
*/
use MediaWiki\Languages\LanguageFactory;
/**
* Resort normal UTF-8 order by putting a bunch of stuff in PUA
*
@ -51,19 +53,22 @@ class CustomUppercaseCollation extends NumericUppercaseCollation {
/**
* @note This assumes $alphabet does not contain U+F3000-U+F3FFF
*
* @param LanguageFactory $languageFactory
* @param array $alphabet Sorted array of uppercase characters.
* @param Language $digitTransformLang What language for number sorting.
* @param Language $enLanguage
* @param string|Language $digitTransformLang What language for number sorting.
*/
public function __construct(
LanguageFactory $languageFactory,
array $alphabet,
Language $digitTransformLang,
Language $enLanguage
$digitTransformLang
) {
if ( count( $alphabet ) < 1 || count( $alphabet ) >= 4096 ) {
throw new UnexpectedValueException( "Alphabet must be < 4096 items" );
}
$this->firstLetters = $alphabet;
$digitTransformLang = $digitTransformLang instanceof Language
? $digitTransformLang
: $languageFactory->getLanguage( $digitTransformLang );
// For digraphs, only the first letter is capitalized in input
$this->alphabet = array_map( [ $digitTransformLang, 'uc' ], $alphabet );
@ -78,7 +83,7 @@ class CustomUppercaseCollation extends NumericUppercaseCollation {
$lengths = array_map( 'mb_strlen', $this->alphabet );
array_multisort( $lengths, SORT_DESC, $this->firstLetters, $this->alphabet, $this->puaSubset );
parent::__construct( $digitTransformLang, $enLanguage );
parent::__construct( $languageFactory, $digitTransformLang );
}
private function convertToPua( $string ) {

View file

@ -18,6 +18,8 @@
* @file
*/
use MediaWiki\Languages\LanguageFactory;
/**
* Collation that orders text with numbers "naturally", so that 'Foo 1' < 'Foo 2' < 'Foo 12'.
*
@ -40,17 +42,19 @@ class NumericUppercaseCollation extends UppercaseCollation {
private $digitTransformLang;
/**
* @param Language $digitTransformLang How to convert digits.
* @param LanguageFactory $languageFactory
* @param string|Language $digitTransformLang How to convert digits.
* For example, if given language "my" than is treated like 7.
* It is expected that usually this is given the content language.
* @param Language $enLanguage
*/
public function __construct(
Language $digitTransformLang,
Language $enLanguage
LanguageFactory $languageFactory,
$digitTransformLang
) {
$this->digitTransformLang = $digitTransformLang;
parent::__construct( $enLanguage );
$this->digitTransformLang = $digitTransformLang instanceof Language
? $digitTransformLang
: $languageFactory->getLanguage( $digitTransformLang );
parent::__construct( $languageFactory );
}
public function getSortKey( $string ) {

View file

@ -20,17 +20,20 @@
* @file
*/
use MediaWiki\Languages\LanguageFactory;
class UppercaseCollation extends Collation {
/** @var Language */
/** @var Language Language object for English, so we can use the generic
* UTF-8 uppercase function there
*/
private $lang;
/**
* @param Language $enLanguage Language object for English, so we can use the generic
* UTF-8 uppercase function there
* @param LanguageFactory $languageFactory
*/
public function __construct( Language $enLanguage ) {
$this->lang = $enLanguage;
public function __construct( LanguageFactory $languageFactory ) {
$this->lang = $languageFactory->getLanguage( 'en' );
}
public function getSortKey( $string ) {

View file

@ -649,7 +649,7 @@ class LinksUpdate extends DataUpdate {
$languageConverter = MediaWikiServices::getInstance()->getLanguageConverterFactory()
->getLanguageConverter();
$collation = Collation::singleton();
$collation = MediaWikiServices::getInstance()->getCollationFactory()->getCategoryCollation();
foreach ( $diffs as $name => $prefix ) {
$nt = Title::makeTitleSafe( NS_CATEGORY, $name );
$languageConverter->findVariantLink( $name, $nt, true );

View file

@ -25,6 +25,7 @@ namespace MediaWiki\Page;
use ActorMigration;
use Config;
use ContentModelChange;
use MediaWiki\Collation\CollationFactory;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\Content\IContentHandlerFactory;
use MediaWiki\EditPage\SpamChecker;
@ -110,6 +111,9 @@ class PageCommandFactory implements
/** @var UserEditTracker */
private $userEditTracker;
/** @var CollationFactory */
private $collationFactory;
public function __construct(
Config $config,
ILoadBalancer $loadBalancer,
@ -127,7 +131,8 @@ class PageCommandFactory implements
ActorMigration $actorMigration,
ActorNormalization $actorNormalization,
TitleFactory $titleFactory,
UserEditTracker $userEditTracker
UserEditTracker $userEditTracker,
CollationFactory $collationFactory
) {
$this->config = $config;
$this->loadBalancer = $loadBalancer;
@ -146,6 +151,7 @@ class PageCommandFactory implements
$this->actorNormalization = $actorNormalization;
$this->titleFactory = $titleFactory;
$this->userEditTracker = $userEditTracker;
$this->collationFactory = $collationFactory;
}
/**
@ -218,7 +224,8 @@ class PageCommandFactory implements
$this->wikiPageFactory,
$this->userFactory,
$this->userEditTracker,
$this
$this,
$this->collationFactory
);
}

View file

@ -75,11 +75,10 @@ TEXT
$verboseStats = $this->getOption( 'verbose-stats' );
if ( $this->hasOption( 'target-collation' ) ) {
$collationName = $this->getOption( 'target-collation' );
$collation = Collation::factory( $collationName );
} else {
$collationName = $this->getConfig()->get( 'CategoryCollation' );
$collation = Collation::singleton();
}
$collation = MediaWikiServices::getInstance()->getCollationFactory()->makeCollation( $collationName );
// Collation sanity check: in some cases the constructor will work,
// but this will raise an exception, breaking all category pages

View file

@ -84,7 +84,7 @@ class CollationTest extends MediaWikiLangTestCase {
* @dataProvider firstLetterProvider
*/
public function testGetFirstLetter( $collation, $string, $firstLetter ) {
$col = Collation::factory( $collation );
$col = $this->getServiceContainer()->getCollationFactory()->makeCollation( $collation );
$this->assertEquals( $firstLetter, $col->getFirstLetter( $string ) );
}

View file

@ -11,16 +11,15 @@ class CustomUppercaseCollationTest extends MediaWikiIntegrationTestCase {
protected function setUp(): void {
parent::setUp();
$enLanguage = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'en' );
$this->collation = new CustomUppercaseCollation(
MediaWikiServices::getInstance()->getLanguageFactory(),
[
'D',
'C',
'Cs',
'B'
],
$enLanguage, // digital transformation language
$enLanguage // English, for generic uppercase
'en' // digital transformation language
);
}