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 LookupRef 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 LookupRef, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
30 | class LookupRef |
||
31 | { |
||
32 | /** |
||
33 | * CELL_ADDRESS. |
||
34 | * |
||
35 | * Creates a cell address as text, given specified row and column numbers. |
||
36 | * |
||
37 | * Excel Function: |
||
38 | * =ADDRESS(row, column, [relativity], [referenceStyle], [sheetText]) |
||
39 | * |
||
40 | * @param row Row number to use in the cell reference |
||
41 | * @param column Column number to use in the cell reference |
||
42 | * @param relativity Flag indicating the type of reference to return |
||
43 | * 1 or omitted Absolute |
||
44 | * 2 Absolute row; relative column |
||
45 | * 3 Relative row; absolute column |
||
46 | * 4 Relative |
||
47 | * @param referenceStyle A logical value that specifies the A1 or R1C1 reference style. |
||
48 | * TRUE or omitted CELL_ADDRESS returns an A1-style reference |
||
49 | * FALSE CELL_ADDRESS returns an R1C1-style reference |
||
50 | * @param sheetText Optional Name of worksheet to use |
||
51 | * @param mixed $row |
||
52 | * @param mixed $column |
||
53 | * @param mixed $relativity |
||
54 | * @param mixed $referenceStyle |
||
55 | * @param mixed $sheetText |
||
56 | * |
||
57 | * @return string |
||
58 | */ |
||
59 | public static function cellAddress($row, $column, $relativity = 1, $referenceStyle = true, $sheetText = '') |
||
60 | { |
||
61 | $row = Functions::flattenSingleValue($row); |
||
62 | $column = Functions::flattenSingleValue($column); |
||
63 | $relativity = Functions::flattenSingleValue($relativity); |
||
64 | $sheetText = Functions::flattenSingleValue($sheetText); |
||
65 | |||
66 | if (($row < 1) || ($column < 1)) { |
||
67 | return Functions::VALUE(); |
||
68 | } |
||
69 | |||
70 | if ($sheetText > '') { |
||
71 | if (strpos($sheetText, ' ') !== false) { |
||
72 | $sheetText = "'" . $sheetText . "'"; |
||
73 | } |
||
74 | $sheetText .= '!'; |
||
75 | } |
||
76 | if ((!is_bool($referenceStyle)) || $referenceStyle) { |
||
77 | $rowRelative = $columnRelative = '$'; |
||
78 | $column = Cell::stringFromColumnIndex($column - 1); |
||
79 | if (($relativity == 2) || ($relativity == 4)) { |
||
80 | $columnRelative = ''; |
||
81 | } |
||
82 | if (($relativity == 3) || ($relativity == 4)) { |
||
83 | $rowRelative = ''; |
||
84 | } |
||
85 | |||
86 | return $sheetText . $columnRelative . $column . $rowRelative . $row; |
||
87 | } |
||
88 | if (($relativity == 2) || ($relativity == 4)) { |
||
89 | $column = '[' . $column . ']'; |
||
90 | } |
||
91 | if (($relativity == 3) || ($relativity == 4)) { |
||
92 | $row = '[' . $row . ']'; |
||
93 | } |
||
94 | |||
95 | return $sheetText . 'R' . $row . 'C' . $column; |
||
96 | } |
||
97 | |||
98 | /** |
||
99 | * COLUMN. |
||
100 | * |
||
101 | * Returns the column number of the given cell reference |
||
102 | * If the cell reference is a range of cells, COLUMN returns the column numbers of each column in the reference as a horizontal array. |
||
103 | * If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the |
||
104 | * reference of the cell in which the COLUMN function appears; otherwise this function returns 0. |
||
105 | * |
||
106 | * Excel Function: |
||
107 | * =COLUMN([cellAddress]) |
||
108 | * |
||
109 | * @param cellAddress A reference to a range of cells for which you want the column numbers |
||
110 | * @param null|mixed $cellAddress |
||
111 | * |
||
112 | * @return int or array of integer |
||
113 | */ |
||
114 | public static function COLUMN($cellAddress = null) |
||
115 | { |
||
116 | if (is_null($cellAddress) || trim($cellAddress) === '') { |
||
117 | return 0; |
||
118 | } |
||
119 | |||
120 | if (is_array($cellAddress)) { |
||
121 | foreach ($cellAddress as $columnKey => $value) { |
||
122 | $columnKey = preg_replace('/[^a-z]/i', '', $columnKey); |
||
123 | |||
124 | return (int) Cell::columnIndexFromString($columnKey); |
||
125 | } |
||
126 | View Code Duplication | } else { |
|
|
|||
127 | if (strpos($cellAddress, '!') !== false) { |
||
128 | list($sheet, $cellAddress) = explode('!', $cellAddress); |
||
129 | } |
||
130 | if (strpos($cellAddress, ':') !== false) { |
||
131 | list($startAddress, $endAddress) = explode(':', $cellAddress); |
||
132 | $startAddress = preg_replace('/[^a-z]/i', '', $startAddress); |
||
133 | $endAddress = preg_replace('/[^a-z]/i', '', $endAddress); |
||
134 | $returnValue = []; |
||
135 | do { |
||
136 | $returnValue[] = (int) Cell::columnIndexFromString($startAddress); |
||
137 | } while ($startAddress++ != $endAddress); |
||
138 | |||
139 | return $returnValue; |
||
140 | } |
||
141 | $cellAddress = preg_replace('/[^a-z]/i', '', $cellAddress); |
||
142 | |||
143 | return (int) Cell::columnIndexFromString($cellAddress); |
||
144 | } |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * COLUMNS. |
||
149 | * |
||
150 | * Returns the number of columns in an array or reference. |
||
151 | * |
||
152 | * Excel Function: |
||
153 | * =COLUMNS(cellAddress) |
||
154 | * |
||
155 | * @param cellAddress An array or array formula, or a reference to a range of cells for which you want the number of columns |
||
156 | * @param null|mixed $cellAddress |
||
157 | * |
||
158 | * @return int The number of columns in cellAddress |
||
159 | */ |
||
160 | View Code Duplication | public static function COLUMNS($cellAddress = null) |
|
161 | { |
||
162 | if (is_null($cellAddress) || $cellAddress === '') { |
||
163 | return 1; |
||
164 | } elseif (!is_array($cellAddress)) { |
||
165 | return Functions::VALUE(); |
||
166 | } |
||
167 | |||
168 | reset($cellAddress); |
||
169 | $isMatrix = (is_numeric(key($cellAddress))); |
||
170 | list($columns, $rows) = Calculation::_getMatrixDimensions($cellAddress); |
||
171 | |||
172 | if ($isMatrix) { |
||
173 | return $rows; |
||
174 | } |
||
175 | |||
176 | return $columns; |
||
177 | } |
||
178 | |||
179 | /** |
||
180 | * ROW. |
||
181 | * |
||
182 | * Returns the row number of the given cell reference |
||
183 | * If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference as a vertical array. |
||
184 | * If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the |
||
185 | * reference of the cell in which the ROW function appears; otherwise this function returns 0. |
||
186 | * |
||
187 | * Excel Function: |
||
188 | * =ROW([cellAddress]) |
||
189 | * |
||
190 | * @param cellAddress A reference to a range of cells for which you want the row numbers |
||
191 | * @param null|mixed $cellAddress |
||
192 | * |
||
193 | * @return int or array of integer |
||
194 | */ |
||
195 | public static function ROW($cellAddress = null) |
||
196 | { |
||
197 | if (is_null($cellAddress) || trim($cellAddress) === '') { |
||
198 | return 0; |
||
199 | } |
||
200 | |||
201 | if (is_array($cellAddress)) { |
||
202 | foreach ($cellAddress as $columnKey => $rowValue) { |
||
203 | foreach ($rowValue as $rowKey => $cellValue) { |
||
204 | return (int) preg_replace('/[^0-9]/', '', $rowKey); |
||
205 | } |
||
206 | } |
||
207 | View Code Duplication | } else { |
|
208 | if (strpos($cellAddress, '!') !== false) { |
||
209 | list($sheet, $cellAddress) = explode('!', $cellAddress); |
||
210 | } |
||
211 | if (strpos($cellAddress, ':') !== false) { |
||
212 | list($startAddress, $endAddress) = explode(':', $cellAddress); |
||
213 | $startAddress = preg_replace('/[^0-9]/', '', $startAddress); |
||
214 | $endAddress = preg_replace('/[^0-9]/', '', $endAddress); |
||
215 | $returnValue = []; |
||
216 | do { |
||
217 | $returnValue[][] = (int) $startAddress; |
||
218 | } while ($startAddress++ != $endAddress); |
||
219 | |||
220 | return $returnValue; |
||
221 | } |
||
222 | list($cellAddress) = explode(':', $cellAddress); |
||
223 | |||
224 | return (int) preg_replace('/[^0-9]/', '', $cellAddress); |
||
225 | } |
||
226 | } |
||
227 | |||
228 | /** |
||
229 | * ROWS. |
||
230 | * |
||
231 | * Returns the number of rows in an array or reference. |
||
232 | * |
||
233 | * Excel Function: |
||
234 | * =ROWS(cellAddress) |
||
235 | * |
||
236 | * @param cellAddress An array or array formula, or a reference to a range of cells for which you want the number of rows |
||
237 | * @param null|mixed $cellAddress |
||
238 | * |
||
239 | * @return int The number of rows in cellAddress |
||
240 | */ |
||
241 | View Code Duplication | public static function ROWS($cellAddress = null) |
|
242 | { |
||
243 | if (is_null($cellAddress) || $cellAddress === '') { |
||
244 | return 1; |
||
245 | } elseif (!is_array($cellAddress)) { |
||
246 | return Functions::VALUE(); |
||
247 | } |
||
248 | |||
249 | reset($cellAddress); |
||
250 | $isMatrix = (is_numeric(key($cellAddress))); |
||
251 | list($columns, $rows) = Calculation::_getMatrixDimensions($cellAddress); |
||
252 | |||
253 | if ($isMatrix) { |
||
254 | return $columns; |
||
255 | } |
||
256 | |||
257 | return $rows; |
||
258 | } |
||
259 | |||
260 | /** |
||
261 | * HYPERLINK. |
||
262 | * |
||
263 | * Excel Function: |
||
264 | * =HYPERLINK(linkURL,displayName) |
||
265 | * |
||
266 | * @category Logical Functions |
||
267 | * |
||
268 | * @param string $linkURL Value to check, is also the value returned when no error |
||
269 | * @param string $displayName Value to return when testValue is an error condition |
||
270 | * @param Cell $pCell The cell to set the hyperlink in |
||
271 | * |
||
272 | * @return mixed The value of $displayName (or $linkURL if $displayName was blank) |
||
273 | */ |
||
274 | 1 | public static function HYPERLINK($linkURL = '', $displayName = null, Cell $pCell = null) |
|
275 | { |
||
276 | 1 | $linkURL = (is_null($linkURL)) ? '' : Functions::flattenSingleValue($linkURL); |
|
277 | 1 | $displayName = (is_null($displayName)) ? '' : Functions::flattenSingleValue($displayName); |
|
278 | |||
279 | 1 | if ((!is_object($pCell)) || (trim($linkURL) == '')) { |
|
280 | return Functions::REF(); |
||
281 | } |
||
282 | |||
283 | 1 | if ((is_object($displayName)) || trim($displayName) == '') { |
|
284 | $displayName = $linkURL; |
||
285 | } |
||
286 | |||
287 | 1 | $pCell->getHyperlink()->setUrl($linkURL); |
|
288 | 1 | $pCell->getHyperlink()->setTooltip($displayName); |
|
289 | |||
290 | 1 | return $displayName; |
|
291 | } |
||
292 | |||
293 | /** |
||
294 | * INDIRECT. |
||
295 | * |
||
296 | * Returns the reference specified by a text string. |
||
297 | * References are immediately evaluated to display their contents. |
||
298 | * |
||
299 | * Excel Function: |
||
300 | * =INDIRECT(cellAddress) |
||
301 | * |
||
302 | * NOTE - INDIRECT() does not yet support the optional a1 parameter introduced in Excel 2010 |
||
303 | * |
||
304 | * @param cellAddress $cellAddress The cell address of the current cell (containing this formula) |
||
305 | * @param Cell $pCell The current cell (containing this formula) |
||
306 | * |
||
307 | * @return mixed The cells referenced by cellAddress |
||
308 | * |
||
309 | * @todo Support for the optional a1 parameter introduced in Excel 2010 |
||
310 | */ |
||
311 | public static function INDIRECT($cellAddress = null, Cell $pCell = null) |
||
351 | |||
352 | /** |
||
353 | * OFFSET. |
||
354 | * |
||
355 | * Returns a reference to a range that is a specified number of rows and columns from a cell or range of cells. |
||
356 | * The reference that is returned can be a single cell or a range of cells. You can specify the number of rows and |
||
357 | * the number of columns to be returned. |
||
358 | * |
||
359 | * Excel Function: |
||
360 | * =OFFSET(cellAddress, rows, cols, [height], [width]) |
||
361 | * |
||
362 | * @param cellAddress The reference from which you want to base the offset. Reference must refer to a cell or |
||
363 | * range of adjacent cells; otherwise, OFFSET returns the #VALUE! error value. |
||
364 | * @param rows The number of rows, up or down, that you want the upper-left cell to refer to. |
||
365 | * Using 5 as the rows argument specifies that the upper-left cell in the reference is |
||
366 | * five rows below reference. Rows can be positive (which means below the starting reference) |
||
367 | * or negative (which means above the starting reference). |
||
368 | * @param cols The number of columns, to the left or right, that you want the upper-left cell of the result |
||
369 | * to refer to. Using 5 as the cols argument specifies that the upper-left cell in the |
||
370 | * reference is five columns to the right of reference. Cols can be positive (which means |
||
371 | * to the right of the starting reference) or negative (which means to the left of the |
||
372 | * starting reference). |
||
373 | * @param height The height, in number of rows, that you want the returned reference to be. Height must be a positive number. |
||
374 | * @param width The width, in number of columns, that you want the returned reference to be. Width must be a positive number. |
||
375 | * @param null|mixed $cellAddress |
||
376 | * @param mixed $rows |
||
377 | * @param mixed $columns |
||
378 | * @param null|mixed $height |
||
379 | * @param null|mixed $width |
||
380 | * @param Cell $pCell |
||
381 | * |
||
382 | * @return string A reference to a cell or range of cells |
||
383 | */ |
||
384 | public static function OFFSET($cellAddress = null, $rows = 0, $columns = 0, $height = null, $width = null, Cell $pCell = null) |
||
450 | |||
451 | /** |
||
452 | * CHOOSE. |
||
453 | * |
||
454 | * Uses lookup_value to return a value from the list of value arguments. |
||
455 | * Use CHOOSE to select one of up to 254 values based on the lookup_value. |
||
456 | * |
||
457 | * Excel Function: |
||
458 | * =CHOOSE(index_num, value1, [value2], ...) |
||
459 | * |
||
460 | * @param index_num Specifies which value argument is selected. |
||
461 | * Index_num must be a number between 1 and 254, or a formula or reference to a cell containing a number |
||
462 | * between 1 and 254. |
||
463 | * @param value1... Value1 is required, subsequent values are optional. |
||
464 | * Between 1 to 254 value arguments from which CHOOSE selects a value or an action to perform based on |
||
465 | * index_num. The arguments can be numbers, cell references, defined names, formulas, functions, or |
||
466 | * text. |
||
467 | * |
||
468 | * @return mixed The selected value |
||
469 | */ |
||
470 | public static function CHOOSE(...$chooseArgs) |
||
494 | |||
495 | /** |
||
496 | * MATCH. |
||
497 | * |
||
498 | * The MATCH function searches for a specified item in a range of cells |
||
499 | * |
||
500 | * Excel Function: |
||
501 | * =MATCH(lookup_value, lookup_array, [match_type]) |
||
502 | * |
||
503 | * @param mixed $lookupValue The value that you want to match in lookup_array |
||
504 | * @param mixed $lookupArray The range of cells being searched |
||
505 | * @param mixed $matchType The number -1, 0, or 1. -1 means above, 0 means exact match, 1 means below. If match_type is 1 or -1, the list has to be ordered. |
||
506 | * |
||
507 | * @return int The relative position of the found item |
||
508 | */ |
||
509 | 15 | public static function MATCH($lookupValue, $lookupArray, $matchType = 1) |
|
606 | |||
607 | /** |
||
608 | * INDEX. |
||
609 | * |
||
610 | * Uses an index to choose a value from a reference or array |
||
611 | * |
||
612 | * Excel Function: |
||
613 | * =INDEX(range_array, row_num, [column_num]) |
||
614 | * |
||
615 | * @param range_array A range of cells or an array constant |
||
616 | * @param row_num The row in array from which to return a value. If row_num is omitted, column_num is required. |
||
617 | * @param column_num The column in array from which to return a value. If column_num is omitted, row_num is required. |
||
618 | * @param mixed $arrayValues |
||
619 | * @param mixed $rowNum |
||
620 | * @param mixed $columnNum |
||
621 | * |
||
622 | * @return mixed the value of a specified cell or array of cells |
||
623 | */ |
||
624 | 8 | public static function INDEX($arrayValues, $rowNum = 0, $columnNum = 0) |
|
625 | { |
||
626 | 8 | if (($rowNum < 0) || ($columnNum < 0)) { |
|
627 | 2 | return Functions::VALUE(); |
|
628 | } |
||
629 | |||
630 | 6 | if (!is_array($arrayValues) || ($rowNum > count($arrayValues))) { |
|
631 | 1 | return Functions::REF(); |
|
632 | } |
||
633 | |||
634 | 5 | $rowKeys = array_keys($arrayValues); |
|
635 | 5 | $columnKeys = @array_keys($arrayValues[$rowKeys[0]]); |
|
636 | |||
637 | 5 | if ($columnNum > count($columnKeys)) { |
|
638 | 1 | return Functions::VALUE(); |
|
639 | 4 | } elseif ($columnNum == 0) { |
|
640 | 3 | if ($rowNum == 0) { |
|
641 | 1 | return $arrayValues; |
|
642 | } |
||
643 | 2 | $rowNum = $rowKeys[--$rowNum]; |
|
644 | 2 | $returnArray = []; |
|
645 | 2 | foreach ($arrayValues as $arrayColumn) { |
|
646 | 2 | if (is_array($arrayColumn)) { |
|
647 | 2 | if (isset($arrayColumn[$rowNum])) { |
|
648 | $returnArray[] = $arrayColumn[$rowNum]; |
||
649 | } else { |
||
650 | 2 | return [$rowNum => $arrayValues[$rowNum]]; |
|
651 | } |
||
652 | } else { |
||
653 | return $arrayValues[$rowNum]; |
||
654 | } |
||
655 | } |
||
656 | |||
657 | return $returnArray; |
||
658 | } |
||
659 | 1 | $columnNum = $columnKeys[--$columnNum]; |
|
660 | 1 | if ($rowNum > count($rowKeys)) { |
|
661 | return Functions::VALUE(); |
||
662 | 1 | } elseif ($rowNum == 0) { |
|
663 | return $arrayValues[$columnNum]; |
||
664 | } |
||
665 | 1 | $rowNum = $rowKeys[--$rowNum]; |
|
666 | |||
667 | 1 | return $arrayValues[$rowNum][$columnNum]; |
|
668 | } |
||
669 | |||
670 | /** |
||
671 | * TRANSPOSE. |
||
672 | * |
||
673 | * @param array $matrixData A matrix of values |
||
674 | * |
||
675 | * @return array |
||
676 | * |
||
677 | * Unlike the Excel TRANSPOSE function, which will only work on a single row or column, this function will transpose a full matrix |
||
678 | */ |
||
679 | public static function TRANSPOSE($matrixData) |
||
698 | |||
699 | 2 | private static function vlookupSort($a, $b) |
|
709 | |||
710 | /** |
||
711 | * VLOOKUP |
||
712 | * The VLOOKUP function searches for value in the left-most column of lookup_array and returns the value in the same row based on the index_number. |
||
713 | * |
||
714 | * @param lookup_value The value that you want to match in lookup_array |
||
715 | * @param lookup_array The range of cells being searched |
||
716 | * @param index_number The column number in table_array from which the matching value must be returned. The first column is 1. |
||
717 | * @param not_exact_match determines if you are looking for an exact match based on lookup_value |
||
718 | * @param mixed $lookup_value |
||
719 | * @param mixed $lookup_array |
||
720 | * @param mixed $index_number |
||
721 | * @param mixed $not_exact_match |
||
722 | * |
||
723 | * @return mixed The value of the found cell |
||
724 | */ |
||
725 | 5 | public static function VLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match = true) |
|
778 | |||
779 | /** |
||
780 | * HLOOKUP |
||
781 | * The HLOOKUP function searches for value in the top-most row of lookup_array and returns the value in the same column based on the index_number. |
||
782 | * |
||
783 | * @param lookup_value The value that you want to match in lookup_array |
||
784 | * @param lookup_array The range of cells being searched |
||
785 | * @param index_number The row number in table_array from which the matching value must be returned. The first row is 1. |
||
786 | * @param not_exact_match determines if you are looking for an exact match based on lookup_value |
||
787 | * @param mixed $lookup_value |
||
788 | * @param mixed $lookup_array |
||
789 | * @param mixed $index_number |
||
790 | * @param mixed $not_exact_match |
||
791 | * |
||
792 | * @return mixed The value of the found cell |
||
793 | */ |
||
794 | 9 | public static function HLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match = true) |
|
843 | |||
844 | /** |
||
845 | * LOOKUP |
||
846 | * The LOOKUP function searches for value either from a one-row or one-column range or from an array. |
||
847 | * |
||
848 | * @param lookup_value The value that you want to match in lookup_array |
||
849 | * @param lookup_vector The range of cells being searched |
||
850 | * @param result_vector The column from which the matching value must be returned |
||
851 | * @param mixed $lookup_value |
||
852 | * @param mixed $lookup_vector |
||
853 | * @param null|mixed $result_vector |
||
854 | * |
||
855 | * @return mixed The value of the found cell |
||
856 | */ |
||
857 | public static function LOOKUP($lookup_value, $lookup_vector, $result_vector = null) |
||
916 | } |
||
917 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.