GetEntities::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 9.456
c 0
b 0
f 0
cc 1
nc 1
nop 13

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
declare( strict_types = 1 );
4
5
namespace Wikibase\Repo\Api;
6
7
use ApiBase;
8
use ApiMain;
9
use IBufferingStatsdDataFactory;
10
use Wikibase\DataModel\Entity\EntityId;
11
use Wikibase\DataModel\Entity\EntityIdParser;
12
use Wikibase\DataModel\Entity\EntityIdParsingException;
13
use Wikibase\DataModel\Services\Entity\EntityPrefetcher;
14
use Wikibase\Lib\LanguageFallbackChainFactory;
15
use Wikibase\Lib\Store\DivergingEntityIdException;
16
use Wikibase\Lib\Store\EntityRevision;
17
use Wikibase\Lib\Store\EntityRevisionLookup;
18
use Wikibase\Lib\Store\RevisionedUnresolvedRedirectException;
19
use Wikibase\Lib\StringNormalizer;
20
use Wikibase\Repo\SiteLinkTargetProvider;
21
use Wikibase\Repo\WikibaseRepo;
22
23
/**
24
 * API module to get the data for one or more Wikibase entities.
25
 *
26
 * @license GPL-2.0-or-later
27
 */
28
class GetEntities extends ApiBase {
29
30
	use FederatedPropertyApiValidatorTrait;
31
32
	/**
33
	 * @var StringNormalizer
34
	 */
35
	private $stringNormalizer;
36
37
	/**
38
	 * @var LanguageFallbackChainFactory
39
	 */
40
	private $languageFallbackChainFactory;
41
42
	/**
43
	 * @var SiteLinkTargetProvider
44
	 */
45
	private $siteLinkTargetProvider;
46
47
	/**
48
	 * @var EntityPrefetcher
49
	 */
50
	private $entityPrefetcher;
51
52
	/**
53
	 * @var string[]
54
	 */
55
	private $siteLinkGroups;
56
57
	/**
58
	 * @var ApiErrorReporter
59
	 */
60
	protected $errorReporter;
61
62
	/**
63
	 * @var ResultBuilder
64
	 */
65
	private $resultBuilder;
66
67
	/**
68
	 * @var EntityRevisionLookup
69
	 */
70
	private $entityRevisionLookup;
71
72
	/**
73
	 * @var EntityIdParser
74
	 */
75
	private $idParser;
76
77
	/** @var IBufferingStatsdDataFactory */
78
	private $stats;
79
80
	/**
81
	 * @param ApiMain $mainModule
82
	 * @param string $moduleName
83
	 * @param StringNormalizer $stringNormalizer
84
	 * @param LanguageFallbackChainFactory $languageFallbackChainFactory
85
	 * @param SiteLinkTargetProvider $siteLinkTargetProvider
86
	 * @param EntityPrefetcher $entityPrefetcher
87
	 * @param string[] $siteLinkGroups
88
	 * @param ApiErrorReporter $errorReporter
89
	 * @param ResultBuilder $resultBuilder
90
	 * @param EntityRevisionLookup $entityRevisionLookup
91
	 * @param EntityIdParser $idParser
92
	 * @param IBufferingStatsdDataFactory $stats
93
	 * @param bool $federatedPropertiesEnabled
94
	 *
95
	 * @see ApiBase::__construct
96
	 */
97
	public function __construct(
98
		ApiMain $mainModule,
99
		string $moduleName,
100
		StringNormalizer $stringNormalizer,
101
		LanguageFallbackChainFactory $languageFallbackChainFactory,
102
		SiteLinkTargetProvider $siteLinkTargetProvider,
103
		EntityPrefetcher $entityPrefetcher,
104
		array $siteLinkGroups,
105
		ApiErrorReporter $errorReporter,
106
		ResultBuilder $resultBuilder,
107
		EntityRevisionLookup $entityRevisionLookup,
108
		EntityIdParser $idParser,
109
		IBufferingStatsdDataFactory $stats,
110
		bool $federatedPropertiesEnabled
111
	) {
112
		parent::__construct( $mainModule, $moduleName );
113
114
		$this->stringNormalizer = $stringNormalizer;
115
		$this->languageFallbackChainFactory = $languageFallbackChainFactory;
116
		$this->siteLinkTargetProvider = $siteLinkTargetProvider;
117
		$this->entityPrefetcher = $entityPrefetcher;
118
		$this->siteLinkGroups = $siteLinkGroups;
119
		$this->errorReporter = $errorReporter;
120
		$this->resultBuilder = $resultBuilder;
121
		$this->entityRevisionLookup = $entityRevisionLookup;
122
		$this->idParser = $idParser;
123
		$this->stats = $stats;
124
		$this->federatedPropertiesEnabled = $federatedPropertiesEnabled;
125
	}
126
127
	public static function factory( ApiMain $apiMain, string $moduleName, IBufferingStatsdDataFactory $stats ): self {
128
		$wikibaseRepo = WikibaseRepo::getDefaultInstance();
129
		$settings = $wikibaseRepo->getSettings();
130
		$apiHelperFactory = $wikibaseRepo->getApiHelperFactory( $apiMain->getContext() );
131
132
		$siteLinkTargetProvider = new SiteLinkTargetProvider(
133
			$wikibaseRepo->getSiteLookup(),
134
			$settings->getSetting( 'specialSiteLinkGroups' )
135
		);
136
137
		return new self(
138
			$apiMain,
139
			$moduleName,
140
			$wikibaseRepo->getStringNormalizer(),
141
			$wikibaseRepo->getLanguageFallbackChainFactory(),
142
			$siteLinkTargetProvider,
143
			$wikibaseRepo->getStore()->getEntityPrefetcher(),
144
			$settings->getSetting( 'siteLinkGroups' ),
145
			$apiHelperFactory->getErrorReporter( $apiMain ),
146
			$apiHelperFactory->getResultBuilder( $apiMain ),
147
			$wikibaseRepo->getEntityRevisionLookup(),
148
			$wikibaseRepo->getEntityIdParser(),
149
			$stats,
150
			$wikibaseRepo->inFederatedPropertyMode()
151
		);
152
	}
153
154
	/**
155
	 * @inheritDoc
156
	 */
157
	public function execute(): void {
158
		$this->getMain()->setCacheMode( 'public' );
159
160
		$params = $this->extractRequestParams();
161
162
		if ( !isset( $params['ids'] ) && ( empty( $params['sites'] ) || empty( $params['titles'] ) ) ) {
163
			$this->errorReporter->dieWithError(
164
				'wikibase-api-illegal-ids-or-sites-titles-selector',
165
				'param-missing'
166
			);
167
		}
168
169
		$resolveRedirects = $params['redirects'] === 'yes';
170
171
		$entityIds = $this->getEntityIdsFromParams( $params );
172
		foreach ( $entityIds as $entityId ) {
173
			$this->validateAlteringEntityById( $entityId );
174
		}
175
176
		$this->stats->updateCount( 'wikibase.repo.api.getentities.entities', count( $entityIds ) );
177
178
		$entityRevisions = $this->getEntityRevisionsFromEntityIds( $entityIds, $resolveRedirects );
179
180
		foreach ( $entityRevisions as $sourceEntityId => $entityRevision ) {
181
			$this->handleEntity( $sourceEntityId, $entityRevision, $params );
182
		}
183
184
		$this->resultBuilder->markSuccess( 1 );
185
	}
186
187
	/**
188
	 * Get a unique array of EntityIds from api request params
189
	 *
190
	 * @param array $params
191
	 *
192
	 * @return EntityId[]
193
	 */
194
	private function getEntityIdsFromParams( array $params ): array {
195
		$fromIds = $this->getEntityIdsFromIdParam( $params );
196
		$fromSiteTitleCombinations = $this->getEntityIdsFromSiteTitleParams( $params );
197
		$ids = array_merge( $fromIds, $fromSiteTitleCombinations );
198
		return array_unique( $ids );
199
	}
200
201
	/**
202
	 * @param array $params
203
	 *
204
	 * @return EntityId[]
205
	 */
206
	private function getEntityIdsFromIdParam( array $params ): array {
207
		if ( !isset( $params['ids'] ) ) {
208
			return [];
209
		}
210
211
		$ids = [];
212
		foreach ( $params['ids'] as $id ) {
213
			try {
214
				$ids[] = $this->idParser->parse( $id );
215
			} catch ( EntityIdParsingException $e ) {
216
				$this->errorReporter->dieWithError(
217
					[ 'wikibase-api-no-such-entity', $id ],
218
					'no-such-entity',
219
					0,
220
					[ 'id' => $id ]
221
				);
222
			}
223
		}
224
		return $ids;
225
	}
226
227
	/**
228
	 * @param array $params
229
	 * @return EntityId[]
230
	 */
231
	private function getEntityIdsFromSiteTitleParams( array $params ): array {
232
		$ids = [];
233
		if ( !empty( $params['sites'] ) && !empty( $params['titles'] ) ) {
234
			$entityByTitleHelper = $this->getItemByTitleHelper();
235
236
			list( $ids, $missingItems ) = $entityByTitleHelper->getEntityIds(
237
				$params['sites'],
238
				$params['titles'],
239
				$params['normalize']
240
			);
241
242
			$this->addMissingItemsToResult( $missingItems );
243
		}
244
		return $ids;
245
	}
246
247
	private function getItemByTitleHelper(): EntityByTitleHelper {
248
		$wikibaseRepo = WikibaseRepo::getDefaultInstance();
249
		$siteLinkStore = $wikibaseRepo->getStore()->getEntityByLinkedTitleLookup();
250
		return new EntityByTitleHelper(
251
			$this,
252
			$this->resultBuilder,
253
			$siteLinkStore,
254
			$wikibaseRepo->getSiteLookup(),
255
			$this->stringNormalizer
256
		);
257
	}
258
259
	/**
260
	 * @param array[] $missingItems Array of arrays, Each internal array has a key 'site' and 'title'
261
	 */
262
	private function addMissingItemsToResult( array $missingItems ): void {
263
		foreach ( $missingItems as $missingItem ) {
264
			$this->resultBuilder->addMissingEntity( null, $missingItem );
265
		}
266
	}
267
268
	/**
269
	 * Returns props based on request parameters
270
	 *
271
	 * @param array $params
272
	 *
273
	 * @return array
274
	 */
275
	private function getPropsFromParams( array $params ): array {
276
		if ( in_array( 'sitelinks/urls', $params['props'] ) ) {
277
			$params['props'][] = 'sitelinks';
278
		}
279
280
		return $params['props'];
281
	}
282
283
	/**
284
	 * @param EntityId[] $entityIds
285
	 * @param bool $resolveRedirects
286
	 *
287
	 * @return EntityRevision[]
288
	 */
289
	private function getEntityRevisionsFromEntityIds( array $entityIds, bool $resolveRedirects = false ): array {
290
		$revisionArray = [];
291
292
		$this->entityPrefetcher->prefetch( $entityIds );
293
294
		foreach ( $entityIds as $entityId ) {
295
			$sourceEntityId = $entityId->getSerialization();
296
			$entityRevision = $this->getEntityRevision( $entityId, $resolveRedirects );
297
298
			$revisionArray[$sourceEntityId] = $entityRevision;
299
		}
300
301
		return $revisionArray;
302
	}
303
304
	private function getEntityRevision( EntityId $entityId, bool $resolveRedirects = false ): ?EntityRevision {
305
		$entityRevision = null;
306
307
		try {
308
			$entityRevision = $this->entityRevisionLookup->getEntityRevision( $entityId );
309
		} catch ( RevisionedUnresolvedRedirectException $ex ) {
310
			if ( $resolveRedirects ) {
311
				$entityId = $ex->getRedirectTargetId();
312
				$entityRevision = $this->getEntityRevision( $entityId, false );
313
			}
314
		} catch ( DivergingEntityIdException $ex ) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
315
			// DivergingEntityIdException is thrown when the repository $entityId is from other
316
			// repository than the entityRevisionLookup was configured to read from.
317
			// Such cases are input errors (e.g. specifying non-existent repository prefix)
318
			// and should be ignored and treated as non-existing entities.
319
		}
320
321
		return $entityRevision;
322
	}
