Completed
Push — master ( 186529...e7bbb4 )
by mw
35:45
created

CompoundConditionBuilder   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 590
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 15

Test Coverage

Coverage 94.97%

Importance

Changes 0
Metric Value
dl 0
loc 590
ccs 151
cts 159
cp 0.9497
rs 5.5555
c 0
b 0
f 0
wmc 56
lcom 2
cbo 15

27 Methods

Rating   Name   Duplication   Size   Complexity  
A getSortKeys() 0 3 1
A getPropertyHierarchyLookup() 0 3 1
A __construct() 0 10 2
A setResultVariable() 0 4 1
A getNextVariable() 0 3 1
A setSortKeys() 0 4 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 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\CircularReferenceGuard;
7
use SMW\DataTypeRegistry;
8
use SMW\DIProperty;
9
use SMW\DIWikiPage;
10
use SMW\PropertyHierarchyLookup;
11
use SMW\Query\Language\Description;
12
use SMW\Query\Language\SomeProperty;
13
use SMW\Query\Language\ThingDescription;
14
use SMW\SPARQLStore\HierarchyFinder;
15
use SMW\SPARQLStore\QueryEngine\Condition\Condition;
16
use SMW\SPARQLStore\QueryEngine\Condition\SingletonCondition;
17
use SMW\SPARQLStore\QueryEngine\Condition\TrueCondition;
18
use SMWDataItem as DataItem;
19
use SMWExpElement as ExpElement;
20
use SMWExpNsResource as ExpNsResource;
21
use SMWExporter as Exporter;
22
use SMWTurtleSerializer as TurtleSerializer;
23
use SMW\Query\DescriptionFactory;
24
use SMW\DataValues\PropertyChainValue;
25
26
/**
27
 * Build an internal representation for a SPARQL condition from individual query
28
 * descriptions
29
 *
30
 * @license GNU GPL v2+
31
 * @since 2.0
32
 *
33
 * @author Markus Krötzsch
34
 * @author mwjames
35
 */
