Completed
Pull Request — master (#455)
by
unknown
18:09
created

Gallery::buildResult()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 3
cts 6
cp 0.5
rs 9.7666
c 0
b 0
f 0
cc 4
nc 2
nop 1
crap 6
1
<?php
2
3
namespace SRF;
4
5
use Html;
6
use SMW\ResultPrinter;
7
use SMWDataItem;
8
use SMWOutputs;
9
use SMWPrintRequest;
10
use SMWQueryResult;
11
use SRFUtils;
12
use Title;
13
use TraditionalImageGallery;
14
15
/**
16
 * Result printer that outputs query results as a image gallery.
17
 *
18
 * @author Jeroen De Dauw < [email protected] >
19
 * @author mwjames
20
 * @author Rowan Rodrik van der Molen
21
 */
22
class Gallery extends ResultPrinter {
23
24
	/**
25
	 * @see SMWResultPrinter::getName
26
	 *
27
	 * @return string
28
	 */
29
	public function getName() {
30
		return $this->msg( 'srf_printername_gallery' )->text();
31
	}
32
33
	/**
34
	 * @see SMWResultPrinter::buildResult
35
	 *
36
	 * @since 1.8
37
	 *
38
	 * @param SMWQueryResult $results
39
	 *
40
	 * @return string
41
	 */
42 1
	protected function buildResult( SMWQueryResult $results ) {
43
44
		// Intro/outro are not planned to work with the widget option
45 1
		if ( ( $this->params['intro'] !== '' || $this->params['outro'] !== '' ) && $this->params['widget'] !== '' ) {
46
			$results->addErrors(
47
				[
48
					$this->msg( 'srf-error-option-mix', 'widget' )->inContentLanguage()->text()
49
				]
50
			);
51
52
			return '';
53
		};
54
55 1
		return $this->getResultText( $results, $this->outputMode );
56
	}
57
58
	/**
59
	 * @see SMWResultPrinter::getResultText
60
	 *
61
	 * @param $results SMWQueryResult
62
	 * @param $outputmode integer
63
	 *
64
	 * @return string | array
65
	 */
66 1
	public function getResultText( SMWQueryResult $results, $outputmode ) {
0 ignored issues
show
Coding Style introduced by
getResultText uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
67
		$numbers = $this->getNumbers( $results );
0 ignored issues
show
Unused Code introduced by
$numbers is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
68 1
				if ( $results->getCount() === 0 ) {
69
					return $this->params['default'];
70 1
				}
71 1
		$ig = new TraditionalImageGallery();
72
73 1
		$ig->setShowBytes( false );
74
		$ig->setShowFilename( false );
75
76
		if ( method_exists( $ig, 'setShowDimensions' ) ) {
77 1
			$ig->setShowDimensions( false );
78
		}
79
80
		$ig->setCaption( $this->mIntro ); // set caption to IQ header
81 1
82 1
		// No need for a special page to use the parser but for the "normal" page
83
		// view we have to ensure caption text is parsed correctly through the parser
84
		if ( !$this->isSpecialPage() ) {
85 1
			$ig->setParser( $GLOBALS['wgParser'] );
86 1
		}
87
88 1
		$html = '';
89
		$processing = '';
90
91 1
		if ( $this->params['widget'] == 'carousel' ) {
92
			// Carousel widget
93
			$ig->setAttributes( $this->getCarouselWidget() );
94
		} elseif ( $this->params['widget'] == 'slideshow' ) {
95
			// Slideshow widget
96
			$ig->setAttributes( $this->getSlideshowWidget() );
97
		} else {
98 1
99 1
			// Standard gallery attributes
100
			$attribs = [
101
				'id' => uniqid(),
102 1
				'class' => $this->getImageOverlay(),
103
			];
104
105
			$ig->setAttributes( $attribs );
106
		}
107 1
108
		// Only use redirects where the overlay option is not used and redirect
109
		// thumb images towards a different target
110
		if ( $this->params['redirects'] !== '' && !$this->params['overlay'] ) {
111
			SMWOutputs::requireResource( 'ext.srf.gallery.redirect' );
112 1
		}
113
114
		// For the carousel widget, the perrow option should not be set
115
		if ( $this->params['perrow'] !== '' && $this->params['widget'] !== 'carousel' ) {
116 1
			$ig->setPerRow( $this->params['perrow'] );
117
		}
118
119
		if ( $this->params['widths'] !== '' ) {
120 1
			$ig->setWidths( $this->params['widths'] );
121
		}
122
123
		if ( $this->params['heights'] !== '' ) {
124 1
			$ig->setHeights( $this->params['heights'] );
125 1
		}
126
127
		$printReqLabels = [];
128
		$redirectType = '';
129
130 1
		/**
131 1
		 * @var SMWPrintRequest $printReq
132
		 */
133
		foreach ( $results->getPrintRequests() as $printReq ) {
134 1
			$printReqLabels[] = $printReq->getLabel();
135 1
136
			// Get redirect type
137
			if ( $this->params['redirects'] === $printReq->getLabel() ) {
138
				$redirectType = $printReq->getTypeID();
139 1
			}
140 1
		}
141
142
		if ( $this->params['imageproperty'] !== '' && in_array( $this->params['imageproperty'], $printReqLabels ) ||
143
			$this->params['redirects'] !== '' && in_array( $this->params['redirects'], $printReqLabels ) ) {
144
145
			$this->addImageProperties(
146
				$results,
147
				$ig,
148
				$this->params['imageproperty'],
149
				$this->params['captionproperty'],
150
				$this->params['redirects'],
151 1
				$outputmode
152
			);
153
		} else {
154
			$this->addImagePages( $results, $ig );
155 1
		}
156
157
		// SRF Global settings
158 1
		SRFUtils::addGlobalJSVariables();
159
160
		// Display a processing image as long as the DOM is no ready
161
		if ( $this->params['widget'] !== '' ) {
162
			$processing = SRFUtils::htmlProcessingElement();
163 1
		}
164 1
165 1
		// Beautify the class selector
166
		$class = $this->params['widget'] ? '-' . $this->params['widget'] . ' ' : '';
167
		$class = $this->params['redirects'] !== '' && $this->params['overlay'] === false ? $class . ' srf-redirect' . ' ' : $class;
168 1
		$class = $this->params['class'] ? $class . ' ' . $this->params['class'] : $class;
169
170 1
		// Separate content from result output
171 1
		if ( !$ig->isEmpty() ) {
172 1
			$attribs = [
173
				'class' => 'srf-gallery' . $class,
174
				'data-redirect-type' => $redirectType,
175 1
				'data-ns-text' => $this->getFileNsTextForPageLanguage()
176
			];
177
178
			$html = Html::rawElement( 'div', $attribs, $processing . $ig->toHTML() );
179 1
		}
180
181
		// If available, create a link that points to further results
182
		if ( $this->linkFurtherResults( $results ) ) {
183 1
			$html .= $this->getLink( $results, SMW_OUTPUT_HTML )->getText( SMW_OUTPUT_HTML, $this->mLinker );
184
		}
185
186
		return [ $html, 'nowiki' => true, 'isHTML' => true ];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array($html, 'now...rue, 'isHTML' => true); (array) is incompatible with the return type documented by SRF\Gallery::getResultText of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
187
	}
188
189
	/**
190
	 * Handles queries where the images (and optionally their captions) are specified as properties.
191
	 *
192
	 * @since 1.5.3
193
	 *
194
	 * @param SMWQueryResult $results
195
	 * @param TraditionalImageGallery $ig
196
	 * @param string $imageProperty
197
	 * @param string $captionProperty
198
	 * @param string $redirectProperty
199
	 * @param $outputMode
200
	 */
201
	protected function addImageProperties( SMWQueryResult $results, &$ig, $imageProperty, $captionProperty, $redirectProperty, $outputMode ) {
202
		while ( /* array of SMWResultArray */
203
		$rows = $results->getNext() ) { // Objects (pages)
204
			$images = [];
205
			$captions = [];
206
			$redirects = [];
207
208
			for ( $i = 0, $n = count( $rows ); $i < $n; $i++ ) { // Properties
209
				/**
210
				 * @var \SMWResultArray $resultArray
211
				 * @var \SMWDataValue $dataValue
212
				 */
213
				$resultArray = $rows[$i];
214
215
				$label = $resultArray->getPrintRequest()->getMode() == SMWPrintRequest::PRINT_THIS
216
					? '-' : $resultArray->getPrintRequest()->getLabel();
217
218
				// Make sure always use real label here otherwise it results in an empty array
219
				if ( $resultArray->getPrintRequest()->getLabel() == $imageProperty ) {
220
					while ( ( $dataValue = $resultArray->getNextDataValue() ) !== false ) { // Property values
221
						if ( $dataValue->getTypeID() == '_wpg' ) {
222
							$images[] = $dataValue->getTitle();
223
						}
224
					}
225
				} elseif ( $label == $captionProperty ) {
226
					while ( ( $dataValue = $resultArray->getNextDataValue() ) !== false ) { // Property values
227
						$captions[] = $dataValue->getShortText( $outputMode, $this->getLinker( true ) );
228
					}
229
				} elseif ( $label == $redirectProperty ) {
230
					while ( ( $dataValue = $resultArray->getNextDataValue() ) !== false ) { // Property values
231
						if ( $dataValue->getDataItem()->getDIType() == SMWDataItem::TYPE_WIKIPAGE ) {
232
							$redirects[] = $dataValue->getTitle();
233
						} elseif ( $dataValue->getDataItem()->getDIType() == SMWDataItem::TYPE_URI ) {
234
							$redirects[] = $dataValue->getURL();
235
						}
236
					}
237
				}
238
			}
239
240
			// Check available matches against captions
241
			$amountMatches = count( $captions ) == count( $images );
242
			$hasCaption = $amountMatches || count( $captions ) > 0;
243
244
			// Check available matches against redirects
245
			$amountRedirects = count( $redirects ) == count( $images );
246
			$hasRedirect = $amountRedirects || count( $redirects ) > 0;
247
248
			/**
249
			 * @var Title $imgTitle
250
			 */
251
			foreach ( $images as $imgTitle ) {
252
				if ( $imgTitle->exists() ) {
253
					$imgCaption = $hasCaption ? ( $amountMatches ? array_shift( $captions ) : $captions[0] ) : '';
254
					$imgRedirect = $hasRedirect ? ( $amountRedirects ? array_shift( $redirects ) : $redirects[0] ) : '';
255
					$this->addImageToGallery( $ig, $imgTitle, $imgCaption, $imgRedirect );
256
				}
257
			}
258
		}
259
	}
260
261
	/**
262
	 * Handles queries where the result objects are image pages.
263
	 *
264
	 * @since 1.5.3
265
	 *
266 1
	 * @param SMWQueryResult $results
267 1
	 * @param TraditionalImageGallery $ig
268
	 */
269
	protected function addImagePages( SMWQueryResult $results, &$ig ) {
270
		while ( $row = $results->getNext() ) {
271 1
			/**
272 1
			 * @var \SMWResultArray $firstField
273
			 */
274 1
			$firstField = $row[0];
275 1
			$nextObject = $firstField->getNextDataValue();
276
277
			if ( $nextObject !== false ) {
278 1
				$imgTitle = method_exists( $nextObject, 'getTitle' ) ? $nextObject->getTitle() : null;
279 1
280
				// Ensure the title belongs to the image namespace
281
				if ( $imgTitle instanceof Title && $imgTitle->getNamespace() === NS_FILE ) {
0 ignored issues
show
Bug introduced by
The class Title does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
282 1
					$imgCaption = '';
283 1
284 1
					// Is there a property queried for display with ?property
285 1
					if ( isset( $row[1] ) ) {
286
						$imgCaption = $row[1]->getNextDataValue();
287
						if ( is_object( $imgCaption ) ) {
288
							$imgCaption = $imgCaption->getShortText( $this->outputMode, $this->getLinker( true ) );
289 1
						}
290
					}
291
292
					$this->addImageToGallery( $ig, $imgTitle, $imgCaption );
293 1
				}
294
			}
295
		}
296
	}
297
298
	/**
299
	 * Adds a single image to the gallery.
300
	 * Takes care of automatically adding a caption when none is provided and parsing it's wikitext.
301
	 *
302
	 * @since 1.5.3
303
	 *
304
	 * @param TraditionalImageGallery $ig The gallery to add the image to
305
	 * @param Title $imgTitle The title object of the page of the image
306 1
	 * @param string $imgCaption An optional caption for the image
307
	 * @param string $imgRedirect
308 1
	 */
309
	protected function addImageToGallery( &$ig, Title $imgTitle, $imgCaption, $imgRedirect = '' ) {
310
311
		if ( empty( $imgCaption ) ) {
312
			if ( $this->params['autocaptions'] ) {
313
				$imgCaption = $imgTitle->getBaseText();
314
315
				if ( !$this->params['fileextensions'] ) {
316
					$imgCaption = preg_replace( '#\.[^.]+$#', '', $imgCaption );
317
				}
318
			} else {
319 1
				$imgCaption = '';
320 1
			}
321
		} else {
322
			if ( $imgTitle instanceof Title && $imgTitle->getNamespace() == NS_FILE && !$this->isSpecialPage() ) {
0 ignored issues
show
Bug introduced by
The class Title does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
323
				$imgCaption = $ig->mParser->recursiveTagParse( $imgCaption );
324 1
			}
325 1
		}
326 1
		// Use image alt as helper for either text
327
		$imgAlt = $this->params['redirects'] === '' ? $imgCaption : $imgRedirect !== '' ? $imgRedirect : '';
328
		$ig->add( $imgTitle, $imgCaption, $imgAlt );
329
	}
330
331
	/**
332
	 * Returns the overlay setting
333
	 *
334
	 * @since 1.8
335 1
	 *
336 1
	 * @return string
337
	 */
338
	private function getImageOverlay() {
339
		if ( array_key_exists( 'overlay', $this->params ) && $this->params['overlay'] == true ) {
340 1
			SMWOutputs::requireResource( 'ext.srf.gallery.overlay' );
341
			return ' srf-overlay';
342
		} else {
343
			return '';
344
		}
345
	}
346
347
	/**
348
	 * Init carousel widget
349
	 *
350
	 * @since 1.8
351
	 *
352
	 * @return string[]
353
	 */
354
	private function getCarouselWidget() {
355
356
		// Set attributes for jcarousel
357
		$dataAttribs = [
358
			'wrap' => 'both', // Whether to wrap at the first/last item (or both) and jump back to the start/end.
359
			'vertical' => 'false', // Orientation: vertical = false means horizontal
360
			'rtl' => 'false', // Directionality: rtl = false means ltr
361
		];
362
363
		// Use the perrow parameter to determine the scroll sequence.
364
		if ( empty( $this->params['perrow'] ) ) {
365
			$dataAttribs['scroll'] = 1;  // default 1
366
		} else {
367
			$dataAttribs['scroll'] = $this->params['perrow'];
368
			$dataAttribs['visible'] = $this->params['perrow'];
369
		}
370
371
		$attribs = [
372
			'id' => uniqid(),
373
			'class' => 'jcarousel jcarousel-skin-smw' . $this->getImageOverlay(),
374
			'style' => 'display:none;',
375
		];
376
377
		foreach ( $dataAttribs as $name => $value ) {
378
			$attribs['data-' . $name] = $value;
379
		}
380
381
		SMWOutputs::requireResource( 'ext.srf.gallery.carousel' );
382
383
		return $attribs;
384
	}
385
386
	/**
387
	 * Init slideshow widget
388
	 *
389
	 * @since 1.8
390
	 *
391
	 * @return string[]
392
	 */
393
	private function getSlideshowWidget() {
394
395
		$attribs = [
396
			'id' => uniqid(),
397
			'class' => $this->getImageOverlay(),
398
			'style' => 'display:none;',
399
			'data-nav-control' => $this->params['navigation']
400
		];
401
402
		SMWOutputs::requireResource( 'ext.srf.gallery.slideshow' );
403
404
		return $attribs;
405
	}
406
407
	/**
408
	 * @see SMWResultPrinter::getParamDefinitions
409
	 *
410
	 * @since 1.8
411
	 *
412
	 * @param $definitions array of IParamDefinition
413 1
	 *
414 1
	 * @return array of IParamDefinition|array
415
	 */
416 1
	public function getParamDefinitions( array $definitions ) {
417
		$params = parent::getParamDefinitions( $definitions );
418
419
		$params['class'] = [
420
			'type' => 'string',
421
			'message' => 'srf-paramdesc-class',
422 1
			'default' => ''
423
		];
424
425
		$params['widget'] = [
426
			'type' => 'string',
427
			'default' => '',
428
			'message' => 'srf-paramdesc-widget',
429 1
			'values' => [ 'carousel', 'slideshow', '' ]
430
		];
431
432
		$params['navigation'] = [
433
			'type' => 'string',
434
			'default' => 'nav',
435
			'message' => 'srf-paramdesc-navigation',
436 1
			'values' => [ 'nav', 'pager', 'auto' ]
437
		];
438
439
		$params['overlay'] = [
440
			'type' => 'boolean',
441
			'default' => false,
442 1
			'message' => 'srf-paramdesc-overlay'
443
		];
444
445
		$params['perrow'] = [
446
			'type' => 'integer',
447
			'default' => '',
448 1
			'message' => 'srf_paramdesc_perrow'
449
		];
450
451
		$params['widths'] = [
452
			'type' => 'integer',
453
			'default' => '',
454 1
			'message' => 'srf_paramdesc_widths'
455
		];
456
457
		$params['heights'] = [
458
			'type' => 'integer',
459
			'default' => '',
460 1
			'message' => 'srf_paramdesc_heights'
461
		];
462
463
		$params['autocaptions'] = [
464
			'type' => 'boolean',
465
			'default' => true,
466 1
			'message' => 'srf_paramdesc_autocaptions'
467
		];
468
469
		$params['fileextensions'] = [
470
			'type' => 'boolean',
471
			'default' => false,
472 1
			'message' => 'srf_paramdesc_fileextensions'
473
		];
474
475
		$params['captionproperty'] = [
476
			'type' => 'string',
477
			'default' => '',
478 1
			'message' => 'srf_paramdesc_captionproperty'
479
		];
480
481
		$params['imageproperty'] = [
482
			'type' => 'string',
483
			'default' => '',
484 1
			'message' => 'srf_paramdesc_imageproperty'
485
		];
486
487
		$params['redirects'] = [
488
			'type' => 'string',
489
			'default' => '',
490 1
			'message' => 'srf-paramdesc-redirects'
491
		];
492
493
		return $params;
494
	}
495
496 1
	/**
497 1
	 * @return bool
498 1
	 */
499
	private function isSpecialPage() {
0 ignored issues
show
Coding Style introduced by
isSpecialPage uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
500
		$title = $GLOBALS['wgTitle'];
501
		return $title instanceof Title && $title->isSpecialPage();
0 ignored issues
show
Bug introduced by
The class Title does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
502
	}
503
504 1
	/**
505 1
	 * @return bool|null|string
506 1
	 */
507
	private function getFileNsTextForPageLanguage() {
0 ignored issues
show
Coding Style introduced by
getFileNsTextForPageLanguage uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
508
		$title = $GLOBALS['wgTitle'];
509
		return $title instanceof Title ? $title->getPageLanguage()->getNsText( NS_FILE ) : null;
0 ignored issues
show
Bug introduced by
The class Title does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
510
	}
511
	/**
512
	 * @param SMWQueryResult $res
513
	 * #396
514
	 * @return float[]
515
	 */
516
	private function getNumbers( SMWQueryResult $res ) {
517
		$numbers = [];
518
		while ( $row = $res->getNext() ) {
519
			foreach ( $row as $resultArray ) {
520
				foreach ( $resultArray->getContent() as $dataItem ) {
521
					self::addNumbersForDataItem( $dataItem, $numbers );
522
				}
523
			}
524
		}
525
		return $numbers;
526
	}
527
	/**
528
	 * @param SMWDataItem $dataItem
529
	 * @param float[] $numbers
530
	 */
531
	private function addNumbersForDataItem( SMWDataItem $dataItem, array &$numbers ) {
532
		switch ( $dataItem->getDIType() ) {
533
			case SMWDataItem::TYPE_NUMBER:
534
				$numbers[] = $dataItem->getNumber();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMWDataItem as the method getNumber() does only exist in the following sub-classes of SMWDataItem: SMWDINumber. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
535
				break;
536
			case SMWDataItem::TYPE_CONTAINER:
537
				foreach ( $dataItem->getDataItems() as $di ) {
0 ignored issues
show
Bug introduced by
The method getDataItems() does not seem to exist on object<SMWDataItem>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
538
					self::addNumbersForDataItem( $di, $numbers );
539
				}
540
				break;
541
			default:
542
		}
543
	}
544
}
545