323
324
	/**
325
	 * Adds the given EntityRevision to the API result.
326
	 *
327
	 * @param string|null $sourceEntityId
328
	 * @param EntityRevision|null $entityRevision
329
	 * @param array $params
330
	 */
331
	private function handleEntity(
332
		?string $sourceEntityId,
333
		EntityRevision $entityRevision = null,
334
		array $params = []
335
	): void {
336
		if ( $entityRevision === null ) {
337
			$this->resultBuilder->addMissingEntity( $sourceEntityId, [ 'id' => $sourceEntityId ] );
338
		} else {
339
			list( $languageCodeFilter, $fallbackChains ) = $this->getLanguageCodesAndFallback( $params );
340
			$this->resultBuilder->addEntityRevision(
341
				$sourceEntityId,
342
				$entityRevision,
343
				$this->getPropsFromParams( $params ),
344
				$params['sitefilter'],
345
				$languageCodeFilter,
346
				$fallbackChains
347
			);
348
		}
349
	}
350
351
	/**
352
	 * @param array $params
353
	 *
354
	 * @return array
355
	 *     0 => string[] languageCodes that the user wants returned
356
	 *     1 => TermLanguageFallbackChain[] Keys are requested lang codes
357
	 */
358
	private function getLanguageCodesAndFallback( array $params ): array {
359
		$languageCodes = ( is_array( $params['languages'] ) ? $params['languages'] : [] );
360
		$fallbackChains = [];
361
362
		if ( $params['languagefallback'] ) {
363
			$fallbackMode = LanguageFallbackChainFactory::FALLBACK_ALL;
364
			foreach ( $languageCodes as $languageCode ) {
365
				$fallbackChains[$languageCode] = $this->languageFallbackChainFactory
366
					->newFromLanguageCode( $languageCode, $fallbackMode );
367
			}
368
		}
369
370
		return [ array_unique( $languageCodes ), $fallbackChains ];
371
	}
