SingleEntitySourceServices::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 32
rs 9.408
c 0
b 0
f 0
cc 1
nc 1
nop 11

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace Wikibase\DataAccess;
4
5
use Deserializers\Deserializer;
6
use Deserializers\DispatchingDeserializer;
7
use MediaWiki\Logger\LoggerFactory;
8
use MediaWiki\MediaWikiServices;
9
use MediaWiki\Storage\NameTableStore;
10
use Wikibase\DataAccess\Serializer\ForbiddenSerializer;
11
use Wikibase\DataModel\DeserializerFactory;
12
use Wikibase\DataModel\Entity\EntityId;
13
use Wikibase\DataModel\Entity\EntityIdParser;
14
use Wikibase\DataModel\Entity\EntityRedirect;
15
use Wikibase\DataModel\Entity\Property;
16
use Wikibase\DataModel\Services\EntityId\EntityIdComposer;
17
use Wikibase\InternalSerialization\DeserializerFactory as InternalDeserializerFactory;
18
use Wikibase\Lib\Interactors\MatchingTermsSearchInteractorFactory;
19
use Wikibase\Lib\Interactors\TermSearchInteractorFactory;
20
use Wikibase\Lib\Store\EntityContentDataCodec;
21
use Wikibase\Lib\Store\EntityNamespaceLookup;
22
use Wikibase\Lib\Store\EntityRevision;
23
use Wikibase\Lib\Store\EntityStoreWatcher;
24
use Wikibase\Lib\Store\MatchingTermsLookup;
25
use Wikibase\Lib\Store\Sql\EntityIdLocalPartPageTableEntityQuery;
26
use Wikibase\Lib\Store\Sql\PrefetchingWikiPageEntityMetaDataAccessor;
27
use Wikibase\Lib\Store\Sql\PropertyInfoTable;
28
use Wikibase\Lib\Store\Sql\Terms\DatabaseMatchingTermsLookup;
29
use Wikibase\Lib\Store\Sql\Terms\DatabaseTermInLangIdsResolver;
30
use Wikibase\Lib\Store\Sql\Terms\DatabaseTypeIdsStore;
31
use Wikibase\Lib\Store\Sql\Terms\PrefetchingItemTermLookup;
32
use Wikibase\Lib\Store\Sql\TypeDispatchingWikiPageEntityMetaDataAccessor;
33
use Wikibase\Lib\Store\Sql\WikiPageEntityDataLoader;
34
use Wikibase\Lib\Store\Sql\WikiPageEntityMetaDataAccessor;
35
use Wikibase\Lib\Store\Sql\WikiPageEntityMetaDataLookup;
36
use Wikibase\Lib\Store\Sql\WikiPageEntityRevisionLookup;
37
use Wikibase\Lib\Store\TypeDispatchingEntityRevisionLookup;
38
use Wikibase\Lib\WikibaseSettings;
39
use Wikimedia\Assert\Assert;
40
41
/**
42
 * Collection of services for a single EntitySource.
43
 * Some GenericServices are injected alongside some more specific services for the EntitySource.
44
 * Various logic then pulls these services together into more composed services.
45
 *
46
 * TODO fixme, lots of things in this class bind to wikibase lib and mediawiki directly.
47
 *
48
 * @license GPL-2.0-or-later
49
 */
