Completed
Push — master ( 1a1840...6d8e1d )
by
unknown
06:19
created

parseTimeRangeParameter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 7
nc 1
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 Wikibase\DataModel\DeserializerFactory;
12
use Wikibase\DataModel\Deserializers\SnakDeserializer;
13
use Wikibase\DataModel\Entity\EntityId;
14
use Wikibase\DataModel\Entity\EntityIdValue;
15
use Wikibase\DataModel\Entity\ItemId;
16
use Wikibase\DataModel\Entity\PropertyId;
17
use Wikibase\DataModel\Snak\PropertyNoValueSnak;
18
use Wikibase\DataModel\Snak\PropertySomeValueSnak;
19
use Wikibase\DataModel\Snak\PropertyValueSnak;
20
use Wikibase\DataModel\Snak\Snak;
21
use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\Context;
22
use WikibaseQuality\ConstraintReport\ConstraintCheck\ItemIdSnakValue;
23
use WikibaseQuality\ConstraintReport\ConstraintCheck\Message\ViolationMessage;
24
use WikibaseQuality\ConstraintReport\ConstraintParameterRenderer;
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\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 ConstraintParameterRenderer
54
	 */
55
	private $constraintParameterRenderer;
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 ConstraintParameterRenderer $constraintParameterRenderer
63
	 *   used to render incorrect parameters for error messages
64
	 */
65
	public function __construct(
66
		Config $config,
67
		DeserializerFactory $factory,
68
		ConstraintParameterRenderer $constraintParameterRenderer
69
	) {
70
		$this->config = $config;
71
		$this->snakDeserializer = $factory->newSnakDeserializer();
72
		$this->constraintParameterRenderer = $constraintParameterRenderer;
73
	}
74
75
	/**
76
	 * Check if any errors are recorded in the constraint parameters.
77
	 * @param array $parameters
78
	 * @throws ConstraintParameterException
79
	 */
80
	public function checkError( array $parameters ) {
81
		if ( array_key_exists( '@error', $parameters ) ) {
82
			$error = $parameters['@error'];
83
			if ( array_key_exists( 'toolong', $error ) && $error['toolong'] ) {
84
				$msg = 'wbqc-violation-message-parameters-error-toolong';
85
			} else {
86
				$msg = 'wbqc-violation-message-parameters-error-unknown';
87
			}
88
			throw new ConstraintParameterException( new ViolationMessage( $msg ) );
89
		}
90
	}
91
92
	/**
93
	 * Require that $parameters contains exactly one $parameterId parameter.
94
	 * @param array $parameters
95
	 * @param string $parameterId
96
	 * @throws ConstraintParameterException
97
	 */
98 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...
99
		if ( count( $parameters[$parameterId] ) !== 1 ) {
100
			throw new ConstraintParameterException(
101
				( new ViolationMessage( 'wbqc-violation-message-parameter-single' ) )
102
					->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
103
			);
104
		}
105
	}
106
107
	/**
108
	 * Require that $snak is a {@link PropertyValueSnak}.
109
	 * @param Snak $snak
110
	 * @param string $parameterId
111
	 * @return void
112
	 * @throws ConstraintParameterException
113
	 */
114 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...
115
		if ( !( $snak instanceof PropertyValueSnak ) ) {
116
			throw new ConstraintParameterException(
117
				( new ViolationMessage( 'wbqc-violation-message-parameter-value' ) )
118
					->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
119
			);
120
		}
121
	}
122
123
	/**
124
	 * Parse a single entity ID parameter.
125
	 * @param array $snakSerialization
126
	 * @param string $parameterId
127
	 * @throws ConstraintParameterException
128
	 * @return EntityId
129
	 */
130 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...
131
		$snak = $this->snakDeserializer->deserialize( $snakSerialization );
132
		$this->requireValueParameter( $snak, $parameterId );
133
		$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...
134
		if ( $value instanceof EntityIdValue ) {
135
			return $value->getEntityId();
136
		} else {
137
			throw new ConstraintParameterException(
138
				( new ViolationMessage( 'wbqc-violation-message-parameter-entity' ) )
139
					->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
140
					->withDataValue( $value, Role::CONSTRAINT_PARAMETER_VALUE )
141
			);
142
		}