372
373
	/**
374
	 * @inheritDoc
375
	 */
376
	protected function getAllowedParams(): array {
377
		$sites = $this->siteLinkTargetProvider->getSiteList( $this->siteLinkGroups );
378
379
		return array_merge( parent::getAllowedParams(), [
380
			'ids' => [
381
				self::PARAM_TYPE => 'string',
382
				self::PARAM_ISMULTI => true,
383
			],
384
			'sites' => [
385
				self::PARAM_TYPE => $sites->getGlobalIdentifiers(),
386
				self::PARAM_ISMULTI => true,
387
				self::PARAM_ALLOW_DUPLICATES => true
388
			],
389
			'titles' => [
390
				self::PARAM_TYPE => 'string',
391
				self::PARAM_ISMULTI => true,
392
				self::PARAM_ALLOW_DUPLICATES => true
393
			],
394
			'redirects' => [
395
				self::PARAM_TYPE => [ 'yes', 'no' ],
396
				self::PARAM_DFLT => 'yes',
397
			],
398
			'props' => [
399
				self::PARAM_TYPE => [ 'info', 'sitelinks', 'sitelinks/urls', 'aliases', 'labels',
400
					'descriptions', 'claims', 'datatype' ],
401
				self::PARAM_DFLT => 'info|sitelinks|aliases|labels|descriptions|claims|datatype',
402
				self::PARAM_ISMULTI => true,
403
			],
404
			'languages' => [
405
				self::PARAM_TYPE => WikibaseRepo::getDefaultInstance()->getTermsLanguages()->getLanguages(),
406
				self::PARAM_ISMULTI => true,
407
			],
408
			'languagefallback' => [
409
				self::PARAM_TYPE => 'boolean',
410
				self::PARAM_DFLT => false
411
			],
412
			'normalize' => [
413
				self::PARAM_TYPE => 'boolean',
414
				self::PARAM_DFLT => false
415
			],
416
			'sitefilter' => [
417
				self::PARAM_TYPE => $sites->getGlobalIdentifiers(),
418
				self::PARAM_ISMULTI => true,
419
				self::PARAM_ALLOW_DUPLICATES => true
420
			],
421
		] );
422
	}