36
class CompoundConditionBuilder {
37
38
	/**
39
	 * @var EngineOptions
40
	 */
41
	private $engineOptions;
42
43
	/**
44
	 * @var DispatchingDescriptionInterpreter
45
	 */
46
	private $dispatchingDescriptionInterpreter;
47
48
	/**
49
	 * @var CircularReferenceGuard
50
	 */
51
	private $circularReferenceGuard;
52
53
	/**
54
	 * @var PropertyHierarchyLookup
55
	 */
56
	private $propertyHierarchyLookup;
57
58
	/**
59
	 * @var DescriptionFactory
60
	 */
61
	private $descriptionFactory;
62
63
	/**
64
	 * @var array
65
	 */
66
	private $errors = array();
67
68
	/**
69
	 * Counter used to generate globally fresh variables.
70
	 * @var integer
71
	 */
72
	private $variableCounter = 0;
73
74
	/**
75
	 * sortKeys that are being used while building the query conditions
76
	 * @var array
77
	 */
78
	private $sortKeys = array();
79
80
	/**
81
	 * The name of the SPARQL variable that represents the query result
82
	 * @var string
83
	 */
84
	private $resultVariable = 'result';
85
86
	/**
87
	 * @var string
88
	 */
89
	private $joinVariable;
90
91
	/**
92
	 * @var DIProperty|null
93
	 */
94
	private $orderByProperty;
95
96
	/**
97
	 * @var array
98
	 */
99
	private $redirectByVariableReplacementMap = array();
100 33
101 33
	/**
102 33
	 * @since 2.2
103
	 *
104 33
	 * @param DescriptionInterpreterFactory $descriptionInterpreterFactory
105 28
	 * @param EngineOptions|null $engineOptions
106
	 */
107 33
	public function __construct( DescriptionInterpreterFactory $descriptionInterpreterFactory, EngineOptions $engineOptions = null ) {
108
		$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...
109
		$this->engineOptions = $engineOptions;
110
111
		if ( $this->engineOptions === null ) {
112
			$this->engineOptions = new EngineOptions();
113
		}
114 5
115 5
		$this->descriptionFactory = new DescriptionFactory();
116 5
	}
117
118
	/**
119
	 * @since 2.0
120
	 *
121
	 * @param string $resultVariable
122
	 */
123
	public function setResultVariable( $resultVariable ) {
124 24
		$this->resultVariable = $resultVariable;
125 24
		return $this;
126
	}
127
128
	/**
129
	 * Get a fresh unused variable name for building SPARQL conditions.
130
	 *
131
	 * @return string
132
	 */
133 5
	public function getNextVariable( $prefix = 'v' ) {
134 5
		return $prefix . ( ++$this->variableCounter );
135 5
	}
136
137
	/**
138
	 * @since 2.0
139
	 *
140
	 * @param array $sortKeys
141
	 */
142
	public function setSortKeys( $sortKeys ) {
143 19
		$this->sortKeys = $sortKeys;
144 19
		return $this;
145
	}
146
147
	/**
148
	 * @since 2.1
149
	 *
150
	 * @return array
151
	 */
152 1
	public function getSortKeys() {
153 1
		return $this->sortKeys;
154
	}
155
156
	/**
157
	 * @since 2.2
158
	 *
159
	 * @return array
160
	 */
161
	public function getErrors() {
162
		return $this->errors;
163
	}
164
165
	/**
166
	 * @since 2.2
167
	 *
168
	 * @param string $error
169
	 */
170 5
	public function addError( $error ) {
171 5
		$this->errors[] = $error;
172 5
	}
173
174
	/**
175
	 * @since 2.2
176
	 *
177
	 * @param CircularReferenceGuard $circularReferenceGuard
178
	 */
179
	public function setCircularReferenceGuard( CircularReferenceGuard $circularReferenceGuard ) {
180
		$this->circularReferenceGuard = $circularReferenceGuard;
181
	}
182
183
	/**
184
	 * @since 2.2
185
	 *
186
	 * @return CircularReferenceGuard
187
	 */
188 5
	public function getCircularReferenceGuard() {
189 5
		return $this->circularReferenceGuard;
190 5
	}
191
192
	/**
193
	 * @since 2.3
194
	 *
195
	 * @param PropertyHierarchyLookup $propertyHierarchyLookup
196
	 */
197 18
	public function setPropertyHierarchyLookup( PropertyHierarchyLookup $propertyHierarchyLookup ) {
198 18
		$this->propertyHierarchyLookup = $propertyHierarchyLookup;
199
	}
200
201
	/**
202
	 * @since 2.3
203
	 *
204
	 * @return PropertyHierarchyLookup
205
	 */
206
	public function getPropertyHierarchyLookup() {
207 24
		return $this->propertyHierarchyLookup;
208 24
	}
209 24
210
	/**
211
	 * @since 2.2
212
	 *
213
	 * @param string $joinVariable name of the variable that conditions
214
	 * will refer to
215
	 */
216 24
	public function setJoinVariable( $joinVariable ) {
217 24
		$this->joinVariable = $joinVariable;
218
	}
219
220
	/**
221
	 * @since 2.2
222
	 *
223
	 * @return string
224
	 */
225
	public function getJoinVariable() {
226
		return $this->joinVariable;
227 24
	}
228 24
229 24
	/**
230
	 * @since 2.2
231
	 *
232
	 * @param DIProperty|null $orderByProperty if given then
233
	 * this is the property the values of which this condition will refer
234
	 * to, and the condition should also enable ordering by this value
235
	 */
236 24
	public function setOrderByProperty( $orderByProperty ) {
237 24
		$this->orderByProperty = $orderByProperty;
238
	}
239
240
	/**
241
	 * @since 2.2
242
	 *
243
	 * @return DIProperty|null
244
	 */
245
	public function getOrderByProperty() {
246
		return $this->orderByProperty;
247
	}
248
249
	/**
250
	 * Get a Condition object for a Description.
251
	 *
252
	 * This conversion is implemented by a number of recursive functions,
253
	 * and this is the main entry point for this recursion. In particular,
254
	 * it resets global variables that are used for the construction.
255 24
	 *
256 24
	 * If property value variables should be recorded for ordering results
257
	 * later on, the keys of the respective properties need to be given in
258 24
	 * sortKeys earlier.
259 24
	 *
260
	 * @param Description $description
261 24
	 *
262
	 * @return Condition
263 24
	 */
264
	public function getConditionFrom( Description $description ) {
265
		$this->variableCounter = 0;
266
267 23
		$this->setJoinVariable( $this->resultVariable );
268
		$this->setOrderByProperty( null );
269
270
		$condition = $this->mapDescriptionToCondition( $description );
271 23
272
		$this->addMissingOrderByConditions(
273
			$condition
274
		);
275 23
276
		$this->addPropertyPathToMatchRedirectTargets(
277
			$condition
278
		);
279
280
		$this->addFilterToRemoveEntitiesThatContainRedirectPredicate(
281
			$condition
282
		);
283
284
		return $condition;
285 24
	}
286 24
287
	/**
288
	 * Recursively create a Condition from a Description
289
	 *
290
	 * @param Description $description
291
	 *
292
	 * @return Condition
293
	 */
294
	public function mapDescriptionToCondition( Description $description ) {
295
		return $this->dispatchingDescriptionInterpreter->interpretDescription( $description );
296
	}
297
298
	/**
299 23
	 * Build the condition (WHERE) string for a given Condition.
300
	 * The function also expresses the single value of
301 23
	 * SingletonCondition objects in the condition, which may
302
	 * lead to additional namespaces for serializing its URI.
303 23
	 *
304 1
	 * @param Condition $condition
305 1
	 *
306
	 * @return string
307
	 */
308 23
	public function convertConditionToString( Condition &$condition ) {
309 23
310
		$conditionAsString = $condition->getWeakConditionString();
311 23
312
		if ( ( $conditionAsString === '' ) && !$condition->isSafe() ) {
313 2
			$swivtPageResource = Exporter::getInstance()->getSpecialNsResource( 'swivt', 'page' );
314
			$conditionAsString = '?' . $this->resultVariable . ' ' . $swivtPageResource->getQName() . " ?url .\n";
315 2
		}
316 1
317
		$conditionAsString .= $condition->getCondition();
318 1
		$conditionAsString .= $condition->getCogentConditionString();
319
320
		if ( $condition instanceof SingletonCondition ) { // prepare for ASK, maybe rather use BIND?
321 2
322
			$matchElement = $condition->matchElement;
323
324
			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...
325 2
				$matchElementName = TurtleSerializer::getTurtleNameForExpElement( $matchElement );
326
			} else {
327
				$matchElementName = $matchElement;
328 23
			}
329
330
			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...
331
				$condition->namespaces[$matchElement->getNamespaceId()] = $matchElement->getNamespace();
332
			}
333
334
			$conditionAsString = str_replace( '?' . $this->resultVariable . ' ', "$matchElementName ", $conditionAsString );
335
		}
