wikimedia/mediawiki-extensions-Wikibase

View on GitHub
client/WikibaseClient.ServiceWiring.php

Summary

Maintainability
D
2 days
Test Coverage
<?php

declare( strict_types = 1 );

use DataValues\Deserializers\DataValueDeserializer;
use DataValues\Serializers\DataValueSerializer;
use DataValues\UnknownValue;
use MediaWiki\Language\Language;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use MediaWiki\Registration\ExtensionRegistry;
use MediaWiki\Site\MediaWikiSite;
use MediaWiki\Site\Site;
use MediaWiki\StubObject\StubObject;
use MediaWiki\User\ExternalUserNames;
use Psr\Log\LoggerInterface;
use Serializers\DispatchingSerializer;
use Serializers\Serializer;
use Wikibase\Client\CachingOtherProjectsSitesProvider;
use Wikibase\Client\Changes\AffectedPagesFinder;
use Wikibase\Client\Changes\ChangeHandler;
use Wikibase\Client\Changes\ChangeRunCoalescer;
use Wikibase\Client\Changes\WikiPageUpdater;
use Wikibase\Client\DataAccess\ClientSiteLinkTitleLookup;
use Wikibase\Client\DataAccess\DataAccessSnakFormatterFactory;
use Wikibase\Client\DataAccess\ParserFunctions\Runner;
use Wikibase\Client\DataAccess\ParserFunctions\StatementGroupRendererFactory;
use Wikibase\Client\DataAccess\ReferenceFormatterFactory;
use Wikibase\Client\DataAccess\SnaksFinder;
use Wikibase\Client\Hooks\LangLinkHandlerFactory;
use Wikibase\Client\Hooks\LanguageLinkBadgeDisplay;
use Wikibase\Client\Hooks\OtherProjectsSidebarGeneratorFactory;
use Wikibase\Client\Hooks\SidebarLinkBadgeDisplay;
use Wikibase\Client\Hooks\WikibaseClientHookRunner;
use Wikibase\Client\NamespaceChecker;
use Wikibase\Client\OtherProjectsSitesGenerator;
use Wikibase\Client\OtherProjectsSitesProvider;
use Wikibase\Client\ParserOutput\ClientParserOutputDataUpdater;
use Wikibase\Client\RecentChanges\RecentChangeFactory;
use Wikibase\Client\RecentChanges\SiteLinkCommentCreator;
use Wikibase\Client\RepoLinker;
use Wikibase\Client\Store\ClientStore;
use Wikibase\Client\Store\DescriptionLookup;
use Wikibase\Client\Store\Sql\DirectSqlStore;
use Wikibase\Client\Store\Sql\PagePropsEntityIdLookup;
use Wikibase\Client\Usage\EntityUsageFactory;
use Wikibase\Client\Usage\UsageAccumulatorFactory;
use Wikibase\Client\Usage\UsageDeduplicator;
use Wikibase\Client\WikibaseClient;
use Wikibase\DataAccess\AliasTermBuffer;
use Wikibase\DataAccess\ByTypeDispatchingEntityIdLookup;
use Wikibase\DataAccess\DataAccessSettings;
use Wikibase\DataAccess\DatabaseEntitySource;
use Wikibase\DataAccess\EntitySource;
use Wikibase\DataAccess\EntitySourceDefinitions;
use Wikibase\DataAccess\EntitySourceDefinitionsConfigParser;
use Wikibase\DataAccess\EntitySourceLookup;
use Wikibase\DataAccess\MultipleEntitySourceServices;
use Wikibase\DataAccess\PrefetchingTermLookup;
use Wikibase\DataAccess\Serializer\ForbiddenSerializer;
use Wikibase\DataAccess\SingleEntitySourceServicesFactory;
use Wikibase\DataAccess\SourceAndTypeDispatchingPrefetchingTermLookup;
use Wikibase\DataAccess\WikibaseServices;
use Wikibase\DataModel\Deserializers\DeserializerFactory;
use Wikibase\DataModel\Entity\DispatchingEntityIdParser;
use Wikibase\DataModel\Entity\EntityIdParser;
use Wikibase\DataModel\Entity\Item;
use Wikibase\DataModel\Entity\ItemIdParser;
use Wikibase\DataModel\Entity\Property;
use Wikibase\DataModel\Serializers\SerializerFactory;
use Wikibase\DataModel\Services\Diff\EntityDiffer;
use Wikibase\DataModel\Services\EntityId\EntityIdComposer;
use Wikibase\DataModel\Services\EntityId\SuffixEntityIdParser;
use Wikibase\DataModel\Services\Lookup\DisabledEntityTypesEntityLookup;
use Wikibase\DataModel\Services\Lookup\EntityLookup;
use Wikibase\DataModel\Services\Lookup\EntityRetrievingDataTypeLookup;
use Wikibase\DataModel\Services\Lookup\PropertyDataTypeLookup;
use Wikibase\DataModel\Services\Lookup\RestrictedEntityLookup;
use Wikibase\DataModel\Services\Lookup\TermLookup;
use Wikibase\DataModel\Services\Term\PropertyLabelResolver;
use Wikibase\DataModel\Services\Term\TermBuffer;
use Wikibase\Lib\Changes\EntityChange;
use Wikibase\Lib\Changes\EntityChangeFactory;
use Wikibase\Lib\Changes\ItemChange;
use Wikibase\Lib\ContentLanguages;
use Wikibase\Lib\DataTypeDefinitions;
use Wikibase\Lib\DataTypeFactory;
use Wikibase\Lib\EntitySourceAndTypeDefinitions;
use Wikibase\Lib\EntityTypeDefinitions;
use Wikibase\Lib\Formatters\CachingKartographerEmbeddingHandler;
use Wikibase\Lib\Formatters\FormatterLabelDescriptionLookupFactory;
use Wikibase\Lib\Formatters\OutputFormatSnakFormatterFactory;
use Wikibase\Lib\Formatters\OutputFormatValueFormatterFactory;
use Wikibase\Lib\Formatters\Reference\WellKnownReferenceProperties;
use Wikibase\Lib\Formatters\WikibaseSnakFormatterBuilders;
use Wikibase\Lib\Formatters\WikibaseValueFormatterBuilders;
use Wikibase\Lib\LanguageFallbackChainFactory;
use Wikibase\Lib\LanguageNameLookupFactory;
use Wikibase\Lib\MediaWikiMessageInLanguageProvider;
use Wikibase\Lib\MessageInLanguageProvider;
use Wikibase\Lib\PropertyInfoDataTypeLookup;
use Wikibase\Lib\Rdbms\ClientDomainDbFactory;
use Wikibase\Lib\Rdbms\DomainDb;
use Wikibase\Lib\Rdbms\RepoDomainDbFactory;
use Wikibase\Lib\ServiceBySourceAndTypeDispatcher;
use Wikibase\Lib\SettingsArray;
use Wikibase\Lib\Store\CachingPropertyInfoLookup;
use Wikibase\Lib\Store\CachingPropertyOrderProvider;
use Wikibase\Lib\Store\EntityIdLookup;
use Wikibase\Lib\Store\EntityNamespaceLookup;
use Wikibase\Lib\Store\EntityRevisionLookup;
use Wikibase\Lib\Store\FallbackLabelDescriptionLookupFactory;
use Wikibase\Lib\Store\FallbackPropertyOrderProvider;
use Wikibase\Lib\Store\HttpUrlPropertyOrderProvider;
use Wikibase\Lib\Store\PropertyInfoLookup;
use Wikibase\Lib\Store\RedirectResolvingLatestRevisionLookup;
use Wikibase\Lib\Store\RevisionBasedEntityRedirectTargetLookup;
use Wikibase\Lib\Store\Sql\EntityChangeLookup;
use Wikibase\Lib\Store\Sql\PropertyInfoTable;
use Wikibase\Lib\Store\Sql\Terms\CachedDatabasePropertyLabelResolver;
use Wikibase\Lib\Store\Sql\Terms\DatabaseTermInLangIdsResolver;
use Wikibase\Lib\Store\Sql\Terms\DatabaseTypeIdsStore;
use Wikibase\Lib\Store\Sql\Terms\TermInLangIdsResolverFactory;
use Wikibase\Lib\Store\TitleLookupBasedEntityExistenceChecker;
use Wikibase\Lib\Store\TitleLookupBasedEntityRedirectChecker;
use Wikibase\Lib\Store\TitleLookupBasedEntityTitleTextLookup;
use Wikibase\Lib\Store\TitleLookupBasedEntityUrlLookup;
use Wikibase\Lib\Store\WikiPagePropertyOrderProvider;
use Wikibase\Lib\StringNormalizer;
use Wikibase\Lib\SubEntityTypesMapper;
use Wikibase\Lib\TermFallbackCache\TermFallbackCacheFacade;
use Wikibase\Lib\TermFallbackCache\TermFallbackCacheServiceFactory;
use Wikibase\Lib\TermFallbackCacheFactory;
use Wikibase\Lib\WikibaseContentLanguages;
use Wikibase\Lib\WikibaseSettings;