50
class SingleEntitySourceServices implements EntityStoreWatcher {
51
52
	/**
53
	 * @var GenericServices
54
	 */
55
	private $genericServices;
56
57
	/**
58
	 * @var EntityIdParser
59
	 */
60
	private $entityIdParser;
61
62
	private $entityIdComposer;
63
64
	private $dataValueDeserializer;
65
66
	/**
67
	 * @var DataAccessSettings
68
	 */
69
	private $dataAccessSettings;
70
71
	/**
72
	 * @var EntitySource
73
	 */
74
	private $entitySource;
75
	private $deserializerFactoryCallbacks;
76
	private $entityMetaDataAccessorCallbacks;
77
78
	/**
79
	 * @var callable[]
80
	 */
81
	private $prefetchingTermLookupCallbacks;
82
83
	private $slotRoleStore;
84
	private $entityRevisionLookup = null;
85
86
	private $termSearchInteractorFactory = null;
87
88
	private $prefetchingTermLookup = null;
89
90
	/**
91
	 * @var PrefetchingWikiPageEntityMetaDataAccessor|null
92
	 */
93
	private $entityMetaDataAccessor = null;
94
95
	private $propertyInfoLookup = null;
96
97
	private $entityRevisionLookupFactoryCallbacks;
98
	private $entityNamespaceLookup;
99
100
	public function __construct(
101
		GenericServices $genericServices,
102
		EntityIdParser $entityIdParser,
103
		EntityIdComposer $entityIdComposer,
104
		Deserializer $dataValueDeserializer,
105
		NameTableStore $slotRoleStore,
106
		DataAccessSettings $dataAccessSettings,
107
		EntitySource $entitySource,
108
		array $deserializerFactoryCallbacks,
109
		array $entityMetaDataAccessorCallbacks,
110
		array $prefetchingTermLookupCallbacks,
111
		array $entityRevisionFactoryLookupCallbacks
112
	) {
113
		$this->assertCallbackArrayTypes(
114
			$deserializerFactoryCallbacks,
115
			$entityMetaDataAccessorCallbacks,
116
			$prefetchingTermLookupCallbacks,
117
			$entityRevisionFactoryLookupCallbacks
118
		);
119
120
		$this->genericServices = $genericServices;
121
		$this->entityIdParser = $entityIdParser;
122
		$this->entityIdComposer = $entityIdComposer;
123
		$this->dataValueDeserializer = $dataValueDeserializer;
124
		$this->slotRoleStore = $slotRoleStore;
125
		$this->dataAccessSettings = $dataAccessSettings;
126
		$this->entitySource = $entitySource;
127
		$this->deserializerFactoryCallbacks = $deserializerFactoryCallbacks;
128
		$this->entityMetaDataAccessorCallbacks = $entityMetaDataAccessorCallbacks;
129
		$this->prefetchingTermLookupCallbacks = $prefetchingTermLookupCallbacks;
130
		$this->entityRevisionLookupFactoryCallbacks = $entityRevisionFactoryLookupCallbacks;
131
	}
132
133
	private function assertCallbackArrayTypes(
134
		array $deserializerFactoryCallbacks,
135
		array $entityMetaDataAccessorCallbacks,
136
		array $prefetchingTermLookupCallbacks,
137
		array $entityRevisionFactoryLookupCallbacks
138
	) {
139
		Assert::parameterElementType(
140
			'callable',
141
			$deserializerFactoryCallbacks,
142
			'$deserializerFactoryCallbacks'
143
		);
144
		Assert::parameterElementType(
145
			'callable',
146
			$entityMetaDataAccessorCallbacks,
147
			'$entityMetaDataAccessorCallbacks'
148
		);
149
		Assert::parameterElementType(
150
			'callable',
151
			$prefetchingTermLookupCallbacks,
152
			'$prefetchingTermLookupCallbacks'
153
		);
154
		Assert::parameterElementType(
155
			'callable',
156
			$entityRevisionFactoryLookupCallbacks,
157
			'$entityRevisionFactoryLookupCallbacks'
158
		);
159
	}
160
161
	/**
162
	 * @return EntitySource The EntitySource object for this set of services
163
	 */
164
	public function getEntitySource() : EntitySource {
165
		return $this->entitySource;
166
	}
167
168
	/**
169
	 * @return EntityNamespaceLookup The EntityNamespaceLookup object for this EntitySource
170
	 */
171
	public function getEntityNamespaceLookup() : EntityNamespaceLookup {
172
		if ( $this->entityNamespaceLookup === null ) {
173
			$this->entityNamespaceLookup = new EntityNamespaceLookup(
174
				$this->entitySource->getEntityNamespaceIds(),
175
				$this->entitySource->getEntitySlotNames()
176
			);
177
		}
178
179
		return $this->entityNamespaceLookup;
180
	}
181
182
	/**
183
	 * It would be nice to only return hint against the TermInLangIdsResolver interface here,
184
	 * but current users need a method only provided by DatabaseTermInLangIdsResolver
185
	 * @return DatabaseTermInLangIdsResolver
186
	 */
187
	public function getTermInLangIdsResolver() : DatabaseTermInLangIdsResolver {
188
		$mediaWikiServices = MediaWikiServices::getInstance();
189
		$logger = LoggerFactory::getInstance( 'Wikibase' );
190
191
		$databaseName = $this->entitySource->getDatabaseName();
192
		$loadBalancer = $mediaWikiServices->getDBLoadBalancerFactory()
193
			->getMainLB( $databaseName );
194
195
		$databaseTypeIdsStore = new DatabaseTypeIdsStore(
196
			$loadBalancer,
197
			$mediaWikiServices->getMainWANObjectCache(),
198
			$databaseName,
0 ignored issues
show
Bug introduced by
It seems like $databaseName defined by $this->entitySource->getDatabaseName() on line 191 can also be of type string; however, Wikibase\Lib\Store\Sql\T...IdsStore::__construct() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
199
			$logger
200
		);
201
		return new DatabaseTermInLangIdsResolver(
202
			$databaseTypeIdsStore,
203
			$databaseTypeIdsStore,
204
			$loadBalancer,
205
			$databaseName,
206
			$logger
207
		);
208
	}
209
210
	public function getEntityRevisionLookup() {
211
		if ( $this->entityRevisionLookup === null ) {
212
			if ( !WikibaseSettings::isRepoEnabled() ) {
213
				$serializer = new ForbiddenSerializer( 'Entity serialization is not supported on the client!' );
214
			} else {
215
				$serializer = $this->genericServices->getStorageEntitySerializer();
216
			}
217
218
			$codec = new EntityContentDataCodec(
219
				$this->entityIdParser,
220
				$serializer,
221
				$this->getEntityDeserializer(),
222
				$this->dataAccessSettings->maxSerializedEntitySizeInBytes()
223
			);
224
225
			/** @var WikiPageEntityMetaDataAccessor $metaDataAccessor */
226
			$metaDataAccessor = $this->getEntityMetaDataAccessor();
227
228
			// TODO: instead calling static getInstance randomly here, inject two db-specific services
229
			$revisionStoreFactory = MediaWikiServices::getInstance()->getRevisionStoreFactory();
230
			$blobStoreFactory = MediaWikiServices::getInstance()->getBlobStoreFactory();
231
232
			$databaseName = $this->entitySource->getDatabaseName();
233
234
			// TODO: This wikiPageEntityRevisionStoreLookup should probably instead be built by a factory
235
			// that is returned by a method somewhere in data-access and then instead of being used here
236
			// as a default it should go in the wiring files for each entity type. See: T246451
237
			$wikiPageEntityRevisionStoreLookup = new WikiPageEntityRevisionLookup(
238
				$metaDataAccessor,
239
				new WikiPageEntityDataLoader( $codec, $blobStoreFactory->newBlobStore( $databaseName ) ),
240
				$revisionStoreFactory->getRevisionStore( $databaseName ),
241
				$databaseName
242
			);
243
244
			$this->entityRevisionLookup = new TypeDispatchingEntityRevisionLookup(
245
				$this->entityRevisionLookupFactoryCallbacks,
246
				$wikiPageEntityRevisionStoreLookup
247
			);
248
		}
249
250
		return $this->entityRevisionLookup;
251
	}
252
253
	private function getEntityDeserializer() {
254
		$deserializerFactory = new DeserializerFactory(
255
			$this->dataValueDeserializer,
256
			$this->entityIdParser
257
		);
258
259
		$deserializers = [];
260
		foreach ( $this->deserializerFactoryCallbacks as $callback ) {
261
			$deserializers[] = call_user_func( $callback, $deserializerFactory );
262
		}
263
264
		$internalDeserializerFactory = new InternalDeserializerFactory(
265
			$this->dataValueDeserializer,
266
			$this->entityIdParser,
267
			new DispatchingDeserializer( $deserializers )
268
		);
269
270
		return $internalDeserializerFactory->newEntityDeserializer();
271
	}
272
273
	private function getEntityMetaDataAccessor() {
274
		if ( $this->entityMetaDataAccessor === null ) {
275
			$entityNamespaceLookup = $this->getEntityNamespaceLookup();
276
			$repositoryName = '';
277
			$databaseName = $this->entitySource->getDatabaseName();
278
			$this->entityMetaDataAccessor = new PrefetchingWikiPageEntityMetaDataAccessor(
279
				new TypeDispatchingWikiPageEntityMetaDataAccessor(
280
					$this->entityMetaDataAccessorCallbacks,
281
					new WikiPageEntityMetaDataLookup(
282
						$entityNamespaceLookup,
283
						new EntityIdLocalPartPageTableEntityQuery(
284
							$entityNamespaceLookup,
285
							$this->slotRoleStore
286
						),
287
						$this->entitySource
288
					),
289
					$databaseName,
290
					$repositoryName
291
				),
292
				// TODO: inject?
293
				LoggerFactory::getInstance( 'Wikibase' )
294
			);
295
		}
296
297
		return $this->entityMetaDataAccessor;
298
	}
299
300
	public function getTermSearchInteractorFactory(): TermSearchInteractorFactory {
301
		if ( $this->termSearchInteractorFactory === null ) {
302
			$this->termSearchInteractorFactory = new MatchingTermsSearchInteractorFactory(
303
				$this->getMatchingTermsLookup(),
304
				$this->genericServices->getLanguageFallbackChainFactory(),
305
				$this->getPrefetchingTermLookup()
306
			);
307
		}
308
309
		return $this->termSearchInteractorFactory;
310
	}
311
312
	private function getMatchingTermsLookup(): MatchingTermsLookup {
313
		$mediaWikiServices = MediaWikiServices::getInstance();
314
		$logger = LoggerFactory::getInstance( 'Wikibase' );
315
		$repoDbDomain = $this->entitySource->getDatabaseName();
316
		$loadBalancer = $mediaWikiServices->getDBLoadBalancerFactory()->getMainLB( $repoDbDomain );
317
		$databaseTypeIdsStore = new DatabaseTypeIdsStore(
318
			$loadBalancer,
319
			$mediaWikiServices->getMainWANObjectCache(),
320
			$repoDbDomain,
0 ignored issues
show
Bug introduced by
It seems like $repoDbDomain defined by $this->entitySource->getDatabaseName() on line 315 can also be of type string; however, Wikibase\Lib\Store\Sql\T...IdsStore::__construct() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
321
			$logger
322
		);
323
		return new DatabaseMatchingTermsLookup(
324
			$loadBalancer,
325
			$databaseTypeIdsStore,
326
			$databaseTypeIdsStore,
327
			$this->entityIdComposer,
328
			$logger
329
		);
330
	}
331
332
	public function getPrefetchingTermLookup() {
333
		if ( $this->prefetchingTermLookup === null ) {
334
			$this->prefetchingTermLookup = new ByTypeDispatchingPrefetchingTermLookup(
335
				$this->getPrefetchingTermLookups(),
336
				new NullPrefetchingTermLookup()
337
			);
338
		}
339
340
		return $this->prefetchingTermLookup;
341
	}
342
343
	/**
344
	 * @return PrefetchingItemTermLookup[] indexed by entity type
345
	 */
346
	private function getPrefetchingTermLookups(): array {
347
		$typesWithCustomLookups = array_keys( $this->prefetchingTermLookupCallbacks );
348
349
		$lookupConstructorsByType = array_intersect( $typesWithCustomLookups, $this->entitySource->getEntityTypes() );
350
		$customLookups = [];
351
		foreach ( $lookupConstructorsByType as $type ) {
352
			$callback = $this->prefetchingTermLookupCallbacks[$type];
353
			$lookup = call_user_func( $callback, $this );
354
355
			Assert::postcondition(
356
				$lookup instanceof PrefetchingTermLookup,
357
				"Callback creating a lookup for $type must create an instance of PrefetchingTermLookup"
358
			);
359
360
			$customLookups[$type] = $lookup;
361
		}
362
		return $customLookups;
363
	}
364
365
	public function getEntityPrefetcher() {
366
		return $this->getEntityMetaDataAccessor();
367
	}
368
369
	public function getPropertyInfoLookup() {
370
		if ( !in_array( Property::ENTITY_TYPE, $this->entitySource->getEntityTypes() ) ) {
371
			throw new \LogicException( 'Entity source: ' . $this->entitySource->getSourceName() . ' does no provide properties' );
372
		}
373
		if ( $this->propertyInfoLookup === null ) {
374
			$this->propertyInfoLookup = new PropertyInfoTable(
375
				$this->entityIdComposer,
376
				$this->entitySource->getDatabaseName(),
377
				false
378
			);
379
		}
380
		return $this->propertyInfoLookup;
381
	}
382
383
	public function entityUpdated( EntityRevision $entityRevision ) {
384
		// TODO: should this become more "generic" and somehow enumerate all services and
385
		// update all of these which are instances of EntityStoreWatcher?
386
387
		// Only notify entityMetaDataAccessor if the service is created, as the EntityStoreWatcher
388
		// is only used for purging of an in process cache.
389
		if ( $this->entityMetaDataAccessor !== null ) {
390
			$this->entityMetaDataAccessor->entityUpdated( $entityRevision );
391
		}
392
	}
393
394
	public function redirectUpdated( EntityRedirect $entityRedirect, $revisionId ) {
395
		// TODO: should this become more "generic" and somehow enumerate all services and
396
		// update all of these which are instances of EntityStoreWatcher?
397
398
		// Only notify entityMetaDataAccessor if the service is created, as the EntityStoreWatcher
399
		// is only used for purging of an in process cache.
400
		if ( $this->entityMetaDataAccessor !== null ) {
401
			$this->entityMetaDataAccessor->redirectUpdated( $entityRedirect, $revisionId );
402
		}
403
	}
404
405
	public function entityDeleted( EntityId $entityId ) {
406
		// TODO: should this become more "generic" and somehow enumerate all services and
407
		// update all of these which are instances of EntityStoreWatcher?
408
409
		// Only notify entityMetaDataAccessor if the service is created, as the EntityStoreWatcher
410
		// is only used for purging of an in process cache.
411
		if ( $this->entityMetaDataAccessor !== null ) {
412
			$this->entityMetaDataAccessor->entityDeleted( $entityId );
413
		}
414
	}
415
416
}
417