Completed
Push — master ( f2f357...ccb861 )
by
unknown
02:21
created

DelegatingConstraintChecker::checkStatement()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 38
rs 9.312
c 0
b 0
f 0
cc 3
nc 4
nop 4
1
<?php
2
3
namespace WikibaseQuality\ConstraintReport\ConstraintCheck;
4
5
use InvalidArgumentException;
6
use LogicException;
7
use Wikibase\DataModel\Entity\EntityDocument;
8
use Wikibase\DataModel\Entity\PropertyId;
9
use Wikibase\DataModel\Reference;
10
use Wikibase\DataModel\Services\Lookup\EntityLookup;
11
use Wikibase\DataModel\Services\Statement\StatementGuidParser;
12
use Wikibase\DataModel\Statement\Statement;
13
use Wikibase\DataModel\Statement\StatementListProvider;
14
use WikibaseQuality\ConstraintReport\ConstraintCheck\Cache\DependencyMetadata;
15
use WikibaseQuality\ConstraintReport\ConstraintCheck\Cache\Metadata;
16
use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\Context;
17
use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\EntityContextCursor;
18
use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\MainSnakContext;
19
use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\QualifierContext;
20
use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\ReferenceContext;
21
use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterException;
22
use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterParser;
23
use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\LoggingHelper;
24
use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\SparqlHelperException;
25
use WikibaseQuality\ConstraintReport\ConstraintCheck\Message\ViolationMessage;
26
use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult;
27
use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\NullResult;
28
use WikibaseQuality\ConstraintReport\ConstraintLookup;
29
use WikibaseQuality\ConstraintReport\Constraint;
30
use Wikibase\DataModel\Entity\EntityId;
31
32
/**
33
 * Used to start the constraint-check process and to delegate
34
 * the statements that has to be checked to the corresponding checkers
35
 *
36
 * @author BP2014N1
37
 * @license GPL-2.0-or-later
38
 */
