VartypeCompare   C
last analyzed

Complexity

Total Complexity 53

Size/Duplication

Total Lines 600
Duplicated Lines 5.5 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 33
loc 600
rs 6.96
c 0
b 0
f 0
wmc 53
lcom 1
cbo 1

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 2
A VartypeCompare() 0 3 1
A get_tab_title() 8 8 4
A get_tab_list() 0 3 1
A get_test_group() 0 7 2
A print_tabs() 8 29 5
A print_tables() 0 17 2
B print_table() 11 73 6
A create_table_header() 0 48 4
A get_table_header_group_label() 0 15 5
A get_table_header_group_notes() 0 9 5
A get_table_header_cell_class() 3 8 3
A get_table_header_cell_title() 0 14 2
A print_compare_row_cells() 0 20 2
A get_table_cell_class() 3 10 4
A print_other_footnotes() 0 15 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
/**
3
 * Variable comparison tests.
4
 *
5
 * @package PHPCheatsheets
6
 */
7
8
// Prevent direct calls to this file.
9
if ( ! defined( 'APP_DIR' ) ) {
10
	header( 'Status: 403 Forbidden' );
11
	header( 'HTTP/1.1 403 Forbidden' );
12
	exit();
13
}
14
15
16
require_once APP_DIR . '/class.vartype.php';
17
18
/**
19
 * Variable comparison tests.
20
 */
