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 classes like Cell 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 Cell, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
13 | class Cell |
||
14 | { |
||
15 | /** |
||
16 | * Default range variable constant. |
||
17 | * |
||
18 | * @var string |
||
19 | */ |
||
20 | const DEFAULT_RANGE = 'A1:A1'; |
||
21 | |||
22 | /** |
||
23 | * Value binder to use. |
||
24 | * |
||
25 | * @var IValueBinder |
||
26 | */ |
||
27 | private static $valueBinder; |
||
28 | |||
29 | /** |
||
30 | * Value of the cell. |
||
31 | * |
||
32 | * @var mixed |
||
33 | */ |
||
34 | private $value; |
||
35 | |||
36 | /** |
||
37 | * Calculated value of the cell (used for caching) |
||
38 | * This returns the value last calculated by MS Excel or whichever spreadsheet program was used to |
||
39 | * create the original spreadsheet file. |
||
40 | * Note that this value is not guaranteed to reflect the actual calculated value because it is |
||
41 | * possible that auto-calculation was disabled in the original spreadsheet, and underlying data |
||
42 | * values used by the formula have changed since it was last calculated. |
||
43 | * |
||
44 | * @var mixed |
||
45 | */ |
||
46 | private $calculatedValue; |
||
47 | |||
48 | /** |
||
49 | * Type of the cell data. |
||
50 | * |
||
51 | * @var string |
||
52 | */ |
||
53 | private $dataType; |
||
54 | |||
55 | /** |
||
56 | * Collection of cells. |
||
57 | * |
||
58 | * @var Cells |
||
59 | */ |
||
60 | private $parent; |
||
61 | |||
62 | /** |
||
63 | * Index to cellXf. |
||
64 | * |
||
65 | * @var int |
||
66 | */ |
||
67 | private $xfIndex = 0; |
||
68 | |||
69 | /** |
||
70 | * Attributes of the formula. |
||
71 | */ |
||
72 | private $formulaAttributes; |
||
73 | |||
74 | /** |
||
75 | * Update the cell into the cell collection. |
||
76 | * |
||
77 | * @return self |
||
78 | */ |
||
79 | 121 | public function updateInCollection() |
|
85 | |||
86 | 111 | public function detach() |
|
90 | |||
91 | 103 | public function attach(Cells $parent) |
|
95 | |||
96 | /** |
||
97 | * Create a new Cell. |
||
98 | * |
||
99 | * @param mixed $pValue |
||
100 | * @param string $pDataType |
||
101 | * @param Worksheet $pSheet |
||
102 | * |
||
103 | * @throws Exception |
||
104 | */ |
||
105 | 125 | public function __construct($pValue, $pDataType, Worksheet $pSheet) |
|
123 | |||
124 | /** |
||
125 | * Get cell coordinate column. |
||
126 | * |
||
127 | * @return string |
||
128 | */ |
||
129 | 48 | public function getColumn() |
|
133 | |||
134 | /** |
||
135 | * Get cell coordinate row. |
||
136 | * |
||
137 | * @return int |
||
138 | */ |
||
139 | 46 | public function getRow() |
|
143 | |||
144 | /** |
||
145 | * Get cell coordinate. |
||
146 | * |
||
147 | * @return string |
||
148 | */ |
||
149 | 125 | public function getCoordinate() |
|
153 | |||
154 | /** |
||
155 | * Get cell value. |
||
156 | * |
||
157 | * @return mixed |
||
158 | */ |
||
159 | 109 | public function getValue() |
|
163 | |||
164 | /** |
||
165 | * Get cell value with formatting. |
||
166 | * |
||
167 | * @return string |
||
168 | */ |
||
169 | 5 | public function getFormattedValue() |
|
177 | |||
178 | /** |
||
179 | * Set cell value. |
||
180 | * |
||
181 | * Sets the value for a cell, automatically determining the datatype using the value binder |
||
182 | * |
||
183 | * @param mixed $pValue Value |
||
184 | * |
||
185 | * @throws Exception |
||
186 | * |
||
187 | * @return Cell |
||
188 | */ |
||
189 | 79 | public function setValue($pValue) |
|
197 | |||
198 | /** |
||
199 | * Set the value for a cell, with the explicit data type passed to the method (bypassing any use of the value binder). |
||
200 | * |
||
201 | * @param mixed $pValue Value |
||
202 | * @param string $pDataType Explicit data type, see DataType::TYPE_* |
||
203 | * |
||
204 | * @throws Exception |
||
205 | * |
||
206 | * @return Cell |
||
207 | */ |
||
208 | 117 | public function setValueExplicit($pValue, $pDataType) |
|
252 | |||
253 | /** |
||
254 | * Get calculated cell value. |
||
255 | * |
||
256 | * @param bool $resetLog Whether the calculation engine logger should be reset or not |
||
257 | * |
||
258 | * @throws Exception |
||
259 | * |
||
260 | * @return mixed |
||
261 | */ |
||
262 | 71 | public function getCalculatedValue($resetLog = true) |
|
296 | |||
297 | /** |
||
298 | * Set old calculated value (cached). |
||
299 | * |
||
300 | * @param mixed $pValue Value |
||
301 | * |
||
302 | * @return Cell |
||
303 | */ |
||
304 | 22 | public function setCalculatedValue($pValue) |
|
312 | |||
313 | /** |
||
314 | * Get old calculated value (cached) |
||
315 | * This returns the value last calculated by MS Excel or whichever spreadsheet program was used to |
||
316 | * create the original spreadsheet file. |
||
317 | * Note that this value is not guaranteed to refelect the actual calculated value because it is |
||
318 | * possible that auto-calculation was disabled in the original spreadsheet, and underlying data |
||
319 | * values used by the formula have changed since it was last calculated. |
||
320 | * |
||
321 | * @return mixed |
||
322 | */ |
||
323 | public function getOldCalculatedValue() |
||
327 | |||
328 | /** |
||
329 | * Get cell data type. |
||
330 | * |
||
331 | * @return string |
||
332 | */ |
||
333 | 70 | public function getDataType() |
|
337 | |||
338 | /** |
||
339 | * Set cell data type. |
||
340 | * |
||
341 | * @param string $pDataType see DataType::TYPE_* |
||
342 | * |
||
343 | * @return Cell |
||
344 | */ |
||
345 | public function setDataType($pDataType) |
||
354 | |||
355 | /** |
||
356 | * Identify if the cell contains a formula. |
||
357 | * |
||
358 | * @return bool |
||
359 | */ |
||
360 | public function isFormula() |
||
364 | |||
365 | /** |
||
366 | * Does this cell contain Data validation rules? |
||
367 | * |
||
368 | * @throws Exception |
||
369 | * |
||
370 | * @return bool |
||
371 | */ |
||
372 | 3 | public function hasDataValidation() |
|
373 | { |
||
374 | 3 | if (!isset($this->parent)) { |
|
375 | throw new Exception('Cannot check for data validation when cell is not bound to a worksheet'); |
||
376 | } |
||
377 | |||
378 | 3 | return $this->getWorksheet()->dataValidationExists($this->getCoordinate()); |
|
379 | } |
||
380 | |||
381 | /** |
||
382 | * Get Data validation rules. |
||
383 | * |
||
384 | * @throws Exception |
||
385 | * |
||
386 | * @return DataValidation |
||
387 | */ |
||
388 | 4 | public function getDataValidation() |
|
396 | |||
397 | /** |
||
398 | * Set Data validation rules. |
||
399 | * |
||
400 | * @param DataValidation $pDataValidation |
||
401 | * |
||
402 | * @throws Exception |
||
403 | * |
||
404 | * @return Cell |
||
405 | */ |
||
406 | View Code Duplication | public function setDataValidation(DataValidation $pDataValidation = null) |
|
416 | |||
417 | /** |
||
418 | * Does this cell contain valid value? |
||
419 | * |
||
420 | * @return bool |
||
421 | */ |
||
422 | 3 | public function hasValidValue() |
|
428 | |||
429 | /** |
||
430 | * Does this cell contain a Hyperlink? |
||
431 | * |
||
432 | * @throws Exception |
||
433 | * |
||
434 | * @return bool |
||
435 | */ |
||
436 | public function hasHyperlink() |
||
444 | |||
445 | /** |
||
446 | * Get Hyperlink. |
||
447 | * |
||
448 | * @throws Exception |
||
449 | * |
||
450 | * @return Hyperlink |
||
451 | */ |
||
452 | 21 | public function getHyperlink() |
|
460 | |||
461 | /** |
||
462 | * Set Hyperlink. |
||
463 | * |
||
464 | * @param Hyperlink $pHyperlink |
||
465 | * |
||
466 | * @throws Exception |
||
467 | * |
||
468 | * @return Cell |
||
469 | */ |
||
470 | View Code Duplication | public function setHyperlink(Hyperlink $pHyperlink = null) |
|
480 | |||
481 | /** |
||
482 | * Get cell collection. |
||
483 | * |
||
484 | * @return Cells |
||
485 | */ |
||
486 | 64 | public function getParent() |
|
490 | |||
491 | /** |
||
492 | * Get parent worksheet. |
||
493 | * |
||
494 | * @return Worksheet |
||
495 | */ |
||
496 | 80 | public function getWorksheet() |
|
500 | |||
501 | /** |
||
502 | * Is this cell in a merge range. |
||
503 | * |
||
504 | * @return bool |
||
505 | */ |
||
506 | public function isInMergeRange() |
||
510 | |||
511 | /** |
||
512 | * Is this cell the master (top left cell) in a merge range (that holds the actual data value). |
||
513 | * |
||
514 | * @return bool |
||
515 | */ |
||
516 | public function isMergeRangeValueCell() |
||
528 | |||
529 | /** |
||
530 | * If this cell is in a merge range, then return the range. |
||
531 | * |
||
532 | * @return string |
||
533 | */ |
||
534 | public function getMergeRange() |
||
544 | |||
545 | /** |
||
546 | * Get cell style. |
||
547 | * |
||
548 | * @return Style |
||
549 | */ |
||
550 | 6 | public function getStyle() |
|
554 | |||
555 | /** |
||
556 | * Re-bind parent. |
||
557 | * |
||
558 | * @param Worksheet $parent |
||
559 | * |
||
560 | * @return Cell |
||
561 | */ |
||
562 | public function rebindParent(Worksheet $parent) |
||
568 | |||
569 | /** |
||
570 | * Is cell in a specific range? |
||
571 | * |
||
572 | * @param string $pRange Cell range (e.g. A1:A1) |
||
573 | * |
||
574 | * @return bool |
||
575 | */ |
||
576 | public function isInRange($pRange) |
||
588 | |||
589 | /** |
||
590 | * Coordinate from string. |
||
591 | * |
||
592 | * @param string $pCoordinateString eg: 'A1' |
||
593 | * |
||
594 | * @throws Exception |
||
595 | * |
||
596 | * @return string[] Array containing column and row (indexes 0 and 1) |
||
597 | */ |
||
598 | 174 | public static function coordinateFromString($pCoordinateString) |
|
610 | |||
611 | /** |
||
612 | * Make string row, column or cell coordinate absolute. |
||
613 | * |
||
614 | * @param string $pCoordinateString e.g. 'A' or '1' or 'A1' |
||
615 | * Note that this value can be a row or column reference as well as a cell reference |
||
616 | * |
||
617 | * @throws Exception |
||
618 | * |
||
619 | * @return string Absolute coordinate e.g. '$A' or '$1' or '$A$1' |
||
620 | */ |
||
621 | 21 | public static function absoluteReference($pCoordinateString) |
|
646 | |||
647 | /** |
||
648 | * Make string coordinate absolute. |
||
649 | * |
||
650 | * @param string $pCoordinateString e.g. 'A1' |
||
651 | * |
||
652 | * @throws Exception |
||
653 | * |
||
654 | * @return string Absolute coordinate e.g. '$A$1' |
||
655 | */ |
||
656 | 32 | public static function absoluteCoordinate($pCoordinateString) |
|
679 | |||
680 | /** |
||
681 | * Split range into coordinate strings. |
||
682 | * |
||
683 | * @param string $pRange e.g. 'B4:D9' or 'B4:D9,H2:O11' or 'B4' |
||
684 | * |
||
685 | * @return array Array containg one or more arrays containing one or two coordinate strings |
||
686 | * e.g. array('B4','D9') or array(array('B4','D9'),array('H2','O11')) |
||
687 | * or array('B4') |
||
688 | */ |
||
689 | 104 | public static function splitRange($pRange) |
|
704 | |||
705 | /** |
||
706 | * Build range from coordinate strings. |
||
707 | * |
||
708 | * @param array $pRange Array containg one or more arrays containing one or two coordinate strings |
||
709 | * |
||
710 | * @throws Exception |
||
711 | * |
||
712 | * @return string String representation of $pRange |
||
713 | */ |
||
714 | 19 | public static function buildRange(array $pRange) |
|
731 | |||
732 | /** |
||
733 | * Calculate range boundaries. |
||
734 | * |
||
735 | * @param string $pRange Cell range (e.g. A1:A1) |
||
736 | * |
||
737 | * @return array Range coordinates array(Start Cell, End Cell) |
||
738 | * where Start Cell and End Cell are arrays (Column Number, Row Number) |
||
739 | */ |
||
740 | 65 | public static function rangeBoundaries($pRange) |
|
767 | |||
768 | /** |
||
769 | * Calculate range dimension. |
||
770 | * |
||
771 | * @param string $pRange Cell range (e.g. A1:A1) |
||
772 | * |
||
773 | * @return array Range dimension (width, height) |
||
774 | */ |
||
775 | 16 | public static function rangeDimension($pRange) |
|
782 | |||
783 | /** |
||
784 | * Calculate range boundaries. |
||
785 | * |
||
786 | * @param string $pRange Cell range (e.g. A1:A1) |
||
787 | * |
||
788 | * @return array Range coordinates array(Start Cell, End Cell) |
||
789 | * where Start Cell and End Cell are arrays (Column ID, Row Number) |
||
790 | */ |
||
791 | 13 | public static function getRangeBoundaries($pRange) |
|
810 | |||
811 | /** |
||
812 | * Column index from string. |
||
813 | * |
||
814 | * @param string $pString eg 'A' |
||
815 | * |
||
816 | * @return int Column index (base 1 !!!) |
||
817 | */ |
||
818 | 166 | public static function columnIndexFromString($pString) |
|
858 | |||
859 | /** |
||
860 | * String from columnindex. |
||
861 | * |
||
862 | * @param int $columnIndex Column index (A = 0) |
||
863 | * |
||
864 | * @return string |
||
865 | */ |
||
866 | 133 | public static function stringFromColumnIndex($columnIndex) |
|
883 | |||
884 | /** |
||
885 | * Extract all cell references in range. |
||
886 | * |
||
887 | * @param string $pRange Range (e.g. A1 or A1:C10 or A1:E10 A20:E25) |
||
888 | * |
||
889 | * @return array Array containing single cell references |
||
890 | */ |
||
891 | 76 | public static function extractAllCellReferencesInRange($pRange) |
|
949 | |||
950 | /** |
||
951 | * Convert an associative array of single cell coordinates to values to an associative array |
||
952 | * of cell ranges to values. Only adjacent cell coordinates with the same |
||
953 | * value will be merged. If the value is an object, it must implement the method getHashCode(). |
||
954 | * |
||
955 | * For example, this function converts: |
||
956 | * |
||
957 | * [ 'A1' => 'x', 'A2' => 'x', 'A3' => 'x', 'A4' => 'y' ] |
||
958 | * |
||
959 | * to: |
||
960 | * |
||
961 | * [ 'A1:A3' => 'x', 'A4' => 'y' ] |
||
962 | * |
||
963 | * @param array $pCoordCollection associative array mapping coordinates to values |
||
964 | * |
||
965 | * @return array associative array mapping coordinate ranges to valuea |
||
966 | */ |
||
967 | 6 | public static function mergeRangesInCollection(array $pCoordCollection) |
|
1029 | |||
1030 | /** |
||
1031 | * Compare 2 cells. |
||
1032 | * |
||
1033 | * @param Cell $a Cell a |
||
1034 | * @param Cell $b Cell b |
||
1035 | * |
||
1036 | * @return int Result of comparison (always -1 or 1, never zero!) |
||
1037 | */ |
||
1038 | public static function compareCells(Cell $a, Cell $b) |
||
1050 | |||
1051 | /** |
||
1052 | * Get value binder to use. |
||
1053 | * |
||
1054 | * @return IValueBinder |
||
1055 | */ |
||
1056 | 79 | public static function getValueBinder() |
|
1064 | |||
1065 | /** |
||
1066 | * Set value binder to use. |
||
1067 | * |
||
1068 | * @param IValueBinder $binder |
||
1069 | * |
||
1070 | * @throws Exception |
||
1071 | */ |
||
1072 | 2 | public static function setValueBinder(IValueBinder $binder) |
|
1076 | |||
1077 | /** |
||
1078 | * Implement PHP __clone to create a deep clone, not just a shallow copy. |
||
1079 | */ |
||
1080 | 2 | View Code Duplication | public function __clone() |
1091 | |||
1092 | /** |
||
1093 | * Get index to cellXf. |
||
1094 | * |
||
1095 | * @return int |
||
1096 | */ |
||
1097 | 99 | public function getXfIndex() |
|
1101 | |||
1102 | /** |
||
1103 | * Set index to cellXf. |
||
1104 | * |
||
1105 | * @param int $pValue |
||
1106 | * |
||
1107 | * @return Cell |
||
1108 | */ |
||
1109 | 92 | public function setXfIndex($pValue) |
|
1115 | |||
1116 | /** |
||
1117 | * Set the formula attributes. |
||
1118 | * |
||
1119 | * @param mixed $pAttributes |
||
1120 | */ |
||
1121 | public function setFormulaAttributes($pAttributes) |
||
1127 | |||
1128 | /** |
||
1129 | * Get the formula attributes. |
||
1130 | */ |
||
1131 | 18 | public function getFormulaAttributes() |
|
1135 | |||
1136 | /** |
||
1137 | * Convert to string. |
||
1138 | * |
||
1139 | * @return string |
||
1140 | */ |
||
1141 | public function __toString() |
||
1145 | } |
||
1146 |
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.