143
	}
144
145
	/**
146
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
147
	 * @param string $constraintTypeItemId used in error messages
148
	 * @throws ConstraintParameterException if the parameter is invalid or missing
149
	 * @return string[] class entity ID serializations
150
	 */
151
	public function parseClassParameter( array $constraintParameters, $constraintTypeItemId ) {
152
		$this->checkError( $constraintParameters );
153
		$classId = $this->config->get( 'WBQualityConstraintsClassId' );
154
		if ( !array_key_exists( $classId, $constraintParameters ) ) {
155
			throw new ConstraintParameterException(
156
				( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) )
157
					->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
158
					->withEntityId( new PropertyId( $classId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
159
			);
160
		}
161
162
		$classes = [];
163
		foreach ( $constraintParameters[$classId] as $class ) {
164
			$classes[] = $this->parseEntityIdParameter( $class, $classId )->getSerialization();
165
		}
166
		return $classes;
167
	}
168
169
	/**
170
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
171
	 * @param string $constraintTypeItemId used in error messages
172
	 * @throws ConstraintParameterException if the parameter is invalid or missing
173
	 * @return string 'instance', 'subclass', or 'instanceOrSubclass'
174
	 */
175
	public function parseRelationParameter( array $constraintParameters, $constraintTypeItemId ) {
176
		$this->checkError( $constraintParameters );
177
		$relationId = $this->config->get( 'WBQualityConstraintsRelationId' );
178
		if ( !array_key_exists( $relationId, $constraintParameters ) ) {
179
			throw new ConstraintParameterException(
180
				( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) )
181
					->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
182
					->withEntityId( new PropertyId( $relationId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
183
			);
184
		}
185
186
		$this->requireSingleParameter( $constraintParameters, $relationId );
187
		$relationEntityId = $this->parseEntityIdParameter( $constraintParameters[$relationId][0], $relationId );
188
		$instanceId = $this->config->get( 'WBQualityConstraintsInstanceOfRelationId' );
189
		$subclassId = $this->config->get( 'WBQualityConstraintsSubclassOfRelationId' );
190
		$instanceOrSubclassId = $this->config->get( 'WBQualityConstraintsInstanceOrSubclassOfRelationId' );
191
		switch ( $relationEntityId ) {
192
			case $instanceId:
193
				return 'instance';
194
			case $subclassId:
195
				return 'subclass';
196
			case $instanceOrSubclassId:
197
				return 'instanceOrSubclass';
198 View Code Duplication
			default:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
199
				throw new ConstraintParameterException(
200
					( new ViolationMessage( 'wbqc-violation-message-parameter-oneof' ) )
201
						->withEntityId( new PropertyId( $relationId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
202
						->withEntityIdList(
203
							[
204
								new ItemId( $instanceId ),
205
								new ItemId( $subclassId ),
206
								new ItemId( $instanceOrSubclassId ),
207
							],
208
							Role::CONSTRAINT_PARAMETER_VALUE
209
						)
210
				);
211
		}
212
	}
213
214
	/**
215
	 * Parse a single property ID parameter.
216
	 * @param array $snakSerialization
217
	 * @param string $parameterId used in error messages
218
	 * @throws ConstraintParameterException
219
	 * @return PropertyId
220
	 */
221
	private function parsePropertyIdParameter( array $snakSerialization, $parameterId ) {
222
		$snak = $this->snakDeserializer->deserialize( $snakSerialization );
223
		$this->requireValueParameter( $snak, $parameterId );
224
		$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...
225
		if ( $value instanceof EntityIdValue ) {
226
			$id = $value->getEntityId();
227
			if ( $id instanceof PropertyId ) {
228
				return $id;
229
			}
230
		}
231
		throw new ConstraintParameterException(
232
			( new ViolationMessage( 'wbqc-violation-message-parameter-property' ) )
233
				->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
234
				->withDataValue( $value, Role::CONSTRAINT_PARAMETER_VALUE )
235
		);
236
	}
237
238
	/**
239
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
240
	 * @param string $constraintTypeItemId used in error messages
241
	 *
242
	 * @throws ConstraintParameterException if the parameter is invalid or missing
243
	 * @return PropertyId
244
	 */
245 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...
246
		$this->checkError( $constraintParameters );
247
		$propertyId = $this->config->get( 'WBQualityConstraintsPropertyId' );
248
		if ( !array_key_exists( $propertyId, $constraintParameters ) ) {
249
			throw new ConstraintParameterException(
250
				( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) )
251
					->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
252
					->withEntityId( new PropertyId( $propertyId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
253
			);
254
		}
255
256
		$this->requireSingleParameter( $constraintParameters, $propertyId );
257
		return $this->parsePropertyIdParameter( $constraintParameters[$propertyId][0], $propertyId );
258
	}
259
260
	private function parseItemIdParameter( PropertyValueSnak $snak, $parameterId ) {
261
		$dataValue = $snak->getDataValue();
262
		if ( $dataValue instanceof EntityIdValue &&
263
			$dataValue->getEntityId() instanceof ItemId
264
		) {
265
			return ItemIdSnakValue::fromItemId( $dataValue->getEntityId() );
266
		} else {
267
			throw new ConstraintParameterException(
268
				( new ViolationMessage( 'wbqc-violation-message-parameter-item' ) )
269
					->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
270
					->withDataValue( $dataValue, Role::CONSTRAINT_PARAMETER_VALUE )
271
			);
272
		}
273
	}
274
275
	/**
276
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
277
	 * @param string $constraintTypeItemId used in error messages
278
	 * @param bool $required whether the parameter is required (error if absent) or not ([] if absent)
279
	 * @throws ConstraintParameterException if the parameter is invalid or missing
280
	 * @return ItemIdSnakValue[] array of values
281
	 */
282
	public function parseItemsParameter( array $constraintParameters, $constraintTypeItemId, $required ) {
283
		$this->checkError( $constraintParameters );
284
		$qualifierId = $this->config->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' );
285
		if ( !array_key_exists( $qualifierId, $constraintParameters ) ) {
286
			if ( $required ) {
287
				throw new ConstraintParameterException(
288
					( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) )
289
						->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
290
						->withEntityId( new PropertyId( $qualifierId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
291
				);
292
			} else {
293
				return [];
294
			}
295
		}
296
297
		$values = [];
298
		foreach ( $constraintParameters[$qualifierId] as $parameter ) {
299
			$snak = $this->snakDeserializer->deserialize( $parameter );
300
			switch ( true ) {
301
				case $snak instanceof PropertyValueSnak:
302
					$values[] = $this->parseItemIdParameter( $snak, $qualifierId );
303
					break;
304
				case $snak instanceof PropertySomeValueSnak:
305
					$values[] = ItemIdSnakValue::someValue();
306
					break;
307
				case $snak instanceof PropertyNoValueSnak:
308
					$values[] = ItemIdSnakValue::noValue();
309
					break;
310
			}
311
		}
312
		return $values;
313
	}
314
315
	/**
316
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
317
	 * @param string $constraintTypeItemId used in error messages
318
	 * @throws ConstraintParameterException if the parameter is invalid or missing
319
	 * @return PropertyId[]
320
	 */
321
	public function parsePropertiesParameter( array $constraintParameters, $constraintTypeItemId ) {
322
		$this->checkError( $constraintParameters );
323
		$propertyId = $this->config->get( 'WBQualityConstraintsPropertyId' );
324
		if ( !array_key_exists( $propertyId, $constraintParameters ) ) {
325
			throw new ConstraintParameterException(
326
				( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) )
327
					->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
328
					->withEntityId( new PropertyId( $propertyId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
329
			);
330
		}
331
332
		$parameters = $constraintParameters[$propertyId];
333
		if ( count( $parameters ) === 1 &&
334
			$this->snakDeserializer->deserialize( $parameters[0] ) instanceof PropertyNoValueSnak
335
		) {
336
			return [];
337
		}
338
339
		$properties = [];
340
		foreach ( $parameters as $parameter ) {
341
			$properties[] = $this->parsePropertyIdParameter( $parameter, $propertyId );
342
		}
343
		return $properties;
344
	}
345
346
	/**
347
	 * @param array $snakSerialization
348
	 * @param string $parameterId
349
	 * @throws ConstraintParameterException
350
	 * @return DataValue|null
351
	 */
352
	private function parseValueOrNoValueParameter( array $snakSerialization, $parameterId ) {
353
		$snak = $this->snakDeserializer->deserialize( $snakSerialization );
354
		if ( $snak instanceof PropertyValueSnak ) {
355
			return $snak->getDataValue();
356
		} elseif ( $snak instanceof PropertyNoValueSnak ) {
357
			return null;
358
		} else {
359
			throw new ConstraintParameterException(
360
				( new ViolationMessage( 'wbqc-violation-message-parameter-value-or-novalue' ) )
361
					->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
362
			);
363
		}
364
	}
365
366
	/**
367
	 * @param array $snakSerialization
368
	 * @param string $parameterId
369
	 * @return DataValue|null
370
	 */
371
	private function parseValueOrNoValueOrNowParameter( array $snakSerialization, $parameterId ) {
372
		try {
373
			return $this->parseValueOrNoValueParameter( $snakSerialization, $parameterId );
374
		} catch ( ConstraintParameterException $e ) {
375
			// unknown value means “now”
376
			return new NowValue();
377
		}
378
	}
379
380
	/**
381
	 * Checks whether there is exactly one non-null quantity with the given unit.
382
	 * @param DataValue|null $min
383
	 * @param DataValue|null $max
384
	 * @param string $unit
385
	 * @return bool
386
	 */
387
	private function exactlyOneQuantityWithUnit( DataValue $min = null, DataValue $max = null, $unit ) {
388
		if ( !( $min instanceof UnboundedQuantityValue ) ||
0 ignored issues
show
Bug introduced by
The class DataValues\UnboundedQuantityValue does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
389
			!( $max instanceof UnboundedQuantityValue )
0 ignored issues
show
Bug introduced by
The class DataValues\UnboundedQuantityValue does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
390
		) {
391
			return false;
392
		}
393
394
		return ( $min->getUnit() === $unit ) !== ( $max->getUnit() === $unit );
395
	}
396
397
	/**
398
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
399
	 * @param string $minimumId
400
	 * @param string $maximumId
401
	 * @param string $constraintTypeItemId used in error messages
402
	 * @param string $type 'quantity' or 'time' (can be data type or data value type)
403
	 *
404
	 * @throws ConstraintParameterException if the parameter is invalid or missing
405
	 * @return DataValue[] if the parameter is invalid or missing
406
	 */
407
	private function parseRangeParameter( array $constraintParameters, $minimumId, $maximumId, $constraintTypeItemId, $type ) {
408
		$this->checkError( $constraintParameters );
409
		if ( !array_key_exists( $minimumId, $constraintParameters ) ||
410
			!array_key_exists( $maximumId, $constraintParameters )
411
		) {
412
			throw new ConstraintParameterException(
413
				( new ViolationMessage( 'wbqc-violation-message-range-parameters-needed' ) )
414
					->withDataValueType( $type )
415
					->withEntityId( new PropertyId( $minimumId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
416
					->withEntityId( new PropertyId( $maximumId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
417
					->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
418
			);
419
		}
420
421
		$this->requireSingleParameter( $constraintParameters, $minimumId );
422
		$this->requireSingleParameter( $constraintParameters, $maximumId );
423
		$parseFunction = $type === 'time' ? 'parseValueOrNoValueOrNowParameter' : 'parseValueOrNoValueParameter';
424
		$min = $this->$parseFunction( $constraintParameters[$minimumId][0], $minimumId );
425
		$max = $this->$parseFunction( $constraintParameters[$maximumId][0], $maximumId );
426
427
		$yearUnit = $this->config->get( 'WBQualityConstraintsYearUnit' );
428
		if ( $this->exactlyOneQuantityWithUnit( $min, $max, $yearUnit ) ) {
429
			throw new ConstraintParameterException(
430
				new ViolationMessage( 'wbqc-violation-message-range-parameters-one-year' )
431
			);
432
		}
433
		if ( $min === null && $max === null ||
434
			$min !== null && $max !== null && $min->equals( $max ) ) {
435
			throw new ConstraintParameterException(
436
				( new ViolationMessage( 'wbqc-violation-message-range-parameters-same' ) )
437
					->withEntityId( new PropertyId( $minimumId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
438
					->withEntityId( new PropertyId( $maximumId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
439
			);
440
		}
441
442
		return [ $min, $max ];
443
	}
444
445
	/**
446
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
447
	 * @param string $constraintTypeItemId used in error messages
448
	 *
449
	 * @throws ConstraintParameterException if the parameter is invalid or missing
450
	 * @return DataValue[] a pair of two data values, either of which may be null to signify an open range
451
	 */
452
	public function parseQuantityRangeParameter( array $constraintParameters, $constraintTypeItemId ) {
453
		return $this->parseRangeParameter(
454
			$constraintParameters,
455
			$this->config->get( 'WBQualityConstraintsMinimumQuantityId' ),
456
			$this->config->get( 'WBQualityConstraintsMaximumQuantityId' ),
457
			$constraintTypeItemId,
458
			'quantity'
459
		);
460
	}
461
462
	/**
463
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
464
	 * @param string $constraintTypeItemId used in error messages
465
	 *
466
	 * @throws ConstraintParameterException if the parameter is invalid or missing
467
	 * @return DataValue[] a pair of two data values, either of which may be null to signify an open range
468
	 */
469
	public function parseTimeRangeParameter( array $constraintParameters, $constraintTypeItemId ) {
470
		return $this->parseRangeParameter(
471
			$constraintParameters,
472
			$this->config->get( 'WBQualityConstraintsMinimumDateId' ),
473
			$this->config->get( 'WBQualityConstraintsMaximumDateId' ),
474
			$constraintTypeItemId,
475
			'time'
476
		);
477
	}
478
479
	/**
480
	 * Parse a single string parameter.
481
	 * @param array $snakSerialization
482
	 * @param string $parameterId
483
	 * @throws ConstraintParameterException
484
	 * @return string
485
	 */
486 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...
487
		$snak = $this->snakDeserializer->deserialize( $snakSerialization );
488
		$this->requireValueParameter( $snak, $parameterId );
489
		$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...
490
		if ( $value instanceof StringValue ) {
491
			return $value->getValue();
492
		} else {
493
			throw new ConstraintParameterException(
494
				( new ViolationMessage( 'wbqc-violation-message-parameter-string' ) )
495
					->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
496
					->withDataValue( $value, Role::CONSTRAINT_PARAMETER_VALUE )
497
			);
498
		}
499
	}
500
501
	/**
502
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
503
	 * @param string $constraintTypeItemId used in error messages
504
	 * @throws ConstraintParameterException if the parameter is invalid or missing
505
	 * @return string
506
	 */
507
	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...
508
		$this->checkError( $constraintParameters );
509
		$namespaceId = $this->config->get( 'WBQualityConstraintsNamespaceId' );
510
		if ( !array_key_exists( $namespaceId, $constraintParameters ) ) {
511
			return '';
512
		}
513
514
		$this->requireSingleParameter( $constraintParameters, $namespaceId );
515
		return $this->parseStringParameter( $constraintParameters[$namespaceId][0], $namespaceId );
516
	}
517
518
	/**
519
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
520
	 * @param string $constraintTypeItemId used in error messages
521
	 * @throws ConstraintParameterException if the parameter is invalid or missing
522
	 * @return string
523
	 */
524 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...
525
		$this->checkError( $constraintParameters );
526
		$formatId = $this->config->get( 'WBQualityConstraintsFormatAsARegularExpressionId' );
527
		if ( !array_key_exists( $formatId, $constraintParameters ) ) {
528
			throw new ConstraintParameterException(
529
				( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) )
530
					->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
531
					->withEntityId( new PropertyId( $formatId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
532
			);
533
		}
534
535
		$this->requireSingleParameter( $constraintParameters, $formatId );
536
		return $this->parseStringParameter( $constraintParameters[$formatId][0], $formatId );
537
	}
538
539
	/**
540
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
541
	 * @throws ConstraintParameterException if the parameter is invalid
542
	 * @return EntityId[]
543
	 */
544
	public function parseExceptionParameter( array $constraintParameters ) {
545
		$this->checkError( $constraintParameters );
546
		$exceptionId = $this->config->get( 'WBQualityConstraintsExceptionToConstraintId' );
547
		if ( !array_key_exists( $exceptionId, $constraintParameters ) ) {
548
			return [];
549
		}
550
551
		return array_map(
552
			function( $snakSerialization ) use ( $exceptionId ) {
553
				return $this->parseEntityIdParameter( $snakSerialization, $exceptionId );
554
			},
555
			$constraintParameters[$exceptionId]
556
		);
557
	}
558
559
	/**
560
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
561
	 * @throws ConstraintParameterException if the parameter is invalid
562
	 * @return string|null 'mandatory' or null
563
	 */
564
	public function parseConstraintStatusParameter( array $constraintParameters ) {
565
		$this->checkError( $constraintParameters );
566
		$constraintStatusId = $this->config->get( 'WBQualityConstraintsConstraintStatusId' );
567
		if ( !array_key_exists( $constraintStatusId, $constraintParameters ) ) {
568
			return null;
569
		}
570
571
		$mandatoryId = $this->config->get( 'WBQualityConstraintsMandatoryConstraintId' );
572
		$this->requireSingleParameter( $constraintParameters, $constraintStatusId );
573
		$snak = $this->snakDeserializer->deserialize( $constraintParameters[$constraintStatusId][0] );
574
		$this->requireValueParameter( $snak, $constraintStatusId );
575
		$statusId = $snak->getDataValue()->getEntityId()->getSerialization();
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...
576
577
		if ( $statusId === $mandatoryId ) {
578
			return 'mandatory';
579
		} else {
580
			throw new ConstraintParameterException(
581
				( new ViolationMessage( 'wbqc-violation-message-parameter-oneof' ) )
582
					->withEntityId( new PropertyId( $constraintStatusId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
583
					->withEntityIdList( [ new ItemId( $mandatoryId ) ], Role::CONSTRAINT_PARAMETER_VALUE )
584
			);
585
		}
586
	}
587
588
	/**
589
	 * Require that $dataValue is a {@link MonolingualTextValue}.
590
	 * @param DataValue $dataValue
591
	 * @param string $parameterId
592
	 * @return void
593
	 * @throws ConstraintParameterException
594
	 */
595
	private function requireMonolingualTextParameter( DataValue $dataValue, $parameterId ) {
596
		if ( !( $dataValue instanceof MonolingualTextValue ) ) {
0 ignored issues
show
Bug introduced by
The class DataValues\MonolingualTextValue does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
597
			throw new ConstraintParameterException(
598
				( new ViolationMessage( 'wbqc-violation-message-parameter-monolingualtext' ) )
599
					->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
600
					->withDataValue( $dataValue, Role::CONSTRAINT_PARAMETER_VALUE )
601
			);
602
		}
603
	}
604
605
	/**
606
	 * Parse a series of monolingual text snaks (serialized) into a map from language code to string.
607
	 *
608
	 * @param array $snakSerializations
609
	 * @param string $parameterId
610
	 * @throws ConstraintParameterException if invalid snaks are found or a language has multiple texts
611
	 * @return MultilingualTextValue
612
	 */
613
	private function parseMultilingualTextParameter( array $snakSerializations, $parameterId ) {
614
		$result = [];
615
616
		foreach ( $snakSerializations as $snakSerialization ) {
617
			$snak = $this->snakDeserializer->deserialize( $snakSerialization );
618
			$this->requireValueParameter( $snak, $parameterId );
619
620
			$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...
621
			$this->requireMonolingualTextParameter( $value, $parameterId );
622
			/** @var MonolingualTextValue $value */
623
624
			$code = $value->getLanguageCode();
625
			if ( array_key_exists( $code, $result ) ) {
626
				throw new ConstraintParameterException(
627
					( new ViolationMessage( 'wbqc-violation-message-parameter-single-per-language' ) )
628
						->withEntityId( new PropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
629
						->withLanguage( $code )
630
				);
631
			}
632
633
			$result[$code] = $value;
634
		}
635
636
		return new MultilingualTextValue( $result );
637
	}
638
639
	/**
640
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
641
	 * @throws ConstraintParameterException if the parameter is invalid
642
	 * @return MultilingualTextValue
643
	 */
644
	public function parseSyntaxClarificationParameter( array $constraintParameters ) {
645
		$syntaxClarificationId = $this->config->get( 'WBQualityConstraintsSyntaxClarificationId' );
646
647
		if ( !array_key_exists( $syntaxClarificationId, $constraintParameters ) ) {
648
			return new MultilingualTextValue( [] );
649
		}
650
651
		$syntaxClarifications = $this->parseMultilingualTextParameter(
652
			$constraintParameters[$syntaxClarificationId],
653
			$syntaxClarificationId
654
		);
655
656
		return $syntaxClarifications;
657
	}
658
659
	/**
660
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
661
	 * @param string $constraintTypeItemId used in error messages
662
	 * @param string[]|null $validScopes a list of Context::TYPE_* constants which are valid where this parameter appears.
663
	 * If this is not null and one of the specified scopes is not in this list, a ConstraintParameterException is thrown.
664
	 * @throws ConstraintParameterException if the parameter is invalid
665
	 * @return string[]|null Context::TYPE_* constants
666
	 */
667
	public function parseConstraintScopeParameter( array $constraintParameters, $constraintTypeItemId, array $validScopes = null ) {
668
		$constraintScopeId = $this->config->get( 'WBQualityConstraintsConstraintScopeId' );
669
		$mainSnakId = $this->config->get( 'WBQualityConstraintsConstraintCheckedOnMainValueId' );
670
		$qualifiersId = $this->config->get( 'WBQualityConstraintsConstraintCheckedOnQualifiersId' );
671
		$referencesId = $this->config->get( 'WBQualityConstraintsConstraintCheckedOnReferencesId' );
672
673
		if ( !array_key_exists( $constraintScopeId, $constraintParameters ) ) {
674
			return null;
675
		}
676
677
		$contextTypes = [];
678
		foreach ( $constraintParameters[$constraintScopeId] as $snakSerialization ) {
679
			$scopeEntityId = $this->parseEntityIdParameter( $snakSerialization, $constraintScopeId );
680
			switch ( $scopeEntityId->getSerialization() ) {
681
				case $mainSnakId:
682
					$contextTypes[] = Context::TYPE_STATEMENT;
683
					break;
684
				case $qualifiersId:
685
					$contextTypes[] = Context::TYPE_QUALIFIER;
686
					break;
687
				case $referencesId:
688
					$contextTypes[] = Context::TYPE_REFERENCE;
689
					break;
690 View Code Duplication
				default:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
691
					throw new ConstraintParameterException(
692
						( new ViolationMessage( 'wbqc-violation-message-parameter-oneof' ) )
693
							->withEntityId( new PropertyId( $constraintScopeId ), Role::CONSTRAINT_PARAMETER_PROPERTY )
694
							->withEntityIdList(
695
								[
696
									new ItemId( $mainSnakId ),
697
									new ItemId( $qualifiersId ),
698
									new ItemId( $referencesId ),
699
								],
700
								Role::CONSTRAINT_PARAMETER_VALUE
701
							)
702
					);
703
			}
704
		}
705
706
		if ( $validScopes !== null ) {
707
			$invalidScopes = array_diff( $contextTypes, $validScopes );
708
			if ( $invalidScopes !== [] ) {
709
				$invalidScope = array_pop( $invalidScopes );
710
				throw new ConstraintParameterException(
711
					( new ViolationMessage( 'wbqc-violation-message-invalid-scope' ) )
712
						->withConstraintScope( $invalidScope, Role::CONSTRAINT_PARAMETER_VALUE )
713
						->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
714
						->withConstraintScopeList( $validScopes, Role::CONSTRAINT_PARAMETER_VALUE )
715
				);
716
			}
717
		}
718
719
		return $contextTypes;
720
	}
721
722
}
723