Completed
Push — master ( 5f9e66...1f3475 )
by mw
123:57 queued 89:01
created

ResultFieldMatchFinder::findAndMatch()   C

Complexity

Conditions 10
Paths 11

Size

Total Lines 57
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 10.0064

Importance

Changes 0
Metric Value
cc 10
eloc 28
nc 11
nop 1
dl 0
loc 57
ccs 24
cts 25
cp 0.96
crap 10.0064
rs 6.7123
c 0
b 0
f 0

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 SMW\Query\Result;
4
5
use SMW\Query\PrintRequest;
6
use SMW\DataValueFactory;
7
use SMW\RequestOptions;
8
use SMW\DIProperty;
9
use SMW\DIWikiPage;
10
use SMW\Store;
11
use SMWDataItem as DataItem;
12
use SMWDIBoolean as DIBoolean;
13
use SMWDIBlob as DIBlob;
14
use SMW\DataValues\MonolingualTextValue;
15
use SMW\Query\QueryToken;
16
use SMW\InTextAnnotationParser;
17
18
/**
19
 * Returns the result content (DI objects) for a single PrintRequest, representing
20
 * as cell of the intersection between a subject row and a print column.
21
 *
22
 * @license GNU GPL v2+
23
 * @since 2.5
24
 *
25
 * @author Markus Krötzsch
26
 * @author Jeroen De Dauw < [email protected] >
27
 * @author mwjames
28
 */
29
class ResultFieldMatchFinder {
30
31
	/**
32
	 * @var Store
33
	 */
34
	private $store;
35
36
	/**
37
	 * @var PrintRequest
38
	 */
39
	private $printRequest;
40
41
	/**
42
	 * @var QueryToken
43
	 */
44
	private $queryToken;
45
46
	/**
47
	 * @var boolean|array
48
	 */
49
	private static $catCacheObj = false;
50
51
	/**
52
	 * @var boolean|array
53
	 */
54 111
	private static $catCache = false;
55 111
56 111
	/**
57 111
	 * @since 2.5
58
	 *
59
	 * @param Store $store
60
	 * @param PrintRequest $printRequest
61
	 */
62
	public function __construct( Store $store, PrintRequest $printRequest ) {
63
		$this->printRequest = $printRequest;
64
		$this->store = $store;
65
	}
66 109
67
	/**
68 109
	 * @since 2.5
69
	 *
70
	 * @param QueryToken|null $queryToken
71
	 */
72 109
	public function setQueryToken( QueryToken $queryToken = null ) {
73 77
74
		if ( $queryToken === null ) {
75
			return;
76
		}
77
78 98
		$this->queryToken = $queryToken;
79 5
80
		$this->queryToken->canHighlight(
81 5
			$this->printRequest->getOutputFormat()
82 5
		);
83
	}
84
85 5
	/**
86
	 * @since 2.5
87 5
	 *
88
	 * @param DataItem $dataItem
89 5
	 *
90
	 * @param DataItem[]|[]
91
	 */
92
	public function findAndMatch( DataItem $dataItem ) {
93
94 96
		$content = array();
95 1
96 1
		// Request the current element (page in result set).
97
		// The limit is ignored here.
98 1
		if ( $this->printRequest->isMode( PrintRequest::PRINT_THIS ) ) {
99
			return array( $dataItem );
100 1
		}
101
102
		// Request all direct categories of the current element
103 1
		// Always recompute cache here to ensure output format is respected.
104 1
		if ( $this->printRequest->isMode( PrintRequest::PRINT_CATS ) ) {
105
			self::$catCache = $this->store->getPropertyValues(
106 1
				$dataItem,
107 1
				new DIProperty( '_INST' ),
108 1
				$this->getRequestOptions( false )
109 1
			);
110
111
			self::$catCacheObj = $dataItem->getHash();
0 ignored issues
show
Documentation Bug introduced by
It seems like $dataItem->getHash() of type string is incompatible with the declared type boolean|array of property $catCacheObj.

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...
112
113 1
			$limit = $this->printRequest->getParameter( 'limit' );
114
115
			return ( $limit === false ) ? ( self::$catCache ) : array_slice( self::$catCache, 0, $limit );
116
		}
117 95
118 95
		// Request to whether current element is in given category (Boolean printout).
119
		// The limit is ignored here.
120
		if ( $this->printRequest->isMode( PrintRequest::PRINT_CCAT ) ) {
121
			if ( self::$catCacheObj !== $dataItem->getHash() ) {
122
				self::$catCache = $this->store->getPropertyValues(
123
					$dataItem,
124
					new DIProperty( '_INST' )
125
				);
126
				self::$catCacheObj = $dataItem->getHash();
127
			}
128
129
			$found = false;
130
			$prkey = $this->printRequest->getData()->getDBkey();
131
132
			foreach ( self::$catCache as $cat ) {
0 ignored issues
show
Bug introduced by
The expression self::$catCache of type boolean|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
133
				if ( $cat->getDBkey() == $prkey ) {
134 98
					$found = true;
135 98
					break;
136 98
				}
137 98
			}
138
139
			return array( new DIBoolean( $found ) );
140 98
		}
141 5
142
		// Request all property values of a certain attribute of the current element.
143 5
		if ( $this->printRequest->isMode( PrintRequest::PRINT_PROP ) || $this->printRequest->isMode( PrintRequest::PRINT_CHAIN ) ) {
144 3
			return $this->getResultsForProperty( $dataItem );
145
		}
146
147 5
		return $content;
148 3
	}
149 3
150 4
	/**
151 3
	 * Make a request option object based on the given parameters, and
152 3
	 * return NULL if no such object is required. The parameter defines
153
	 * if the limit should be taken into account, which is not always desired
154
	 * (especially if results are to be cached for future use).
155
	 *
156 98
	 * @param boolean $useLimit
157
	 *
158
	 * @return RequestOptions|null
159 95
	 */
160
	public function getRequestOptions( $useLimit = true ) {
161 95
		$limit = $useLimit ? $this->printRequest->getParameter( 'limit' ) : false;
162
		$order = trim( $this->printRequest->getParameter( 'order' ) );
163
		$options = null;
164
165 95
		// Important: use "!=" for order, since trim() above does never return "false", use "!==" for limit since "0" is meaningful here.
166 93
		if ( ( $limit !== false ) || ( $order != false ) ) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $order of type string to the boolean false. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
167
			$options = new RequestOptions();
168
169
			if ( $limit !== false ) {
170
				$options->limit = trim( $limit );
0 ignored issues
show
Documentation Bug introduced by
The property $limit was declared of type integer, but trim($limit) is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
171
			}
172
173
			if ( ( $order == 'descending' ) || ( $order == 'reverse' ) || ( $order == 'desc' ) ) {
174 7
				$options->sort = true;
175
				$options->ascending = false;
176
			} elseif ( ( $order == 'ascending' ) || ( $order == 'asc' ) ) {
177 7
				$options->sort = true;
178
				$options->ascending = true;
179
			}
180 7
		}
181 7
182 7
		return $options;
183
	}