336
337
		return $conditionAsString;
338
	}
339
340 9
	/**
341 9
	 * Create an Condition from an empty (true) description.
342 9
	 * May still require helper conditions for ordering.
343 9
	 *
344
	 * @param $joinVariable string name, see mapDescriptionToCondition()
345
	 * @param $orderByProperty mixed DIProperty or null, see mapDescriptionToCondition()
346
	 *
347
	 * @return Condition
348
	 */
349
	public function newTrueCondition( $joinVariable, $orderByProperty ) {
350
		$result = new TrueCondition();
351
		$this->addOrderByDataForProperty( $result, $joinVariable, $orderByProperty );
352
		return $result;
353 23
	}
354
355 23
	/**
356 7
	 * @since 2.3
357
	 *
358
	 * @param DataItem|null $dataItem
359
	 *
360
	 * @return string|null
361 21
	 */
362 19
	public function tryToFindRedirectVariableForDataItem( DataItem $dataItem = null ) {
363
364
		if ( !$dataItem instanceof DIWikiPage || !$this->canUseQFeature( SMW_SPARQL_QF_REDI ) ) {
365 2
			return null;
366
		}
367
368 2
		// Maybe there is a better way to verify the "isRedirect" state other
369
		// than by using the Title object
370
		if ( $dataItem->getTitle() === null || !$dataItem->getTitle()->isRedirect() ) {
371
			return null;
372 2
		}
373
374
		$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...
375 2
376
		// If the resource was matched to an imported vocab then no redirect is required
377 2
		if ( $redirectExpElement->isImported() ) {
378 2
			return null;
379
		}
380 2
381 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...
382 2
383
		// Add unknow redirect target/variable for value
384
		if ( !isset( $this->redirectByVariableReplacementMap[$valueName] ) ) {
385
386
			$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...
387
			$redirectByVariable = '?' . $this->getNextVariable( 'r' );
388 2
389
			$this->redirectByVariableReplacementMap[$valueName] = array(
390 2
				$redirectByVariable,
391
				$namespaces
392
			);
393
		}
394
395
		// Reuse an existing variable for the value to allow to be used more than
396
		// once when referring to the same property/value redirect
397
		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...
398
399
		return $redirectByVariable;
400 21
	}
