CompoundConditionBuilder   D
last analyzed

Complexity

Total Complexity 56

Size/Duplication

Total Lines 590
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 16

Test Coverage

Coverage 87.43%

Importance

Changes 0
Metric Value
dl 0
loc 590
ccs 153
cts 175
cp 0.8743
rs 4.5205
c 0
b 0
f 0
wmc 56
lcom 2
cbo 16

27 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
A setResultVariable() 0 4 1
A getNextVariable() 0 3 1
A setSortKeys() 0 4 1
A getSortKeys() 0 3 1
A getErrors() 0 3 1
A addError() 0 3 1
A setCircularReferenceGuard() 0 3 1
A getCircularReferenceGuard() 0 3 1
A setPropertyHierarchyLookup() 0 3 1
A getPropertyHierarchyLookup() 0 3 1
A setJoinVariable() 0 3 1
A getJoinVariable() 0 3 1
A setOrderByProperty() 0 3 1
A getOrderByProperty() 0 3 1
A getConditionFrom() 0 22 1
A mapDescriptionToCondition() 0 3 1
B convertConditionToString() 0 31 6
A newTrueCondition() 0 5 1
C tryToFindRedirectVariableForDataItem() 0 39 7
A canUseQFeature() 0 15 4
A addOrderByDataForProperty() 0 11 3
A addOrderByData() 0 15 2
B addMissingOrderByConditions() 0 16 5
B addOrderForUnknownPropertyKey() 0 60 6
A addPropertyPathToMatchRedirectTargets() 0 21 3
A addFilterToRemoveEntitiesThatContainRedirectPredicate() 0 11 1

How to fix   Complexity   

Complex Class

Complex classes like CompoundConditionBuilder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CompoundConditionBuilder, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SMW\SPARQLStore\QueryEngine;
4
5
use RuntimeException;
6
use SMW\Utils\CircularReferenceGuard;
7
use SMW\DataTypeRegistry;
8
use SMW\DIProperty;
9
use SMW\DIWikiPage;
10
use SMW\Message;
11
use SMW\PropertyHierarchyLookup;
12
use SMW\Query\Language\Description;
13
use SMW\Query\Language\SomeProperty;
14
use SMW\Query\Language\ThingDescription;
15
use SMW\SPARQLStore\HierarchyFinder;
16
use SMW\SPARQLStore\QueryEngine\Condition\Condition;
17
use SMW\SPARQLStore\QueryEngine\Condition\SingletonCondition;
18
use SMW\SPARQLStore\QueryEngine\Condition\TrueCondition;
19
use SMWDataItem as DataItem;
20
use SMWExpElement as ExpElement;
21
use SMWExpNsResource as ExpNsResource;
22
use SMWExporter as Exporter;
23
use SMWTurtleSerializer as TurtleSerializer;
24
use SMW\Query\DescriptionFactory;
25
use SMW\DataValues\PropertyChainValue;
26
27
/**
28
 * Build an internal representation for a SPARQL condition from individual query
29
 * descriptions
30
 *
31
 * @license GNU GPL v2+
32
 * @since 2.0
33
 *
34
 * @author Markus Krötzsch
35
 * @author mwjames
36
 */
37
class CompoundConditionBuilder {
38
39
	/**
40
	 * @var EngineOptions
41
	 */
42
	private $engineOptions;
43
44
	/**
45
	 * @var DispatchingDescriptionInterpreter
46
	 */
47
	private $dispatchingDescriptionInterpreter;
48
49
	/**
50
	 * @var CircularReferenceGuard
51
	 */
52
	private $circularReferenceGuard;
53
54
	/**
55
	 * @var PropertyHierarchyLookup
56
	 */
57
	private $propertyHierarchyLookup;
58
59
	/**
60
	 * @var DescriptionFactory
61
	 */
62
	private $descriptionFactory;
63
64
	/**
65
	 * @var array
66
	 */
67
	private $errors = array();
68
69
	/**
70
	 * Counter used to generate globally fresh variables.
71
	 * @var integer
72
	 */
73
	private $variableCounter = 0;
74
75
	/**
76
	 * sortKeys that are being used while building the query conditions
77
	 * @var array
78
	 */
79
	private $sortKeys = array();
80
81
	/**
82
	 * The name of the SPARQL variable that represents the query result
83
	 * @var string
84
	 */
85
	private $resultVariable = 'result';
86
87
	/**
88
	 * @var string
89
	 */
90
	private $joinVariable;
91
92
	/**
93
	 * @var DIProperty|null
94
	 */
95
	private $orderByProperty;
96
97
	/**
98
	 * @var array
99
	 */
100
	private $redirectByVariableReplacementMap = array();
101
102
	/**
103
	 * @since 2.2
104
	 *
105
	 * @param DescriptionInterpreterFactory $descriptionInterpreterFactory
106
	 * @param EngineOptions|null $engineOptions
107
	 */
108 33
	public function __construct( DescriptionInterpreterFactory $descriptionInterpreterFactory, EngineOptions $engineOptions = null ) {
109 33
		$this->dispatchingDescriptionInterpreter = $descriptionInterpreterFactory->newDispatchingDescriptionInterpreter( $this );
0 ignored issues
show
Documentation Bug introduced by
It seems like $descriptionInterpreterF...ptionInterpreter($this) of type object<SMW\SPARQLStore\Q...DescriptionInterpreter> is incompatible with the declared type object<SMW\SPARQLStore\Q...DescriptionInterpreter> of property $dispatchingDescriptionInterpreter.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
110 33
		$this->engineOptions = $engineOptions;
111
112 33
		if ( $this->engineOptions === null ) {
113 28
			$this->engineOptions = new EngineOptions();
114
		}
115
116 33
		$this->descriptionFactory = new DescriptionFactory();
117 33
	}
118
119
	/**
120
	 * @since 2.0
121
	 *
122
	 * @param string $resultVariable
123
	 */
124 5
	public function setResultVariable( $resultVariable ) {
125 5
		$this->resultVariable = $resultVariable;
126 5
		return $this;
127
	}
128
129
	/**
130
	 * Get a fresh unused variable name for building SPARQL conditions.
131
	 *
132
	 * @return string
133
	 */
134 24
	public function getNextVariable( $prefix = 'v' ) {
135 24
		return $prefix . ( ++$this->variableCounter );
136
	}
137
138
	/**
139
	 * @since 2.0
140
	 *
141
	 * @param array $sortKeys
142
	 */
143 5
	public function setSortKeys( $sortKeys ) {
144 5
		$this->sortKeys = $sortKeys;
145 5
		return $this;
146
	}
147
148
	/**
149
	 * @since 2.1
150
	 *
151
	 * @return array
152
	 */
153 19
	public function getSortKeys() {
154 19
		return $this->sortKeys;
155
	}
156
157
	/**
158
	 * @since 2.2
159
	 *
160
	 * @return array
161
	 */
162 1
	public function getErrors() {
163 1
		return $this->errors;
164
	}
165
166
	/**
167
	 * @since 2.2
168
	 *
169
	 * @param string $error
170
	 */
171
	public function addError( $error, $type = Message::TEXT ) {
172
		$this->errors[Message::getHash( $error, $type )] = Message::encode( $error, $type );
0 ignored issues
show
Documentation introduced by
$error is of type string, but the function expects a array.

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...
173
	}
174
175
	/**
176
	 * @since 2.2
177
	 *
178
	 * @param CircularReferenceGuard $circularReferenceGuard
179
	 */
180 5
	public function setCircularReferenceGuard( CircularReferenceGuard $circularReferenceGuard ) {
181 5
		$this->circularReferenceGuard = $circularReferenceGuard;
182 5
	}
183
184
	/**
185
	 * @since 2.2
186
	 *
187
	 * @return CircularReferenceGuard
188
	 */
189
	public function getCircularReferenceGuard() {
190
		return $this->circularReferenceGuard;
191
	}
192
193
	/**
194
	 * @since 2.3
195
	 *
196
	 * @param PropertyHierarchyLookup $propertyHierarchyLookup
197
	 */
198 5
	public function setPropertyHierarchyLookup( PropertyHierarchyLookup $propertyHierarchyLookup ) {
199 5
		$this->propertyHierarchyLookup = $propertyHierarchyLookup;
200 5
	}
201
202
	/**
203
	 * @since 2.3
204
	 *
205
	 * @return PropertyHierarchyLookup
206
	 */
207 18
	public function getPropertyHierarchyLookup() {
208 18
		return $this->propertyHierarchyLookup;
209
	}
210
211
	/**
212
	 * @since 2.2
213
	 *
214
	 * @param string $joinVariable name of the variable that conditions
215
	 * will refer to
216
	 */
217 24
	public function setJoinVariable( $joinVariable ) {
218 24
		$this->joinVariable = $joinVariable;
219 24
	}
220
221
	/**
222
	 * @since 2.2
223
	 *
224
	 * @return string
225
	 */
226 24
	public function getJoinVariable() {
227 24
		return $this->joinVariable;
228
	}
229
230
	/**
231
	 * @since 2.2
232
	 *
233
	 * @param DIProperty|null $orderByProperty if given then
234
	 * this is the property the values of which this condition will refer
235
	 * to, and the condition should also enable ordering by this value
236
	 */
237 24
	public function setOrderByProperty( $orderByProperty ) {
238 24
		$this->orderByProperty = $orderByProperty;
239 24
	}
240
241
	/**
242
	 * @since 2.2
243
	 *
244
	 * @return DIProperty|null
245
	 */
246 24
	public function getOrderByProperty() {
247 24
		return $this->orderByProperty;
248
	}
249
250
	/**
251
	 * Get a Condition object for a Description.
252
	 *
253
	 * This conversion is implemented by a number of recursive functions,
254
	 * and this is the main entry point for this recursion. In particular,
255
	 * it resets global variables that are used for the construction.
256
	 *
257
	 * If property value variables should be recorded for ordering results
258
	 * later on, the keys of the respective properties need to be given in
259
	 * sortKeys earlier.
260
	 *
261
	 * @param Description $description
262
	 *
263
	 * @return Condition
264
	 */
265 24
	public function getConditionFrom( Description $description ) {
266 24
		$this->variableCounter = 0;
267
268 24
		$this->setJoinVariable( $this->resultVariable );
269 24
		$this->setOrderByProperty( null );
270
271 24
		$condition = $this->mapDescriptionToCondition( $description );
272
273 24
		$this->addMissingOrderByConditions(
274
			$condition
275
		);
276
277 23
		$this->addPropertyPathToMatchRedirectTargets(
278
			$condition
279
		);
280
281 23
		$this->addFilterToRemoveEntitiesThatContainRedirectPredicate(
282
			$condition
283
		);
284
285 23
		return $condition;
286
	}
287
288
	/**
289
	 * Recursively create a Condition from a Description
290
	 *
291
	 * @param Description $description
292
	 *
293
	 * @return Condition
294
	 */
295 24
	public function mapDescriptionToCondition( Description $description ) {
296 24
		return $this->dispatchingDescriptionInterpreter->interpretDescription( $description );
297
	}
298
299
	/**
300
	 * Build the condition (WHERE) string for a given Condition.
301
	 * The function also expresses the single value of
302
	 * SingletonCondition objects in the condition, which may
303
	 * lead to additional namespaces for serializing its URI.
304
	 *
305
	 * @param Condition $condition
306
	 *
307
	 * @return string
308
	 */
309 23
	public function convertConditionToString( Condition &$condition ) {
310
311 23
		$conditionAsString = $condition->getWeakConditionString();
312
313 23
		if ( ( $conditionAsString === '' ) && !$condition->isSafe() ) {
314 1
			$swivtPageResource = Exporter::getInstance()->getSpecialNsResource( 'swivt', 'page' );
315 1
			$conditionAsString = '?' . $this->resultVariable . ' ' . $swivtPageResource->getQName() . " ?url .\n";
316
		}
317
318 23
		$conditionAsString .= $condition->getCondition();
319 23
		$conditionAsString .= $condition->getCogentConditionString();
320
321 23
		if ( $condition instanceof SingletonCondition ) { // prepare for ASK, maybe rather use BIND?
322
323 2
			$matchElement = $condition->matchElement;
324
325 2
			if ( $matchElement instanceof ExpElement ) {
0 ignored issues
show
Bug introduced by
The class SMWExpElement 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...
326 1
				$matchElementName = TurtleSerializer::getTurtleNameForExpElement( $matchElement );
327
			} else {
328 1
				$matchElementName = $matchElement;
329
			}
330
331 2
			if ( $matchElement instanceof ExpNsResource ) {
0 ignored issues
show
Bug introduced by
The class SMWExpNsResource 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...
332
				$condition->namespaces[$matchElement->getNamespaceId()] = $matchElement->getNamespace();
333
			}
334
335 2
			$conditionAsString = str_replace( '?' . $this->resultVariable . ' ', "$matchElementName ", $conditionAsString );
336
		}
337
338 23
		return $conditionAsString;
339
	}
340
341
	/**
342
	 * Create an Condition from an empty (true) description.
343
	 * May still require helper conditions for ordering.
344
	 *
345
	 * @param $joinVariable string name, see mapDescriptionToCondition()
346
	 * @param $orderByProperty mixed DIProperty or null, see mapDescriptionToCondition()
347
	 *
348
	 * @return Condition
349
	 */
350 9
	public function newTrueCondition( $joinVariable, $orderByProperty ) {
351 9
		$result = new TrueCondition();
352 9
		$this->addOrderByDataForProperty( $result, $joinVariable, $orderByProperty );
353 9
		return $result;
354
	}
355
356
	/**
357
	 * @since 2.3
358
	 *
359
	 * @param DataItem|null $dataItem
360
	 *
361
	 * @return string|null
362
	 */
363 23
	public function tryToFindRedirectVariableForDataItem( DataItem $dataItem = null ) {
364
365 23
		if ( !$dataItem instanceof DIWikiPage || !$this->canUseQFeature( SMW_SPARQL_QF_REDI ) ) {
366 7
			return null;
367
		}
368
369
		// Maybe there is a better way to verify the "isRedirect" state other
370
		// than by using the Title object
371 21
		if ( $dataItem->getTitle() === null || !$dataItem->getTitle()->isRedirect() ) {
372 19
			return null;
373
		}
374
375 2
		$redirectExpElement = Exporter::getInstance()->getResourceElementForWikiPage( $dataItem );
0 ignored issues
show
Compatibility introduced by
$dataItem of type object<SMW\DIWikiPage> is not a sub-type of object<SMWDIWikiPage>. It seems like you assume a child class of the class SMW\DIWikiPage 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...
376
377
		// If the resource was matched to an imported vocab then no redirect is required
378 2
		if ( $redirectExpElement->isImported() ) {
379
			return null;
380
		}
381
382 2
		$valueName = TurtleSerializer::getTurtleNameForExpElement( $redirectExpElement );
0 ignored issues
show
Documentation introduced by
$redirectExpElement is of type object<SMW\Exporter\Element\ExpResource>, but the function expects a object<SMWExpElement>.

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...
383
384
		// Add unknow redirect target/variable for value
385 2
		if ( !isset( $this->redirectByVariableReplacementMap[$valueName] ) ) {
386
387 2
			$namespaces[$redirectExpElement->getNamespaceId()] = $redirectExpElement->getNamespace();
0 ignored issues
show
Coding Style Comprehensibility introduced by
$namespaces was never initialized. Although not strictly required by PHP, it is generally a good practice to add $namespaces = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
388 2
			$redirectByVariable = '?' . $this->getNextVariable( 'r' );
389
390 2
			$this->redirectByVariableReplacementMap[$valueName] = array(
391 2
				$redirectByVariable,
392 2
				$namespaces
393
			);
394
		}
395
396
		// Reuse an existing variable for the value to allow to be used more than
397
		// once when referring to the same property/value redirect
398 2
		list( $redirectByVariable, $namespaces ) = $this->redirectByVariableReplacementMap[$valueName];
0 ignored issues
show
Unused Code introduced by
The assignment to $namespaces is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
399
400 2
		return $redirectByVariable;
401
	}
402
403
	/**
404
	 * @since 2.3
405
	 *
406
	 * @param integer $queryFeatureFlag
407
	 *
408
	 * @return boolean
409
	 */
410 21
	public function canUseQFeature( $queryFeatureFlag ) {
411
412 21
		$canUse = true;
413
414
		// Adhere additional condition
415 21
		if ( $queryFeatureFlag === SMW_SPARQL_QF_SUBP ) {
416 18
			$canUse = $this->engineOptions->get( 'smwgQSubpropertyDepth' ) > 0;
417
		}
418
419 21
		if ( $queryFeatureFlag === SMW_SPARQL_QF_SUBC ) {
420 2
			$canUse = $this->engineOptions->get( 'smwgQSubcategoryDepth' ) > 0;
421
		}
422
423 21
		return $this->engineOptions->get( 'smwgSparqlQFeatures' ) === ( (int)$this->engineOptions->get( 'smwgSparqlQFeatures' ) | (int)$queryFeatureFlag ) && $canUse;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->engineOptions->get('smwgSparqlQFeatures') (string) and (int) $this->engineOptio...(int) $queryFeatureFlag (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
424
	}
425
426
	/**
427
	 * Extend the given SPARQL condition by a suitable order by variable,
428
	 * if an order by property is set.
429
	 *
430
	 * @param Condition $sparqlCondition condition to modify
431
	 * @param string $mainVariable the variable that represents the value to be ordered
432
	 * @param mixed $orderByProperty DIProperty or null
433
	 * @param integer $diType DataItem type id if known, or DataItem::TYPE_NOTYPE to determine it from the property
434
	 */
435 24
	public function addOrderByDataForProperty( Condition &$sparqlCondition, $mainVariable, $orderByProperty, $diType = DataItem::TYPE_NOTYPE ) {
436 24
		if ( is_null( $orderByProperty ) ) {
437 24
			return;
438
		}
439
440 2
		if ( $diType == DataItem::TYPE_NOTYPE ) {
441 2
			$diType = DataTypeRegistry::getInstance()->getDataItemId( $orderByProperty->findPropertyTypeID() );
0 ignored issues
show
Deprecated Code introduced by
The method SMW\DataTypeRegistry::getDataItemId() has been deprecated with message: since 2.5, use DataTypeRegistry::getDataItemByType

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
442
		}
443
444 2
		$this->addOrderByData( $sparqlCondition, $mainVariable, $diType );
445 2
	}
446
447
	/**
448
	 * Extend the given SPARQL condition by a suitable order by variable,
449
	 * possibly adding conditions if required for the type of data.
450
	 *
451
	 * @param Condition $sparqlCondition condition to modify
0 ignored issues
show
Documentation introduced by
There is no parameter named $sparqlCondition. Did you maybe mean $condition?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
452
	 * @param string $mainVariable the variable that represents the value to be ordered
453
	 * @param integer $diType DataItem type id
454
	 */
455 10
	public function addOrderByData( Condition &$condition, $mainVariable, $diType ) {
456
457 10
		if ( $diType !== DataItem::TYPE_WIKIPAGE ) {
458 3
			return $condition->orderByVariable = $mainVariable;
459
		}
460
461 7
		$condition->orderByVariable = $mainVariable . 'sk';
462 7
		$skeyExpElement = Exporter::getInstance()->getSpecialPropertyResource( '_SKEY' );
463
464
		$weakConditions = array(
465 7
			$condition->orderByVariable =>"?$mainVariable " . $skeyExpElement->getQName() . " ?{$condition->orderByVariable} .\n"
466
		);
467
468 7
		$condition->weakConditions += $weakConditions;
469 7
	}
470
471
	/**
472
	 * Extend the given Condition with additional conditions to
473
	 * ensure that it can be ordered by all requested properties. After
474
	 * this operation, every key in sortKeys is assigned to a query
475
	 * variable by $sparqlCondition->orderVariables.
476
	 *
477
	 * @param Condition $condition condition to modify
478
	 */
479 24
	protected function addMissingOrderByConditions( Condition &$condition ) {
480 24
		foreach ( $this->sortKeys as $propertyKey => $order ) {
481
482 5
			if ( !is_string( $propertyKey ) ) {
483 1
				throw new RuntimeException( "Expected a string value as sortkey" );
484
			}
485
486 4
			if ( strpos( $propertyKey, " " ) !== false ) {
487
				throw new RuntimeException( "Expected the canonical form of {$propertyKey} (without any whitespace)" );
488
			}
489
490 4
			if ( !array_key_exists( $propertyKey, $condition->orderVariables ) ) { // Find missing property to sort by.
491 4
				$this->addOrderForUnknownPropertyKey( $condition, $propertyKey, $order );
492
			}
493
		}
494 23
	}
495
496 3
	private function addOrderForUnknownPropertyKey( Condition &$condition, $propertyKey, $order ) {
497
498 3
		if ( $propertyKey === '' || $propertyKey === '#' ) { // order by result page sortkey
499
500 2
			$this->addOrderByData(
501
				$condition,
502 2
				$this->resultVariable,
503 2
				DataItem::TYPE_WIKIPAGE
504
			);
505
506 2
			$condition->orderVariables[$propertyKey] = $condition->orderByVariable;
507 2
			return;
508 1
		} elseif ( PropertyChainValue::isChained( $propertyKey ) ) { // Try to extend query.
509
			$propertyChainValue = new PropertyChainValue();
510
			$propertyChainValue->setUserValue( $propertyKey );
511
512
			if ( !$propertyChainValue->isValid() ) {
513
				return $description;
0 ignored issues
show
Bug introduced by
The variable $description seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
514
			}
515
516
			$lastDataItem = $propertyChainValue->getLastPropertyChainValue()->getDataItem();
517
518
			$description = $this->descriptionFactory->newSomeProperty(
519
				$lastDataItem,
0 ignored issues
show
Compatibility introduced by
$lastDataItem of type object<SMWDataItem> is not a sub-type of object<SMW\DIProperty>. It seems like you assume a child class of the class SMWDataItem 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...
520
				$this->descriptionFactory->newThingDescription()
521
			);
522
523
			foreach ( $propertyChainValue->getPropertyChainValues() as $val ) {
524
				$description = $this->descriptionFactory->newSomeProperty(
525
					$val->getDataItem(),
0 ignored issues
show
Compatibility introduced by
$val->getDataItem() of type object<SMWDataItem> is not a sub-type of object<SMW\DIProperty>. It seems like you assume a child class of the class SMWDataItem 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...
526
					$description
527
				);
528
			}
529
530
			// Add and replace Foo.Bar=asc with Bar=asc as we ultimately only
531
			// order to the result of the last element
532
			$this->sortKeys[$lastDataItem->getKey()] = $order;
533
			unset( $this->sortKeys[$propertyKey] );
534
			$propertyKey = $lastDataItem->getKey();
535
536
			$auxDescription = $description;
537
		} else {
538 1
			$auxDescription = $this->descriptionFactory->newSomeProperty(
539 1
				new DIProperty( $propertyKey ),
540 1
				$this->descriptionFactory->newThingDescription()
541
			);
542
		}
543
544 1
		$this->setJoinVariable( $this->resultVariable );
545 1
		$this->setOrderByProperty( null );
546
547 1
		$auxCondition = $this->mapDescriptionToCondition(
548
			$auxDescription
549
		);
550
551
		// orderVariables MUST be set for $propertyKey -- or there is a bug; let it show!
552 1
		$condition->orderVariables[$propertyKey] = $auxCondition->orderVariables[$propertyKey];
553 1
		$condition->weakConditions[$condition->orderVariables[$propertyKey]] = $auxCondition->getWeakConditionString() . $auxCondition->getCondition();
554 1
		$condition->namespaces = array_merge( $condition->namespaces, $auxCondition->namespaces );
555 1
	}
556
557
	/**
558
	 * @see http://www.w3.org/TR/sparql11-query/#propertypaths
559
	 *
560
	 * Query of:
561
	 *
562
	 * SELECT DISTINCT ?result WHERE {
563
	 *	?result swivt:wikiPageSortKey ?resultsk .
564
	 *	{
565
	 *		?result property:FOO ?v1 .
566
	 *		FILTER( ?v1sk >= "=BAR" )
567
	 *		?v1 swivt:wikiPageSortKey ?v1sk .
568
	 *	} UNION {
569
	 *		?result property:FOO ?v2 .
570
	 *	}
571
	 * }
572
	 *
573
	 * results in:
574
	 *
575
	 * SELECT DISTINCT ?result WHERE {
576
	 *	?result swivt:wikiPageSortKey ?resultsk .
577
	 *	?r2 ^swivt:redirectsTo property:FOO .
578
	 *	{
579
	 *		?result ?r2 ?v1 .
580
	 *		FILTER( ?v1sk >= "=BAR" )
581
	 *		?v1 swivt:wikiPageSortKey ?v1sk .
582
	 *	} UNION {
583
	 *		?result ?r2 ?v3 .
584
	 *	}
585
	 * }
586
	 */
587 23
	private function addPropertyPathToMatchRedirectTargets( Condition &$condition ) {
588
589 23
		if ( $this->redirectByVariableReplacementMap === array() ) {
590 21
			return;
591
		}
592
593 2
		$weakConditions = array();
594 2
		$namespaces = array();
595
596 2
		$rediExpElement = Exporter::getInstance()->getSpecialPropertyResource( '_REDI' );
597 2
		$namespaces[$rediExpElement->getNamespaceId()] = $rediExpElement->getNamespace();
598
599 2
		foreach ( $this->redirectByVariableReplacementMap as $valueName => $content ) {
600 2
			list( $redirectByVariable, $ns ) = $content;
601 2
			$weakConditions[] = "$redirectByVariable " . "^" . $rediExpElement->getQName() . " $valueName .\n";
602 2
			$namespaces = array_merge( $namespaces, $ns );
603
		}
604
605 2
		$condition->namespaces = array_merge( $condition->namespaces, $namespaces );
606 2
		$condition->weakConditions += $weakConditions;
607 2
	}
608
609
	/**
610
	 * @see https://www.w3.org/TR/rdf-sparql-query/#func-bound
611
	 *
612
	 * Remove entities that contain a "swivt:redirectsTo" predicate
613
	 */
614 23
	private function addFilterToRemoveEntitiesThatContainRedirectPredicate( Condition &$condition ) {
615
616 23
		$rediExpElement = Exporter::getInstance()->getSpecialPropertyResource( '_REDI' );
617 23
		$namespaces[$rediExpElement->getNamespaceId()] = $rediExpElement->getNamespace();
0 ignored issues
show
Coding Style Comprehensibility introduced by
$namespaces was never initialized. Although not strictly required by PHP, it is generally a good practice to add $namespaces = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
618
619 23
		$boundVariable = '?' . $this->getNextVariable( 'o' );
620 23
		$cogentCondition = " OPTIONAL { ?$this->resultVariable " . $rediExpElement->getQName() . " $boundVariable } .\n FILTER ( !bound( $boundVariable ) ) .\n";
621
622 23
		$condition->addNamespaces( $namespaces );
623 23
		$condition->cogentConditions[$boundVariable] = $cogentCondition;
624 23
	}
625
626
}
627