184
185 7
	private function getResultsForProperty( $dataItem ) {
186
187
		$content = $this->getResultContent(
188 7
			$dataItem
189
		);
190 7
191
		if ( !$this->isMultiValueWithParameter( 'index' ) && !$this->isMultiValueWithParameter( 'lang' ) ) {
192
			return $content;
193 7
		}
194
195
		// Print one component of a multi-valued string.
196
		//
197
		// Known limitation: the printrequest still is of type _rec, so if
198 2
		// printers check for this then they will not recognize that it returns
199
		// some more concrete type.
200
		if ( $this->printRequest->isMode( PrintRequest::PRINT_CHAIN ) ) {
201
			$propertyValue = $this->printRequest->getData()->getLastPropertyChainValue();
202
		} else {
203 2
			$propertyValue = $this->printRequest->getData();
204 7
		}
205 7
206
		$index = $this->printRequest->getParameter( 'index' );
207
		$lang = $this->printRequest->getParameter( 'lang' );
208
		$newcontent = array();
209 7
210 7
		// Replace content with specific content from a Container/MultiValue
211
		foreach ( $content as $diContainer ) {
212 7
213
			/* AbstractMultiValue */
214
			$multiValue = DataValueFactory::getInstance()->newDataValueByItem(
215 95
				$diContainer,
216 95
				$propertyValue->getDataItem()
217
			);
218
219 95
			if ( $multiValue instanceof MonolingualTextValue && $lang !== false && ( $textValue = $multiValue->getTextValueByLanguage( $lang ) ) !== null ) {
220
221 95
				// Return the text representation without a language reference
222 95
				// (tag) since the value has been filtered hence only matches
223
				// that language
224 95
				$newcontent[] = $this->applyContentManipulation( $textValue->getDataItem() );
225
226
				// Set the index so ResultArray::getNextDataValue can
227
				// find the correct PropertyDataItem (_TEXT;_LCODE) position
228
				// to match the DI
229
				$this->printRequest->setParameter( 'index', 1 );
230
			} elseif ( $lang === false && $index !== false && ( $dataItemByRecord = $multiValue->getDataItemByIndex( $index ) ) !== null ) {
0 ignored issues
show
Bug introduced by
The method getDataItemByIndex() does not exist on SMWDataValue. Did you maybe mean getDataItem()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
231
				$newcontent[] = $this->applyContentManipulation( $dataItemByRecord );
232
			}
233
		}
234
235
		$content = $newcontent;
236 95
		unset( $newcontent );
237
238
		return $content;
239 1
	}
