Completed
Push — master ( 2472c5...a814e8 )
by mw
35:03
created

src/PropertySpecificationLookup.php (5 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace SMW;
4
5
use SMW\Query\DescriptionFactory;
6
use Onoi\Cache\Cache;
7
use SMW\Message;
8
use SMWDIBlob as DIBlob;
9
use SMWQuery as Query;
10
11
/**
12
 * This class should be accessed via ApplicationFactory::getPropertySpecificationLookup
13
 * to ensure a singleton instance.
14
 *
15
 * @license GNU GPL v2+
16
 * @since 2.4
17
 *
18
 * @author mwjames
19
 */
20
class PropertySpecificationLookup {
21
22
	const POOLCACHE_ID = 'property.specification.lookup';
23
24
	/**
25
	 * @var CachedPropertyValuesPrefetcher
26
	 */
27
	private $cachedPropertyValuesPrefetcher;
28
29
	/**
30
	 * @var string
31
	 */
32
	private $languageCode = 'en';
33
34
	/**
35
	 * @var Cache
36
	 */
37
	private $intermediaryMemoryCache;
38
39
	/**
40
	 * @since 2.4
41
	 *
42
	 * @param CachedPropertyValuesPrefetcher $cachedPropertyValuesPrefetcher
43
	 * @param Cache $intermediaryMemoryCache
44
	 */
45 243
	public function __construct( CachedPropertyValuesPrefetcher $cachedPropertyValuesPrefetcher, Cache $intermediaryMemoryCache ) {
46 243
		$this->cachedPropertyValuesPrefetcher = $cachedPropertyValuesPrefetcher;
47 243
		$this->intermediaryMemoryCache = $intermediaryMemoryCache;
48 243
	}
49
50
	/**
51
	 * @since 2.4
52
	 */
53 158
	public function resetCacheBy( DIWikiPage $subject ) {
54 158
		$this->cachedPropertyValuesPrefetcher->resetCacheBy( $subject );
55 158
	}
56
57
	/**
58
	 * @since 2.4
59
	 *
60
	 * @param string
61
	 */
62
	public function getLanguageCode() {
63
		return $this->languageCode;
64
	}
65
66
	/**
67
	 * @since 2.4
68
	 *
69
	 * @param string $languageCode
70
	 */
71 233
	public function setLanguageCode( $languageCode ) {
72 233
		$this->languageCode = Localizer::asBCP47FormattedLanguageCode( $languageCode );
73 233
	}
74
75
	/**
76
	 * @since 2.5
77
	 *
78
	 * @param string $id
79
	 * @param string $languageCode
80
	 *
81
	 * @return string
82
	 */
83 232
	public function getPreferredPropertyLabelBy( $id, $languageCode = '' ) {
84
85 232
		$languageCode = $languageCode === '' ? $this->languageCode : $languageCode;
86 232
		$key = 'ppl:' . $languageCode  . ':'. $id;
87
88
		// Guard against high frequency lookup
89 232
		if ( ( $preferredPropertyLabel = $this->intermediaryMemoryCache->fetch( $key ) ) !== false ) {
90 203
			return $preferredPropertyLabel;
91
		}
92
93 201
		$preferredPropertyLabel = $this->findPreferredPropertyLabel(
94 201
			new DIProperty( str_replace( ' ', '_', $id ) ),
95
			$languageCode
96
		);
97
98 201
		$this->intermediaryMemoryCache->save( $key, $preferredPropertyLabel );
99
100 201
		return $preferredPropertyLabel;
101
	}
102
103
	/**
104
	 * @since 2.4
105
	 *
106
	 * @param string $displayTitle
107
	 *
108
	 * @return DIProperty|false
109
	 */
110 1
	public function getPropertyFromDisplayTitle( $displayTitle ) {
111
112 1
		$descriptionFactory = new DescriptionFactory();
113
114 1
		$description = $descriptionFactory->newSomeProperty(
115 1
			new DIProperty( '_DTITLE' ),
116 1
			$descriptionFactory->newValueDescription( new DIBlob( $displayTitle ) )
117
		);
118
119 1
		$query = new Query( $description );
120 1
		$query->setLimit( 1 );
121
122 1
		$dataItems = $this->cachedPropertyValuesPrefetcher->queryPropertyValuesFor(
123
			$query
124
		);
125
126 1
		if ( is_array( $dataItems ) && $dataItems !== array() ) {
127 1
			$dataItem = end( $dataItems );
128
129
			// Cache results as a linked list attached to
130
			// the property so that it can be purged all together
131
132 1
			return new DIProperty( $dataItem->getDBKey() );
133
		}
134
135
		return false;
136
	}
137
138
	/**
139
	 * @since 2.4
140
	 *
141
	 * @param DIProperty $property
142
	 *
143
	 * @return boolean
144
	 */
145 4
	public function hasUniquenessConstraintBy( DIProperty $property ) {
146
147 4
		$hasUniquenessConstraint = false;
148 4
		$key = 'uc:'. $property->getKey();
149
150
		// Guard against high frequency lookup
151 4
		if ( $this->intermediaryMemoryCache->contains( $key ) ) {
152 3
			return $this->intermediaryMemoryCache->fetch( $key );
153
		}
154
155 4
		$dataItems = $this->cachedPropertyValuesPrefetcher->getPropertyValues(
156 4
			$property->getCanonicalDiWikiPage(),
0 ignored issues
show
It seems like $property->getCanonicalDiWikiPage() can be null; however, getPropertyValues() 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...
157 4
			new DIProperty( '_PVUC' )
158
		);
159
160 4
		if ( is_array( $dataItems ) && $dataItems !== array() ) {
161 4
			$hasUniquenessConstraint = end( $dataItems )->getBoolean();
162
		}
163
164 4
		$this->intermediaryMemoryCache->save( $key, $hasUniquenessConstraint );
165
166 4
		return $hasUniquenessConstraint;
167
	}
168
169
	/**
170
	 * @since 2.5
171
	 *
172
	 * @param DIProperty $property
173
	 *
174
	 * @return DataItem|null
175
	 */
176 2
	public function getExternalFormatterUriBy( DIProperty $property ) {
177
178 2
		$dataItem = null;
179
180 2
		$dataItems = $this->cachedPropertyValuesPrefetcher->getPropertyValues(
181 2
			$property->getCanonicalDiWikiPage(),
0 ignored issues
show
It seems like $property->getCanonicalDiWikiPage() can be null; however, getPropertyValues() 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...
182 2
			new DIProperty( '_PEFU' )
183
		);
184
185 2
		if ( is_array( $dataItems ) && $dataItems !== array() ) {
186 2
			$dataItem = end( $dataItems );
187
		}
188
189 2
		return $dataItem;
190
	}
191
192
	/**
193
	 * @since 2.4
194
	 *
195
	 * @param DIProperty $property
196
	 *
197
	 * @return string
198
	 */
199 198
	public function getAllowedPatternBy( DIProperty $property ) {
200
201 198
		$allowsPattern = '';
202 198
		$key = 'ap:'. $property->getKey();
203
204
		// Guard against high frequency lookup
205 198
		if ( $this->intermediaryMemoryCache->contains( $key ) ) {
206 163
			return $this->intermediaryMemoryCache->fetch( $key );
207
		}
208
209 177
		$dataItems = $this->cachedPropertyValuesPrefetcher->getPropertyValues(
210 177
			$property->getCanonicalDiWikiPage(),
0 ignored issues
show
It seems like $property->getCanonicalDiWikiPage() can be null; however, getPropertyValues() 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...
211 177
			new DIProperty( '_PVAP' )
212
		);
213
214 177
		if ( is_array( $dataItems ) && $dataItems !== array() ) {
215 2
			$allowsPattern = end( $dataItems )->getString();
216
		}
217
218 177
		$this->intermediaryMemoryCache->save( $key, $allowsPattern );
219
220 177
		return $allowsPattern;
221
	}
222
223
	/**
224
	 * @since 2.4
225
	 *
226
	 * @param DIProperty $property
227
	 *
228
	 * @return integer|false
229
	 */
230 206
	public function getAllowedValuesBy( DIProperty $property ) {
231
232 206
		$allowsValues = array();
233 206
		$key = 'al:'. $property->getKey();
234
235
		// Guard against high frequency lookup
236 206
		if ( $this->intermediaryMemoryCache->contains( $key ) ) {
237 174
			return $this->intermediaryMemoryCache->fetch( $key );
238
		}
239
240 185
		$dataItems = $this->cachedPropertyValuesPrefetcher->getPropertyValues(
241 185
			$property->getCanonicalDiWikiPage(),
242 185
			new DIProperty( '_PVAL' )
243
		);
244
245 185
		if ( is_array( $dataItems ) && $dataItems !== array() ) {
246 5
			$allowsValues = $dataItems;
247
		}
248
249 185
		$this->intermediaryMemoryCache->save( $key, $allowsValues );
250
251 185
		return $allowsValues;
252
	}
253
254
	/**
255
	 * @since 2.4
256
	 *
257
	 * @param DIProperty $property
258
	 *
259
	 * @return integer|false
260
	 */
261 40
	public function getDisplayPrecisionBy( DIProperty $property ) {
262
263 40
		$displayPrecision = false;
264
265 40
		$dataItems = $this->cachedPropertyValuesPrefetcher->getPropertyValues(
266 40
			$property->getCanonicalDiWikiPage(),
0 ignored issues
show
It seems like $property->getCanonicalDiWikiPage() can be null; however, getPropertyValues() 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...
267 40
			new DIProperty( '_PREC' )
268
		);
269
270 40
		if ( $dataItems !== false && $dataItems !== array() ) {
271 4
			$dataItem = end( $dataItems );
272 4
			$displayPrecision = abs( (int)$dataItem->getNumber() );
273
		}
274
275 40
		return $displayPrecision;
276
	}
277
278
	/**
279
	 * @since 2.4
280
	 *
281
	 * @param DIProperty $property
282
	 *
283
	 * @return array
284
	 */
285 18
	public function getDisplayUnitsBy( DIProperty $property ) {
286
287 18
		$units = array();
288
289 18
		$dataItems = $this->cachedPropertyValuesPrefetcher->getPropertyValues(
290 18
			$property->getCanonicalDiWikiPage(),
0 ignored issues
show
It seems like $property->getCanonicalDiWikiPage() can be null; however, getPropertyValues() 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...
291 18
			new DIProperty( '_UNIT' )
292
		);
293
294 18
		if ( $dataItems !== false && $dataItems !== array() ) {
295 6
			foreach ( $dataItems as $dataItem ) {
296 6
				$units = array_merge( $units, preg_split( '/\s*,\s*/u', $dataItem->getString() ) );
297
			}
298
		}
299
300 18
		return $units;
301
	}
302
303
	/**
304
	 * We try to cache anything to avoid unnecessary store connections or DB
305
	 * lookups. For cases where a property was changed, the EventDipatcher will
306
	 * receive a 'property.specification.change' event (emitted as soon as the content of
307
	 * a property page was altered) with PropertySpecificationLookup::resetCacheBy
308
	 * being invoked to remove the cache entry for that specific property.
309
	 *
310
	 * @since 2.4
311
	 *
312
	 * @param DIProperty $property
313
	 * @param mixed|null $linker
314
	 * @param string $languageCode
315
	 *
316
	 * @return string
317
	 */
318 66
	public function getPropertyDescriptionBy( DIProperty $property, $linker = null, $languageCode = '' ) {
319
320
		// Take the linker into account (Special vs. in page rendering etc.)
321 66
		$languageCode = $languageCode === '' ? $this->languageCode : $languageCode;
322 66
		$key = '--pdesc:' . $languageCode . ':' . ( $linker === null ? '0' : '1' );
323
324 66
		$blobStore = $this->cachedPropertyValuesPrefetcher->getBlobStore();
325
326 66
		$container = $blobStore->read(
327 66
			$this->cachedPropertyValuesPrefetcher->getRootHashFrom( $property->getCanonicalDiWikiPage() )
328
		);
329
330 66
		if ( $container->has( $key ) ) {
331 51
			return $container->get( $key );
332
		}
333
334 65
		$localPropertyDescription = $this->tryToFindLocalPropertyDescription(
335
			$property,
336
			$linker,
337
			$languageCode
338
		);
339
340
		// If a local property description wasn't available for a predefined property
341
		// the try to find a system translation
342 65
		if ( trim( $localPropertyDescription ) === '' && !$property->isUserDefined() ) {
343 14
			$localPropertyDescription = $this->getPredefinedPropertyDescription( $property, $linker, $languageCode );
344
		}
345
346 65
		$container->set( $key, $localPropertyDescription );
347
348 65
		$blobStore->save(
349
			$container
350
		);
351
352 65
		return $localPropertyDescription;
353
	}
354
355 14
	private function getPredefinedPropertyDescription( $property, $linker, $languageCode ) {
356
357 14
		$description = '';
358 14
		$key = $property->getKey();
359
360 14
		if ( ( $msgKey = PropertyRegistry::getInstance()->findPropertyDescriptionMsgKeyById( $key ) ) === '' ) {
361 14
			$msgKey = 'smw-pa-property-predefined' . strtolower( $key );
362
		}
363
364 14
		if ( !Message::exists( $msgKey ) ) {
365 1
			return $description;
366
		}
367
368 14
		$dataValue = DataValueFactory::getInstance()->newDataValueByItem(
369
			$property
370
		);
371
372 14
		$label = $dataValue->getFormattedLabel();
373
374 14
		$message = Message::get(
375 14
			array( $msgKey, $label ),
376 14
			$linker === null ? Message::ESCAPED : Message::PARSE,
377
			$languageCode
378
		);
379
380 14
		return $message;
381
	}
382
383 65
	private function tryToFindLocalPropertyDescription( $property, $linker, $languageCode ) {
384
385 65
		$text = '';
386 65
		$descriptionProperty = new DIProperty( '_PDESC' );
387
388 65
		$dataItems = $this->cachedPropertyValuesPrefetcher->getPropertyValues(
389 65
			$property->getCanonicalDiWikiPage(),
390
			$descriptionProperty
391
		);
392
393 65
		if ( ( $dataValue = $this->findTextValueByLanguage( $dataItems, $descriptionProperty, $languageCode ) ) !== null ) {
394 6
			$text = $dataValue->getShortWikiText( $linker );
395
		}
396
397 65
		return $text;
398
	}
399
400 201
	private function findPreferredPropertyLabel( $property, $languageCode ) {
401
402 201
		$text = '';
403 201
		$preferredProperty = new DIProperty( '_PPLB' );
404
405 201
		$dataItems = $this->cachedPropertyValuesPrefetcher->getPropertyValues(
406 201
			$property->getCanonicalDiWikiPage(),
407
			$preferredProperty
408
		);
409
410 201
		if ( ( $dataValue = $this->findTextValueByLanguage( $dataItems, $preferredProperty, $languageCode ) ) !== null ) {
411 3
			$text = $dataValue->getShortWikiText();
412
		}
413
414 201
		return $text;
415
	}
416
417 204
	private function findTextValueByLanguage( $dataItems, $property, $languageCode ) {
418
419 204
		if ( $dataItems === null || $dataItems === array() ) {
420 203
			return null;
421
		}
422
423 8
		foreach ( $dataItems as $dataItem ) {
424
425 8
			$dataValue = DataValueFactory::getInstance()->newDataValueByItem(
426
				$dataItem,
427
				$property
428
			);
429
430
			// Here a MonolingualTextValue was retunred therefore the method
431
			// can be called without validation
432 8
			$dv = $dataValue->getTextValueByLanguage( $languageCode );
433
434 8
			if ( $dv !== null ) {
435 8
				return $dv;
436
			}
437
		}
438
439 1
		return null;
440
	}
441
442
}
443