39
class DelegatingConstraintChecker {
40
41
	/**
42
	 * Wikibase entity lookup.
43
	 *
44
	 * @var EntityLookup
45
	 */
46
	private $entityLookup;
47
48
	/**
49
	 * @var ConstraintChecker[]
50
	 */
51
	private $checkerMap;
52
53
	/**
54
	 * @var ConstraintLookup
55
	 */
56
	private $constraintLookup;
57
58
	/**
59
	 * @var ConstraintParameterParser
60
	 */
61
	private $constraintParameterParser;
62
63
	/**
64
	 * @var StatementGuidParser
65
	 */
66
	private $statementGuidParser;
67
68
	/**
69
	 * @var LoggingHelper
70
	 */
71
	private $loggingHelper;
72
73
	/**
74
	 * @var bool
75
	 */
76
	private $checkQualifiers;
77
78
	/**
79
	 * @var bool
80
	 */
81
	private $checkReferences;
82
83
	/**
84
	 * @var string[]
85
	 */
86
	private $propertiesWithViolatingQualifiers;
87
88
	/**
89
	 * @param EntityLookup $lookup
90
	 * @param ConstraintChecker[] $checkerMap
91
	 * @param ConstraintLookup $constraintRepository
92
	 * @param ConstraintParameterParser $constraintParameterParser
93
	 * @param StatementGuidParser $statementGuidParser
94
	 * @param LoggingHelper $loggingHelper
95
	 * @param bool $checkQualifiers whether to check qualifiers
96
	 * @param bool $checkReferences whether to check references
97
	 * @param string[] $propertiesWithViolatingQualifiers on statements of these properties,
98
	 * qualifiers will not be checked
99
	 */
100
	public function __construct(
101
		EntityLookup $lookup,
102
		array $checkerMap,
103
		ConstraintLookup $constraintRepository,
104
		ConstraintParameterParser $constraintParameterParser,
105
		StatementGuidParser $statementGuidParser,
106
		LoggingHelper $loggingHelper,
107
		$checkQualifiers,
108
		$checkReferences,
109
		array $propertiesWithViolatingQualifiers
110
	) {
111
		$this->entityLookup = $lookup;
112
		$this->checkerMap = $checkerMap;
113
		$this->constraintLookup = $constraintRepository;
114
		$this->constraintParameterParser = $constraintParameterParser;
115
		$this->statementGuidParser = $statementGuidParser;
116
		$this->loggingHelper = $loggingHelper;
117
		$this->checkQualifiers = $checkQualifiers;
118
		$this->checkReferences = $checkReferences;
119
		$this->propertiesWithViolatingQualifiers = $propertiesWithViolatingQualifiers;
120
	}
121
122
	/**
123
	 * Starts the whole constraint-check process for entity or constraint ID on entity.
124
	 * Statements of the entity will be checked against every constraint that is defined on the property.
125
	 *
126
	 * @param EntityId $entityId
127
	 * @param string[]|null $constraintIds
128
	 * @param callable|null $defaultResultsPerContext
129
	 * Optional function to pre-populate the check results per context.
130
	 * For each {@link Context} where constraints will be checked,
131
	 * this function (if not null) is first called with that context as argument,
132
	 * and may return an array of check results to which the regular results are appended.
133
	 * @param callable|null $defaultResultsPerEntity
134
	 * Optional function to pre-populate the check results per entity.
135
	 * This function (if not null) is called once with $entityId as argument,
136
	 * and may return an array of check results to which the regular results are appended.
137
	 *
138
	 * @return CheckResult[]
139
	 */
140
	public function checkAgainstConstraintsOnEntityId(
141
		EntityId $entityId,
142
		array $constraintIds = null,
143
		callable $defaultResultsPerContext = null,
144
		callable $defaultResultsPerEntity = null
145
	) {
146
		$checkResults = [];
147
		$entity = $this->entityLookup->getEntity( $entityId );
148
149
		if ( $entity instanceof StatementListProvider ) {
150
			$checkResults = $this->checkEveryStatement(
151
				$this->entityLookup->getEntity( $entityId ),
0 ignored issues
show
Bug introduced by
It seems like $this->entityLookup->getEntity($entityId) can be null; however, checkEveryStatement() 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...
152
				$constraintIds,
153
				$defaultResultsPerContext
154
			);
155
		}
156
157
		if ( $defaultResultsPerEntity !== null ) {
158
			$checkResults = array_merge( $defaultResultsPerEntity( $entityId ), $checkResults );
159
		}
160
161
		return $this->sortResult( $checkResults );
162
	}
163
164
	/**
165
	 * Starts the whole constraint-check process.
166
	 * Statements of the entity will be checked against every constraint that is defined on the claim.
167
	 *
168
	 * @param string $guid
169
	 * @param string[]|null $constraintIds
170
	 * @param callable|null $defaultResults Optional function to pre-populate the check results.
171
	 * For each {@link Context} where constraints will be checked,
172
	 * this function (if not null) is first called with that context as argument,
173
	 * and may return an array of check results to which the regular results are appended.
174
	 *
175
	 * @return CheckResult[]
176
	 */
177
	public function checkAgainstConstraintsOnClaimId(
178
		$guid,
179
		array $constraintIds = null,
180
		callable $defaultResults = null
181
	) {
182
183
		$parsedGuid = $this->statementGuidParser->parse( $guid );
184
		$entityId = $parsedGuid->getEntityId();
185
		$entity = $this->entityLookup->getEntity( $entityId );
186
		if ( $entity instanceof StatementListProvider ) {
187
			$statement = $entity->getStatements()->getFirstStatementWithGuid( $guid );
188
			if ( $statement ) {
189
				$result = $this->checkStatement(
190
					$entity,
191
					$statement,
192
					$constraintIds,
193
					$defaultResults
194
				);
195
				$output = $this->sortResult( $result );
196
				return $output;
197
			}
198
		}
199
200
		return [];
201
	}
202
203
	private function getAllowedContextTypes( Constraint $constraint ) {
204
		if ( !array_key_exists( $constraint->getConstraintTypeItemId(), $this->checkerMap ) ) {
205
			return [
206
				Context::TYPE_STATEMENT,
207
				Context::TYPE_QUALIFIER,
208
				Context::TYPE_REFERENCE,
209
			];
210
		}
211
212
		return array_keys( array_filter(
213
			$this->checkerMap[$constraint->getConstraintTypeItemId()]->getSupportedContextTypes(),
214
			function ( $resultStatus ) {
215
				return $resultStatus !== CheckResult::STATUS_NOT_IN_SCOPE;
216
			}
217
		) );
218
	}
219
220
	/**
221
	 * Like ConstraintChecker::checkConstraintParameters,
222
	 * but for meta-parameters common to all checkers.
223
	 *
224
	 * @param Constraint $constraint
225
	 *
226
	 * @return ConstraintParameterException[]
227
	 */
228
	private function checkCommonConstraintParameters( Constraint $constraint ) {
229
		$constraintParameters = $constraint->getConstraintParameters();
230
		try {
231
			$this->constraintParameterParser->checkError( $constraintParameters );
232
		} catch ( ConstraintParameterException $e ) {
233
			return [ $e ];
234
		}
235
236
		$problems = [];
237
		try {
238
			$this->constraintParameterParser->parseExceptionParameter( $constraintParameters );
239
		} catch ( ConstraintParameterException $e ) {
240
			$problems[] = $e;
241
		}
242
		try {
243
			$this->constraintParameterParser->parseConstraintStatusParameter( $constraintParameters );
244
		} catch ( ConstraintParameterException $e ) {
245
			$problems[] = $e;
246
		}
247
		try {
248
			$this->constraintParameterParser->parseConstraintScopeParameter(
249
				$constraintParameters,
250
				$constraint->getConstraintTypeItemId(),
251
				$this->getAllowedContextTypes( $constraint )
252
			);
253
		} catch ( ConstraintParameterException $e ) {
254
			$problems[] = $e;
255
		}
256
		return $problems;
257
	}
258
259
	/**
260
	 * Check the constraint parameters of all constraints for the given property ID.
261
	 *
262
	 * @param PropertyId $propertyId
263
	 * @return ConstraintParameterException[][] first level indexed by constraint ID,
264
	 * second level like checkConstraintParametersOnConstraintId (but without possibility of null)
265
	 */
266
	public function checkConstraintParametersOnPropertyId( PropertyId $propertyId ) {
267
		$constraints = $this->constraintLookup->queryConstraintsForProperty( $propertyId );
268
		$result = [];
269
270 View Code Duplication
		foreach ( $constraints as $constraint ) {
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...
271
			$problems = $this->checkCommonConstraintParameters( $constraint );
272
273
			if ( array_key_exists( $constraint->getConstraintTypeItemId(), $this->checkerMap ) ) {
274
				$checker = $this->checkerMap[$constraint->getConstraintTypeItemId()];
275
				$problems = array_merge( $problems, $checker->checkConstraintParameters( $constraint ) );
276
			}
277
278
			$result[$constraint->getConstraintId()] = $problems;
279
		}
280
281
		return $result;
282
	}
283
284
	/**
285
	 * Check the constraint parameters of the constraint with the given ID.
286
	 *
287
	 * @param string $constraintId
288
	 *
289
	 * @return ConstraintParameterException[]|null list of constraint parameter exceptions
290
	 * (empty means all parameters okay), or null if constraint is not found
291
	 */
292
	public function checkConstraintParametersOnConstraintId( $constraintId ) {
293
		$propertyId = $this->statementGuidParser->parse( $constraintId )->getEntityId();
294
		$constraints = $this->constraintLookup->queryConstraintsForProperty( $propertyId );
0 ignored issues
show
Compatibility introduced by
$propertyId of type object<Wikibase\DataModel\Entity\EntityId> is not a sub-type of object<Wikibase\DataModel\Entity\PropertyId>. It seems like you assume a child class of the class Wikibase\DataModel\Entity\EntityId to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
295
296 View Code Duplication
		foreach ( $constraints as $constraint ) {
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...
297
			if ( $constraint->getConstraintId() === $constraintId ) {
298
				$problems = $this->checkCommonConstraintParameters( $constraint );
299
300
				if ( array_key_exists( $constraint->getConstraintTypeItemId(), $this->checkerMap ) ) {
301
					$checker = $this->checkerMap[$constraint->getConstraintTypeItemId()];
302
					$problems = array_merge( $problems, $checker->checkConstraintParameters( $constraint ) );
303
				}
304
305
				return $problems;
306
			}
307
		}
308
309
		return null;
310
	}
311
312
	/**
313
	 * @param EntityDocument|StatementListProvider $entity
314
	 * @param string[]|null $constraintIds list of constraints to check (if null: all constraints)
315
	 * @param callable|null $defaultResultsPerContext optional function to pre-populate the check results
316
	 *
317
	 * @return CheckResult[]
318
	 */
319
	private function checkEveryStatement(
320
		EntityDocument $entity,
321
		array $constraintIds = null,
322
		callable $defaultResultsPerContext = null
323
	) {
324
		$result = [];
325
326
		/** @var Statement $statement */
327
		foreach ( $entity->getStatements() as $statement ) {
328
			$result = array_merge( $result,
329
				$this->checkStatement(
330
					$entity,
331
					$statement,
332
					$constraintIds,
333
					$defaultResultsPerContext
334
				) );
335
		}
336
337
		return $result;
338
	}
339
340
	/**
341
	 * @param EntityDocument|StatementListProvider $entity
342
	 * @param Statement $statement
343
	 * @param string[]|null $constraintIds list of constraints to check (if null: all constraints)
344
	 * @param callable|null $defaultResultsPerContext optional function to pre-populate the check results
345
	 *
346
	 * @return CheckResult[]
347
	 */
348
	private function checkStatement(
349
		EntityDocument $entity,
350
		Statement $statement,
351
		array $constraintIds = null,
352
		callable $defaultResultsPerContext = null
353
	) {
354
		$result = [];
355
356
		$result = array_merge( $result,
357
			$this->checkConstraintsForMainSnak(
358
				$entity,
359
				$statement,
360
				$constraintIds,
361
				$defaultResultsPerContext
362
			) );
363
364
		if ( $this->checkQualifiers ) {
365
			$result = array_merge( $result,
366
				$this->checkConstraintsForQualifiers(
367
					$entity,
368
					$statement,
369
					$constraintIds,
370
					$defaultResultsPerContext
371
				) );
372
		}
373
374
		if ( $this->checkReferences ) {
375
			$result = array_merge( $result,
376
				$this->checkConstraintsForReferences(
377
					$entity,
378
					$statement,
379
					$constraintIds,
380
					$defaultResultsPerContext
381
				) );
382
		}
383
384
		return $result;
385
	}
386
387
	/**
388
	 * Get the constraints to actually check for a given property ID.
389
	 * If $constraintIds is not null, only check constraints with those constraint IDs,
390
	 * otherwise check all constraints for that property.
391
	 *
392
	 * @param PropertyId $propertyId
393
	 * @param string[]|null $constraintIds
394
	 * @return Constraint[]
395
	 */
396
	private function getConstraintsToUse( PropertyId $propertyId, array $constraintIds = null ) {
397
		$constraints = $this->constraintLookup->queryConstraintsForProperty( $propertyId );
398
		if ( $constraintIds !== null ) {
399
			$constraintsToUse = [];
400
			foreach ( $constraints as $constraint ) {
401
				if ( in_array( $constraint->getConstraintId(), $constraintIds ) ) {
402
					$constraintsToUse[] = $constraint;
403
				}
404
			}
405
			return $constraintsToUse;
406
		} else {
407
			return $constraints;
408
		}
409
	}
410
411
	/**
412
	 * @param EntityDocument $entity
413
	 * @param Statement $statement
414
	 * @param string[]|null $constraintIds list of constraints to check (if null: all constraints)
415
	 * @param callable|null $defaultResults optional function to pre-populate the check results
416
	 *
417
	 * @return CheckResult[]
418
	 */
419
	private function checkConstraintsForMainSnak(
420
		EntityDocument $entity,
421
		Statement $statement,
422
		array $constraintIds = null,
423
		callable $defaultResults = null
424
	) {
425
		$context = new MainSnakContext( $entity, $statement );
426
		$constraints = $this->getConstraintsToUse(
427
			$statement->getPropertyId(),
428
			$constraintIds
429
		);
430
		$result = $defaultResults !== null ? $defaultResults( $context ) : [];
431
432
		foreach ( $constraints as $constraint ) {
433
			$parameters = $constraint->getConstraintParameters();
434
			try {
435
				$exceptions = $this->constraintParameterParser->parseExceptionParameter( $parameters );
436
			} catch ( ConstraintParameterException $e ) {
437
				$result[] = new CheckResult( $context, $constraint, [], CheckResult::STATUS_BAD_PARAMETERS, $e->getViolationMessage() );
438
				continue;
439
			}
440
441
			if ( in_array( $entity->getId(), $exceptions ) ) {
442
				$message = new ViolationMessage( 'wbqc-violation-message-exception' );
443
				$result[] = new CheckResult( $context, $constraint, [], CheckResult::STATUS_EXCEPTION, $message );
444
				continue;
445
			}
446
447
			$result[] = $this->getCheckResultFor( $context, $constraint );
448
		}
449
450
		return $result;
451
	}
452
453
	/**
454
	 * @param EntityDocument $entity
455
	 * @param Statement $statement
456
	 * @param string[]|null $constraintIds list of constraints to check (if null: all constraints)
457
	 * @param callable|null $defaultResultsPerContext optional function to pre-populate the check results
458
	 *
459
	 * @return CheckResult[]
460
	 */
461 View Code Duplication
	private function checkConstraintsForQualifiers(
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...
462
		EntityDocument $entity,
463
		Statement $statement,
464
		array $constraintIds = null,
465
		callable $defaultResultsPerContext = null
466
	) {
467
		$result = [];
468
469
		if ( in_array(
470
			$statement->getPropertyId()->getSerialization(),
471
			$this->propertiesWithViolatingQualifiers
472
		) ) {
473
			return $result;
474
		}
475
476
		foreach ( $statement->getQualifiers() as $qualifier ) {
477
			$qualifierContext = new QualifierContext( $entity, $statement, $qualifier );
478
			if ( $defaultResultsPerContext !== null ) {
479
				$result = array_merge( $result, $defaultResultsPerContext( $qualifierContext ) );
480
			}
481
			$qualifierConstraints = $this->getConstraintsToUse(
482
				$qualifierContext->getSnak()->getPropertyId(),
483
				$constraintIds
484
			);
485
			foreach ( $qualifierConstraints as $qualifierConstraint ) {
486
				$result[] = $this->getCheckResultFor( $qualifierContext, $qualifierConstraint );
487
			}
488
		}
489
490
		return $result;
491
	}
492
493
	/**
494
	 * @param EntityDocument $entity
495
	 * @param Statement $statement
496
	 * @param string[]|null $constraintIds list of constraints to check (if null: all constraints)
497
	 * @param callable|null $defaultResultsPerContext optional function to pre-populate the check results
498
	 *
499
	 * @return CheckResult[]
500
	 */
501 View Code Duplication
	private function checkConstraintsForReferences(
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...
502
		EntityDocument $entity,
503
		Statement $statement,
504
		array $constraintIds = null,
505
		callable $defaultResultsPerContext = null
506
	) {
507
		$result = [];
508
509
		/** @var Reference $reference */
510
		foreach ( $statement->getReferences() as $reference ) {
511
			foreach ( $reference->getSnaks() as $snak ) {
512
				$referenceContext = new ReferenceContext(
513
					$entity, $statement, $reference, $snak
514
				);
515
				if ( $defaultResultsPerContext !== null ) {
516
					$result = array_merge( $result, $defaultResultsPerContext( $referenceContext ) );
517
				}
518
				$referenceConstraints = $this->getConstraintsToUse(
519
					$referenceContext->getSnak()->getPropertyId(),
520
					$constraintIds
521
				);
522
				foreach ( $referenceConstraints as $referenceConstraint ) {
523
					$result[] = $this->getCheckResultFor(
524
						$referenceContext,
525
						$referenceConstraint
526
					);
527
				}
528
			}
529
		}
530
531
		return $result;
532
	}
533
534
	/**
535
	 * @param Context $context
536
	 * @param Constraint $constraint
537
	 *
538
	 * @throws InvalidArgumentException
539
	 * @return CheckResult
540
	 */
541
	private function getCheckResultFor( Context $context, Constraint $constraint ) {
542
		if ( array_key_exists( $constraint->getConstraintTypeItemId(), $this->checkerMap ) ) {
543
			$checker = $this->checkerMap[$constraint->getConstraintTypeItemId()];
544
			$result = $this->handleScope( $checker, $context, $constraint );
545
546
			if ( $result !== null ) {
547
				$this->addMetadata( $context, $result );
548
				return $result;
549
			}
550
551
			$startTime = microtime( true );
552
			try {
553
				$result = $checker->checkConstraint( $context, $constraint );
554
			} catch ( ConstraintParameterException $e ) {
555
				$result = new CheckResult( $context, $constraint, [], CheckResult::STATUS_BAD_PARAMETERS, $e->getViolationMessage() );
556
			} catch ( SparqlHelperException $e ) {
557
				$message = new ViolationMessage( 'wbqc-violation-message-sparql-error' );
558
				$result = new CheckResult( $context, $constraint, [], CheckResult::STATUS_VIOLATION, $message );
559
			}
560
			$endTime = microtime( true );
561
562
			$this->addMetadata( $context, $result );
563
564
			try {
565
				$constraintStatus = $this->constraintParameterParser
566
					->parseConstraintStatusParameter( $constraint->getConstraintParameters() );
567
			} catch ( ConstraintParameterException $e ) {
568
				$result = new CheckResult( $context, $constraint, [], CheckResult::STATUS_BAD_PARAMETERS, $e->getViolationMessage() );
569
				$constraintStatus = null;
570
			}
571
			if ( $constraintStatus === null ) {
572
				// downgrade violation to warning
573
				if ( $result->getStatus() === CheckResult::STATUS_VIOLATION ) {
574
					$result->setStatus( CheckResult::STATUS_WARNING );
575
				}
576
			} else {
577
				if ( $constraintStatus !== 'mandatory' ) {
578
					// @codeCoverageIgnoreStart
579
					throw new LogicException(
580
						"Unknown constraint status '$constraintStatus', " .
581
						"only known status is 'mandatory'"
582
					);
583
					// @codeCoverageIgnoreEnd
584
				}
585
				$result->addParameter( 'constraint_status', $constraintStatus );
586
			}
587
588
			$this->loggingHelper->logConstraintCheck(
589
				$context,
590
				$constraint,
591
				$result,
592
				get_class( $checker ),
593
				$endTime - $startTime,
594
				__METHOD__
595
			);
596
597
			return $result;
598
		} else {
599
			return new CheckResult( $context, $constraint, [], CheckResult::STATUS_TODO, null );
600
		}
601
	}
602
603
	private function handleScope(
604
		ConstraintChecker $checker,
605
		Context $context,
606
		Constraint $constraint
607
	) {
608
		try {
609
			$checkedContextTypes = $this->constraintParameterParser->parseConstraintScopeParameter(
610
				$constraint->getConstraintParameters(),
611
				$constraint->getConstraintTypeItemId()
612
			);
613
		} catch ( ConstraintParameterException $e ) {
614
			return new CheckResult( $context, $constraint, [], CheckResult::STATUS_BAD_PARAMETERS, $e->getViolationMessage() );
615
		}
616
		if ( $checkedContextTypes === null ) {
617
			$checkedContextTypes = $checker->getDefaultContextTypes();
618
		}
619
		if ( !in_array( $context->getType(), $checkedContextTypes ) ) {
620
			return new CheckResult( $context, $constraint, [], CheckResult::STATUS_NOT_IN_SCOPE, null );
621
		}
622
		if ( $checker->getSupportedContextTypes()[$context->getType()] === CheckResult::STATUS_TODO ) {
623
			return new CheckResult( $context, $constraint, [], CheckResult::STATUS_TODO, null );
624
		}
625
		return null;
626
	}
627
628
	private function addMetadata( Context $context, CheckResult $result ) {
629
		$result->withMetadata( Metadata::merge( [
630
			$result->getMetadata(),
631
			Metadata::ofDependencyMetadata( DependencyMetadata::merge( [
632
				DependencyMetadata::ofEntityId( $context->getEntity()->getId() ),
0 ignored issues
show
Bug introduced by
It seems like $context->getEntity()->getId() can be null; however, ofEntityId() 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...
633
				DependencyMetadata::ofEntityId( $result->getConstraint()->getPropertyId() ),
634
			] ) ),
635
		] ) );
636
	}
637
638
	/**
639
	 * @param CheckResult[] $result
640
	 *
641
	 * @return CheckResult[]
642
	 */
643
	private function sortResult( array $result ) {
644
		if ( count( $result ) < 2 ) {
645
			return $result;
646
		}
647
648
		$sortFunction = function ( CheckResult $a, CheckResult $b ) {
649
			$orderNum = 0;
650
			$order = [
651
				CheckResult::STATUS_BAD_PARAMETERS => $orderNum++,
652
				CheckResult::STATUS_VIOLATION => $orderNum++,
653
				CheckResult::STATUS_WARNING => $orderNum++,
654
				CheckResult::STATUS_EXCEPTION => $orderNum++,
655
				CheckResult::STATUS_COMPLIANCE => $orderNum++,
656
				CheckResult::STATUS_DEPRECATED => $orderNum++,
657
				CheckResult::STATUS_NOT_IN_SCOPE => $orderNum++,
658
				'other' => $orderNum++,
659
			];
660
661
			$statusA = $a->getStatus();
662
			$statusB = $b->getStatus();
663
664
			$orderA = array_key_exists( $statusA, $order ) ? $order[ $statusA ] : $order[ 'other' ];
665
			$orderB = array_key_exists( $statusB, $order ) ? $order[ $statusB ] : $order[ 'other' ];
666
667
			if ( $orderA === $orderB ) {
668
				$cursorA = $a->getContextCursor();
669
				$cursorB = $b->getContextCursor();
670
671
				if ( $cursorA instanceof EntityContextCursor ) {
672
					return $cursorB instanceof EntityContextCursor ? 0 : -1;
673
				}
674
				if ( $cursorB instanceof EntityContextCursor ) {
675
					return $cursorA instanceof EntityContextCursor ? 0 : 1;
676
				}
677
678
				$pidA = $cursorA->getSnakPropertyId();
679
				$pidB = $cursorB->getSnakPropertyId();
680
681
				if ( $pidA === $pidB ) {
682
					$hashA = $cursorA->getSnakHash();
683
					$hashB = $cursorB->getSnakHash();
684
685
					if ( $hashA === $hashB ) {
686
						if ( $a instanceof NullResult ) {
687
							return $b instanceof NullResult ? 0 : -1;
688
						}
689
						if ( $b instanceof NullResult ) {
690
							return $a instanceof NullResult ? 0 : 1;
691
						}
692
693
						$typeA = $a->getConstraint()->getConstraintTypeItemId();
694
						$typeB = $b->getConstraint()->getConstraintTypeItemId();
695
696
						if ( $typeA == $typeB ) {
697
							return 0;
698
						} else {
699
							return ( $typeA > $typeB ) ? 1 : -1;
700
						}
701
					} else {
702
						return ( $hashA > $hashB ) ? 1 : -1;
703
					}
704
				} else {
705
					return ( $pidA > $pidB ) ? 1 : -1;
706
				}
707
			} else {
708
				return ( $orderA > $orderB ) ? 1 : -1;
709
			}
710
		};
711
712
		uasort( $result, $sortFunction );
713
714
		return $result;
715
	}
716
717
}
718