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 Style 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 Style, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
27 | class Style extends Style\Supervisor implements IComparable |
||
28 | { |
||
29 | /** |
||
30 | * Font. |
||
31 | * |
||
32 | * @var Style\Font |
||
33 | */ |
||
34 | protected $font; |
||
35 | |||
36 | /** |
||
37 | * Fill. |
||
38 | * |
||
39 | * @var Style\Fill |
||
40 | */ |
||
41 | protected $fill; |
||
42 | |||
43 | /** |
||
44 | * Borders. |
||
45 | * |
||
46 | * @var Style\Borders |
||
47 | */ |
||
48 | protected $borders; |
||
49 | |||
50 | /** |
||
51 | * Alignment. |
||
52 | * |
||
53 | * @var Style\Alignment |
||
54 | */ |
||
55 | protected $alignment; |
||
56 | |||
57 | /** |
||
58 | * Number Format. |
||
59 | * |
||
60 | * @var Style\NumberFormat |
||
61 | */ |
||
62 | protected $numberFormat; |
||
63 | |||
64 | /** |
||
65 | * Conditional styles. |
||
66 | * |
||
67 | * @var Style\Conditional[] |
||
68 | */ |
||
69 | protected $conditionalStyles; |
||
70 | |||
71 | /** |
||
72 | * Protection. |
||
73 | * |
||
74 | * @var Style\Protection |
||
75 | */ |
||
76 | protected $protection; |
||
77 | |||
78 | /** |
||
79 | * Index of style in collection. Only used for real style. |
||
80 | * |
||
81 | * @var int |
||
82 | */ |
||
83 | protected $index; |
||
84 | |||
85 | /** |
||
86 | * Use Quote Prefix when displaying in cell editor. Only used for real style. |
||
87 | * |
||
88 | * @var bool |
||
89 | */ |
||
90 | protected $quotePrefix = false; |
||
91 | |||
92 | /** |
||
93 | * Create a new Style. |
||
94 | * |
||
95 | * @param bool $isSupervisor Flag indicating if this is a supervisor or not |
||
96 | * Leave this value at default unless you understand exactly what |
||
97 | * its ramifications are |
||
98 | * @param bool $isConditional Flag indicating if this is a conditional style or not |
||
99 | * Leave this value at default unless you understand exactly what |
||
100 | * its ramifications are |
||
101 | */ |
||
102 | 80 | public function __construct($isSupervisor = false, $isConditional = false) |
|
126 | |||
127 | /** |
||
128 | * Get the shared style component for the currently active cell in currently active sheet. |
||
129 | * Only used for style supervisor. |
||
130 | * |
||
131 | * @return Style |
||
132 | */ |
||
133 | 3 | public function getSharedComponent() |
|
134 | { |
||
135 | 3 | $activeSheet = $this->getActiveSheet(); |
|
136 | 3 | $selectedCell = $this->getActiveCell(); // e.g. 'A1' |
|
137 | |||
138 | 3 | if ($activeSheet->cellExists($selectedCell)) { |
|
139 | 3 | $xfIndex = $activeSheet->getCell($selectedCell)->getXfIndex(); |
|
140 | } else { |
||
141 | 1 | $xfIndex = 0; |
|
142 | } |
||
143 | |||
144 | 3 | return $this->parent->getCellXfByIndex($xfIndex); |
|
145 | } |
||
146 | |||
147 | /** |
||
148 | * Get parent. Only used for style supervisor. |
||
149 | * |
||
150 | * @return Spreadsheet |
||
151 | */ |
||
152 | public function getParent() |
||
156 | |||
157 | /** |
||
158 | * Build style array from subcomponents. |
||
159 | * |
||
160 | * @param array $array |
||
161 | * |
||
162 | * @return array |
||
163 | */ |
||
164 | public function getStyleArray($array) |
||
168 | |||
169 | /** |
||
170 | * Apply styles from array. |
||
171 | * |
||
172 | * <code> |
||
173 | * $spreadsheet->getActiveSheet()->getStyle('B2')->applyFromArray( |
||
174 | * array( |
||
175 | * 'font' => array( |
||
176 | * 'name' => 'Arial', |
||
177 | * 'bold' => true, |
||
178 | * 'italic' => false, |
||
179 | * 'underline' => \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE, |
||
180 | * 'strikethrough' => false, |
||
181 | * 'color' => array( |
||
182 | * 'rgb' => '808080' |
||
183 | * ) |
||
184 | * ), |
||
185 | * 'borders' => array( |
||
186 | * 'bottom' => array( |
||
187 | * 'borderStyle' => Border::BORDER_DASHDOT, |
||
188 | * 'color' => array( |
||
189 | * 'rgb' => '808080' |
||
190 | * ) |
||
191 | * ), |
||
192 | * 'top' => array( |
||
193 | * 'borderStyle' => Border::BORDER_DASHDOT, |
||
194 | * 'color' => array( |
||
195 | * 'rgb' => '808080' |
||
196 | * ) |
||
197 | * ) |
||
198 | * ), |
||
199 | * 'quotePrefix' => true |
||
200 | * ) |
||
201 | * ); |
||
202 | * </code> |
||
203 | * |
||
204 | * @param array $pStyles Array containing style information |
||
205 | * @param bool $pAdvanced advanced mode for setting borders |
||
206 | * |
||
207 | * @throws Exception |
||
208 | * |
||
209 | * @return Style |
||
210 | */ |
||
211 | 41 | public function applyFromArray(array $pStyles, $pAdvanced = true) |
|
212 | { |
||
213 | 41 | if ($this->isSupervisor) { |
|
214 | 40 | $pRange = $this->getSelectedCells(); |
|
215 | |||
216 | // Uppercase coordinate |
||
217 | 40 | $pRange = strtoupper($pRange); |
|
218 | |||
219 | // Is it a cell range or a single cell? |
||
220 | 40 | View Code Duplication | if (strpos($pRange, ':') === false) { |
221 | 30 | $rangeA = $pRange; |
|
222 | 30 | $rangeB = $pRange; |
|
223 | } else { |
||
224 | 26 | list($rangeA, $rangeB) = explode(':', $pRange); |
|
225 | } |
||
226 | |||
227 | // Calculate range outer borders |
||
228 | 40 | $rangeStart = Cell::coordinateFromString($rangeA); |
|
229 | 40 | $rangeEnd = Cell::coordinateFromString($rangeB); |
|
230 | |||
231 | // Translate column into index |
||
232 | 40 | $rangeStart[0] = Cell::columnIndexFromString($rangeStart[0]) - 1; |
|
233 | 40 | $rangeEnd[0] = Cell::columnIndexFromString($rangeEnd[0]) - 1; |
|
234 | |||
235 | // Make sure we can loop upwards on rows and columns |
||
236 | 40 | View Code Duplication | if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) { |
237 | $tmp = $rangeStart; |
||
238 | $rangeStart = $rangeEnd; |
||
239 | $rangeEnd = $tmp; |
||
240 | } |
||
241 | |||
242 | // ADVANCED MODE: |
||
243 | 40 | if ($pAdvanced && isset($pStyles['borders'])) { |
|
244 | // 'allBorders' is a shorthand property for 'outline' and 'inside' and |
||
245 | // it applies to components that have not been set explicitly |
||
246 | 17 | View Code Duplication | if (isset($pStyles['borders']['allBorders'])) { |
247 | 1 | foreach (['outline', 'inside'] as $component) { |
|
248 | 1 | if (!isset($pStyles['borders'][$component])) { |
|
249 | 1 | $pStyles['borders'][$component] = $pStyles['borders']['allBorders']; |
|
250 | } |
||
251 | } |
||
252 | 1 | unset($pStyles['borders']['allBorders']); // not needed any more |
|
253 | } |
||
254 | // 'outline' is a shorthand property for 'top', 'right', 'bottom', 'left' |
||
255 | // it applies to components that have not been set explicitly |
||
256 | 17 | View Code Duplication | if (isset($pStyles['borders']['outline'])) { |
257 | 13 | foreach (['top', 'right', 'bottom', 'left'] as $component) { |
|
258 | 13 | if (!isset($pStyles['borders'][$component])) { |
|
259 | 13 | $pStyles['borders'][$component] = $pStyles['borders']['outline']; |
|
260 | } |
||
261 | } |
||
262 | 13 | unset($pStyles['borders']['outline']); // not needed any more |
|
263 | } |
||
264 | // 'inside' is a shorthand property for 'vertical' and 'horizontal' |
||
265 | // it applies to components that have not been set explicitly |
||
266 | 17 | View Code Duplication | if (isset($pStyles['borders']['inside'])) { |
267 | 1 | foreach (['vertical', 'horizontal'] as $component) { |
|
268 | 1 | if (!isset($pStyles['borders'][$component])) { |
|
269 | 1 | $pStyles['borders'][$component] = $pStyles['borders']['inside']; |
|
270 | } |
||
271 | } |
||
272 | 1 | unset($pStyles['borders']['inside']); // not needed any more |
|
273 | } |
||
274 | // width and height characteristics of selection, 1, 2, or 3 (for 3 or more) |
||
275 | 17 | $xMax = min($rangeEnd[0] - $rangeStart[0] + 1, 3); |
|
276 | 17 | $yMax = min($rangeEnd[1] - $rangeStart[1] + 1, 3); |
|
277 | |||
278 | // loop through up to 3 x 3 = 9 regions |
||
279 | 17 | for ($x = 1; $x <= $xMax; ++$x) { |
|
280 | // start column index for region |
||
281 | 17 | $colStart = ($x == 3) ? |
|
282 | 13 | Cell::stringFromColumnIndex($rangeEnd[0]) |
|
283 | 17 | : Cell::stringFromColumnIndex($rangeStart[0] + $x - 1); |
|
284 | // end column index for region |
||
285 | 17 | $colEnd = ($x == 1) ? |
|
286 | 17 | Cell::stringFromColumnIndex($rangeStart[0]) |
|
287 | 17 | : Cell::stringFromColumnIndex($rangeEnd[0] - $xMax + $x); |
|
288 | |||
289 | 17 | for ($y = 1; $y <= $yMax; ++$y) { |
|
290 | // which edges are touching the region |
||
291 | 17 | $edges = []; |
|
292 | 17 | if ($x == 1) { |
|
293 | // are we at left edge |
||
294 | 17 | $edges[] = 'left'; |
|
295 | } |
||
296 | 17 | if ($x == $xMax) { |
|
297 | // are we at right edge |
||
298 | 17 | $edges[] = 'right'; |
|
299 | } |
||
300 | 17 | if ($y == 1) { |
|
301 | // are we at top edge? |
||
302 | 17 | $edges[] = 'top'; |
|
303 | } |
||
304 | 17 | if ($y == $yMax) { |
|
305 | // are we at bottom edge? |
||
306 | 17 | $edges[] = 'bottom'; |
|
307 | } |
||
308 | |||
309 | // start row index for region |
||
310 | 17 | $rowStart = ($y == 3) ? |
|
311 | 17 | $rangeEnd[1] : $rangeStart[1] + $y - 1; |
|
312 | |||
313 | // end row index for region |
||
314 | 17 | $rowEnd = ($y == 1) ? |
|
315 | 17 | $rangeStart[1] : $rangeEnd[1] - $yMax + $y; |
|
316 | |||
317 | // build range for region |
||
318 | 17 | $range = $colStart . $rowStart . ':' . $colEnd . $rowEnd; |
|
319 | |||
320 | // retrieve relevant style array for region |
||
321 | 17 | $regionStyles = $pStyles; |
|
322 | 17 | unset($regionStyles['borders']['inside']); |
|
323 | |||
324 | // what are the inner edges of the region when looking at the selection |
||
325 | 17 | $innerEdges = array_diff(['top', 'right', 'bottom', 'left'], $edges); |
|
326 | |||
327 | // inner edges that are not touching the region should take the 'inside' border properties if they have been set |
||
328 | 17 | foreach ($innerEdges as $innerEdge) { |
|
329 | switch ($innerEdge) { |
||
330 | 15 | case 'top': |
|
331 | 15 | View Code Duplication | case 'bottom': |
332 | // should pick up 'horizontal' border property if set |
||
333 | 15 | if (isset($pStyles['borders']['horizontal'])) { |
|
334 | $regionStyles['borders'][$innerEdge] = $pStyles['borders']['horizontal']; |
||
335 | } else { |
||
336 | 15 | unset($regionStyles['borders'][$innerEdge]); |
|
337 | } |
||
338 | 15 | break; |
|
339 | 15 | case 'left': |
|
340 | 15 | View Code Duplication | case 'right': |
341 | // should pick up 'vertical' border property if set |
||
342 | 15 | if (isset($pStyles['borders']['vertical'])) { |
|
343 | $regionStyles['borders'][$innerEdge] = $pStyles['borders']['vertical']; |
||
344 | } else { |
||
345 | 15 | unset($regionStyles['borders'][$innerEdge]); |
|
346 | } |
||
347 | 15 | break; |
|
348 | } |
||
349 | } |
||
350 | |||
351 | // apply region style to region by calling applyFromArray() in simple mode |
||
352 | 17 | $this->getActiveSheet()->getStyle($range)->applyFromArray($regionStyles, false); |
|
353 | } |
||
354 | } |
||
355 | |||
356 | 17 | return $this; |
|
357 | } |
||
358 | |||
359 | // SIMPLE MODE: |
||
360 | // Selection type, inspect |
||
361 | 40 | if (preg_match('/^[A-Z]+1:[A-Z]+1048576$/', $pRange)) { |
|
362 | $selectionType = 'COLUMN'; |
||
363 | 40 | } elseif (preg_match('/^A[0-9]+:XFD[0-9]+$/', $pRange)) { |
|
364 | $selectionType = 'ROW'; |
||
365 | } else { |
||
366 | 40 | $selectionType = 'CELL'; |
|
367 | } |
||
368 | |||
369 | // First loop through columns, rows, or cells to find out which styles are affected by this operation |
||
370 | switch ($selectionType) { |
||
371 | 40 | case 'COLUMN': |
|
372 | $oldXfIndexes = []; |
||
373 | View Code Duplication | for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { |
|
374 | $oldXfIndexes[$this->getActiveSheet()->getColumnDimensionByColumn($col)->getXfIndex()] = true; |
||
375 | } |
||
376 | break; |
||
377 | 40 | case 'ROW': |
|
378 | $oldXfIndexes = []; |
||
379 | for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { |
||
380 | if ($this->getActiveSheet()->getRowDimension($row)->getXfIndex() == null) { |
||
381 | $oldXfIndexes[0] = true; // row without explicit style should be formatted based on default style |
||
382 | } else { |
||
383 | $oldXfIndexes[$this->getActiveSheet()->getRowDimension($row)->getXfIndex()] = true; |
||
384 | } |
||
385 | } |
||
386 | break; |
||
387 | 40 | case 'CELL': |
|
388 | 40 | $oldXfIndexes = []; |
|
389 | 40 | for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { |
|
390 | 40 | View Code Duplication | for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { |
391 | 40 | $oldXfIndexes[$this->getActiveSheet()->getCellByColumnAndRow($col, $row)->getXfIndex()] = true; |
|
392 | } |
||
393 | } |
||
394 | 40 | break; |
|
395 | } |
||
396 | |||
397 | // clone each of the affected styles, apply the style array, and add the new styles to the workbook |
||
398 | 40 | $workbook = $this->getActiveSheet()->getParent(); |
|
399 | 40 | foreach ($oldXfIndexes as $oldXfIndex => $dummy) { |
|
400 | 40 | $style = $workbook->getCellXfByIndex($oldXfIndex); |
|
401 | 40 | $newStyle = clone $style; |
|
402 | 40 | $newStyle->applyFromArray($pStyles); |
|
403 | |||
404 | 40 | if ($existingStyle = $workbook->getCellXfByHashCode($newStyle->getHashCode())) { |
|
405 | // there is already such cell Xf in our collection |
||
406 | 30 | $newXfIndexes[$oldXfIndex] = $existingStyle->getIndex(); |
|
407 | } else { |
||
408 | // we don't have such a cell Xf, need to add |
||
409 | 40 | $workbook->addCellXf($newStyle); |
|
410 | 40 | $newXfIndexes[$oldXfIndex] = $newStyle->getIndex(); |
|
411 | } |
||
412 | } |
||
413 | |||
414 | // Loop through columns, rows, or cells again and update the XF index |
||
415 | switch ($selectionType) { |
||
416 | 40 | View Code Duplication | case 'COLUMN': |
417 | for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { |
||
418 | $columnDimension = $this->getActiveSheet()->getColumnDimensionByColumn($col); |
||
419 | $oldXfIndex = $columnDimension->getXfIndex(); |
||
420 | $columnDimension->setXfIndex($newXfIndexes[$oldXfIndex]); |
||
421 | } |
||
422 | break; |
||
423 | 40 | View Code Duplication | case 'ROW': |
424 | for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { |
||
425 | $rowDimension = $this->getActiveSheet()->getRowDimension($row); |
||
426 | $oldXfIndex = $rowDimension->getXfIndex() === null ? |
||
427 | 0 : $rowDimension->getXfIndex(); // row without explicit style should be formatted based on default style |
||
428 | $rowDimension->setXfIndex($newXfIndexes[$oldXfIndex]); |
||
429 | } |
||
430 | break; |
||
431 | 40 | case 'CELL': |
|
432 | 40 | for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { |
|
433 | 40 | for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { |
|
434 | 40 | $cell = $this->getActiveSheet()->getCellByColumnAndRow($col, $row); |
|
435 | 40 | $oldXfIndex = $cell->getXfIndex(); |
|
436 | 40 | $cell->setXfIndex($newXfIndexes[$oldXfIndex]); |
|
437 | } |
||
438 | } |
||
439 | 40 | break; |
|
440 | } |
||
441 | } else { |
||
442 | // not a supervisor, just apply the style array directly on style object |
||
443 | 41 | if (isset($pStyles['fill'])) { |
|
444 | 18 | $this->getFill()->applyFromArray($pStyles['fill']); |
|
445 | } |
||
446 | 41 | if (isset($pStyles['font'])) { |
|
447 | 23 | $this->getFont()->applyFromArray($pStyles['font']); |
|
448 | } |
||
449 | 41 | if (isset($pStyles['borders'])) { |
|
450 | 18 | $this->getBorders()->applyFromArray($pStyles['borders']); |
|
451 | } |
||
452 | 41 | if (isset($pStyles['alignment'])) { |
|
453 | 21 | $this->getAlignment()->applyFromArray($pStyles['alignment']); |
|
454 | } |
||
455 | 41 | if (isset($pStyles['numberFormat'])) { |
|
456 | 30 | $this->getNumberFormat()->applyFromArray($pStyles['numberFormat']); |
|
457 | } |
||
458 | 41 | if (isset($pStyles['protection'])) { |
|
459 | 13 | $this->getProtection()->applyFromArray($pStyles['protection']); |
|
460 | } |
||
461 | 41 | if (isset($pStyles['quotePrefix'])) { |
|
462 | 1 | $this->quotePrefix = $pStyles['quotePrefix']; |
|
463 | } |
||
464 | } |
||
465 | |||
466 | 41 | return $this; |
|
467 | } |
||
468 | |||
469 | /** |
||
470 | * Get Fill. |
||
471 | * |
||
472 | * @return Style\Fill |
||
473 | */ |
||
474 | 65 | public function getFill() |
|
478 | |||
479 | /** |
||
480 | * Get Font. |
||
481 | * |
||
482 | * @return Style\Font |
||
483 | */ |
||
484 | 65 | public function getFont() |
|
488 | |||
489 | /** |
||
490 | * Set font. |
||
491 | * |
||
492 | * @param Style\Font $font |
||
493 | * |
||
494 | * @return Style |
||
495 | */ |
||
496 | 4 | public function setFont(Style\Font $font) |
|
502 | |||
503 | /** |
||
504 | * Get Borders. |
||
505 | * |
||
506 | * @return Style\Borders |
||
507 | */ |
||
508 | 63 | public function getBorders() |
|
512 | |||
513 | /** |
||
514 | * Get Alignment. |
||
515 | * |
||
516 | * @return Style\Alignment |
||
517 | */ |
||
518 | 63 | public function getAlignment() |
|
522 | |||
523 | /** |
||
524 | * Get Number Format. |
||
525 | * |
||
526 | * @return Style\NumberFormat |
||
527 | */ |
||
528 | 71 | public function getNumberFormat() |
|
532 | |||
533 | /** |
||
534 | * Get Conditional Styles. Only used on supervisor. |
||
535 | * |
||
536 | * @return Style\Conditional[] |
||
537 | */ |
||
538 | 2 | public function getConditionalStyles() |
|
542 | |||
543 | /** |
||
544 | * Set Conditional Styles. Only used on supervisor. |
||
545 | * |
||
546 | * @param Style\Conditional[] $pValue Array of conditional styles |
||
547 | * |
||
548 | * @return Style |
||
549 | */ |
||
550 | 2 | public function setConditionalStyles(array $pValue) |
|
556 | |||
557 | /** |
||
558 | * Get Protection. |
||
559 | * |
||
560 | * @return Style\Protection |
||
561 | */ |
||
562 | 62 | public function getProtection() |
|
566 | |||
567 | /** |
||
568 | * Get quote prefix. |
||
569 | * |
||
570 | * @return bool |
||
571 | */ |
||
572 | 56 | public function getQuotePrefix() |
|
580 | |||
581 | /** |
||
582 | * Set quote prefix. |
||
583 | * |
||
584 | * @param bool $pValue |
||
585 | */ |
||
586 | 9 | public function setQuotePrefix($pValue) |
|
600 | |||
601 | /** |
||
602 | * Get hash code. |
||
603 | * |
||
604 | * @return string Hash code |
||
605 | */ |
||
606 | 70 | public function getHashCode() |
|
625 | |||
626 | /** |
||
627 | * Get own index in style collection. |
||
628 | * |
||
629 | * @return int |
||
630 | */ |
||
631 | 43 | public function getIndex() |
|
635 | |||
636 | /** |
||
637 | * Set own index in style collection. |
||
638 | * |
||
639 | * @param int $pValue |
||
640 | */ |
||
641 | 80 | public function setIndex($pValue) |
|
645 | } |
||
646 |
It seems like the type of the argument is not accepted by the function/method which you are calling.
In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.
We suggest to add an explicit type cast like in the following example: