Completed
Push — master ( 34e257...1bc588 )
by
unknown
04:04
created

parseValueOrNoValueOrNowParameter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
1
<?php
2
3
namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Helper;
4
5
use Config;
6
use DataValues\DataValue;
7
use DataValues\MonolingualTextValue;
8
use DataValues\MultilingualTextValue;
9
use DataValues\StringValue;
10
use DataValues\UnboundedQuantityValue;
11
use LogicException;
12
use Wikibase\DataModel\DeserializerFactory;
13
use Wikibase\DataModel\Deserializers\SnakDeserializer;
14
use Wikibase\DataModel\Entity\EntityId;
15
use Wikibase\DataModel\Entity\EntityIdValue;
16
use Wikibase\DataModel\Entity\ItemId;
17
use Wikibase\DataModel\Entity\PropertyId;
18
use Wikibase\DataModel\Snak\PropertyNoValueSnak;
19
use Wikibase\DataModel\Snak\PropertySomeValueSnak;
20
use Wikibase\DataModel\Snak\PropertyValueSnak;
21
use Wikibase\DataModel\Snak\Snak;
22
use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\Context;
23
use WikibaseQuality\ConstraintReport\ConstraintCheck\ItemIdSnakValue;
24
use WikibaseQuality\ConstraintReport\ConstraintCheck\Message\ViolationMessage;
25
use WikibaseQuality\ConstraintReport\Role;
26
27
/**
28
 * Helper for parsing constraint parameters
29
 * that were imported from constraint statements.
30
 *
31
 * All public methods of this class expect constraint parameters
32
 * (see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()})
33
 * and return parameter objects or throw {@link ConstraintParameterException}s.
34
 * The results are used by the checkers,
35
 * which may include rendering them into violation messages.
36
 *
37
 * @author Lucas Werkmeister
38
 * @license GPL-2.0-or-later
39
 */
