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 JewishCalendar 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 JewishCalendar, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
27 | class JewishCalendar implements CalendarInterface |
||
28 | { |
||
29 | /** Optional behaviour for this calendar. */ |
||
30 | const EMULATE_BUG_54254 = 'EMULATE_BUG_54254'; |
||
31 | |||
32 | /** Place this symbol before the final letter of a sequence of numerals */ |
||
33 | const GERSHAYIM_ISO8859 = '"'; |
||
34 | const GERSHAYIM = "\xd7\xb4"; |
||
35 | |||
36 | /** Place this symbol after a single numeral */ |
||
37 | const GERESH_ISO8859 = '\''; |
||
38 | const GERESH = "\xd7\xb3"; |
||
39 | |||
40 | /** The Hebrew word for thousand */ |
||
41 | const ALAFIM_ISO8859 = "\xe0\xec\xf4\xe9\xed"; |
||
42 | const ALAFIM = "\xd7\x90\xd7\x9c\xd7\xa4\xd7\x99\xd7\x9d"; |
||
43 | |||
44 | /** A year that is one day shorter than normal. */ |
||
45 | const DEFECTIVE_YEAR = -1; |
||
46 | |||
47 | /** A year that has the normal number of days. */ |
||
48 | const REGULAR_YEAR = 0; |
||
49 | |||
50 | /** A year that is one day longer than normal. */ |
||
51 | const COMPLETE_YEAR = 1; |
||
52 | |||
53 | /** @var string[] Hebrew numbers are represented by letters, similar to roman numerals. */ |
||
54 | private static $HEBREW_NUMERALS_ISO8859_8 = array( |
||
55 | 400 => "\xfa", |
||
56 | 300 => "\xf9", |
||
57 | 200 => "\xf8", |
||
58 | 100 => "\xf7", |
||
59 | 90 => "\xf6", |
||
60 | 80 => "\xf4", |
||
61 | 70 => "\xf2", |
||
62 | 60 => "\xf1", |
||
63 | 50 => "\xf0", |
||
64 | 40 => "\xee", |
||
65 | 30 => "\xec", |
||
66 | 20 => "\xeb", |
||
67 | 19 => "\xe9\xe8", |
||
68 | 18 => "\xe9\xe7", |
||
69 | 17 => "\xe9\xe6", |
||
70 | 16 => "\xe8\xe6", |
||
71 | 15 => "\xe8\xe5", |
||
72 | 10 => "\xe9", |
||
73 | 9 => "\xe8", |
||
74 | 8 => "\xe7", |
||
75 | 7 => "\xe6", |
||
76 | 6 => "\xe5", |
||
77 | 5 => "\xe4", |
||
78 | 4 => "\xe3", |
||
79 | 3 => "\xe2", |
||
80 | 2 => "\xe1", |
||
81 | 1 => "\xe0", |
||
82 | ); |
||
83 | |||
84 | /** @var string[] Hebrew numbers are represented by letters, similar to roman numerals. */ |
||
85 | private static $HEBREW_NUMERALS_UTF8 = array( |
||
86 | 400 => "\xd7\xaa", |
||
87 | 300 => "\xd7\xa9", |
||
88 | 200 => "\xd7\xa8", |
||
89 | 100 => "\xd7\xa7", |
||
90 | 90 => "\xd7\xa6", |
||
91 | 80 => "\xd7\xa4", |
||
92 | 70 => "\xd7\xa2", |
||
93 | 60 => "\xd7\xa1", |
||
94 | 50 => "\xd7\xa0", |
||
95 | 40 => "\xd7\x9e", |
||
96 | 30 => "\xd7\x9c", |
||
97 | 20 => "\xd7\x9b", |
||
98 | 19 => "\xd7\x99\xd7\x98", |
||
99 | 18 => "\xd7\x99\xd7\x97", |
||
100 | 17 => "\xd7\x99\xd7\x96", |
||
101 | 16 => "\xd7\x98\xd7\x96", |
||
102 | 15 => "\xd7\x98\xd7\x95", |
||
103 | 10 => "\xd7\x99", |
||
104 | 9 => "\xd7\x98", |
||
105 | 8 => "\xd7\x97", |
||
106 | 7 => "\xd7\x96", |
||
107 | 6 => "\xd7\x95", |
||
108 | 5 => "\xd7\x94", |
||
109 | 4 => "\xd7\x93", |
||
110 | 3 => "\xd7\x92", |
||
111 | 2 => "\xd7\x91", |
||
112 | 1 => "\xd7\x90", |
||
113 | ); |
||
114 | |||
115 | /** @var string[] Some letters have a different final form */ |
||
116 | private static $FINAL_FORMS_UTF8 = array( |
||
117 | "\xd7\x9b" => "\xd7\x9a", |
||
118 | "\xd7\x9e" => "\xd7\x9d", |
||
119 | "\xd7\xa0" => "\xd7\x9f", |
||
120 | "\xd7\xa4" => "\xd7\xa3", |
||
121 | "\xd7\xa6" => "\xd7\xa5", |
||
122 | ); |
||
123 | |||
124 | /** @var int[] These months have fixed lengths. Others are variable. */ |
||
125 | private static $FIXED_MONTH_LENGTHS = array( |
||
126 | 1 => 30, 4 => 29, 5 => 30, 7 => 29, 8 => 30, 9 => 29, 10 => 30, 11 => 29, 12 => 30, 13 => 29 |
||
127 | ); |
||
128 | |||
129 | /** |
||
130 | * Cumulative number of days for each month in each type of year. |
||
131 | * First index is false/true (non-leap year, leap year) |
||
132 | * Second index is year type (-1, 0, 1) |
||
133 | * Third index is month number (1 ... 13) |
||
134 | * |
||
135 | * @var int[][][] |
||
136 | */ |
||
137 | private static $CUMULATIVE_DAYS = array( |
||
138 | 0 => array( // Non-leap years |
||
139 | self::DEFECTIVE_YEAR => array( |
||
140 | 1 => 0, 30, 59, 88, 117, 147, 147, 176, 206, 235, 265, 294, 324 |
||
141 | ), |
||
142 | self::REGULAR_YEAR => array( // Regular years |
||
143 | 1 => 0, 30, 59, 89, 118, 148, 148, 177, 207, 236, 266, 295, 325 |
||
144 | ), |
||
145 | self::COMPLETE_YEAR => array( // Complete years |
||
146 | 1 => 0, 30, 60, 90, 119, 149, 149, 178, 208, 237, 267, 296, 326 |
||
147 | ), |
||
148 | ), |
||
149 | 1 => array( // Leap years |
||
150 | self::DEFECTIVE_YEAR => array( // Deficient years |
||
151 | 1 => 0, 30, 59, 88, 117, 147, 177, 206, 236, 265, 295, 324, 354 |
||
152 | ), |
||
153 | self::REGULAR_YEAR => array( // Regular years |
||
154 | 1 => 0, 30, 59, 89, 118, 148, 178, 207, 237, 266, 296, 325, 355 |
||
155 | ), |
||
156 | self::COMPLETE_YEAR => array( // Complete years |
||
157 | 1 => 0, 30, 60, 90, 119, 149, 179, 208, 238, 267, 297, 326, 356 |
||
158 | ), |
||
159 | ), |
||
160 | ); |
||
161 | |||
162 | /** @var int[] Rosh Hashanah cannot fall on a Sunday, Wednesday or Friday. Move the year start accordingly. */ |
||
163 | private static $ROSH_HASHANAH = array(347998, 347997, 347997, 347998, 347997, 347998, 347997); |
||
164 | |||
165 | /** @var mixed[] special behaviour for this calendar */ |
||
166 | protected $options = array( |
||
167 | self::EMULATE_BUG_54254 => false, |
||
168 | ); |
||
169 | |||
170 | /** |
||
171 | * @param mixed[] $options Some calendars have options that change their behaviour. |
||
172 | */ |
||
173 | public function __construct($options = array()) |
||
177 | |||
178 | /** |
||
179 | * Determine the number of days in a specified month, allowing for leap years, etc. |
||
180 | * |
||
181 | * @param int $year |
||
182 | * @param int $month |
||
183 | * |
||
184 | * @return int |
||
185 | */ |
||
186 | public function daysInMonth($year, $month) |
||
202 | |||
203 | /** |
||
204 | * Determine the number of days in a week. |
||
205 | * |
||
206 | * @return int |
||
207 | */ |
||
208 | public function daysInWeek() |
||
212 | |||
213 | /** |
||
214 | * The escape sequence used to indicate this calendar in GEDCOM files. |
||
215 | * |
||
216 | * @return string |
||
217 | */ |
||
218 | public function gedcomCalendarEscape() |
||
222 | |||
223 | /** |
||
224 | * Determine whether or not a given year is a leap-year. |
||
225 | * |
||
226 | * @param int $year |
||
227 | * |
||
228 | * @return bool |
||
229 | */ |
||
230 | public function isLeapYear($year) |
||
234 | |||
235 | /** |
||
236 | * What is the highest Julian day number that can be converted into this calendar. |
||
237 | * |
||
238 | * @return int |
||
239 | */ |
||
240 | public function jdEnd() |
||
244 | |||
245 | /** |
||
246 | * What is the lowest Julian day number that can be converted into this calendar. |
||
247 | * |
||
248 | * @return int |
||
249 | */ |
||
250 | public function jdStart() |
||
254 | |||
255 | /** |
||
256 | * Convert a Julian day number into a year. |
||
257 | * |
||
258 | * @param int $julian_day |
||
259 | * |
||
260 | * @return int |
||
261 | */ |
||
262 | protected function jdToY($julian_day) |
||
274 | |||
275 | /** |
||
276 | * Convert a Julian day number into a year/month/day. |
||
277 | * |
||
278 | * @param int $julian_day |
||
279 | * |
||
280 | * @return int[] |
||
281 | */ |
||
282 | public function jdToYmd($julian_day) |
||
299 | |||
300 | /** |
||
301 | * Determine the number of months in a year (if given), |
||
302 | * or the maximum number of months in any year. |
||
303 | * |
||
304 | * @param int|null $year |
||
305 | * |
||
306 | * @return int |
||
307 | */ |
||
308 | public function monthsInYear($year = null) |
||
316 | |||
317 | /** |
||
318 | * Calculate the Julian Day number of the first day in a year. |
||
319 | * |
||
320 | * @param int $year |
||
321 | * |
||
322 | * @return int |
||
323 | */ |
||
324 | protected function yToJd($year) |
||
345 | |||
346 | /** |
||
347 | * Convert a year/month/day to a Julian day number. |
||
348 | * |
||
349 | * @param int $year |
||
350 | * @param int $month |
||
351 | * @param int $day |
||
352 | * |
||
353 | * @return int |
||
354 | */ |
||
355 | public function ymdToJd($year, $month, $day) |
||
362 | |||
363 | /** |
||
364 | * Determine whether a year is normal, defective or complete. |
||
365 | * |
||
366 | * @param int $year |
||
367 | * |
||
368 | * @return int defective (-1), normal (0) or complete (1) |
||
369 | */ |
||
370 | private function yearType($year) |
||
382 | |||
383 | /** |
||
384 | * Calculate the number of days in Heshvan. |
||
385 | * |
||
386 | * @param int $year |
||
387 | * |
||
388 | * @return int |
||
389 | */ |
||
390 | private function daysInMonthHeshvan($year) |
||
398 | |||
399 | /** |
||
400 | * Calculate the number of days in Kislev. |
||
401 | * |
||
402 | * @param int $year |
||
403 | * |
||
404 | * @return int |
||
405 | */ |
||
406 | private function daysInMonthKislev($year) |
||
414 | |||
415 | /** |
||
416 | * Calculate the number of days in Adar I. |
||
417 | * |
||
418 | * @param int $year |
||
419 | * |
||
420 | * @return int |
||
421 | */ |
||
422 | private function daysInMonthAdarI($year) |
||
430 | |||
431 | /** |
||
432 | * Hebrew month names. |
||
433 | * |
||
434 | * @link https://bugs.php.net/bug.php?id=54254 |
||
435 | * |
||
436 | * @param int $year |
||
437 | * |
||
438 | * @return string[] |
||
439 | */ |
||
440 | protected function hebrewMonthNames($year) |
||
460 | |||
461 | /** |
||
462 | * The Hebrew name of a given month. |
||
463 | * |
||
464 | * @param int $year |
||
465 | * @param int $month |
||
466 | * |
||
467 | * @return string |
||
468 | */ |
||
469 | protected function hebrewMonthName($year, $month) |
||
475 | |||
476 | /** |
||
477 | * Add geresh (׳) and gershayim (״) punctuation to numeric values. |
||
478 | * |
||
479 | * Gereshayim is a contraction of “geresh” and “gershayim”. |
||
480 | * |
||
481 | * @param string $hebrew |
||
482 | * |
||
483 | * @return string |
||
484 | */ |
||
485 | protected function addGereshayim($hebrew) |
||
499 | |||
500 | /** |
||
501 | * Convert a number into a string, in the style of roman numerals |
||
502 | * |
||
503 | * @param int $number |
||
504 | * @param string[] $numerals |
||
505 | * |
||
506 | * @return string |
||
507 | */ |
||
508 | private function numberToNumerals($number, array $numerals) |
||
524 | |||
525 | /** |
||
526 | * Convert a number into Hebrew numerals using UTF8. |
||
527 | * |
||
528 | * @param int $number |
||
529 | * @param bool $show_thousands |
||
530 | * |
||
531 | * @return string |
||
532 | */ |
||
533 | public function numberToHebrewNumerals($number, $show_thousands) |
||
567 | |||
568 | /** |
||
569 | * Convert a number into Hebrew numerals using ISO8859-8. |
||
570 | * |
||
571 | * @param int $number |
||
572 | * @param bool $gereshayim Add punctuation to numeric values |
||
573 | * |
||
574 | * @return string |
||
575 | */ |
||
576 | protected function numberToHebrewNumeralsIso8859($number, $gereshayim) |
||
587 | |||
588 | /** |
||
589 | * Format a year using Hebrew numerals. |
||
590 | * |
||
591 | * @param int $year |
||
592 | * @param bool $alafim_geresh Add a geresh (׳) after thousands |
||
593 | * @param bool $alafim Add the word for thousands after the thousands |
||
594 | * @param bool $gereshayim Add geresh (׳) and gershayim (״) punctuation to numeric values |
||
595 | * |
||
596 | * @return string |
||
597 | */ |
||
598 | protected function yearToHebrewNumerals($year, $alafim_geresh, $alafim, $gereshayim) |
||
614 | |||
615 | /** |
||
616 | * Convert a Julian Day number into a Hebrew date. |
||
617 | * |
||
618 | * @param int $julian_day |
||
619 | * @param bool $alafim_garesh |
||
620 | * @param bool $alafim |
||
621 | * @param bool $gereshayim |
||
622 | * |
||
623 | * @return string |
||
624 | */ |
||
625 | public function jdToHebrew($julian_day, $alafim_garesh, $alafim, $gereshayim) |
||
634 | } |
||
635 |
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.