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

RangeChecker::checkConstraint()   C

Complexity

Conditions 12
Paths 34

Size

Total Lines 58

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 58
rs 6.4896
c 0
b 0
f 0
cc 12
nc 34
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Checker;
4
5
use DataValues\DataValue;
6
use DataValues\TimeValue;
7
use Wikibase\DataModel\Entity\ItemId;
8
use Wikibase\DataModel\Entity\PropertyId;
9
use Wikibase\DataModel\Services\Lookup\PropertyDataTypeLookup;
10
use Wikibase\DataModel\Snak\PropertyValueSnak;
11
use WikibaseQuality\ConstraintReport\Constraint;
12
use WikibaseQuality\ConstraintReport\ConstraintCheck\Cache\DependencyMetadata;
13
use WikibaseQuality\ConstraintReport\ConstraintCheck\Cache\Metadata;
14
use WikibaseQuality\ConstraintReport\ConstraintCheck\ConstraintChecker;
15
use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\Context;
16
use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterException;
17
use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterParser;
18
use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\NowValue;
19
use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\RangeCheckerHelper;
20
use WikibaseQuality\ConstraintReport\ConstraintCheck\Message\ViolationMessage;
21
use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult;
22
use WikibaseQuality\ConstraintReport\Role;
23
use Wikibase\DataModel\Statement\Statement;
24
25
/**
26
 * @author BP2014N1
27
 * @license GPL-2.0-or-later
28
 */