423
424
	/**
425
	 * @inheritDoc
426
	 */
427
	protected function getExamplesMessages(): array {
428
		return [
429
			"action=wbgetentities&ids=Q42"
430
			=> "apihelp-wbgetentities-example-1",
431
			"action=wbgetentities&ids=P17"
432
			=> "apihelp-wbgetentities-example-2",
433
			"action=wbgetentities&ids=Q42|P17"
434
			=> "apihelp-wbgetentities-example-3",
435
			"action=wbgetentities&ids=Q42&languages=en"
436
			=> "apihelp-wbgetentities-example-4",
437
			"action=wbgetentities&ids=Q42&languages=ii&languagefallback="
438
			=> "apihelp-wbgetentities-example-5",
439
			"action=wbgetentities&ids=Q42&props=labels"
440
			=> "apihelp-wbgetentities-example-6",
441
			"action=wbgetentities&ids=P17|P3&props=datatype"
442
			=> "apihelp-wbgetentities-example-7",
443
			"action=wbgetentities&ids=Q42&props=aliases&languages=en"
444
			=> "apihelp-wbgetentities-example-8",
445
			"action=wbgetentities&ids=Q1|Q42&props=descriptions&languages=en|de|fr"
446
			=> "apihelp-wbgetentities-example-9",
447
			'action=wbgetentities&sites=enwiki&titles=Berlin&languages=en'
448
			=> 'apihelp-wbgetentities-example-10',
449
			'action=wbgetentities&sites=enwiki&titles=berlin&normalize='
450
			=> 'apihelp-wbgetentities-example-11',
451
			'action=wbgetentities&ids=Q42&props=sitelinks'
452
			=> 'apihelp-wbgetentities-example-12',
453
			'action=wbgetentities&ids=Q42&sitefilter=enwiki'
454
			=> 'apihelp-wbgetentities-example-13'
455
		];
456
	}
457
458
}
459