/** @phpcs-require-sorted-array */
return [

    'WikibaseClient.AffectedPagesFinder' => function ( MediaWikiServices $services ): AffectedPagesFinder {
        return new AffectedPagesFinder(
            WikibaseClient::getStore( $services )->getUsageLookup(),
            $services->getTitleFactory(),
            $services->getPageStore(),
            $services->getLinkBatchFactory(),
            WikibaseClient::getSettings( $services )->getSetting( 'siteGlobalID' ),
            WikibaseClient::getLogger( $services )
        );
    },

    'WikibaseClient.AliasTermBuffer' => function ( MediaWikiServices $services ): AliasTermBuffer {
        return WikibaseClient::getPrefetchingTermLookup( $services );
    },

    'WikibaseClient.BaseDataModelDeserializerFactory' => function ( MediaWikiServices $services ): DeserializerFactory {
        $dataTypeDefs = WikibaseClient::getDataTypeDefinitions( $services );

        return new DeserializerFactory(
            WikibaseClient::getDataValueDeserializer( $services ),
            WikibaseClient::getEntityIdParser( $services ),
            WikibaseClient::getPropertyDataTypeLookup( $services ),
            $dataTypeDefs->getDeserializerBuilders( DataTypeDefinitions::PREFIXED_MODE ),
            $dataTypeDefs->getValueTypes()
        );
    },

    'WikibaseClient.ChangeHandler' => function ( MediaWikiServices $services ): ChangeHandler {
        $logger = WikibaseClient::getLogger( $services );

        $pageUpdater = new WikiPageUpdater(
            $services->getJobQueueGroup(),
            $logger,
            $services->getStatsFactory()
        );

        $settings = WikibaseClient::getSettings( $services );

        $pageUpdater->setPurgeCacheBatchSize( $settings->getSetting( 'purgeCacheBatchSize' ) );
        $pageUpdater->setRecentChangesBatchSize( $settings->getSetting( 'recentChangesBatchSize' ) );

        $changeListTransformer = new ChangeRunCoalescer(
            WikibaseClient::getEntityRevisionLookup( $services ),
            WikibaseClient::getEntityChangeFactory( $services ),
            $logger,
            $settings->getSetting( 'siteGlobalID' )
        );

        return new ChangeHandler(
            WikibaseClient::getAffectedPagesFinder( $services ),
            $services->getTitleFactory(),
            $services->getPageStore(),
            $pageUpdater,
            $changeListTransformer,
            $logger,
            WikibaseClient::getHookRunner( $services ),
            $settings->getSetting( 'injectRecentChanges' )
        );
    },

    'WikibaseClient.ClientDomainDbFactory' => function( MediaWikiServices $services ): ClientDomainDbFactory {
        $lbFactory = $services->getDBLoadBalancerFactory();

        return new ClientDomainDbFactory(
            $lbFactory,
            [ DomainDb::LOAD_GROUP_FROM_CLIENT ]
        );
    },

    'WikibaseClient.CompactBaseDataModelSerializerFactory' => function ( MediaWikiServices $services ): SerializerFactory {
        /* Note: Unlike in repo, we do not pass
         * SerializerFactory::OPTION_SERIALIZE_USE_OBJECTS_FOR_EMPTY_MAPS
         * because as of January 2024, Scribunto does not support stdClass objects as Lua arrays */
        return new SerializerFactory(
            new DataValueSerializer(),
            SerializerFactory::OPTION_SERIALIZE_MAIN_SNAKS_WITHOUT_HASH +
            SerializerFactory::OPTION_SERIALIZE_REFERENCE_SNAKS_WITHOUT_HASH
        );
    },

    'WikibaseClient.CompactEntitySerializer' => function ( MediaWikiServices $services ): Serializer {
        $serializerFactoryCallbacks = WikibaseClient::getEntityTypeDefinitions( $services )
            ->get( EntityTypeDefinitions::SERIALIZER_FACTORY_CALLBACK );
        $baseSerializerFactory = WikibaseClient::getCompactBaseDataModelSerializerFactory( $services );
        $serializers = [];

        foreach ( $serializerFactoryCallbacks as $callback ) {
            $serializers[] = $callback( $baseSerializerFactory );
        }

        return new DispatchingSerializer( $serializers );
    },

    'WikibaseClient.DataAccessSettings' => function ( MediaWikiServices $services ): DataAccessSettings {
        return new DataAccessSettings(
            WikibaseClient::getSettings( $services )->getSetting( 'maxSerializedEntitySize' )
        );
    },

    'WikibaseClient.DataAccessSnakFormatterFactory' => function ( MediaWikiServices $services ): DataAccessSnakFormatterFactory {
        return new DataAccessSnakFormatterFactory(
            WikibaseClient::getLanguageFallbackChainFactory( $services ),
            WikibaseClient::getSnakFormatterFactory( $services ),
            WikibaseClient::getPropertyDataTypeLookup( $services ),
            WikibaseClient::getRepoItemUriParser( $services ),
            WikibaseClient::getFallbackLabelDescriptionLookupFactory( $services ),
            Wikibaseclient::getSettings( $services )->getSetting( 'allowDataAccessInUserLanguage' )
        );
    },

    'WikibaseClient.DataTypeDefinitions' => function ( MediaWikiServices $services ): DataTypeDefinitions {
        $baseDataTypes = require __DIR__ . '/../lib/WikibaseLib.datatypes.php';
        $clientDataTypes = require __DIR__ . '/WikibaseClient.datatypes.php';

        $dataTypes = wfArrayPlus2d(
            $clientDataTypes,
            $baseDataTypes
        );

        $services->getHookContainer()->run( 'WikibaseClientDataTypes', [ &$dataTypes ] );

        $settings = WikibaseClient::getSettings( $services );

        return new DataTypeDefinitions(
            $dataTypes,
            $settings->getSetting( 'disabledDataTypes' )
        );
    },

    'WikibaseClient.DataTypeFactory' => function ( MediaWikiServices $services ): DataTypeFactory {
        return new DataTypeFactory(
            WikibaseClient::getDataTypeDefinitions( $services )->getValueTypes()
        );
    },

    'WikibaseClient.DataValueDeserializer' => function ( MediaWikiServices $services ): DataValueDeserializer {
        return new DataValueDeserializer( array_merge(
            WikibaseClient::getDataTypeDefinitions( $services )->getDataValueDeserializerBuilders(),
            [ 'unknown' => UnknownValue::class ]
        ) );
    },

    /**
     * Returns a low level factory object for creating formatters for well known data types.
     *
     * @warning This is for use with {@link WikibaseClient::getDefaultSnakFormatterBuilders()} during bootstrap only!
     * Program logic should use {@link WikibaseClient::getSnakFormatterFactory()} instead!
     */
    'WikibaseClient.DefaultSnakFormatterBuilders' => function ( MediaWikiServices $services ): WikibaseSnakFormatterBuilders {
        return new WikibaseSnakFormatterBuilders(
            WikibaseClient::getDefaultValueFormatterBuilders( $services ),
            WikibaseClient::getPropertyInfoLookup( $services ),
            WikibaseClient::getPropertyDataTypeLookup( $services ),
            WikibaseClient::getDataTypeFactory( $services )
        );
    },

    'WikibaseClient.DefaultValueFormatterBuilders' => function ( MediaWikiServices $services ): WikibaseValueFormatterBuilders {
        $clientStore = WikibaseClient::getStore( $services );
        $settings = WikibaseClient::getSettings( $services );
        $entityTitleLookup = new ClientSiteLinkTitleLookup(
            $clientStore->getSiteLinkLookup(),
            $settings->getSetting( 'siteGlobalID' )
        );
        $termFallbackCache = WikibaseClient::getTermFallbackCache( $services );
        $redirectResolvingLatestRevisionLookup = WikibaseClient::getRedirectResolvingLatestRevisionLookup( $services );
        $languageNameLookupFactory = new LanguageNameLookupFactory(
            $services->getLanguageNameUtils(),
            WikibaseClient::getMessageInLanguageProvider( $services )
        );

        return new WikibaseValueFormatterBuilders(
            new FormatterLabelDescriptionLookupFactory(
                WikibaseClient::getTermLookup( $services ),
                $termFallbackCache,
                $redirectResolvingLatestRevisionLookup
            ),
            $languageNameLookupFactory,
            WikibaseClient::getRepoItemUriParser( $services ),
            $settings->getSetting( 'geoShapeStorageBaseUrl' ),
            $settings->getSetting( 'tabularDataStorageBaseUrl' ),
            $termFallbackCache,
            WikibaseClient::getEntityLookup( $services ),
            $redirectResolvingLatestRevisionLookup,
            new TitleLookupBasedEntityExistenceChecker(
                $entityTitleLookup,
                $services->getLinkBatchFactory()
            ),
            new TitleLookupBasedEntityTitleTextLookup( $entityTitleLookup ),
            new TitleLookupBasedEntityUrlLookup( $entityTitleLookup ),
            new TitleLookupBasedEntityRedirectChecker( $entityTitleLookup ),
            $services->getLanguageFactory(),
            $entityTitleLookup,
            WikibaseClient::getKartographerEmbeddingHandler( $services ),
            $settings->getSetting( 'useKartographerMaplinkInWikitext' ),
            $services->getMainConfig()->get( 'ThumbLimits' )
        );
    },

    'WikibaseClient.DescriptionLookup' => function ( MediaWikiServices $services ): DescriptionLookup {
        return new DescriptionLookup(
            WikibaseClient::getEntityIdLookup( $services ),
            WikibaseClient::getTermBuffer( $services ),
            $services->getPageProps()
        );
    },

    'WikibaseClient.EntityChangeFactory' => function ( MediaWikiServices $services ): EntityChangeFactory {
        // TODO: take this from a setting or registry.
        $changeClasses = [
            Item::ENTITY_TYPE => ItemChange::class,
            // Other types of entities will use EntityChange
        ];

        return new EntityChangeFactory(
            WikibaseClient::getEntityDiffer( $services ),
            WikibaseClient::getEntityIdParser( $services ),
            $changeClasses,
            EntityChange::class,
            WikibaseClient::getLogger( $services )
        );
    },

    'WikibaseClient.EntityChangeLookup' => function ( MediaWikiServices $services ): EntityChangeLookup {
        return new EntityChangeLookup(
            WikibaseClient::getEntityChangeFactory( $services ),
            WikibaseClient::getEntityIdParser( $services ),
            WikibaseClient::getRepoDomainDbFactory( $services )
                ->newForEntitySource( WikibaseClient::getItemAndPropertySource( $services ) )
        );
    },

    'WikibaseClient.EntityDiffer' => function ( MediaWikiServices $services ): EntityDiffer {
        $entityDiffer = new EntityDiffer();
        $entityTypeDefinitions = WikibaseClient::getEntityTypeDefinitions( $services );
        $builders = $entityTypeDefinitions->get( EntityTypeDefinitions::ENTITY_DIFFER_STRATEGY_BUILDER );
        foreach ( $builders as $builder ) {
            $entityDiffer->registerEntityDifferStrategy( $builder() );
        }
        return $entityDiffer;
    },

    'WikibaseClient.EntityIdComposer' => function ( MediaWikiServices $services ): EntityIdComposer {
        return new EntityIdComposer(
            WikibaseClient::getEntityTypeDefinitions( $services )
                ->get( EntityTypeDefinitions::ENTITY_ID_COMPOSER_CALLBACK )
        );
    },

    'WikibaseClient.EntityIdLookup' => function ( MediaWikiServices $services ): EntityIdLookup {
        $entityTypeDefinitions = WikibaseClient::getEntityTypeDefinitions( $services );
        return new ByTypeDispatchingEntityIdLookup(
            $entityTypeDefinitions->get( EntityTypeDefinitions::CONTENT_MODEL_ID ),
            $entityTypeDefinitions->get( EntityTypeDefinitions::ENTITY_ID_LOOKUP_CALLBACK ),
            new PagePropsEntityIdLookup(
                $services->getPageProps(),
                WikibaseClient::getEntityIdParser( $services )
            ),
            $services->getHookContainer()
        );
    },

    'WikibaseClient.EntityIdParser' => function ( MediaWikiServices $services ): EntityIdParser {
        return new DispatchingEntityIdParser(
            WikibaseClient::getEntityTypeDefinitions( $services )->getEntityIdBuilders()
        );
    },

    'WikibaseClient.EntityLookup' => function ( MediaWikiServices $services ): EntityLookup {
        return WikibaseClient::getStore( $services )->getEntityLookup();
    },

    'WikibaseClient.EntityNamespaceLookup' => function ( MediaWikiServices $services ): EntityNamespaceLookup {
        return array_reduce(
            WikibaseClient::getEntitySourceDefinitions( $services )->getSources(),
            function ( EntityNamespaceLookup $nsLookup, DatabaseEntitySource $source ): EntityNamespaceLookup {
                return $nsLookup->merge( new EntityNamespaceLookup(
                    $source->getEntityNamespaceIds(),
                    $source->getEntitySlotNames()
                ) );
            },
            new EntityNamespaceLookup( [], [] )
        );
    },

    'WikibaseClient.EntityRevisionLookup' => function ( MediaWikiServices $services ): EntityRevisionLookup {
        return WikibaseClient::getStore( $services )->getEntityRevisionLookup();
    },

    'WikibaseClient.EntitySourceAndTypeDefinitions' => function ( MediaWikiServices $services ): EntitySourceAndTypeDefinitions {
        // note: when adding support for further entity source types here,
        // also adjust the default 'entitySources' setting to copy sources of those types from the repo
        return new EntitySourceAndTypeDefinitions(
            [ DatabaseEntitySource::TYPE => WikibaseClient::getEntityTypeDefinitions( $services ) ],
            WikibaseClient::getEntitySourceDefinitions( $services )->getSources()
        );
    },

    'WikibaseClient.EntitySourceDefinitions' => function ( MediaWikiServices $services ): EntitySourceDefinitions {
        $settings = WikibaseClient::getSettings( $services );
        $subEntityTypesMapper = new SubEntityTypesMapper( WikibaseClient::getEntityTypeDefinitions( $services )
            ->get( EntityTypeDefinitions::SUB_ENTITY_TYPES ) );

        $configParser = new EntitySourceDefinitionsConfigParser();

        return $configParser->newDefinitionsFromConfigArray( $settings->getSetting( 'entitySources' ), $subEntityTypesMapper );
    },

    'WikibaseClient.EntityTypeDefinitions' => function ( MediaWikiServices $services ): EntityTypeDefinitions {
        $baseEntityTypes = require __DIR__ . '/../lib/WikibaseLib.entitytypes.php';
        $clientEntityTypes = require __DIR__ . '/WikibaseClient.entitytypes.php';

        $entityTypes = wfArrayPlus2d(
            $clientEntityTypes,
            $baseEntityTypes
        );

        $services->getHookContainer()->run( 'WikibaseClientEntityTypes', [ &$entityTypes ] );

        return new EntityTypeDefinitions( $entityTypes );
    },

    'WikibaseClient.ExternalUserNames' => function ( MediaWikiServices $services ): ?ExternalUserNames {
        $databaseName = WikibaseClient::getItemAndPropertySource( $services )->getDatabaseName();
        if ( $databaseName !== false ) {
            $siteLookup = $services->getSiteLookup();
            $repoSite = $siteLookup->getSite( $databaseName );
            if ( $repoSite === null ) {
                WikibaseClient::getLogger( $services )
                    ->warning(
                        'WikibaseClient.ExternalUserNames service wiring: ' .
                        'itemAndPropertySource databaseName {databaseName} is not known as a global site ID',
                        [ 'databaseName' => $databaseName ]
                    );
                return null;
            }
        } else {
            $repoSite = WikibaseClient::getSite( $services );
        }
        $interwikiPrefixes = $repoSite->getInterwikiIds();
        if ( $interwikiPrefixes === [] ) {
            WikibaseClient::getLogger( $services )
                ->warning(
                    'WikibaseClient.ExternalUserNames service wiring: ' .
                    'repo site {siteInternalId}/{siteGlobalId} has no interwiki IDs/prefixes',
                    [
                        'siteInternalId' => $repoSite->getInternalId(),
                        'siteGlobalId' => $repoSite->getGlobalId(),
                    ]
                );
            return null;
        }
        $interwikiPrefix = $interwikiPrefixes[0];
        return new ExternalUserNames( $interwikiPrefix, false );
    },

    'WikibaseClient.FallbackLabelDescriptionLookupFactory' => function (
        MediaWikiServices $services
    ): FallbackLabelDescriptionLookupFactory {
        return new FallbackLabelDescriptionLookupFactory(
            WikibaseClient::getLanguageFallbackChainFactory( $services ),
            WikibaseClient::getRedirectResolvingLatestRevisionLookup( $services ),
            WikibaseClient::getTermFallbackCache( $services ),
            WikibaseClient::getTermLookup( $services ),
            WikibaseClient::getTermBuffer( $services )
        );
    },

    'WikibaseClient.HookRunner' => function ( MediaWikiServices $services ): WikibaseClientHookRunner {
        return new WikibaseClientHookRunner(
            $services->getHookContainer()
        );
    },

    'WikibaseClient.ItemAndPropertySource' => function ( MediaWikiServices $services ): EntitySource {
        $itemAndPropertySourceName = WikibaseClient::getSettings( $services )->getSetting( 'itemAndPropertySourceName' );
        $sources = WikibaseClient::getEntitySourceDefinitions( $services )->getSources();
        foreach ( $sources as $source ) {
            if ( $source->getSourceName() === $itemAndPropertySourceName ) {
                return $source;
            }
        }

        throw new LogicException( 'No source configured: ' . $itemAndPropertySourceName );
    },

    'WikibaseClient.ItemSource' => function ( MediaWikiServices $services ): EntitySource {
        $itemSource = WikibaseClient::getEntitySourceDefinitions( $services )
            ->getDatabaseSourceForEntityType( Item::ENTITY_TYPE );

        if ( $itemSource === null ) {
            throw new LogicException( 'No source providing Items configured!' );
        }

        return $itemSource;
    },

    'WikibaseClient.KartographerEmbeddingHandler' => function (
        MediaWikiServices $services
    ): ?CachingKartographerEmbeddingHandler {
        $settings = WikibaseClient::getSettings( $services );

        if (
            $settings->getSetting( 'useKartographerGlobeCoordinateFormatter' ) &&
            ExtensionRegistry::getInstance()->isLoaded( 'Kartographer' ) // TODO T257586
        ) {
            return new CachingKartographerEmbeddingHandler(
                $services->getParserFactory()->create()
            );
        } else {
            return null;
        }
    },

    'WikibaseClient.LangLinkHandlerFactory' => function ( MediaWikiServices $services ): LangLinkHandlerFactory {
        return new LangLinkHandlerFactory(
            WikibaseClient::getLanguageLinkBadgeDisplay( $services ),
            WikibaseClient::getNamespaceChecker( $services ),
            WikibaseClient::getStore( $services )->getSiteLinkLookup(),
            WikibaseClient::getEntityLookup( $services ),
            $services->getSiteLookup(),
            $services->getHookContainer(),
            WikibaseClient::getLogger( $services ),
            WikibaseClient::getSettings( $services )->getSetting( 'siteGlobalID' ),
            WikibaseClient::getLangLinkSiteGroups( $services )
        );
    },

    'WikibaseClient.LangLinkSiteGroup' => function ( MediaWikiServices $services ): string {
        $group = WikibaseClient::getSettings( $services )
            ->getSetting( 'languageLinkSiteGroup' );

        if ( $group === null ) {
            $group = WikibaseClient::getSiteGroup( $services );
        }

        return $group;
    },

    'WikibaseClient.LangLinkSiteGroups' => function ( MediaWikiServices $services ): array {
        $groups = WikibaseClient::getSettings( $services )->getSetting( 'languageLinkAllowedSiteGroups' );
        if ( $groups === null ) {
            $groups = [ WikibaseClient::getLangLinkSiteGroup( $services ) ];
        }

        return $groups;
    },

    'WikibaseClient.LanguageFallbackChainFactory' => function ( MediaWikiServices $services ): LanguageFallbackChainFactory {
        return new LanguageFallbackChainFactory(
            WikibaseClient::getTermsLanguages( $services ),
            $services->getLanguageFactory(),
            $services->getLanguageConverterFactory(),
            $services->getLanguageFallback()
        );
    },

    'WikibaseClient.LanguageLinkBadgeDisplay' => function ( MediaWikiServices $services ): LanguageLinkBadgeDisplay {
        return new LanguageLinkBadgeDisplay(
            WikibaseClient::getSidebarLinkBadgeDisplay( $services )
        );
    },

    'WikibaseClient.Logger' => function ( MediaWikiServices $services ): LoggerInterface {
        return LoggerFactory::getInstance( 'Wikibase' );
    },

    'WikibaseClient.MessageInLanguageProvider' => function ( MediaWikiServices $services ): MessageInLanguageProvider {
        return new MediaWikiMessageInLanguageProvider();
    },

    'WikibaseClient.MobileSite' => function ( MediaWikiServices $services ): bool {
        if ( $services->has( 'MobileFrontend.Context' ) ) {
            $mobileContext = $services->get( 'MobileFrontend.Context' );
            return $mobileContext->shouldDisplayMobileView();
        }
        return false;
    },

    'WikibaseClient.NamespaceChecker' => function ( MediaWikiServices $services ): NamespaceChecker {
        $settings = WikibaseClient::getSettings( $services );
        return new NamespaceChecker(
            $settings->getSetting( 'excludeNamespaces' ),
            $settings->getSetting( 'namespaces' ),
            $services->getNamespaceInfo()
        );
    },

    'WikibaseClient.OtherProjectsSidebarGeneratorFactory' => function (
        MediaWikiServices $services
    ): OtherProjectsSidebarGeneratorFactory {
        return new OtherProjectsSidebarGeneratorFactory(
            WikibaseClient::getSettings( $services ),
            WikibaseClient::getStore( $services )->getSiteLinkLookup(),
            $services->getSiteLookup(),
            WikibaseClient::getEntityLookup( $services ),
            WikibaseClient::getSidebarLinkBadgeDisplay( $services ),
            $services->getHookContainer(),
            WikibaseClient::getLogger( $services )
        );
    },

    'WikibaseClient.OtherProjectsSitesProvider' => function ( MediaWikiServices $services ): OtherProjectsSitesProvider {
        $settings = WikibaseClient::getSettings( $services );

        return new CachingOtherProjectsSitesProvider(
            new OtherProjectsSitesGenerator(
                $services->getSiteLookup(),
                $settings->getSetting( 'siteGlobalID' ),
                $settings->getSetting( 'specialSiteLinkGroups' )
            ),
            // TODO: Make configurable? Should be similar, maybe identical to sharedCacheType and
            // sharedCacheDuration, but can not reuse these because this here is not shared.
            $services->getObjectCacheFactory()->getLocalClusterInstance(),
            60 * 60
        );
    },

    'WikibaseClient.ParserOutputDataUpdater' => function ( MediaWikiServices $services ): ClientParserOutputDataUpdater {
        $settings = WikibaseClient::getSettings( $services );

        return new ClientParserOutputDataUpdater(
            WikibaseClient::getOtherProjectsSidebarGeneratorFactory( $services ),
            WikibaseClient::getStore( $services )->getSiteLinkLookup(),
            WikibaseClient::getEntityLookup( $services ),
            WikibaseClient::getUsageAccumulatorFactory( $services ),
            $settings->getSetting( 'siteGlobalID' ),
            WikibaseClient::getLogger( $services )
        );
    },

    'WikibaseClient.PrefetchingTermLookup' => function ( MediaWikiServices $services ): PrefetchingTermLookup {
        return new SourceAndTypeDispatchingPrefetchingTermLookup(
            new ServiceBySourceAndTypeDispatcher(
                PrefetchingTermLookup::class,
                WikibaseClient::getEntitySourceAndTypeDefinitions( $services )
                    ->getServiceBySourceAndType( EntityTypeDefinitions::PREFETCHING_TERM_LOOKUP_CALLBACK )
            ),
            new EntitySourceLookup(
                WikibaseClient::getEntitySourceDefinitions( $services ),
                new SubEntityTypesMapper( WikibaseClient::getEntityTypeDefinitions( $services )
                ->get( EntityTypeDefinitions::SUB_ENTITY_TYPES ) )
            )
        );
    },

    'WikibaseClient.PropertyDataTypeLookup' => function ( MediaWikiServices $services ): PropertyDataTypeLookup {
        $infoLookup = WikibaseClient::getPropertyInfoLookup( $services );
        return new PropertyInfoDataTypeLookup(
            $infoLookup,
            WikibaseClient::getLogger( $services ),
            fn () => new EntityRetrievingDataTypeLookup( WikibaseClient::getEntityLookup( $services ) )
        );
    },

    'WikibaseClient.PropertyInfoLookup' => function ( MediaWikiServices $services ): PropertyInfoLookup {
        $settings = WikibaseClient::getSettings( $services );
        $cacheKeyGroup = $settings->getSetting( 'sharedCacheKeyGroup' );
        $cacheDuration = $settings->getSetting( 'sharedCacheDuration' );

        $wanCachedPropertyInfoLookup = new CachingPropertyInfoLookup(
            new PropertyInfoTable(
                WikibaseClient::getEntityIdComposer( $services ),
                WikibaseClient::getRepoDomainDbFactory( $services )->newRepoDb(),
                false
            ),
            $services->getMainWANObjectCache(),
            $cacheKeyGroup,
            $cacheDuration
        );

        return new CachingPropertyInfoLookup(
            $wanCachedPropertyInfoLookup,
            $services->getLocalServerObjectCache(),
            $cacheKeyGroup,
            $cacheDuration
        );
    },

    'WikibaseClient.PropertyLabelResolver' => function ( MediaWikiServices $services ): PropertyLabelResolver {
        // Required services
        $languageCode = $services->getContentLanguage()->getCode();

        $settings = WikibaseClient::getSettings( $services );
        $cacheKeyPrefix = $settings->getSetting( 'sharedCacheKeyPrefix' );
        $cacheType = $settings->getSetting( 'sharedCacheType' );
        $cacheDuration = $settings->getSetting( 'sharedCacheDuration' );

        // Cache key needs to be language specific
        $cacheKey = $cacheKeyPrefix . ':TermPropertyLabelResolver' . '/' . $languageCode;

        $repoDb = WikibaseClient::getRepoDomainDbFactory( $services )
            ->newForEntitySource( WikibaseClient::getPropertySource( $services ) );
        $wanObjectCache = $services->getMainWANObjectCache();

        $typeIdsStore = new DatabaseTypeIdsStore(
            $repoDb,
            $wanObjectCache
        );

        $databaseTermIdsResolver = new DatabaseTermInLangIdsResolver(
            $typeIdsStore,
            $typeIdsStore,
            $repoDb
        );

        return new CachedDatabasePropertyLabelResolver(
            $languageCode,
            $databaseTermIdsResolver,
            $services->getObjectCacheFactory()->getInstance( $cacheType ),
            $cacheDuration,
            $cacheKey
        );
    },

    'WikibaseClient.PropertyOrderProvider' => function ( MediaWikiServices $services ): CachingPropertyOrderProvider {
        $title = $services->getTitleFactory()->newFromTextThrow( 'MediaWiki:Wikibase-SortedProperties' );
        $innerProvider = new WikiPagePropertyOrderProvider( $services->getWikiPageFactory(), $title );

        $url = WikibaseClient::getSettings( $services )->getSetting( 'propertyOrderUrl' );

        if ( $url !== null ) {
            $innerProvider = new FallbackPropertyOrderProvider(
                $innerProvider,
                new HttpUrlPropertyOrderProvider(
                    $url,
                    $services->getHttpRequestFactory(),
                    WikibaseClient::getLogger( $services )
                )
            );
        }

        return new CachingPropertyOrderProvider(
            $innerProvider,
            $services->getObjectCacheFactory()->getLocalClusterInstance()
        );
    },

    'WikibaseClient.PropertyParserFunctionRunner' => function ( MediaWikiServices $services ): Runner {
        $settings = WikibaseClient::getSettings( $services );
        return new Runner(
            WikibaseClient::getStatementGroupRendererFactory( $services ),
            WikibaseClient::getStore( $services )->getSiteLinkLookup(),
            WikibaseClient::getEntityIdParser( $services ),
            WikibaseClient::getRestrictedEntityLookup( $services ),
            $settings->getSetting( 'siteGlobalID' ),
            $settings->getSetting( 'allowArbitraryDataAccess' )
        );
    },

    'WikibaseClient.PropertySource' => function ( MediaWikiServices $services ): EntitySource {
        $propertySource = WikibaseClient::getEntitySourceDefinitions( $services )
            ->getDatabaseSourceForEntityType( Property::ENTITY_TYPE );

        if ( $propertySource === null ) {
            throw new LogicException( 'No source providing Properties configured!' );
        }

        return $propertySource;
    },

    'WikibaseClient.RecentChangeFactory' => function ( MediaWikiServices $services ): RecentChangeFactory {
        $contentLanguage = $services->getContentLanguage();
        $siteLookup = $services->getSiteLookup();

        return new RecentChangeFactory(
            $contentLanguage,
            new SiteLinkCommentCreator(
                $contentLanguage,
                $siteLookup,
                WikibaseClient::getSettings( $services )->getSetting( 'siteGlobalID' )
            ),
            WikibaseClient::getEntitySourceDefinitions( $services ),
            WikibaseClient::getClientDomainDbFactory( $services )->newLocalDb(),
            $services->getCentralIdLookupFactory()->getNonLocalLookup(),
            WikibaseClient::getExternalUserNames( $services )
        );
    },

    'WikibaseClient.RedirectResolvingLatestRevisionLookup' => function (
        MediaWikiServices $services
    ): RedirectResolvingLatestRevisionLookup {
        return new RedirectResolvingLatestRevisionLookup(
            WikibaseClient::getEntityRevisionLookup( $services )
        );
    },

    'WikibaseClient.ReferenceFormatterFactory' => function ( MediaWikiServices $services ): ReferenceFormatterFactory {
        $logger = WikibaseClient::getLogger( $services );
        return new ReferenceFormatterFactory(
            WikibaseClient::getDataAccessSnakFormatterFactory( $services ),
            WellKnownReferenceProperties::newFromArray(
                WikibaseClient::getSettings( $services )
                    ->getSetting( 'wellKnownReferencePropertyIds' ),
                $logger
            ),
            $logger
        );
    },

    'WikibaseClient.RepoDomainDbFactory' => function ( MediaWikiServices $services ): RepoDomainDbFactory {
        $lbFactory = $services->getDBLoadBalancerFactory();

        return new RepoDomainDbFactory(
            $lbFactory,
            WikibaseClient::getItemAndPropertySource( $services )->getDatabaseName() ?: $lbFactory->getLocalDomainID(),
            [ DomainDb::LOAD_GROUP_FROM_CLIENT ]
        );
    },

    'WikibaseClient.RepoItemUriParser' => function ( MediaWikiServices $services ): EntityIdParser {
        $itemConceptUriBase = WikibaseClient::getItemSource( $services )
            ->getConceptBaseUri();

        return new SuffixEntityIdParser(
            $itemConceptUriBase,
            new ItemIdParser()
        );
    },

    'WikibaseClient.RepoLinker' => function ( MediaWikiServices $services ): RepoLinker {
        $settings = WikibaseClient::getSettings( $services );

        return new RepoLinker(
            WikibaseClient::getEntitySourceDefinitions( $services ),
            $settings->getSetting( 'repoUrl' ),
            $settings->getSetting( 'repoArticlePath' ),
            $settings->getSetting( 'repoScriptPath' )
        );
    },

    'WikibaseClient.RestrictedEntityLookup' => function ( MediaWikiServices $services ): RestrictedEntityLookup {
        $settings = WikibaseClient::getSettings( $services );
        $disabledEntityTypesEntityLookup = new DisabledEntityTypesEntityLookup(
            WikibaseClient::getEntityLookup( $services ),
            $settings->getSetting( 'disabledAccessEntityTypes' )
        );
        return new RestrictedEntityLookup(
            $disabledEntityTypesEntityLookup,
            $settings->getSetting( 'entityAccessLimit' )
        );
    },

    'WikibaseClient.Settings' => function ( MediaWikiServices $services ): SettingsArray {
        return WikibaseSettings::getClientSettings();
    },

    'WikibaseClient.SidebarLinkBadgeDisplay' => function ( MediaWikiServices $services ): SidebarLinkBadgeDisplay {
        $badgeClassNames = WikibaseClient::getSettings( $services )->getSetting( 'badgeClassNames' );
        $labelDescriptionLookupFactory = WikibaseClient::getFallbackLabelDescriptionLookupFactory( $services );
        $lang = WikibaseClient::getUserLanguage( $services );

        return new SidebarLinkBadgeDisplay(
            $labelDescriptionLookupFactory->newLabelDescriptionLookup( $lang ),
            is_array( $badgeClassNames ) ? $badgeClassNames : [],
            $lang
        );
    },

    'WikibaseClient.Site' => function ( MediaWikiServices $services ): Site {
        $settings = WikibaseClient::getSettings( $services );
        $globalId = $settings->getSetting( 'siteGlobalID' );
        $localId = $settings->getSetting( 'siteLocalID' );

        $site = $services->getSiteLookup()->getSite( $globalId );

        $logger = WikibaseClient::getLogger( $services );

        if ( !$site ) {
            $logger->debug(
                'WikibaseClient.ServiceWiring.php::WikibaseClient.Site: ' .
                'Unable to resolve site ID {globalId}!',
                [ 'globalId' => $globalId ]
            );

            $site = new MediaWikiSite();
            $site->setGlobalId( $globalId );
            $site->addLocalId( Site::ID_INTERWIKI, $localId );
            $site->addLocalId( Site::ID_EQUIVALENT, $localId );
        }

        if ( !in_array( $localId, array_merge( ...array_values( $site->getLocalIds() ) ) ) ) {
            $logger->debug(
                'WikibaseClient.ServiceWiring.php::WikibaseClient.Site: ' .
                'The configured local id {localId} does not match any local IDs of site {globalId}: {localIds}',
                [
                    'localId' => $localId,
                    'globalId' => $globalId,
                    'localIds' => json_encode( $site->getLocalIds() ),
                ]
            );
        }

        return $site;
    },

    'WikibaseClient.SiteGroup' => function ( MediaWikiServices $services ): string {
        $settings = WikibaseClient::getSettings( $services );
        $siteGroup = $settings->getSetting( 'siteGroup' );

        if ( !$siteGroup ) {
            $siteId = $settings->getSetting( 'siteGlobalID' );

            $site = $services->getSiteLookup()->getSite( $siteId );

            if ( !$site ) {
                // TODO we should log some warning here,
                // but currently that breaks CI (T153729, T153597)
                return Site::GROUP_NONE;
            }

            $siteGroup = $site->getGroup();
        }

        return $siteGroup;
    },

    'WikibaseClient.SnakFormatterFactory' => function ( MediaWikiServices $services ): OutputFormatSnakFormatterFactory {
        return new OutputFormatSnakFormatterFactory(
            WikibaseClient::getDataTypeDefinitions( $services )
                ->getSnakFormatterFactoryCallbacks(),
            WikibaseClient::getValueFormatterFactory( $services ),
            WikibaseClient::getPropertyDataTypeLookup( $services ),
            WikibaseClient::getDataTypeFactory( $services ),
            WikibaseClient::getMessageInLanguageProvider( $services )
        );
    },

    'WikibaseClient.StatementGroupRendererFactory' => function ( MediaWikiServices $services ): StatementGroupRendererFactory {
        return new StatementGroupRendererFactory(
            WikibaseClient::getPropertyLabelResolver( $services ),
            new SnaksFinder(),
            WikibaseClient::getRestrictedEntityLookup( $services ),
            WikibaseClient::getDataAccessSnakFormatterFactory( $services ),
            WikibaseClient::getUsageAccumulatorFactory( $services ),
            $services->getLanguageConverterFactory(),
            $services->getLanguageFactory(),
            WikibaseClient::getSettings( $services )
                ->getSetting( 'allowDataAccessInUserLanguage' )
        );
    },

    'WikibaseClient.Store' => function ( MediaWikiServices $services ): ClientStore {
        return new DirectSqlStore(
            WikibaseClient::getEntityIdParser( $services ),
            WikibaseClient::getEntityIdLookup( $services ),
            WikibaseClient::getWikibaseServices( $services ),
            WikibaseClient::getSettings( $services ),
            WikibaseClient::getTermBuffer( $services ),
            WikibaseClient::getRepoDomainDbFactory( $services )
                ->newForEntitySource( WikibaseClient::getItemAndPropertySource( $services ) ),
            WikibaseClient::getClientDomainDbFactory( $services )->newLocalDb()
        );
    },

    'WikibaseClient.StringNormalizer' => function ( MediaWikiServices $services ): StringNormalizer {
        return new StringNormalizer();
    },

    'WikibaseClient.TermBuffer' => function ( MediaWikiServices $services ): TermBuffer {
        return WikibaseClient::getPrefetchingTermLookup( $services );
    },

    'WikibaseClient.TermFallbackCache' => function ( MediaWikiServices $services ): TermFallbackCacheFacade {
        return new TermFallbackCacheFacade(
            WikibaseClient::getTermFallbackCacheFactory( $services )->getTermFallbackCache(),
            WikibaseClient::getSettings( $services )->getSetting( 'sharedCacheDuration' )
        );
    },

    'WikibaseClient.TermFallbackCacheFactory' => function ( MediaWikiServices $services ): TermFallbackCacheFactory {
        $settings = WikibaseClient::getSettings( $services );
        return new TermFallbackCacheFactory(
            $settings->getSetting( 'sharedCacheType' ),
            WikibaseClient::getLogger( $services ),
            $services->getStatsdDataFactory(),
            hash( 'sha256', $services->getMainConfig()->get( 'SecretKey' ) ),
            new TermFallbackCacheServiceFactory(),
            $settings->getSetting( 'termFallbackCacheVersion' ),
            $services->getObjectCacheFactory()
        );
    },

    'WikibaseClient.TermInLangIdsResolverFactory' => function (
        MediaWikiServices $services
    ): TermInLangIdsResolverFactory {
        return new TermInLangIdsResolverFactory(
            WikibaseClient::getRepoDomainDbFactory( $services ),
            WikibaseClient::getLogger( $services ),
            $services->getMainWANObjectCache()
        );
    },

    'WikibaseClient.TermLookup' => function ( MediaWikiServices $services ): TermLookup {
        return WikibaseClient::getPrefetchingTermLookup( $services );
    },

    'WikibaseClient.TermsLanguages' => function ( MediaWikiServices $services ): ContentLanguages {
        return WikibaseClient::getWikibaseContentLanguages( $services )
            ->getContentLanguages( WikibaseContentLanguages::CONTEXT_TERM );
    },

    'WikibaseClient.UsageAccumulatorFactory' => function ( MediaWikiServices $services ): UsageAccumulatorFactory {
        $usageModifierLimits = WikibaseClient::getSettings( $services )->getSetting(
            'entityUsageModifierLimits'
        );
        return new UsageAccumulatorFactory(
            new EntityUsageFactory( WikibaseClient::getEntityIdParser( $services ) ),
            new UsageDeduplicator( $usageModifierLimits ),
            new RevisionBasedEntityRedirectTargetLookup(
                WikibaseClient::getEntityRevisionLookup( $services )
            )
        );
    },

    'WikibaseClient.UserLanguage' => function ( MediaWikiServices $services ): Language {
        global $wgLang;

        // TODO: define a LanguageProvider service instead of using a global directly.
        // NOTE: The $wgLang global may still be null when the SetupAfterCache hook is
        // run during bootstrapping.

        if ( !$wgLang ) {
            throw new RuntimeException( 'Premature access: $wgLang is not yet initialized!' );
        }

        StubObject::unstub( $wgLang );
        return $wgLang;
    },

    'WikibaseClient.ValueFormatterFactory' => function ( MediaWikiServices $services ): OutputFormatValueFormatterFactory {
        return new OutputFormatValueFormatterFactory(
            WikibaseClient::getDataTypeDefinitions( $services )
                ->getFormatterFactoryCallbacks( DataTypeDefinitions::PREFIXED_MODE ),
            $services->getContentLanguage(),
            WikibaseClient::getLanguageFallbackChainFactory( $services )
        );
    },

    'WikibaseClient.WikibaseContentLanguages' => function ( MediaWikiServices $services ): WikibaseContentLanguages {
        return WikibaseContentLanguages::getDefaultInstance(
            $services->getHookContainer(),
            $services->getLanguageNameUtils()
        );
    },

    'WikibaseClient.WikibaseServices' => function ( MediaWikiServices $services ): WikibaseServices {
        $entitySourceDefinitions = WikibaseClient::getEntitySourceDefinitions( $services );
        $singleEntitySourceServicesFactory = new SingleEntitySourceServicesFactory(
            WikibaseClient::getEntityIdParser( $services ),
            WikibaseClient::getEntityIdComposer( $services ),
            WikibaseClient::getDataValueDeserializer( $services ),
            $services->getNameTableStoreFactory(),
            WikibaseClient::getDataAccessSettings( $services ),
            WikibaseClient::getLanguageFallbackChainFactory( $services ),
            new ForbiddenSerializer( 'Entity serialization is not supported on the client!' ),
            WikibaseClient::getEntityTypeDefinitions( $services ),
            WikibaseClient::getRepoDomainDbFactory( $services ),
            WikibaseClient::getBaseDataModelDeserializerFactory( $services )
        );

        $singleSourceServices = [];
        foreach ( $entitySourceDefinitions->getSources() as $source ) {
            $singleSourceServices[$source->getSourceName()] = $singleEntitySourceServicesFactory
                // @phan-suppress-next-line PhanTypeMismatchArgumentSuperType
                ->getServicesForSource( $source );
        }

        return new MultipleEntitySourceServices( $entitySourceDefinitions, $singleSourceServices );
    },

];