getEntityDeserializer()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0
cc 2
nc 2
nop 0
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