401
402 21
	/**
403
	 * @since 2.3
404
	 *
405 21
	 * @param integer $queryFeatureFlag
406 18
	 *
407
	 * @return boolean
408
	 */
409 21
	public function canUseQFeature( $queryFeatureFlag ) {
410 2
411
		$canUse = true;
412
413 21
		// Adhere additional condition
414
		if ( $queryFeatureFlag === SMW_SPARQL_QF_SUBP ) {
415
			$canUse = $this->engineOptions->get( 'smwgQSubpropertyDepth' ) > 0;
416
		}
417
418
		if ( $queryFeatureFlag === SMW_SPARQL_QF_SUBC ) {
419
			$canUse = $this->engineOptions->get( 'smwgQSubcategoryDepth' ) > 0;
420
		}
421
422
		return $this->engineOptions->get( 'smwgSparqlQFeatures' ) === ( $this->engineOptions->get( 'smwgSparqlQFeatures' ) | $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 $this->engineOptions->ge...s') | $queryFeatureFlag (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
423
	}
424
425 24
	/**
426 24
	 * Extend the given SPARQL condition by a suitable order by variable,
427 24
	 * if an order by property is set.
428
	 *
429
	 * @param Condition $sparqlCondition condition to modify
430 2
	 * @param string $mainVariable the variable that represents the value to be ordered
431 2
	 * @param mixed $orderByProperty DIProperty or null
432
	 * @param integer $diType DataItem type id if known, or DataItem::TYPE_NOTYPE to determine it from the property
433
	 */
434 2
	public function addOrderByDataForProperty( Condition &$sparqlCondition, $mainVariable, $orderByProperty, $diType = DataItem::TYPE_NOTYPE ) {
435 2
		if ( is_null( $orderByProperty ) ) {
436
			return;
437
		}
438
439
		if ( $diType == DataItem::TYPE_NOTYPE ) {
440
			$diType = DataTypeRegistry::getInstance()->getDataItemId( $orderByProperty->findPropertyTypeID() );
441
		}
442
443
		$this->addOrderByData( $sparqlCondition, $mainVariable, $diType );
444
	}
445 10
446
	/**
447 10
	 * Extend the given SPARQL condition by a suitable order by variable,
448 3
	 * possibly adding conditions if required for the type of data.
449
	 *
450
	 * @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...
451 7
	 * @param string $mainVariable the variable that represents the value to be ordered
452 7
	 * @param integer $diType DataItem type id
453
	 */
454
	public function addOrderByData( Condition &$condition, $mainVariable, $diType ) {
455 7
456
		if ( $diType !== DataItem::TYPE_WIKIPAGE ) {
457
			return $condition->orderByVariable = $mainVariable;
458 7
		}
459 7
460
		$condition->orderByVariable = $mainVariable . 'sk';
461
		$skeyExpElement = Exporter::getInstance()->getSpecialPropertyResource( '_SKEY' );
462
463
		$weakConditions = array(
464
			$condition->orderByVariable =>"?$mainVariable " . $skeyExpElement->getQName() . " ?{$condition->orderByVariable} .\n"
465
		);
466
467
		$condition->weakConditions += $weakConditions;
468
	}
469 24
470 24
	/**
471
	 * Extend the given Condition with additional conditions to
472 5
	 * ensure that it can be ordered by all requested properties. After
473 1
	 * this operation, every key in sortKeys is assigned to a query
474
	 * variable by $sparqlCondition->orderVariables.
475
	 *
476 4
	 * @param Condition $condition condition to modify
477
	 */
478
	protected function addMissingOrderByConditions( Condition &$condition ) {
479
		foreach ( $this->sortKeys as $propertyKey => $order ) {
480 4
481 4
			if ( !is_string( $propertyKey ) ) {
482
				throw new RuntimeException( "Expected a string value as sortkey" );
483
			}
484 23
485
			if ( strpos( $propertyKey, " " ) !== false ) {
486 3
				throw new RuntimeException( "Expected the canonical form of {$propertyKey} (without any whitespace)" );
487
			}
488 3
489
			if ( !array_key_exists( $propertyKey, $condition->orderVariables ) ) { // Find missing property to sort by.
490 2
				$this->addOrderForUnknownPropertyKey( $condition, $propertyKey, $order );
491
			}
492 2
		}
493 2
	}
494
495
	private function addOrderForUnknownPropertyKey( Condition &$condition, $propertyKey, $order ) {
496 2
497 2
		if ( $propertyKey === '' || $propertyKey === '#' ) { // order by result page sortkey
498
499
			$this->addOrderByData(
500 1
				$condition,
501 1
				$this->resultVariable,
502 1
				DataItem::TYPE_WIKIPAGE
503
			);
504
505 1
			$condition->orderVariables[$propertyKey] = $condition->orderByVariable;
506 1
			return;
507
		} elseif ( PropertyChainValue::isChained( $propertyKey ) ) { // Try to extend query.
508 1
			$propertyChainValue = new PropertyChainValue();
509
			$propertyChainValue->setUserValue( $propertyKey );
510
511
			if ( !$propertyChainValue->isValid() ) {
512
				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...
513 1
			}
514 1
515 1
			$lastDataItem = $propertyChainValue->getLastPropertyChainValue()->getDataItem();
516 1
517
			$description = $this->descriptionFactory->newSomeProperty(
518
				$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...
519
				$this->descriptionFactory->newThingDescription()
520
			);
521
522
			foreach ( $propertyChainValue->getPropertyChainValues() as $val ) {
523
				$description = $this->descriptionFactory->newSomeProperty(
524
					$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...
525
					$description
526
				);
527
			}
528
529
			// Add and replace Foo.Bar=asc with Bar=asc as we ultimately only
530
			// order to the result of the last element
531
			$this->sortKeys[$lastDataItem->getKey()] = $order;
532
			unset( $this->sortKeys[$propertyKey] );
533
			$propertyKey = $lastDataItem->getKey();
534
535
			$auxDescription = $description;
536
		} else {
537
			$auxDescription = $this->descriptionFactory->newSomeProperty(
538
				new DIProperty( $propertyKey ),
539
				$this->descriptionFactory->newThingDescription()
540
			);
541
		}
542
543
		$this->setJoinVariable( $this->resultVariable );
544
		$this->setOrderByProperty( null );
545
546
		$auxCondition = $this->mapDescriptionToCondition(
547
			$auxDescription
548 23
		);
549
550 23
		// orderVariables MUST be set for $propertyKey -- or there is a bug; let it show!
551 21
		$condition->orderVariables[$propertyKey] = $auxCondition->orderVariables[$propertyKey];
552
		$condition->weakConditions[$condition->orderVariables[$propertyKey]] = $auxCondition->getWeakConditionString() . $auxCondition->getCondition();
553
		$condition->namespaces = array_merge( $condition->namespaces, $auxCondition->namespaces );
554 2
	}
555 2
556
	/**
557 2
	 * @see http://www.w3.org/TR/sparql11-query/#propertypaths
558 2
	 *
559
	 * Query of:
560 2
	 *
561 2
	 * SELECT DISTINCT ?result WHERE {
562 2
	 *	?result swivt:wikiPageSortKey ?resultsk .
563 2
	 *	{
564
	 *		?result property:FOO ?v1 .
565
	 *		FILTER( ?v1sk >= "=BAR" )
566 2
	 *		?v1 swivt:wikiPageSortKey ?v1sk .
567 2
	 *	} UNION {
568 2
	 *		?result property:FOO ?v2 .
569
	 *	}
570
	 * }
571
	 *
572
	 * results in:
573
	 *
574
	 * SELECT DISTINCT ?result WHERE {
575 23
	 *	?result swivt:wikiPageSortKey ?resultsk .
576
	 *	?r2 ^swivt:redirectsTo property:FOO .
577 23
	 *	{
578 23
	 *		?result ?r2 ?v1 .
579
	 *		FILTER( ?v1sk >= "=BAR" )
580 23
	 *		?v1 swivt:wikiPageSortKey ?v1sk .
581 23
	 *	} UNION {
582
	 *		?result ?r2 ?v3 .
583 23
	 *	}
584 23
	 * }
585 23
	 */
586
	private function addPropertyPathToMatchRedirectTargets( Condition &$condition ) {
587
588
		if ( $this->redirectByVariableReplacementMap === array() ) {
589
			return;
590
		}
591
592
		$weakConditions = array();
593
		$namespaces = array();
594
595
		$rediExpElement = Exporter::getInstance()->getSpecialPropertyResource( '_REDI' );
596
		$namespaces[$rediExpElement->getNamespaceId()] = $rediExpElement->getNamespace();
597
598
		foreach ( $this->redirectByVariableReplacementMap as $valueName => $content ) {
599
			list( $redirectByVariable, $ns ) = $content;
600
			$weakConditions[] = "$redirectByVariable " . "^" . $rediExpElement->getQName() . " $valueName .\n";
601
			$namespaces = array_merge( $namespaces, $ns );
602
		}
603
604
		$condition->namespaces = array_merge( $condition->namespaces, $namespaces );
605
		$condition->weakConditions += $weakConditions;
606
	}
607
608
	/**
609
	 * @see https://www.w3.org/TR/rdf-sparql-query/#func-bound
610
	 *
611
	 * Remove entities that contain a "swivt:redirectsTo" predicate
612
	 */
613
	private function addFilterToRemoveEntitiesThatContainRedirectPredicate( Condition &$condition ) {
614
615
		$rediExpElement = Exporter::getInstance()->getSpecialPropertyResource( '_REDI' );
616
		$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...
617
618
		$boundVariable = '?' . $this->getNextVariable( 'o' );
619
		$cogentCondition = " OPTIONAL { ?$this->resultVariable " . $rediExpElement->getQName() . " $boundVariable } .\n FILTER ( !bound( $boundVariable ) ) .\n";
620
621
		$condition->addNamespaces( $namespaces );
622
		$condition->cogentConditions[$boundVariable] = $cogentCondition;
623
	}
624
625
}
626