Completed
Pull Request — master (#297)
by
unknown
07:01
created

GraphResultPrinter   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 471
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 4

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 47
lcom 2
cbo 4
dl 0
loc 471
ccs 0
cts 138
cp 0
rs 8.439
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getName() 0 3 1
A handleParameters() 0 17 2
F getResultText() 0 157 31
C processResultRow() 0 42 8
B getWordWrappedText() 0 26 4
B getParamDefinitions() 0 83 1

How to fix   Complexity   

Complex Class

Complex classes like GraphResultPrinter 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 GraphResultPrinter, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SRF\Graph;
4
5
use SMW\ResultPrinter;
6
use SMWQueryResult;
7
use SMWWikiPageValue;
8
use GraphViz;
9
use Html;
10
11
/**
12
 * SMW result printer for graphs using graphViz.
13
 * In order to use this printer you need to have both
14
 * the graphViz library installed on your system and
15
 * have the graphViz MediaWiki extension installed.
16
 *
17
 * @file SRF_Graph.php
18
 * @ingroup SemanticResultFormats
19
 *
20
 * @licence GNU GPL v2+
21
 * @author Frank Dengler
22
 * @author Jeroen De Dauw < [email protected] >
23
 * @author Sebastian Schmid
24
 */
25
class GraphResultPrinter extends ResultPrinter {
26
27
	public static $ARROW_HEAD = [
28
		'none',
29
		'normal',
30
		'inv',
31
		'dot',
32
		'odot',
33
		'tee',
34
		'invdot',
35
		'invodot',
36
		'empty',
37
		'invempty',
38
		'diamond',
39
		'ediamond',
40
		'odiamond',
41
		'crow',
42
		'obox',
43
		'box',
44
		'open',
45
		'vee',
46
		'circle',
47
		'halfopen'
48
	];
49
50
	public static $NODE_SHAPES = [
51
		'box',
52
		'box3d',
53
		'circle',
54
		'component',
55
		'diamond',
56
		'doublecircle',
57
		'doubleoctagon',
58
		'egg',
59
		'ellipse',
60
		'folder',
61
		'hexagon',
62
		'house',
63
		'invhouse',
64
		'invtrapezium',
65
		'invtriangle',
66
		'Mcircle',
67
		'Mdiamond',
68
		'Msquare',
69
		'none',
70
		'note',
71
		'octagon',
72
		'parallelogram',
73
		'pentagon ',
74
		'plaintext',
75
		'point',
76
		'polygon',
77
		'rect',
78
		'rectangle',
79
		'septagon',
80
		'square',
81
		'tab',
82
		'trapezium',
83
		'triangle',
84
		'tripleoctagon',
85
		'record',
86
		'Mrecord'
87
	];
88
89
	protected $graphName;
90
	protected $graphLabel;
91
	protected $graphColor;
92
	protected $graphLegend;
93
	protected $graphLink;
94
	protected $rankdir;
95
	protected $graphSize;
96
	protected $legendItem = [];
97
	protected $graphColors = [
98
		'black',
99
		'red',
100
		'green',
101
		'blue',
102
		'darkviolet',
103
		'gold',
104
		'deeppink',
105
		'brown',
106
		'bisque',
107
		'darkgreen',
108
		'yellow',
109
		'darkblue',
110
		'magenta',
111
		'steelblue2'
112
	];
113
	protected $nameProperty;
114
	protected $nodeShape;
115
	protected $parentRelation;
116
	protected $wordWrapLimit;
117
	protected $arrowHead;
118
	protected $nodes = [];
119
120
121
	public function getName() {
122
		return $this->msg( 'srf-printername-graph' )->text();
123
	}
124
125
126
	/**
127
	 * (non-PHPdoc)
128
	 * @see SMWResultPrinter::handleParameters()
129
	 */
130
	protected function handleParameters( array $params, $outputmode ) {
131
		parent::handleParameters( $params, $outputmode );
132
133
		$this->graphName = trim( $params['graphname'] );
134
		$this->graphSize = trim( $params['graphsize'] );
135
		$this->graphLegend = $params['graphlegend'];
136
		$this->graphLabel = $params['graphlabel'];
137
		$this->rankdir = strtoupper( trim( $params['arrowdirection'] ) );
138
		$this->graphLink = $params['graphlink'];
139
		$this->graphColor = $params['graphcolor'];
140
		$this->arrowHead = $params['arrowhead'];
141
		$this->nameProperty = $params['nameproperty'] === false ? false : trim( $params['nameproperty'] );
142
		$this->parentRelation =
143
			strtolower( trim( $params['relation'] ) ) == 'parent';        // false if anything other than 'parent'
144
		$this->nodeShape = $params['nodeshape'];
145
		$this->wordWrapLimit = $params['wordwraplimit'];
146
	}
147
148
149
	/**
150
	 * @param SMWQueryResult $res
151
	 * @param $outputmode
152
	 * @return string
153
	 */
154
	protected function getResultText( SMWQueryResult $res, $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...
155
		if ( !is_callable( 'GraphViz::graphvizParserHook' ) ) {
156
			wfWarn( 'The SRF Graph printer needs the GraphViz extension to be installed.' );
157
158
			return '';
159
		}
160
161
		$this->isHTML = true;
162
163
		///////////////////////////////////
164
		// GRAPH OPTIONS
165
		///////////////////////////////////        
166
167
		$graphInput = "digraph $this->graphName {";
168
169
		// fontsize and fontname
170
		$graphInput .= "graph [fontsize=10, fontname=\"Verdana\"]\nnode [fontsize=10, fontname=\"Verdana\"];\nedge [fontsize=10, fontname=\"Verdana\"];";
171
172
		// size
173
		if ( $this->graphSize != '' ) {
174
			$graphInput .= "size=\"$this->graphSize\";";
175
		}
176
177
		// shape
178
		if ( $this->nodeShape ) {
179
			$graphInput .= "node [shape=$this->nodeShape];";
180
		}
181
182
		// rankdir
183
		$graphInput .= "rankdir=$this->rankdir;";
184
185
		///////////////////////////////////
186
		// NODES
187
		///////////////////////////////////
188
189
		// iterate query result and create GraphNodes
190
		while ( $row = $res->getNext() ) {
191
			$this->processResultRow( $row, $outputmode, $this->nodes );
192
		}
193
194
		/** @var \SRF\GraphNode $node */
195
		foreach ( $this->nodes as $node ) {
196
197
			// take node ID (title) if we don't have a label1
198
			$nodeName = ( empty( $node->getLabel( 1 ) ) ) ? $node->getID() : $node->getLabel( 1 );
199
200
			// add the node
201
			$graphInput .= "\"" . $nodeName . "\"";
202
203
			if ( $this->graphLink ) {
204
				$nodeLinkURL = "[[" . $node->getID() . "]]";
205
				$graphInput .= "[URL = \"$nodeLinkURL\"] ";
206
			}
207
208
			// build the additional labels only for record or Mrecord
209
			if ( ( $node->getLabel( 2 ) != "" || $node->getLabel( 3 ) != "" ) &&
210
				 ( $this->nodeShape == "record" || $this->nodeShape == "Mrecord" )
211
			) {
212
213
				// label1
214
				$label = ( empty( $node->getLabel( 1 ) ) ) ? $node->getID() : $node->getLabel( 1 );
215
				$graphInput .= "[label=\"{" . $label;
216
217
				// label2 onwards
218
				foreach ( $node->getLabels() as $labelIndex => $label ) {
219
					if ( $labelIndex < 2 ) {
220
						continue;
221
					}
222
					if ( $label != "" ) {
223
						$graphInput .= "|" . $label;
224
					}
225
				}
226
227
				$graphInput .= " }\"];";
228
			} else {
229
				$graphInput .= ";";
230
			}
231
		}
232
233
		///////////////////////////////////
234
		// EDGES
235
		///////////////////////////////////
236
237
		foreach ( $this->nodes as $node ) {
238
239
			if ( count( $node->getParentNode() ) > 0 ) {
240
241
				$nodeName = ( empty( $node->getLabel( 1 ) ) ) ? $node->getID() : $node->getLabel( 1 );
242
243
				foreach ( $node->getParentNode() as $parentNode ) {
244
245
					// handle parent/child switch (parentRelation)
246
					$graphInput .= $this->parentRelation ? " \"" . $parentNode['object'] . "\" -> \"" . $nodeName . "\""
247
						: " \"" . $nodeName . "\" -> \"" . $parentNode['object'] . "\" ";
248
249
					$graphInput .= "[arrowhead = " . $this->arrowHead . "]";
250
251
					if ( $this->graphLabel || $this->graphColor ) {
252
						$graphInput .= ' [';
253
254
						// add legend item only if missing
255
						if ( array_search( $parentNode['predicate'], $this->legendItem, true ) === false ) {
256
							$this->legendItem[] = $parentNode['predicate'];
257
						}
258
259
						// assign color
260
						$color = $this->graphColors[array_search( $parentNode['predicate'], $this->legendItem, true )];
261
262
						// show arrow label (graphLabel is misleading but kept for compatibility reasons)
263
						if ( $this->graphLabel ) {
264
							$graphInput .= "label=\"" . $parentNode['predicate'] . "\"";
265
							if ( $this->graphColor ) {
266
								$graphInput .= ",fontcolor=$color,";
267
							}
268
						}
269
270
						// colorize arrow
271
						if ( $this->graphColor ) {
272
							$graphInput .= "color=$color";
273
						}
274
						$graphInput .= ']';
275
					}
276
				}
277
				$graphInput .= ';';
278
			}
279
		}
280
		$graphInput .= "}";
281
282
283
		// calls graphvizParserHook from GraphViz extension
284
		$result = GraphViz::graphvizParserHook( $graphInput, "", $GLOBALS['wgParser'], true );
285
286
287
		// append legend
288
		if ( $this->graphLegend && $this->graphColor ) {
289
			$itemsHtml = '';
290
			$colorCount = 0;
291
			$arraySize = count( $this->graphColors );
292
293
			foreach ( $this->legendItem as $m_label ) {
294
				if ( $colorCount >= $arraySize ) {
295
					$colorCount = 0;
296
				}
297
298
				$color = $this->graphColors[$colorCount];
299
				$itemsHtml .= Html::rawElement( 'div', [ 'class' => 'graphlegenditem', 'style' => "color: $color" ],
300
					"$color: $m_label" );
301
302
				$colorCount ++;
303
			}
304
305
			$result .= Html::rawElement( 'div', [ 'class' => 'graphlegend' ], "$itemsHtml" );
306
307
		}
308
309
		return $result;
310
	}
311
312
	/**
313
	 * Process a result row and create SRF\GraphNodes
314
	 *
315
	 * @since 2.5.0
316
	 *
317
	 * @param array $row
318
	 * @param $outputmode
319
	 * @param array $nodes
320
	 *
321
	 */
322
	protected function processResultRow( array /* of SMWResultArray */
323
										 $row, $outputmode, $nodes ) {
324
325
		// loop through all row fields
326
		foreach ( $row as $i => $resultArray ) {
327
328
			// loop through all values of a multivalue field
329
			while ( ( /* SMWDataValue */
330
					$object = $resultArray->getNextDataValue() ) !== false ) {
331
332
				// create SRF\GraphNode for column 0
333
				if ( $i == 0 ) {
334
					$node = new GraphNode( str_replace( '_', ' ', $object->getShortText( $outputmode ) ) );
335
					$this->nodes[] = $node;
336
				} else {
337
					// special handling for labels1-3, all other printout statements will add links to parent nodes
338
					$label = $resultArray->getPrintRequest()->getLabel();
339
					switch ( $label ) {
340
						// fixed to three labels
341
						case 'label1':
342
						case 'label2':
343
						case 'label3':
344
							$labelIndex = intval( explode( 'label', $label, 2 )[1] );
345
							if ( $object instanceof SMWWikiPageValue ) {
346
								$node->addLabel( $labelIndex, $object->getDisplayTitle() );
0 ignored issues
show
Bug introduced by
The variable $node does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
347
							} else {
348
								$node->addLabel( $labelIndex, $object->getShortText( $outputmode ) );
349
							}
350
							break;
351
						default:
352
							// add Object (Parent Node) and Predicate (Graph Label) to current node
353
							// <this node> <is part of> <other node>
354
							$node->addParentNode( $resultArray->getPrintRequest()->getLabel(),
355
								str_replace( '_', ' ', $object->getDBkey() ) );
356
							break;
357
					}
358
				}
359
			}
360
		}
361
362
		return true;
363
	}
364
365
	/**
366
	 * Returns the word wrapped version of the provided text.
367
	 *
368
	 * @since 1.5.4
369
	 *
370
	 * @param string $text
371
	 * @param integer $charLimit
372
	 *
373
	 * @return string
374
	 */
375
	protected function getWordWrappedText( $text, $charLimit ) {
376
		$charLimit = max( [ $charLimit, 1 ] );
377
		$segments = [];
378
379
		while ( strlen( $text ) > $charLimit ) {
380
			// Find the last space in the allowed range.
381
			$splitPosition = strrpos( substr( $text, 0, $charLimit ), ' ' );
382
383
			if ( $splitPosition === false ) {
384
				// If there is no space (lond word), find the next space.
385
				$splitPosition = strpos( $text, ' ' );
386
387
				if ( $splitPosition === false ) {
388
					// If there are no spaces, everything goes on one line.
389
					$splitPosition = strlen( $text ) - 1;
390
				}
391
			}
392
393
			$segments[] = substr( $text, 0, $splitPosition + 1 );
394
			$text = substr( $text, $splitPosition + 1 );
395
		}
396
397
		$segments[] = $text;
398
399
		return implode( '\n', $segments );
400
	}
401
402
403
	/**
404
	 * @see SMWResultPrinter::getParamDefinitions
405
	 *
406
	 * @since 1.8
407
	 *
408
	 * @param $definitions array of IParamDefinition
409
	 *
410
	 * @return array of IParamDefinition|array
411
	 */
412
	public function getParamDefinitions( array $definitions ) {
413
		$params = parent::getParamDefinitions( $definitions );
414
415
		$params['graphname'] = [
416
			'default' => 'QueryResult',
417
			'message' => 'srf-paramdesc-graphname',
418
		];
419
420
		$params['graphsize'] = [
421
			'type'              => 'string',
422
			'default'           => '',
423
			'message'           => 'srf-paramdesc-graphsize',
424
			'manipulatedefault' => false,
425
		];
426
427
		$params['graphlegend'] = [
428
			'type'    => 'boolean',
429
			'default' => false,
430
			'message' => 'srf-paramdesc-graphlegend',
431
		];
432
433
		$params['graphlabel'] = [
434
			'type'    => 'boolean',
435
			'default' => false,
436
			'message' => 'srf-paramdesc-graphlabel',
437
		];
438
439
		$params['graphlink'] = [
440
			'type'    => 'boolean',
441
			'default' => false,
442
			'message' => 'srf-paramdesc-graphlink',
443
		];
444
445
		$params['graphcolor'] = [
446
			'type'    => 'boolean',
447
			'default' => false,
448
			'message' => 'srf-paramdesc-graphcolor',
449
		];
450
451
		$params['arrowdirection'] = [
452
			'aliases' => 'rankdir',
453
			'default' => 'LR',
454
			'message' => 'srf-paramdesc-rankdir',
455
			'values'  => [ 'LR', 'RL', 'TB', 'BT' ],
456
		];
457
458
		$params['nodeshape'] = [
459
			'default'           => false,
460
			'message'           => 'srf-paramdesc-graph-nodeshape',
461
			'manipulatedefault' => false,
462
			'values'            => self::$NODE_SHAPES,
463
		];
464
465
		$params['relation'] = [
466
			'default'           => 'child',
467
			'message'           => 'srf-paramdesc-graph-relation',
468
			'manipulatedefault' => false,
469
			'values'            => [ 'parent', 'child' ],
470
		];
471
472
		$params['nameproperty'] = [
473
			'default'           => false,
474
			'message'           => 'srf-paramdesc-graph-nameprop',
475
			'manipulatedefault' => false,
476
		];
477
478
		$params['wordwraplimit'] = [
479
			'type'              => 'integer',
480
			'default'           => 25,
481
			'message'           => 'srf-paramdesc-graph-wwl',
482
			'manipulatedefault' => false,
483
		];
484
485
		$params['arrowhead'] = [
486
			'type'              => 'string',
487
			'default'           => 'normal',
488
			'message'           => 'srf-paramdesc-graph-arrowhead',
489
			'manipulatedefault' => false,
490
			'values'            => self::$ARROW_HEAD,
491
		];
492
493
		return $params;
494
	}
495
}
496