29
class RangeChecker implements ConstraintChecker {
30
31
	/**
32
	 * @var PropertyDataTypeLookup
33
	 */
34
	private $propertyDataTypeLookup;
35
36
	/**
37
	 * @var ConstraintParameterParser
38
	 */
39
	private $constraintParameterParser;
40
41
	/**
42
	 * @var RangeCheckerHelper
43
	 */
44
	private $rangeCheckerHelper;
45
46
	public function __construct(
47
		PropertyDataTypeLookup $propertyDataTypeLookup,
48
		ConstraintParameterParser $constraintParameterParser,
49
		RangeCheckerHelper $rangeCheckerHelper
50
	) {
51
		$this->propertyDataTypeLookup = $propertyDataTypeLookup;
52
		$this->constraintParameterParser = $constraintParameterParser;
53
		$this->rangeCheckerHelper = $rangeCheckerHelper;
54
	}
55
56
	/**
57
	 * @codeCoverageIgnore This method is purely declarative.
58
	 */
59
	public function getSupportedContextTypes() {
60
		return [
61
			Context::TYPE_STATEMENT => CheckResult::STATUS_COMPLIANCE,
62
			Context::TYPE_QUALIFIER => CheckResult::STATUS_COMPLIANCE,
63
			Context::TYPE_REFERENCE => CheckResult::STATUS_COMPLIANCE,
64
		];
65
	}
66
67
	/**
68
	 * @codeCoverageIgnore This method is purely declarative.
69
	 */
70
	public function getDefaultContextTypes() {
71
		return [
72
			Context::TYPE_STATEMENT,
73
			Context::TYPE_QUALIFIER,
74
			Context::TYPE_REFERENCE,
75
		];
76
	}
77
78
	/**
79
	 * Checks 'Range' constraint.
80
	 *
81
	 * @param Context $context
82
	 * @param Constraint $constraint
83
	 *
84
	 * @throws ConstraintParameterException
85
	 * @return CheckResult
86
	 */
87
	public function checkConstraint( Context $context, Constraint $constraint ) {
88
		if ( $context->getSnakRank() === Statement::RANK_DEPRECATED ) {
89
			return new CheckResult( $context, $constraint, [], CheckResult::STATUS_DEPRECATED );
90
		}
91
92
		$parameters = [];
93
		$constraintParameters = $constraint->getConstraintParameters();
94
95
		$snak = $context->getSnak();
96
97
		if ( !$snak instanceof PropertyValueSnak ) {
98
			// nothing to check
99
			return new CheckResult( $context, $constraint, $parameters, CheckResult::STATUS_COMPLIANCE );
100
		}
101
102
		$dataValue = $snak->getDataValue();
103
104
		list( $min, $max ) = $this->parseRangeParameter(
105
			$constraintParameters,
106
			$constraint->getConstraintTypeItemId(),
107
			$dataValue->getType()
108
		);
109
		$parameterKey = $dataValue->getType() === 'quantity' ? 'quantity' : 'date';
110
		if ( $min !== null ) {
111
			$parameters['minimum_' . $parameterKey] = [ $min ];
112
		}
113
		if ( $max !== null ) {
114
			$parameters['maximum_' . $parameterKey] = [ $max ];
115
		}
116
117
		if ( $this->rangeCheckerHelper->getComparison( $min, $dataValue ) > 0 ||
118
			 $this->rangeCheckerHelper->getComparison( $dataValue, $max ) > 0
119
		) {
120
			$message = $this->getViolationMessage(
121
				$context->getSnak()->getPropertyId(),
122
				$dataValue,
123
				$min,
124
				$max
125
			);
126
			$status = CheckResult::STATUS_VIOLATION;
127
		} else {
128
			$message = null;
129
			$status = CheckResult::STATUS_COMPLIANCE;
130
		}
131
132
		if (
133
			$dataValue instanceof  TimeValue &&
0 ignored issues
show
Bug introduced by
The class DataValues\TimeValue 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...
134
			( $min instanceof NowValue || $max instanceof NowValue ) &&
135
			$this->rangeCheckerHelper->isFutureTime( $dataValue )
136
		) {
137
			$dependencyMetadata = DependencyMetadata::ofFutureTime( $dataValue );
138
		} else {
139
			$dependencyMetadata = DependencyMetadata::blank();
140
		}
141
142
		return ( new CheckResult( $context, $constraint, $parameters, $status, $message ) )
143
			->withMetadata( Metadata::ofDependencyMetadata( $dependencyMetadata ) );
144
	}
145
146
	/**
147
	 * @param array $constraintParameters see {@link \WikibaseQuality\Constraint::getConstraintParameters()}
148
	 * @param string $constraintTypeItemId used in error messages
149
	 * @param string $type 'quantity' or 'time' (can be data type or data value type)
150
	 *
151
	 * @throws ConstraintParameterException if the parameter is invalid or missing
152
	 * @return DataValue[] a pair of two data values, either of which may be null to signify an open range
153
	 */
154
	private function parseRangeParameter( array $constraintParameters, $constraintTypeItemId, $type ) {
155
		switch ( $type ) {
156
			case 'quantity':
157
				return $this->constraintParameterParser->parseQuantityRangeParameter(
158
					$constraintParameters,
159
					$constraintTypeItemId
160
				);
161
			case 'time':
162
				return $this->constraintParameterParser->parseTimeRangeParameter(
163
					$constraintParameters,
164
					$constraintTypeItemId
165
				);
166
		}
167
168
		throw new ConstraintParameterException(
169
			( new ViolationMessage( 'wbqc-violation-message-value-needed-of-types-2' ) )
170
				->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
171
				->withDataValueType( 'quantity' )
172
				->withDataValueType( 'time' )
173
		);
174
	}
175
176
	/**
177
	 * @param PropertyId $predicate
178
	 * @param DataValue $value
179
	 * @param DataValue|null $min
180
	 * @param DataValue|null $max
181
	 *
182
	 * @return ViolationMessage
183
	 */
184
	private function getViolationMessage( PropertyId $predicate, DataValue $value, $min, $max ) {
185
		// possible message keys:
186
		// wbqc-violation-message-range-quantity-closed
187
		// wbqc-violation-message-range-quantity-leftopen
188
		// wbqc-violation-message-range-quantity-rightopen
189
		// wbqc-violation-message-range-time-closed
190
		// wbqc-violation-message-range-time-closed-leftnow
191
		// wbqc-violation-message-range-time-closed-rightnow
192
		// wbqc-violation-message-range-time-leftopen
193
		// wbqc-violation-message-range-time-leftopen-rightnow
194
		// wbqc-violation-message-range-time-rightopen
195
		// wbqc-violation-message-range-time-rightopen-leftnow
196
		$messageKey = 'wbqc-violation-message-range';
197
		$messageKey .= '-' . $value->getType();
198
		// at least one of $min, $max is set, otherwise there could be no violation
199
		$messageKey .= '-' . ( $min !== null ? ( $max !== null ? 'closed' : 'rightopen' ) : 'leftopen' );
200
		if ( $min instanceof NowValue ) {
201
			$messageKey .= '-leftnow';
202
		} elseif ( $max instanceof  NowValue ) {
203
			$messageKey .= '-rightnow';
204
		}
205
		$message = ( new ViolationMessage( $messageKey ) )
206
			->withEntityId( $predicate, Role::PREDICATE )
207
			->withDataValue( $value, Role::OBJECT );
208 View Code Duplication
		if ( $min !== null && !( $min instanceof NowValue ) ) {
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...
209
			$message = $message->withDataValue( $min, Role::OBJECT );
210
		}
211 View Code Duplication
		if ( $max !== null && !( $max instanceof  NowValue ) ) {
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...
212
			$message = $message->withDataValue( $max, Role::OBJECT );
213
		}
214
		return $message;
215
	}
216
217
	public function checkConstraintParameters( Constraint $constraint ) {
218
		$constraintParameters = $constraint->getConstraintParameters();
219
		$exceptions = [];
220
		try {
221
			// we don’t have a data value here, so get the type from the property instead
222
			// (the distinction between data type and data value type is irrelevant for 'quantity' and 'time')
223
			$type = $this->propertyDataTypeLookup->getDataTypeIdForProperty( $constraint->getPropertyId() );
224
			$this->parseRangeParameter(
225
				$constraintParameters,
226
				$constraint->getConstraintTypeItemId(),
227
				$type
228
			);
229
		} catch ( ConstraintParameterException $e ) {
230
			$exceptions[] = $e;
231
		}
232
		return $exceptions;
233
	}
234
235
}
236