40
class ConstraintParameterParser {
41
42
	/**
43
	 * @var Config
44
	 */
45
	private $config;
46
47
	/**
48
	 * @var SnakDeserializer
49
	 */
50
	private $snakDeserializer;
51
52
	/**
53
	 * @var string[]
54
	 */
55
	private $conceptBaseUris;
56
57
	/**
58
	 * @param Config $config
59
	 *   contains entity IDs used in constraint parameters (constraint statement qualifiers)
60
	 * @param DeserializerFactory $factory
61
	 *   used to parse constraint statement qualifiers into constraint parameters
62
	 * @param string[] $conceptBaseUris
63
	 *   mapping from repository names to base URIs of concept URIs,
64
	 *   used to obtain the full unit string from an entity ID given in constraint parameters
65
	 */
66
	public function __construct(
67
		Config $config,
68
		DeserializerFactory $factory,
69
		array $conceptBaseUris
70
	) {
71
		$this->config = $config;
72
		$this->snakDeserializer = $factory->newSnakDeserializer();
73
		$this->conceptBaseUris = $conceptBaseUris;
74
	}
75
76
	/**
77
	 * Check if any errors are recorded in the constraint parameters.
78
	 * @param array $parameters
79
	 * @throws ConstraintParameterException
80
	 */
81
	public function checkError( array $parameters ) {
82
		if ( array_key_exists( '@error', $parameters ) ) {
83
			$error = $parameters['@error'];
84
			if ( array_key_exists( 'toolong', $error ) && $error['toolong'] ) {
85
				$msg = 'wbqc-violation-message-parameters-error-toolong';
86
			} else {
87
				$msg = 'wbqc-violation-message-parameters-error-unknown';
88
			}
89
			throw new ConstraintParameterException( new ViolationMessage( $msg ) );
90
		}
91
	}
92
93
	/**
94
	 * Require that $parameters contains exactly one $parameterId parameter.
95
	 * @param array $parameters
96
	 * @param string $parameterId
97
	 * @throws ConstraintParameterException
98
	 */
99 View Code Duplication
	private function requireSingleParameter( array $parameters, $parameterId ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
100
		if ( count( $parameters[$parameterId] ) !== 1 ) {
101
			throw new ConstraintParameterException(
102
				( new ViolationMessage( 'wbqc-violation-message-parameter-single' ) )
103
					->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
104
			);
105
		}
106
	}
107
108
	/**
109
	 * Require that $snak is a {@link PropertyValueSnak}.
110
	 * @param Snak $snak
111
	 * @param string $parameterId
112
	 * @return void
113
	 * @throws ConstraintParameterException
114
	 */
115 View Code Duplication
	private function requireValueParameter( Snak $snak, $parameterId ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
116
		if ( !( $snak instanceof PropertyValueSnak ) ) {
117
			throw new ConstraintParameterException(
118
				( new ViolationMessage( 'wbqc-violation-message-parameter-value' ) )
119
					->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
120
			);
121
		}
122
	}
123
124
	/**
125
	 * Parse a single entity ID parameter.
126
	 * @param array $snakSerialization
127
	 * @param string $parameterId
128
	 * @throws ConstraintParameterException
129
	 * @return EntityId
130
	 */
131 View Code Duplication
	private function parseEntityIdParameter( array $snakSerialization, $parameterId ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
132
		$snak = $this->snakDeserializer->deserialize( $snakSerialization );
133
		$this->requireValueParameter( $snak, $parameterId );
134
		$value = $snak->getDataValue();
0 ignored issues
show
Bug introduced by
The method getDataValue does only exist in Wikibase\DataModel\Snak\PropertyValueSnak, but not in Wikibase\DataModel\Snak\...k\PropertySomeValueSnak.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
135
		if ( $value instanceof EntityIdValue ) {
136
			return $value->getEntityId();
137
		} else {
138
			throw new ConstraintParameterException(
139
				( new ViolationMessage( 'wbqc-violation-message-parameter-entity' ) )
140
					->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
141
					->withDataValue( $value, Role::CONSTRAINT_PARAMETER_VALUE )
142
			);
143
		}
144
	}
145
146
	/**
147
	 * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()}
148
	 * @param string $constraintTypeItemId used in error messages
149
	 * @throws ConstraintParameterException if the parameter is invalid or missing
150
	 * @return string[] class entity ID serializations
151
	 */
152
	public function parseClassParameter( array $constraintParameters, $constraintTypeItemId ) {
153
		$this->checkError( $constraintParameters );
154
		$classId = $this->config->get( 'WBQualityConstraintsClassId' );
155
		if ( !array_key_exists( $classId, $constraintParameters ) ) {
156
			throw new ConstraintParameterException(
157
				( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) )
158
					->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
159
					->withEntityId( new PropertyId( $classId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
160
			);
161
		}
162
163
		$classes = [];
164
		foreach ( $constraintParameters[$classId] as $class ) {
165
			$classes[] = $this->parseEntityIdParameter( $class, $classId )->getSerialization();
166
		}
167
		return $classes;
168
	}
169
170
	/**
171
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
172
	 * @param string $constraintTypeItemId used in error messages
173
	 * @throws ConstraintParameterException if the parameter is invalid or missing
174
	 * @return string 'instance', 'subclass', or 'instanceOrSubclass'
175
	 */
176
	public function parseRelationParameter( array $constraintParameters, $constraintTypeItemId ) {
177
		$this->checkError( $constraintParameters );
178
		$relationId = $this->config->get( 'WBQualityConstraintsRelationId' );
179
		if ( !array_key_exists( $relationId, $constraintParameters ) ) {
180
			throw new ConstraintParameterException(
181
				( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) )
182
					->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
183
					->withEntityId( new PropertyId( $relationId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
184
			);
185
		}
186
187
		$this->requireSingleParameter( $constraintParameters, $relationId );
188
		$relationEntityId = $this->parseEntityIdParameter( $constraintParameters[$relationId][0], $relationId );
189
		$instanceId = $this->config->get( 'WBQualityConstraintsInstanceOfRelationId' );
190
		$subclassId = $this->config->get( 'WBQualityConstraintsSubclassOfRelationId' );
191
		$instanceOrSubclassId = $this->config->get( 'WBQualityConstraintsInstanceOrSubclassOfRelationId' );
192
		switch ( $relationEntityId ) {
193
			case $instanceId:
194
				return 'instance';
195
			case $subclassId:
196
				return 'subclass';
197
			case $instanceOrSubclassId:
198
				return 'instanceOrSubclass';
199
			default:
200
				throw new ConstraintParameterException(
201
					( new ViolationMessage( 'wbqc-violation-message-parameter-oneof' ) )
202
						->withEntityId( new PropertyId( $relationId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
203
						->withEntityIdList(
204
							[
205
								new ItemId( $instanceId ),
206
								new ItemId( $subclassId ),
207
								new ItemId( $instanceOrSubclassId ),
208
							],
209
							Role::CONSTRAINT_PARAMETER_VALUE
210
						)
211
				);
212
		}
213
	}
214
215
	/**
216
	 * Parse a single property ID parameter.
217
	 * @param array $snakSerialization
218
	 * @param string $parameterId used in error messages
219
	 * @throws ConstraintParameterException
220
	 * @return PropertyId
221
	 */
222
	private function parsePropertyIdParameter( array $snakSerialization, $parameterId ) {
223
		$snak = $this->snakDeserializer->deserialize( $snakSerialization );
224
		$this->requireValueParameter( $snak, $parameterId );
225
		$value = $snak->getDataValue();
0 ignored issues
show
Bug introduced by
The method getDataValue does only exist in Wikibase\DataModel\Snak\PropertyValueSnak, but not in Wikibase\DataModel\Snak\...k\PropertySomeValueSnak.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
226
		if ( $value instanceof EntityIdValue ) {
227
			$id = $value->getEntityId();
228
			if ( $id instanceof PropertyId ) {
229
				return $id;
230
			}
231
		}
232
		throw new ConstraintParameterException(
233
			( new ViolationMessage( 'wbqc-violation-message-parameter-property' ) )
234
				->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
235
				->withDataValue( $value, Role::CONSTRAINT_PARAMETER_VALUE )
236
		);
237
	}
238
239
	/**
240
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
241
	 * @param string $constraintTypeItemId used in error messages
242
	 *
243
	 * @throws ConstraintParameterException if the parameter is invalid or missing
244
	 * @return PropertyId
245
	 */
246 View Code Duplication
	public function parsePropertyParameter( array $constraintParameters, $constraintTypeItemId ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
247
		$this->checkError( $constraintParameters );
248
		$propertyId = $this->config->get( 'WBQualityConstraintsPropertyId' );
249
		if ( !array_key_exists( $propertyId, $constraintParameters ) ) {
250
			throw new ConstraintParameterException(
251
				( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) )
252
					->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
253
					->withEntityId( new PropertyId( $propertyId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
254
			);
255
		}
256
257
		$this->requireSingleParameter( $constraintParameters, $propertyId );
258
		return $this->parsePropertyIdParameter( $constraintParameters[$propertyId][0], $propertyId );
259
	}
260
261
	private function parseItemIdParameter( PropertyValueSnak $snak, $parameterId ) {
262
		$dataValue = $snak->getDataValue();
263
		if ( $dataValue instanceof EntityIdValue ) {
264
			$entityId = $dataValue->getEntityId();
265
			if ( $entityId instanceof ItemId ) {
266
				return ItemIdSnakValue::fromItemId( $entityId );
267
			}
268
		}
269
		throw new ConstraintParameterException(
270
			( new ViolationMessage( 'wbqc-violation-message-parameter-item' ) )
271
				->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
272
				->withDataValue( $dataValue, Role::CONSTRAINT_PARAMETER_VALUE )
273
		);
274
	}
275
276
	/**
277
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
278
	 * @param string $constraintTypeItemId used in error messages
279
	 * @param bool $required whether the parameter is required (error if absent) or not ([] if absent)
280
	 * @param string|null $parameterId the property ID to use, defaults to 'qualifier of property constraint'
281
	 * @throws ConstraintParameterException if the parameter is invalid or missing
282
	 * @return ItemIdSnakValue[] array of values
283
	 */
284
	public function parseItemsParameter(
285
		array $constraintParameters,
286
		$constraintTypeItemId,
287
		$required,
288
		$parameterId = null
289
	) {
290
		$this->checkError( $constraintParameters );
291
		if ( $parameterId === null ) {
292
			$parameterId = $this->config->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' );
293
		}
294
		if ( !array_key_exists( $parameterId, $constraintParameters ) ) {
295
			if ( $required ) {
296
				throw new ConstraintParameterException(
297
					( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) )
298
						->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
299
						->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
300
				);
301
			} else {
302
				return [];
303
			}
304
		}
305
306
		$values = [];
307
		foreach ( $constraintParameters[$parameterId] as $parameter ) {
308
			$snak = $this->snakDeserializer->deserialize( $parameter );
309
			switch ( true ) {
310
				case $snak instanceof PropertyValueSnak:
311
					$values[] = $this->parseItemIdParameter( $snak, $parameterId );
312
					break;
313
				case $snak instanceof PropertySomeValueSnak:
314
					$values[] = ItemIdSnakValue::someValue();
315
					break;
316
				case $snak instanceof PropertyNoValueSnak:
317
					$values[] = ItemIdSnakValue::noValue();
318
					break;
319
			}
320
		}
321
		return $values;
322
	}
323
324
	/**
325
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
326
	 * @param string $constraintTypeItemId used in error messages
327
	 * @throws ConstraintParameterException if the parameter is invalid or missing
328
	 * @return PropertyId[]
329
	 */
330
	public function parsePropertiesParameter( array $constraintParameters, $constraintTypeItemId ) {
331
		$this->checkError( $constraintParameters );
332
		$propertyId = $this->config->get( 'WBQualityConstraintsPropertyId' );
333
		if ( !array_key_exists( $propertyId, $constraintParameters ) ) {
334
			throw new ConstraintParameterException(
335
				( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) )
336
					->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
337
					->withEntityId( new PropertyId( $propertyId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
338
			);
339
		}
340
341
		$parameters = $constraintParameters[$propertyId];
342
		if ( count( $parameters ) === 1 &&
343
			$this->snakDeserializer->deserialize( $parameters[0] ) instanceof PropertyNoValueSnak
344
		) {
345
			return [];
346
		}
347
348
		$properties = [];
349
		foreach ( $parameters as $parameter ) {
350
			$properties[] = $this->parsePropertyIdParameter( $parameter, $propertyId );
351
		}
352
		return $properties;
353
	}
354
355
	/**
356
	 * @param array $snakSerialization
357
	 * @param string $parameterId
358
	 * @throws ConstraintParameterException
359
	 * @return DataValue|null
360
	 */
361
	private function parseValueOrNoValueParameter( array $snakSerialization, $parameterId ) {
362
		$snak = $this->snakDeserializer->deserialize( $snakSerialization );
363
		if ( $snak instanceof PropertyValueSnak ) {
364
			return $snak->getDataValue();
365
		} elseif ( $snak instanceof PropertyNoValueSnak ) {
366
			return null;
367
		} else {
368
			throw new ConstraintParameterException(
369
				( new ViolationMessage( 'wbqc-violation-message-parameter-value-or-novalue' ) )
370
					->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
371
			);
372
		}
373
	}
374
375
	/**
376
	 * @param array $snakSerialization
377
	 * @param string $parameterId
378
	 * @return DataValue|null
379
	 */
380
	private function parseValueOrNoValueOrNowParameter( array $snakSerialization, $parameterId ) {
381
		try {
382
			return $this->parseValueOrNoValueParameter( $snakSerialization, $parameterId );
383
		} catch ( ConstraintParameterException $e ) {
384
			// unknown value means “now”
385
			return new NowValue();
386
		}
387
	}
388
389
	/**
390
	 * Checks whether there is exactly one non-null quantity with the given unit.
391
	 * @param DataValue|null $min
392
	 * @param DataValue|null $max
393
	 * @param string $unit
394
	 * @return bool
395
	 */
396
	private function exactlyOneQuantityWithUnit( DataValue $min = null, DataValue $max = null, $unit ) {
397
		if ( !( $min instanceof UnboundedQuantityValue ) ||
398
			!( $max instanceof UnboundedQuantityValue )
399
		) {
400
			return false;
401
		}
402
403
		return ( $min->getUnit() === $unit ) !== ( $max->getUnit() === $unit );
404
	}
405
406
	/**
407
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
408
	 * @param string $minimumId
409
	 * @param string $maximumId
410
	 * @param string $constraintTypeItemId used in error messages
411
	 * @param string $type 'quantity' or 'time' (can be data type or data value type)
412
	 *
413
	 * @throws ConstraintParameterException if the parameter is invalid or missing
414
	 * @return DataValue[] if the parameter is invalid or missing
415
	 */
416
	private function parseRangeParameter( array $constraintParameters, $minimumId, $maximumId, $constraintTypeItemId, $type ) {
417
		$this->checkError( $constraintParameters );
418
		if ( !array_key_exists( $minimumId, $constraintParameters ) ||
419
			!array_key_exists( $maximumId, $constraintParameters )
420
		) {
421
			throw new ConstraintParameterException(
422
				( new ViolationMessage( 'wbqc-violation-message-range-parameters-needed' ) )
423
					->withDataValueType( $type )
424
					->withEntityId( new PropertyId( $minimumId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
425
					->withEntityId( new PropertyId( $maximumId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
426
					->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
427
			);
428
		}
429
430
		$this->requireSingleParameter( $constraintParameters, $minimumId );
431
		$this->requireSingleParameter( $constraintParameters, $maximumId );
432
		$parseFunction = $type === 'time' ? 'parseValueOrNoValueOrNowParameter' : 'parseValueOrNoValueParameter';
433
		$min = $this->$parseFunction( $constraintParameters[$minimumId][0], $minimumId );
434
		$max = $this->$parseFunction( $constraintParameters[$maximumId][0], $maximumId );
435
436
		$yearUnit = $this->config->get( 'WBQualityConstraintsYearUnit' );
437
		if ( $this->exactlyOneQuantityWithUnit( $min, $max, $yearUnit ) ) {
438
			throw new ConstraintParameterException(
439
				new ViolationMessage( 'wbqc-violation-message-range-parameters-one-year' )
440
			);
441
		}
442
		if ( $min === null && $max === null ||
443
			$min !== null && $max !== null && $min->equals( $max ) ) {
444
			throw new ConstraintParameterException(
445
				( new ViolationMessage( 'wbqc-violation-message-range-parameters-same' ) )
446
					->withEntityId( new PropertyId( $minimumId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
447
					->withEntityId( new PropertyId( $maximumId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
448
			);
449
		}
450
451
		return [ $min, $max ];
452
	}
453
454
	/**
455
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
456
	 * @param string $constraintTypeItemId used in error messages
457
	 *
458
	 * @throws ConstraintParameterException if the parameter is invalid or missing
459
	 * @return DataValue[] a pair of two data values, either of which may be null to signify an open range
460
	 */
461
	public function parseQuantityRangeParameter( array $constraintParameters, $constraintTypeItemId ) {
462
		return $this->parseRangeParameter(
463
			$constraintParameters,
464
			$this->config->get( 'WBQualityConstraintsMinimumQuantityId' ),
465
			$this->config->get( 'WBQualityConstraintsMaximumQuantityId' ),
466
			$constraintTypeItemId,
467
			'quantity'
468
		);
469
	}
470
471
	/**
472
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
473
	 * @param string $constraintTypeItemId used in error messages
474
	 *
475
	 * @throws ConstraintParameterException if the parameter is invalid or missing
476
	 * @return DataValue[] a pair of two data values, either of which may be null to signify an open range
477
	 */
478
	public function parseTimeRangeParameter( array $constraintParameters, $constraintTypeItemId ) {
479
		return $this->parseRangeParameter(
480
			$constraintParameters,
481
			$this->config->get( 'WBQualityConstraintsMinimumDateId' ),
482
			$this->config->get( 'WBQualityConstraintsMaximumDateId' ),
483
			$constraintTypeItemId,
484
			'time'
485
		);
486
	}
487
488
	/**
489
	 * Parse a single string parameter.
490
	 * @param array $snakSerialization
491
	 * @param string $parameterId
492
	 * @throws ConstraintParameterException
493
	 * @return string
494
	 */
495 View Code Duplication
	private function parseStringParameter( array $snakSerialization, $parameterId ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
496
		$snak = $this->snakDeserializer->deserialize( $snakSerialization );
497
		$this->requireValueParameter( $snak, $parameterId );
498
		$value = $snak->getDataValue();
0 ignored issues
show
Bug introduced by
The method getDataValue does only exist in Wikibase\DataModel\Snak\PropertyValueSnak, but not in Wikibase\DataModel\Snak\...k\PropertySomeValueSnak.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
499
		if ( $value instanceof StringValue ) {
500
			return $value->getValue();
501
		} else {
502
			throw new ConstraintParameterException(
503
				( new ViolationMessage( 'wbqc-violation-message-parameter-string' ) )
504
					->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
505
					->withDataValue( $value, Role::CONSTRAINT_PARAMETER_VALUE )
506
			);
507
		}
508
	}
509
510
	/**
511
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
512
	 * @param string $constraintTypeItemId used in error messages
513
	 * @throws ConstraintParameterException if the parameter is invalid or missing
514
	 * @return string
515
	 */
516
	public function parseNamespaceParameter( array $constraintParameters, $constraintTypeItemId ) {
0 ignored issues
show
Unused Code introduced by
The parameter $constraintTypeItemId is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
517
		$this->checkError( $constraintParameters );
518
		$namespaceId = $this->config->get( 'WBQualityConstraintsNamespaceId' );
519
		if ( !array_key_exists( $namespaceId, $constraintParameters ) ) {
520
			return '';
521
		}
522
523
		$this->requireSingleParameter( $constraintParameters, $namespaceId );
524
		return $this->parseStringParameter( $constraintParameters[$namespaceId][0], $namespaceId );
525
	}
526
527
	/**
528
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
529
	 * @param string $constraintTypeItemId used in error messages
530
	 * @throws ConstraintParameterException if the parameter is invalid or missing
531
	 * @return string
532
	 */
533 View Code Duplication
	public function parseFormatParameter( array $constraintParameters, $constraintTypeItemId ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
534
		$this->checkError( $constraintParameters );
535
		$formatId = $this->config->get( 'WBQualityConstraintsFormatAsARegularExpressionId' );
536
		if ( !array_key_exists( $formatId, $constraintParameters ) ) {
537
			throw new ConstraintParameterException(
538
				( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) )
539
					->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
540
					->withEntityId( new PropertyId( $formatId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
541
			);
542
		}
543
544
		$this->requireSingleParameter( $constraintParameters, $formatId );
545
		return $this->parseStringParameter( $constraintParameters[$formatId][0], $formatId );
546
	}
547
548
	/**
549
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
550
	 * @throws ConstraintParameterException if the parameter is invalid
551
	 * @return EntityId[]
552
	 */
553
	public function parseExceptionParameter( array $constraintParameters ) {
554
		$this->checkError( $constraintParameters );
555
		$exceptionId = $this->config->get( 'WBQualityConstraintsExceptionToConstraintId' );
556
		if ( !array_key_exists( $exceptionId, $constraintParameters ) ) {
557
			return [];
558
		}
559
560
		return array_map(
561
			function( $snakSerialization ) use ( $exceptionId ) {
562
				return $this->parseEntityIdParameter( $snakSerialization, $exceptionId );
563
			},
564
			$constraintParameters[$exceptionId]
565
		);
566
	}
567
568
	/**
569
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
570
	 * @throws ConstraintParameterException if the parameter is invalid
571
	 * @return string|null 'mandatory', 'suggestion' or null
572
	 */
573
	public function parseConstraintStatusParameter( array $constraintParameters ) {
574
		$this->checkError( $constraintParameters );
575
		$constraintStatusId = $this->config->get( 'WBQualityConstraintsConstraintStatusId' );
576
		if ( !array_key_exists( $constraintStatusId, $constraintParameters ) ) {
577
			return null;
578
		}
579
580
		$mandatoryId = $this->config->get( 'WBQualityConstraintsMandatoryConstraintId' );
581
		$supportedStatuses = [ new ItemId( $mandatoryId ) ];
582
		if ( $this->config->get( 'WBQualityConstraintsEnableSuggestionConstraintStatus' ) ) {
583
			$suggestionId = $this->config->get( 'WBQualityConstraintsSuggestionConstraintId' );
584
			$supportedStatuses[] = new ItemId( $suggestionId );
585
		} else {
586
			$suggestionId = null;
587
		}
588
589
		$this->requireSingleParameter( $constraintParameters, $constraintStatusId );
590
		$snak = $this->snakDeserializer->deserialize( $constraintParameters[$constraintStatusId][0] );
591
		$this->requireValueParameter( $snak, $constraintStatusId );
592
		'@phan-var \Wikibase\DataModel\Snak\PropertyValueSnak $snak';
593
		$dataValue = $snak->getDataValue();
0 ignored issues
show
Bug introduced by
The method getDataValue does only exist in Wikibase\DataModel\Snak\PropertyValueSnak, but not in Wikibase\DataModel\Snak\...k\PropertySomeValueSnak.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
594
		'@phan-var EntityIdValue $dataValue';
595
		$entityId = $dataValue->getEntityId();
596
		$statusId = $entityId->getSerialization();
597
598
		if ( $statusId === $mandatoryId ) {
599
			return 'mandatory';
600
		} elseif ( $statusId === $suggestionId ) {
601
			return 'suggestion';
602
		} else {
603
			throw new ConstraintParameterException(
604
				( new ViolationMessage( 'wbqc-violation-message-parameter-oneof' ) )
605
					->withEntityId( new PropertyId( $constraintStatusId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
606
					->withEntityIdList(
607
						$supportedStatuses,
608
						Role::CONSTRAINT_PARAMETER_VALUE
609
					)
610
			);
611
		}
612
	}
613
614
	/**
615
	 * Require that $dataValue is a {@link MonolingualTextValue}.
616
	 * @param DataValue $dataValue
617
	 * @param string $parameterId
618
	 * @return void
619
	 * @throws ConstraintParameterException
620
	 */
621
	private function requireMonolingualTextParameter( DataValue $dataValue, $parameterId ) {
622
		if ( !( $dataValue instanceof MonolingualTextValue ) ) {
623
			throw new ConstraintParameterException(
624
				( new ViolationMessage( 'wbqc-violation-message-parameter-monolingualtext' ) )
625
					->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
626
					->withDataValue( $dataValue, Role::CONSTRAINT_PARAMETER_VALUE )
627
			);
628
		}
629
	}
630
631
	/**
632
	 * Parse a series of monolingual text snaks (serialized) into a map from language code to string.
633
	 *
634
	 * @param array $snakSerializations
635
	 * @param string $parameterId
636
	 * @throws ConstraintParameterException if invalid snaks are found or a language has multiple texts
637
	 * @return MultilingualTextValue
638
	 */
639
	private function parseMultilingualTextParameter( array $snakSerializations, $parameterId ) {
640
		$result = [];
641
642
		foreach ( $snakSerializations as $snakSerialization ) {
643
			$snak = $this->snakDeserializer->deserialize( $snakSerialization );
644
			$this->requireValueParameter( $snak, $parameterId );
645
646
			$value = $snak->getDataValue();
0 ignored issues
show
Bug introduced by
The method getDataValue does only exist in Wikibase\DataModel\Snak\PropertyValueSnak, but not in Wikibase\DataModel\Snak\...k\PropertySomeValueSnak.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
647
			$this->requireMonolingualTextParameter( $value, $parameterId );
648
			/** @var MonolingualTextValue $value */
649
			'@phan-var MonolingualTextValue $value';
650
651
			$code = $value->getLanguageCode();
652
			if ( array_key_exists( $code, $result ) ) {
653
				throw new ConstraintParameterException(
654
					( new ViolationMessage( 'wbqc-violation-message-parameter-single-per-language' ) )
655
						->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
656
						->withLanguage( $code )
657
				);
658
			}
659
660
			$result[$code] = $value;
661
		}
662
663
		return new MultilingualTextValue( $result );
664
	}
665
666
	/**
667
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
668
	 * @throws ConstraintParameterException if the parameter is invalid
669
	 * @return MultilingualTextValue
670
	 */
671
	public function parseSyntaxClarificationParameter( array $constraintParameters ) {
672
		$syntaxClarificationId = $this->config->get( 'WBQualityConstraintsSyntaxClarificationId' );
673
674
		if ( !array_key_exists( $syntaxClarificationId, $constraintParameters ) ) {
675
			return new MultilingualTextValue( [] );
676
		}
677
678
		$syntaxClarifications = $this->parseMultilingualTextParameter(
679
			$constraintParameters[$syntaxClarificationId],
680
			$syntaxClarificationId
681
		);
682
683
		return $syntaxClarifications;
684
	}
685
686
	/**
687
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
688
	 * @param string $constraintTypeItemId used in error messages
689
	 * @param string[]|null $validScopes a list of Context::TYPE_* constants which are valid where this parameter appears.
690
	 * If this is not null and one of the specified scopes is not in this list, a ConstraintParameterException is thrown.
691
	 * @throws ConstraintParameterException if the parameter is invalid
692
	 * @return string[]|null Context::TYPE_* constants
693
	 */
694
	public function parseConstraintScopeParameter( array $constraintParameters, $constraintTypeItemId, array $validScopes = null ) {
695
		$contextTypes = [];
696
		$parameterId = $this->config->get( 'WBQualityConstraintsConstraintScopeId' );
697
		$items = $this->parseItemsParameter(
698
			$constraintParameters,
699
			$constraintTypeItemId,
700
			false,
701
			$parameterId
702
		);
703
704
		if ( $items === [] ) {
705
			return null;
706
		}
707
708
		foreach ( $items as $item ) {
709
			$contextTypes[] = $this->parseContextTypeItem( $item, 'constraint scope', $parameterId );
710
		}
711
712
		if ( $validScopes !== null ) {
713
			$invalidScopes = array_diff( $contextTypes, $validScopes );
714
			if ( $invalidScopes !== [] ) {
715
				$invalidScope = array_pop( $invalidScopes );
716
				throw new ConstraintParameterException(
717
					( new ViolationMessage( 'wbqc-violation-message-invalid-scope' ) )
718
						->withConstraintScope( $invalidScope, Role::CONSTRAINT_PARAMETER_VALUE )
719
						->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
720
						->withConstraintScopeList( $validScopes, Role::CONSTRAINT_PARAMETER_VALUE )
721
				);
722
			}
723
		}
724
725
		return $contextTypes;
726
	}
727
728
	/**
729
	 * Turn an item ID into a full unit string (using the concept URI).
730
	 *
731
	 * @param ItemId $unitId
732
	 * @return string unit
733
	 */
734
	private function parseUnitParameter( ItemId $unitId ) {
735
		$unitRepositoryName = $unitId->getRepositoryName();
736
		if ( !array_key_exists( $unitRepositoryName, $this->conceptBaseUris ) ) {
737
			throw new LogicException(
738
				'No base URI for concept URI for repository: ' . $unitRepositoryName
739
			);
740
		}
741
		$baseUri = $this->conceptBaseUris[$unitRepositoryName];
742
		return $baseUri . $unitId->getSerialization();
743
	}
744
745
	/**
746
	 * Turn an ItemIdSnakValue into a single unit parameter.
747
	 *
748
	 * @param ItemIdSnakValue $item
749
	 * @return UnitsParameter
750
	 * @throws ConstraintParameterException
751
	 */
752
	private function parseUnitItem( ItemIdSnakValue $item ) {
753
		switch ( true ) {
754
			case $item->isValue():
755
				$unit = $this->parseUnitParameter( $item->getItemId() );
0 ignored issues
show
Bug introduced by
It seems like $item->getItemId() can be null; however, parseUnitParameter() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
756
				return new UnitsParameter(
757
					[ $item->getItemId() ],
0 ignored issues
show
Documentation introduced by
array($item->getItemId()) is of type array<integer,object<Wik...Entity\\ItemId>|null"}>, but the function expects a array<integer,object<Wik...taModel\Entity\ItemId>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
758
					[ UnboundedQuantityValue::newFromNumber( 1, $unit ) ],
759
					false
760
				);
761
			case $item->isSomeValue():
762
				$qualifierId = $this->config->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' );
763
				throw new ConstraintParameterException(
764
					( new ViolationMessage( 'wbqc-violation-message-parameter-value-or-novalue' ) )
765
						->withEntityId( new PropertyId( $qualifierId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
766
				);
767
			case $item->isNoValue():
768
				return new UnitsParameter( [], [], true );
769
		}
770
	}
771
772
	/**
773
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
774
	 * @param string $constraintTypeItemId used in error messages
775
	 * @throws ConstraintParameterException if the parameter is invalid or missing
776
	 * @return UnitsParameter
777
	 */
778
	public function parseUnitsParameter( array $constraintParameters, $constraintTypeItemId ) {
779
		$items = $this->parseItemsParameter( $constraintParameters, $constraintTypeItemId, true );
780
		$unitItems = [];
781
		$unitQuantities = [];
782
		$unitlessAllowed = false;
783
784
		foreach ( $items as $item ) {
785
			$unit = $this->parseUnitItem( $item );
786
			$unitItems = array_merge( $unitItems, $unit->getUnitItemIds() );
787
			$unitQuantities = array_merge( $unitQuantities, $unit->getUnitQuantities() );
788
			$unitlessAllowed = $unitlessAllowed || $unit->getUnitlessAllowed();
789
		}
790
791
		if ( $unitQuantities === [] && !$unitlessAllowed ) {
792
			throw new LogicException(
793
				'The "units" parameter is required, and yet we seem to be missing any allowed unit'
794
			);
795
		}
796
797
		return new UnitsParameter( $unitItems, $unitQuantities, $unitlessAllowed );
798
	}
799
800
	/**
801
	 * Turn an ItemIdSnakValue into a single entity type parameter.
802
	 *
803
	 * @param ItemIdSnakValue $item
804
	 * @return EntityTypesParameter
805
	 * @throws ConstraintParameterException
806
	 */
807
	private function parseEntityTypeItem( ItemIdSnakValue $item ) {
808
		$parameterId = $this->config->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' );
809
810
		if ( !$item->isValue() ) {
811
			throw new ConstraintParameterException(
812
				( new ViolationMessage( 'wbqc-violation-message-parameter-value' ) )
813
					->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
814
			);
815
		}
816
817
		$itemId = $item->getItemId();
818
		switch ( $itemId->getSerialization() ) {
819
			case $this->config->get( 'WBQualityConstraintsWikibaseItemId' ):
820
				$entityType = 'item';
821
				break;
822
			case $this->config->get( 'WBQualityConstraintsWikibasePropertyId' ):
823
				$entityType = 'property';
824
				break;
825
			case $this->config->get( 'WBQualityConstraintsWikibaseLexemeId' ):
826
				$entityType = 'lexeme';
827
				break;
828
			case $this->config->get( 'WBQualityConstraintsWikibaseFormId' ):
829
				$entityType = 'form';
830
				break;
831
			case $this->config->get( 'WBQualityConstraintsWikibaseSenseId' ):
832
				$entityType = 'sense';
833
				break;
834
			case $this->config->get( 'WBQualityConstraintsWikibaseMediaInfoId' ):
835
				$entityType = 'mediainfo';
836
				break;
837
			default:
838
				$allowed = [
839
					new ItemId( $this->config->get( 'WBQualityConstraintsWikibaseItemId' ) ),
840
					new ItemId( $this->config->get( 'WBQualityConstraintsWikibasePropertyId' ) ),
841
					new ItemId( $this->config->get( 'WBQualityConstraintsWikibaseLexemeId' ) ),
842
					new ItemId( $this->config->get( 'WBQualityConstraintsWikibaseFormId' ) ),
843
					new ItemId( $this->config->get( 'WBQualityConstraintsWikibaseSenseId' ) ),
844
					new ItemId( $this->config->get( 'WBQualityConstraintsWikibaseMediaInfoId' ) ),
845
				];
846
				throw new ConstraintParameterException(
847
					( new ViolationMessage( 'wbqc-violation-message-parameter-oneof' ) )
848
						->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
849
						->withEntityIdList( $allowed, Role::CONSTRAINT_PARAMETER_VALUE )
850
				);
851
		}
852
853
		return new EntityTypesParameter( [ $entityType ], [ $itemId ] );
0 ignored issues
show
Documentation introduced by
array($itemId) is of type array<integer,object<Wik...Entity\\ItemId>|null"}>, but the function expects a array<integer,object<Wik...taModel\Entity\ItemId>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
854
	}
855
856
	/**
857
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
858
	 * @param string $constraintTypeItemId used in error messages
859
	 * @throws ConstraintParameterException if the parameter is invalid or missing
860
	 * @return EntityTypesParameter
861
	 */
862
	public function parseEntityTypesParameter( array $constraintParameters, $constraintTypeItemId ) {
863
		$entityTypes = [];
864
		$entityTypeItemIds = [];
865
		$items = $this->parseItemsParameter( $constraintParameters, $constraintTypeItemId, true );
866
867
		foreach ( $items as $item ) {
868
			$entityType = $this->parseEntityTypeItem( $item );
869
			$entityTypes = array_merge( $entityTypes, $entityType->getEntityTypes() );
870
			$entityTypeItemIds = array_merge( $entityTypeItemIds, $entityType->getEntityTypeItemIds() );
871
		}
872
873
		if ( empty( $entityTypes ) ) {
874
			// @codeCoverageIgnoreStart
875
			throw new LogicException(
876
				'The "entity types" parameter is required, ' .
877
				'and yet we seem to be missing any allowed entity type'
878
			);
879
			// @codeCoverageIgnoreEnd
880
		}
881
882
		return new EntityTypesParameter( $entityTypes, $entityTypeItemIds );
883
	}
884
885
	/**
886
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
887
	 * @throws ConstraintParameterException if the parameter is invalid
888
	 * @return PropertyId[]
889
	 */
890
	public function parseSeparatorsParameter( array $constraintParameters ) {
891
		$separatorId = $this->config->get( 'WBQualityConstraintsSeparatorId' );
892
893
		if ( !array_key_exists( $separatorId, $constraintParameters ) ) {
894
			return [];
895
		}
896
897
		$parameters = $constraintParameters[$separatorId];
898
		$separators = [];
899
900
		foreach ( $parameters as $parameter ) {
901
			$separators[] = $this->parsePropertyIdParameter( $parameter, $separatorId );
902
		}
903
904
		return $separators;
905
	}
906
907
	/**
908
	 * Turn an ItemIdSnakValue into a single context type parameter.
909
	 *
910
	 * @param ItemIdSnakValue $item
911
	 * @param string $use 'constraint scope' or 'property scope'
912
	 * @param string $parameterId used in error messages
913
	 * @return string one of the Context::TYPE_* constants
914
	 * @throws ConstraintParameterException
915
	 */
916
	private function parseContextTypeItem( ItemIdSnakValue $item, $use, $parameterId ) {
917
		if ( !$item->isValue() ) {
918
			throw new ConstraintParameterException(
919
				( new ViolationMessage( 'wbqc-violation-message-parameter-value' ) )
920
					->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
921
			);
922
		}
923
924
		if ( $use === 'constraint scope' ) {
925
			$mainSnakId = $this->config->get( 'WBQualityConstraintsConstraintCheckedOnMainValueId' );
926
			$qualifiersId = $this->config->get( 'WBQualityConstraintsConstraintCheckedOnQualifiersId' );
927
			$referencesId = $this->config->get( 'WBQualityConstraintsConstraintCheckedOnReferencesId' );
928
		} else {
929
			$mainSnakId = $this->config->get( 'WBQualityConstraintsAsMainValueId' );
930
			$qualifiersId = $this->config->get( 'WBQualityConstraintsAsQualifiersId' );
931
			$referencesId = $this->config->get( 'WBQualityConstraintsAsReferencesId' );
932
		}
933
934
		$itemId = $item->getItemId();
935
		switch ( $itemId->getSerialization() ) {
936
			case $mainSnakId:
937
				return Context::TYPE_STATEMENT;
938
			case $qualifiersId:
939
				return Context::TYPE_QUALIFIER;
940
			case $referencesId:
941
				return Context::TYPE_REFERENCE;
942
			default:
943
				$allowed = [ $mainSnakId, $qualifiersId, $referencesId ];
944
				throw new ConstraintParameterException(
945
					( new ViolationMessage( 'wbqc-violation-message-parameter-oneof' ) )
946
						->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
947
						->withEntityIdList( $allowed, Role::CONSTRAINT_PARAMETER_VALUE )
948
				);
949
		}
950
	}
951
952
	/**
953
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
954
	 * @param string $constraintTypeItemId used in error messages
955
	 * @throws ConstraintParameterException if the parameter is invalid or missing
956
	 * @return string[] list of Context::TYPE_* constants
957
	 */
958
	public function parsePropertyScopeParameter( array $constraintParameters, $constraintTypeItemId ) {
959
		$contextTypes = [];
960
		$parameterId = $this->config->get( 'WBQualityConstraintsPropertyScopeId' );
961
		$items = $this->parseItemsParameter(
962
			$constraintParameters,
963
			$constraintTypeItemId,
964
			true,
965
			$parameterId
966
		);
967
968
		foreach ( $items as $item ) {
969
			$contextTypes[] = $this->parseContextTypeItem( $item, 'property scope', $parameterId );
970
		}
971
972
		if ( empty( $contextTypes ) ) {
973
			// @codeCoverageIgnoreStart
974
			throw new LogicException(
975
				'The "property scope" parameter is required, ' .
976
				'and yet we seem to be missing any allowed scope'
977
			);
978
			// @codeCoverageIgnoreEnd
979
		}
980
981
		return $contextTypes;
982
	}
983
984
}
985