21
class VartypeCompare extends Vartype {
22
23
	/**
24
	 * The tests to run.
25
	 *
26
	 * @var array $tests  Multi-dimensional array.
27
	 *                    Possible lower level array keys:
28
	 *                    - title     Used as tab title
29
	 *                    - tooltip   Additional code sample for tooltip on tab
30
	 *                    - url       Relevant PHP Manual page
31
	 *                    - arg       Function arguments
32
	 *                    - function  Function to run
33
	 *                    - Notes     Array of notes on this test
34
	 */
35
	var $tests = array(
36
37
		/**
38
		 * Operator based comparisons.
39
		 */
40
		'equal'         => array(
41
			'title'         => '==',
42
			'url'           => 'https://php.net/language.operators.comparison',
43
			'arg'           => '$a, $b',
44
			'function'      => 'pr_bool( $a == $b );',
45
		),
46
		'equal_strict'  => array(
47
			'title'         => '===',
48
			'url'           => 'https://php.net/language.operators.comparison',
49
			'arg'           => '$a, $b',
50
			'function'      => 'pr_bool( $a === $b );',
51
		),
52
		'not_equal'     => array(
53
			'title'         => '!=',
54
			'url'           => 'https://php.net/language.operators.comparison',
55
			'arg'           => '$a, $b',
56
			'function'      => 'pr_bool( $a != $b );',
57
		),
58
		'not_equal2'    => array(
59
			'title'         => '&lt;&gt;',
60
			'url'           => 'https://php.net/language.operators.comparison',
61
			'arg'           => '$a, $b',
62
			'function'      => 'pr_bool( $a <> $b );',
63
		),
64
		'not_equal_strict' => array(
65
			'title'         => '!==',
66
			'url'           => 'https://php.net/language.operators.comparison',
67
			'arg'           => '$a, $b',
68
			'function'      => 'pr_bool( $a !== $b );',
69
		),
70
		'less_than'     => array(
71
			'title'         => '&lt;',
72
			'url'           => 'https://php.net/language.operators.comparison',
73
			'arg'           => '$a, $b',
74
			'function'      => 'pr_bool( $a < $b );',
75
		),
76
		'greater_than'  => array(
77
			'title'         => '&gt;',
78
			'url'           => 'https://php.net/language.operators.comparison',
79
			'arg'           => '$a, $b',
80
			'function'      => 'pr_bool( $a > $b );',
81
		),
82
		'less_than_or_equal' => array(
83
			'title'         => '&lt;=',
84
			'url'           => 'https://php.net/language.operators.comparison',
85
			'arg'           => '$a, $b',
86
			'function'      => 'pr_bool( $a <= $b );',
87
		),
88
		'greater_than_or_equal' => array(
89
			'title'         => '&gt;=',
90
			'url'           => 'https://php.net/language.operators.comparison',
91
			'arg'           => '$a, $b',
92
			'function'      => 'pr_bool( $a >= $b );',
93
		),
94
95
		// Will be removed from $tests property from constructor if not on PHP 7+ to prevent parse errors.
96
		'spaceship'     => array(
97
			'title'         => '&lt;=&gt;',
98
			'url'           => 'https://php.net/language.operators.comparison',
99
			'arg'           => '$a, $b',
100
			'function'      => 'pr_int( $a <=> $b );',
101
			'notes'         => array(
102
				'<p>The Spaceship operator is only available in PHP 7.0.0+.</p>',
103
			),
104
		),
105
106
107
		/**
108
		 * String comparison functions.
109
		 *
110
		 * Note: all of these functions have a PHP5 equivalent in class.vartype-php5.php.
111
		 */
112
		'strcmp'        => array(
113
			'title'         => 'strcmp()',
114
			'url'           => 'https://php.net/strcmp',
115
			'arg'           => '$a, $b',
116
			'function'      => 'pc_compare_strings( $a, $b, "strcmp" );',
117
		),
118
		'strcasecmp'    => array(
119
			'title'         => 'strcasecmp()',
120
			'url'           => 'https://php.net/strcasecmp',
121
			'arg'           => '$a, $b',
122
			'function'      => 'pc_compare_strings( $a, $b, "strcasecmp" );',
123
		),
124
		'strnatcmp'     => array(
125
			'title'         => 'strnatcmp()',
126
			'url'           => 'https://php.net/strnatcmp',
127
			'arg'           => '$a, $b',
128
			'function'      => 'pc_compare_strings( $a, $b, "strnatcmp" );',
129
		),
130
		'strnatcasecmp' => array(
131
			'title'         => 'strnatcasecmp()',
132
			'url'           => 'https://php.net/strnatcasecmp',
133
			'arg'           => '$a, $b',
134
			'function'      => 'pc_compare_strings( $a, $b, "strnatcasecmp" );',
135
		),
136
		'strcoll'       => array(
137
			'title'         => 'strcoll()',
138
			'url'           => 'https://php.net/strcoll',
139
			'arg'           => '$a, $b',
140
			'function'      => 'pc_compare_strings( $a, $b, "strcoll" );',
141
		),
142
		'similar_text'  => array(
143
			'title'         => 'similar_text()',
144
			'url'           => 'https://php.net/similar_text',
145
			'arg'           => '$a, $b',
146
			'function'      => 'pc_compare_strings( $a, $b, "similar_text" );',
147
		),
148
		'levenshtein'   => array(
149
			'title'         => 'levenshtein()',
150
			'url'           => 'https://php.net/levenshtein',
151
			'arg'           => '$a, $b',
152
			'function'      => 'pc_compare_strings( $a, $b, "levenshtein" );',
153
		),
154
155
156
		/**
157
		 * Number comparison functions
158
		 */
159
		'bccomp'        => array(
160
			'title'         => 'bccomp()',
161
			'url'           => 'https://php.net/bccomp',
162
			'arg'           => '$a, $b',
163
			'function'      => 'if ( extension_loaded( \'bcmath\' ) ) { $r = bccomp( $a, $b ); if ( is_int( $r ) ) { pr_int( $r ); } else { pr_var( $r, \'\', true, true ); } } else { print \'E: bcmath extension not installed\'; }',
164
			'notes'         => array(
165
				'<p>Remember that the default <code>bcscale()</code> is 0 !</p>',
166
				'<p>For a reliable implementation of all the BCMath functions which avoids a number of the common pitfalls, see <a href="https://gist.github.com/jrfnl/8449978" target="_blank">this example function</a> (gist).</p>',
167
			),
168
		),
169
		'min'           => array(
170
			'title'         => 'min()',
171
			'url'           => 'https://php.net/min',
172
			'arg'           => '$a, $b',
173
			'function'      => 'pr_var( min( $a, $b ), \'\', true, true );',
174
			'notes'         => array(
175
				'<p><strong>Please note:</strong> <code>min() / max()</code> will evaluate a non-numeric string as 0 if compared to integer, but still return the string if it\'s seen as the numerically lowest/highest value.</p>',
176
				'<p><code>min()</code> If multiple arguments evaluate to 0, will return the lowest alphanumerical string value if any strings are given, else a numeric 0 is returned.</p>',
177
			),
178
		),
179
180
		'max'           => array(
181
			'title'         => 'max()',
182
			'url'           => 'https://php.net/max',
183
			'arg'           => '$a, $b',
184
			'function'      => 'pr_var( max( $a, $b ), \'\', true, true );',
185
			'notes'         => array(
186
				'<p><strong>Please note:</strong> <code>min() / max()</code> will evaluate a non-numeric string as 0 if compared to integer, but still return the string if it\'s seen as the numerically lowest/highest value.</p>',
187
				'<p><code>max()</code> returns the numerically highest of the parameter values. If multiple values can be considered of the same size, the one that is listed first will be returned.<br />
188
				If <code>max()</code> is given multiple arrays, the longest array is returned. If all the arrays have the same length, <code>max()</code> will use lexicographic ordering to find the return value.</p>',
189
			),
190
		),
191
192
		/*
193
		 * Version compare.
194
		 * Expects string input.
195
		 */
196
		'version_compare' => array(
197
			'title'         => 'version_compare()',
198
			'url'           => 'https://php.net/version_compare',
199
			'arg'           => '$a, $b',
200
			'function'      => 'pr_var( version_compare( $a, $b ), \'\', true, true );',
201
		),
202
	);
203
204
205
	/**
206
	 * Constructor.
207
	 */
208
	function __construct() {
209
		// Remove spaceship comparison for PHP < 7.
210
		if ( PHP_VERSION_ID < 70000 ) {
211
			unset( $this->tests['spaceship'] );
212
		}
213
		parent::__construct();
214
	}
215
216
217
	/**
218
	 * PHP4 Constructor.
219
	 */
220
	function VartypeCompare() {
221
		$this->__construct();
222
	}
223
224
225
	/**
226
	 * Get the tab title for the initial tab for use in the page header.
227
	 *
228
	 * @param string $tab
229
	 * @return string
230
	 */
231 View Code Duplication
	function get_tab_title( $tab ) {
232
		if ( isset( $this->tests[ $tab ]['title'] ) && is_string( $this->tests[ $tab ]['title'] ) && $this->tests[ $tab ]['title'] !== '' ) {
233
			return $this->tests[ $tab ]['title'];
234
		}
235
		else {
236
			return '';
237
		}
238
	}
239
240
241
	/**
242
	 * Get a list of all tabs which this class will create.
243
	 *
244
	 * Helper function for the sitemap.
245
	 *
246
	 * @return array
247
	 */
248
	function get_tab_list() {
249
		return array_keys( $this->tests );
250
	}
251
252
253
	/**
254
	 * Determine which tests to run.
255
	 *
256
	 * @param string|null $test_group
257
	 *
258
	 * @return string
259
	 */
260
	function get_test_group( $test_group = null ) {
261
		$key = key( $this->tests ); // Get first item in array.
262
		if ( isset( $test_group, $this->tests[ $test_group ] ) ) {
263
			$key = $test_group;
264
		}
265
		return $key;
266
	}
267
268
269
	/**
270
	 * Generate the subsection tabs (at the top of the page) for the cheatsheet.
271
	 *
272
	 * @param bool $all
273
	 */
274
	function print_tabs( $all = false ) {
275
		echo '
276
	<ul>';
277
278
		foreach ( $this->tests as $key => $test ) {
279
			$tooltip = '';
280
			if ( isset( $test['tooltip'] ) ) {
281
				$tooltip = ' title="' . $test['tooltip'] . '"';
282
			}
283
284
			$active_class = '';
285
			if ( $GLOBALS['tab'] === $key ) {
286
				$active_class = ' class="ui-tabs-active ui-state-active"';
287
			}
288
289 View Code Duplication
			if ( $all === true ) {
290
				echo '
291
		<li', $tooltip, '><a href="#', $key, '" data-tab="', $key, '" data-tab-title="', $test['title'], '"><strong>', $test['title'], '</strong></a></li>';
292
			}
293
			else {
294
				echo '
295
		<li', $active_class, $tooltip, '><a href="', BASE_URI, $GLOBALS['type'], '/', $key, '/ajax" data-tab="', $key, '" data-tab-title="', $test['title'], '"><strong>', $test['title'], '</strong></a></li>';
296
			}
297
		}
298
		unset( $key, $test, $tooltip );
299
300
		echo '
301
	</ul>';
302
	}
303
304
305
	/**
306
	 * Print all tables for the cheatsheet.
307
	 */
308
	function print_tables() {
309
310
		echo '
311
	<div class="tables">';
312
313
		$this->set_test_data();
314
315
		foreach ( $this->tests as $key => $test_settings ) {
316
			$GLOBALS['test'] = $key;
317
			$this->print_table( $key );
318
		}
319
		unset( $key, $test_settings );
320
		$this->clean_up();
321
322
		echo '
323
	</div>';
324
	}
325
326
327
	/**
328
	 * Generate the table for one specific subsection of a comparison cheatsheet.
329
	 *
330
	 * @param string $test The current subsection.
331
	 */
332
	function print_table( $test ) {
333
334
		if ( isset( $this->tests[ $test ] ) ) {
335
			$GLOBALS['encountered_errors'] = array();
336
337
			echo '
338
		<div id="', $test, '">';
339
340
341
			$this->print_tabletop( $test );
342
343
344
			$last_key = null;
345
346
			foreach ( $this->test_data_keys as $key1 ) {
347
				$value1 = $this->test_data[ $key1 ];
348
				$legend = '';
349 View Code Duplication
				if ( isset( $this->test_legend[ $key1 ] ) ) {
350
					$legend = '<sup class="fright"><a href="#var-legend-' . $key1 . '">&dagger;' . $key1 . '</a></sup>';
351
				}
352
353
				$type = substr( $key1, 0, 1 );
354
355
				$class = array();
356
				if ( $type !== $last_key ) {
357
					$class[]  = 'new-var-type';
358
					$last_key = $type;
359
				}
360
361 View Code Duplication
				if ( count( $class ) > 0 ) {
362
					echo '
363
				<tr class="', implode( ' ', $class ), '">';
364
				}
365
				else {
366
					echo '
367
				<tr>';
368
				}
369
370
				echo '
371
					<th>', $legend;
372
				pr_var( $value1, '', true );
373
				echo '
374
					</th>';
375
376
				$this->print_compare_row_cells( $value1, $key1, $test );
377
378
				echo '
379
					<th>', $legend;
380
				pr_var( $value1, '', true );
381
				echo '
382
					</th>
383
				</tr>';
384
385
				unset( $value1, $label, $type, $hr_key, $class );
386
			}
387
			unset( $key1, $last_key );
388
389
			echo '
390
			</tbody>
391
			</table>';
392
393
394
			$this->print_error_footnotes( $test );
395
			$this->print_other_footnotes( $test );
396
397
			echo '
398
		</div>';
399
		}
400
		else {
401
			trigger_error( 'Unknown test <b>' . $test . '</b>', E_USER_WARNING );
402
		}
403
404
	}
405
406
407
	/**
408
	 * Generate the first row of the cheatsheet table.
409
	 *
410
	 * @param string $test The current subsection.
411
	 *
412
	 * @return string
413
	 */
414
	function create_table_header( $test ) {
415
416
		$group_label = $this->get_table_header_group_label( $test );
417
		$group_notes = $this->get_table_header_group_notes( $test );
418
419
		$html = '
420
				<tr>
421
					<th>' . $group_label . $group_notes . '</th>';
422
423
424
		// Top labels.
425
		foreach ( $this->test_data_keys as $i => $key ) {
426
427
			$value = $this->test_data[ $key ];
428
			$class = $this->get_table_header_cell_class( $i, $key );
429
430
			$html .= '
431
					<th' . $class . '>';
432
433
			ob_start();
434
			pr_var( $value, '', false, true, '' );
435
			$label = ob_get_clean();
436
437
			// Add tooltips.
438
			if ( strpos( $label, 'Object: ' ) !== false ) {
439
				$label = $this->get_table_header_cell_title( $label, true );
440
				$html .= '<span title="' . $label . '">Object(&hellip;)</span>';
441
			}
442
			else if ( strpos( $label, 'Array: ' ) !== false ) {
443
				$label = $this->get_table_header_cell_title( $label, false );
444
				$html .= '<span title="' . $label . '">Array(&hellip;)</span>';
445
			}
446
			else {
447
				$html .= $label;
448
			}
449
			$html .= '					</th>';
450
451
			unset( $value, $class, $label );
452
		}
453
		unset( $i, $key );
454
455
456
		$html .= '
457
					<th>' . $group_label . $group_notes . '</th>
458
				</tr>';
459
460
		return $html;
461
	}
462
463
464
	/**
465
	 * Get the - potentially linked - group label (= first cell in the table header).
466
	 *
467
	 * @param string $test
468
	 *
469
	 * @return string
470
	 */
471
	function get_table_header_group_label( $test ) {
472
		$tooltip = '';
473
		if ( isset( $this->tests[ $test ]['tooltip'] ) && $this->tests[ $test ]['tooltip'] !== '' ) {
474
			$tooltip = ' title="' . $this->tests[ $test ]['tooltip'] . '"';
475
		}
476
477
		if ( isset( $this->tests[ $test ]['url'] ) && $this->tests[ $test ]['url'] !== '' ) {
478
			$group_label = '<a href="' . $this->tests[ $test ]['url'] . '" target="_blank"' . $tooltip . '><strong>' . $this->tests[ $test ]['title'] . '</strong></a>';
479
		}
480
		else {
481
			$group_label = '<a href="' . $this->tests[ $test ]['url'] . '" target="_blank"' . $tooltip . '><strong>' . $this->tests[ $test ]['title'] . '</strong></a>';
482
		}
483
484
		return $group_label;
485
	}
486
487
488
	/**
489
	 * Get the notes related to the group label, if any.
490
	 *
491
	 * @param string $test
492
	 *
493
	 * @return string
494
	 */
495
	function get_table_header_group_notes( $test ) {
496
		$notes = '';
497
		if ( isset( $this->tests[ $test ]['notes'] ) && ( is_array( $this->tests[ $test ]['notes'] ) && count( $this->tests[ $test ]['notes'] ) > 0 ) ) {
498
			foreach ( $this->tests[ $test ]['notes'] as $key => $note ) {
499
				$notes .= ' <sup><a href="#' . $test . '-note' . ( $key + 1 ) . '">&Dagger;' . ( $key + 1 ) . '</a></sup>';
500
			}
501
		}
502
		return $notes;
503
	}
504
505
506
	/**
507
	 * Get the CSS class string to attach to a table header cell.
508
	 *
509
	 * @param string $index
510
	 * @param string $key
511
	 *
512
	 * @return string
513
	 */
514
	function get_table_header_cell_class( $index, $key ) {
515
		$class = '';
516 View Code Duplication
		if ( ! isset( $this->test_data_keys[ ( $index + 1 ) ] ) || substr( $key, 0, 1 ) !== substr( $this->test_data_keys[ ( $index + 1 ) ], 0, 1 ) ) {
517
			$class = ' class="end"';
518
		}
519
520
		return $class;
521
	}
522
523
524
	/**
525
	 * Adjust the cell label to make it usable as a title in a jQuery tooltip.
526
	 *
527
	 * @todo: improve upon - preferably in a way that the tooltip is fully HTML capable.
528
	 *
529
	 * @param string $label  Original label.
530
	 * @param bool   $object Whether this is an object or an array. Defaults to false (= array ).
531
	 *
532
	 * @return string Adjusted label
533
	 */
534
	function get_table_header_cell_title( $label, $object = false ) {
535
		$label = str_replace( '&nbsp;', ' ', $label );
536
		$label = str_replace( "\n", '', $label );
537
		if ( $object === true ) {
538
			$label = str_replace( 'null', "null\n", $label );
539
		}
540
		$label = str_replace( '<br />', "\n", $label );
541
		$label = str_replace( '&lsquo;', "'", $label );
542
		$label = str_replace( '&rsquo;', "'", $label );
543
		$label = strip_tags( $label );
544
		$label = htmlspecialchars( $label, ENT_QUOTES, 'UTF-8' );
545
546
		return $label;
547
	}
548
549
550
	/**
551
	 * Generate a cheatsheet result row.
552
	 *
553
	 * @param mixed  $value1 The value this row applies to.
554
	 * @param string $key1   The array key reference to that value in the testdata array.
555
	 * @param string $test   The current subsection.
556
	 */
557
	function print_compare_row_cells( $value1, $key1, $test ) {
558
559
		foreach ( $this->test_data_keys as $i => $key2 ) {
560
			$GLOBALS['has_error'] = array();
561
562
			$value2 = $this->test_data[ $key2 ];
563
			$class  = $this->get_table_cell_class( $key1, $key2, $i );
564
565
			echo '
566
					<td' . $class . '>';
567
568
			$this->tests[ $test ]['test']( $value1, $value2 );
569
			$this->print_row_cell_error_refs();
570
571
			echo '					</td>';
572
573
			unset( $GLOBALS['has_error'], $value2, $type, $class );
574
		}
575
		unset( $i, $key2 );
576
	}
577
578
579
	/**
580
	 * Get the CSS class string to attach to a table cell.
581
	 *
582
	 * @param string $key1
583
	 * @param string $key2
584
	 * @param string $index
585
	 *
586
	 * @return string
587
	 */
588
	function get_table_cell_class( $key1, $key2, $index ) {
589
		$class = array( 'value1-' . $key1, 'value2-' . $key2 );
590 View Code Duplication
		if ( ! isset( $this->test_data_keys[ ( $index + 1 ) ] ) || substr( $key2, 0, 1 ) !== substr( $this->test_data_keys[ ( $index + 1 ) ], 0, 1 ) ) {
591
			$class[] = 'end';
592
		}
593
594
		$class = ( ( count( $class ) > 0 ) ? ' class="' . implode( ' ', $class ) . '"' : '' );
595
596
		return $class;
597
	}
598
599
600
	/**
601
	 * Generate footnotes for a test subsection if applicable.
602
	 *
603
	 * @param string $test The current subsection.
604
	 */
605
	function print_other_footnotes( $test ) {
606
		if ( isset( $this->tests[ $test ]['notes'] ) && ( is_array( $this->tests[ $test ]['notes'] ) && count( $this->tests[ $test ]['notes'] ) > 0 ) ) {
607
			foreach ( $this->tests[ $test ]['notes'] as $key => $note ) {
608
				printf(
609
					'
610
			<div id="%1$s-note%2$s" class="note-appendix">
611
				<sup>&Dagger; %2$s</sup> %3$s
612
			</div>',
613
					$test,
614
					( $key + 1 ),
615
					$note
616
				);
617
			}
618
		}
619
	}
620
}
621