240 1
241
	private function isMultiValueWithParameter( $parameter ) {
242
		return strpos( $this->printRequest->getTypeID(), '_rec' ) !== false && $this->printRequest->getParameter( $parameter ) !== false;
243
	}
244 1
245 1
	private function getResultContent( DataItem $dataItem ) {
246
247
		$dataValue = $this->printRequest->getData();
248
		$dataItems = array( $dataItem );
249 1
250
		if ( !$dataValue->isValid() ) {
251
			return array();
252 95
		}
253
254
		// If it is a chain then try to find a connected DIWikiPage subject that
255 95
		// matches the property on the chained PrintRequest.
256
		// For example, Number.Date.SomeThing will not return any meaningful results
257 95
		// because Number will return a DINumber object and not a DIWikiPage.
258
		// If on the other hand Has page.Number (with Number being the Last and
259 95
		// `Has page` is of type Page) then the iteration will lookup on results
260
		// for `Has page` and try to match a Number annotation on the results
261 95
		// retrieved from `Has page`.
262
		if ( $this->printRequest->isMode( PrintRequest::PRINT_CHAIN ) ) {
263
264
			// Output of the previous iteration is the input for the next iteration
265 95
			foreach ( $dataValue->getPropertyChainValues() as $pv ) {
266
				$dataItems = $this->doFetchPropertyValues( $dataItems, $pv );
267 95
268 95
				// If the results return empty then it means that for this element
269
				// the chain has no matchable items hence we stop
270
				if ( $dataItems === array() ) {
271 95
					return array();
272 95
				}
273
			}
274
275 95
			$dataValue = $dataValue->getLastPropertyChainValue();
276
		}
277
278
		return $this->doFetchPropertyValues( $dataItems, $dataValue );
279
	}
280
281
	private function doFetchPropertyValues( $dataItems, $dataValue ) {
282
283
		$propertyValues = array();
284
285
		foreach ( $dataItems as $dataItem ) {
286
287
			if ( !$dataItem instanceof DIWikiPage ) {
288
				continue;
289
			}
290
291
			$pv = $this->store->getPropertyValues(
292
				$dataItem,
293
				$dataValue->getDataItem(),
294
				$this->getRequestOptions()
295
			);
296
297
			$propertyValues = array_merge( $propertyValues, $pv );
298
			unset( $pv );
299
		}
300
301
		array_walk( $propertyValues, function( &$dataItem ) {
302
			$dataItem = $this->applyContentManipulation( $dataItem );
303
		} );
304
305
		return $propertyValues;
306
	}
307
308
	private function applyContentManipulation( $dataItem ) {
309
310
		if ( !$dataItem instanceof DIBlob ) {
311
			return $dataItem;
312
		}
313
314
		// Avoid `_cod`, `_eid` or similar types that use the DIBlob as storage
315
		// object
316
		if ( $this->printRequest->getTypeID() !== '_txt' && strpos( $this->printRequest->getTypeID(), '_rec' ) === false ) {
317
			return $dataItem;
318
		}
319
320
		// #1314
321
		$string = InTextAnnotationParser::removeAnnotation(
322
			$dataItem->getString()
323
		);
324
325
		// #...
326
		if ( $this->queryToken !== null ) {
327
			$string = $this->queryToken->highlight( $string );
328
		}
329
330
		return new DIBlob( $string );
331
	}
332
333
}
334