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 SosaProvider 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 SosaProvider, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
23 | class SosaProvider { |
||
24 | |||
25 | /** |
||
26 | * Maximum number of generation the database is able to hold. |
||
27 | * @var int MAX_DB_GENERATIONS |
||
28 | */ |
||
29 | const MAX_DB_GENERATIONS = 64; |
||
30 | |||
31 | /** |
||
32 | * System's default user (ID -1 in the database |
||
33 | * @var User $default_user |
||
34 | */ |
||
35 | protected static $default_user; |
||
36 | |||
37 | /** |
||
38 | * Reference user |
||
39 | * @var User $user |
||
40 | */ |
||
41 | protected $user; |
||
42 | |||
43 | /** |
||
44 | * Reference tree |
||
45 | * @var Tree $tree |
||
46 | */ |
||
47 | protected $tree; |
||
48 | |||
49 | /** |
||
50 | * Cached list of Sosa Individuals by generation |
||
51 | * Format: key = generation, value = array ( sosa => Individual ID) |
||
52 | * @var array $sosa_list_by_gen |
||
53 | */ |
||
54 | protected $sosa_list_by_gen; |
||
55 | |||
56 | /** |
||
57 | * Cached list of Sosa Families by generation |
||
58 | * Format: key = generation, value = array ( sosa => Family ID) |
||
59 | * @var unknown $sosa_fam_list_by_gen |
||
60 | */ |
||
61 | protected $sosa_fam_list_by_gen; |
||
62 | |||
63 | /** |
||
64 | * Cached array of statistics by generation |
||
65 | * Format: key = generation, |
||
66 | * value = array( |
||
67 | * sosaCount, sosaTotalCount, diffSosaTotalCount, firstBirth, lastBirth, avgBirth |
||
68 | * ) |
||
69 | * @var array $statistics_tab |
||
70 | */ |
||
71 | protected $statistics_tab; |
||
72 | |||
73 | /** |
||
74 | * Has the provider's initialisation completed |
||
75 | * @var bool $is_setup |
||
76 | */ |
||
77 | protected $is_setup; |
||
78 | |||
79 | /** |
||
80 | * Constructor for Sosa Provider. |
||
81 | * A provider is defined in relation to a specific tree and reference user. |
||
82 | * |
||
83 | * @param Tree $tree |
||
84 | * @param User $user |
||
85 | */ |
||
86 | public function __construct(Tree $tree, User $user = null) { |
||
87 | if(self::$default_user === null) |
||
88 | self::$default_user = User::find(-1); |
||
89 | |||
90 | $this->tree = $tree; |
||
91 | $this->user = $user; |
||
92 | $this->is_setup = true; |
||
93 | if($this->user === null) $this->user = Auth::user(); |
||
94 | if(strlen($this->user->getUserId()) == 0) $this->user = self::$default_user; |
||
95 | |||
96 | // Check if the user, or the default user, has a root already setup; |
||
97 | if(empty($this->getRootIndiId())) { |
||
98 | if($this->user == self::$default_user) { // If the default user is not setup |
||
99 | $this->is_setup = false; |
||
100 | } |
||
101 | else { |
||
102 | $this->user = self::$default_user; |
||
103 | $this->is_setup = $this->getRootIndiId() === null; |
||
104 | } |
||
105 | } |
||
106 | } |
||
107 | |||
108 | /** |
||
109 | * Returns is the Provider has been successfully set up |
||
110 | * @return bool |
||
111 | */ |
||
112 | public function isSetup() { |
||
115 | |||
116 | /** |
||
117 | * Return the root individual ID for the reference tree and user. |
||
118 | * @return string Individual ID |
||
119 | */ |
||
120 | public function getRootIndiId() { |
||
123 | |||
124 | /** |
||
125 | * Return the root individual for the reference tree and user. |
||
126 | * @return Individual Individual |
||
127 | */ |
||
128 | public function getRootIndi() { |
||
135 | |||
136 | /***************** |
||
137 | * DATA CRUD LAYER |
||
138 | *****************/ |
||
139 | |||
140 | /** |
||
141 | * Remove all Sosa entries related to the gedcom file and user |
||
142 | */ |
||
143 | public function deleteAll() { |
||
153 | |||
154 | /** |
||
155 | * Remove all ancestors of a sosa number |
||
156 | * |
||
157 | * @param int $sosa |
||
158 | */ |
||
159 | public function deleteAncestors($sosa) { |
||
174 | |||
175 | /** |
||
176 | * Insert (or update if already existing) a list of Sosa individuals |
||
177 | * @param array $sosa_records |
||
178 | */ |
||
179 | public function insertOrUpdate($sosa_records) { |
||
214 | |||
215 | /**************** |
||
216 | * SIMPLE QUERIES |
||
217 | ****************/ |
||
218 | |||
219 | /** |
||
220 | * Returns the list of Sosa numbers to which an individual is related. |
||
221 | * Format: key = sosa number, value = generation for the Sosa number |
||
222 | * |
||
223 | * @param Individual $indi |
||
224 | * @return array Array of sosa numbers |
||
225 | */ |
||
226 | View Code Duplication | public function getSosaNumbers(Individual $indi) { |
|
237 | |||
238 | /** |
||
239 | * Get the last generation of Sosa ancestors |
||
240 | * |
||
241 | * @return number Last generation if found, 1 otherwise |
||
242 | */ |
||
243 | View Code Duplication | public function getLastGeneration() { |
|
253 | |||
254 | /************* |
||
255 | * SOSA LISTS |
||
256 | *************/ |
||
257 | |||
258 | /** |
||
259 | * Return the list of all sosas, with the generations it belongs to |
||
260 | * |
||
261 | * @param int $ged_id ID of the gedcom file |
||
262 | * @return array Associative array of Sosa ancestors, with their generation, comma separated |
||
263 | */ |
||
264 | View Code Duplication | public function getAllSosaWithGenerations(){ |
|
277 | |||
278 | /** |
||
279 | * Get an associative array of Sosa individuals in generation G. Keys are Sosa numbers, values individuals. |
||
280 | * |
||
281 | * @param number $gen Generation |
||
282 | * @return array Array of Sosa individuals |
||
283 | */ |
||
284 | public function getSosaListAtGeneration($gen){ |
||
308 | |||
309 | /** |
||
310 | * Get an associative array of Sosa families in generation G. Keys are Sosa numbers for the husband, values families. |
||
311 | * |
||
312 | * @param number $gen Generation |
||
313 | * @return array Array of Sosa families |
||
314 | */ |
||
315 | public function getFamilySosaListAtGeneration($gen){ |
||
344 | |||
345 | /** |
||
346 | * Get an associative array of Sosa individuals in generation G who are missing parents. Keys are Sosa numbers, values individuals. |
||
347 | * |
||
348 | * @param number $gen Generation |
||
349 | * @return array Array of Sosa individuals |
||
350 | */ |
||
351 | public function getMissingSosaListAtGeneration($gen){ |
||
371 | |||
372 | |||
373 | |||
374 | /************* |
||
375 | * STATISTICS |
||
376 | *************/ |
||
377 | /** |
||
378 | * Get the statistic array detailed by generation. |
||
379 | * Statistics for each generation are: |
||
380 | * - The number of Sosa in generation |
||
381 | * - The number of Sosa up to generation |
||
382 | * - The number of distinct Sosa up to generation |
||
383 | * - The year of the first birth in generation |
||
384 | * - The year of the last birth in generation |
||
385 | * - The average year of birth in generation |
||
386 | * |
||
387 | * @return array Statistics array |
||
388 | */ |
||
389 | public function getStatisticsByGeneration() { |
||
409 | |||
410 | /** |
||
411 | * How many individuals exist in the tree. |
||
412 | * |
||
413 | * @return int |
||
414 | */ |
||
415 | public function getTotalIndividuals() { |
||
423 | |||
424 | /** |
||
425 | * Get the total Sosa count for all generations |
||
426 | * |
||
427 | * @return number Number of Sosas |
||
428 | */ |
||
429 | View Code Duplication | public function getSosaCount(){ |
|
439 | |||
440 | /** |
||
441 | * Get the number of Sosa in a specific generation. |
||
442 | * |
||
443 | * @param number $gen Generation |
||
444 | * @return number Number of Sosas in generation |
||
445 | */ |
||
446 | View Code Duplication | public function getSosaCountAtGeneration($gen){ |
|
458 | |||
459 | /** |
||
460 | * Get the total number of Sosa up to a specific generation. |
||
461 | * |
||
462 | * @param number $gen Generation |
||
463 | * @return number Total number of Sosas up to generation |
||
464 | */ |
||
465 | View Code Duplication | public function getSosaCountUpToGeneration($gen){ |
|
477 | |||
478 | /** |
||
479 | * Get the total number of distinct Sosa individual for all generations. |
||
480 | * |
||
481 | * @return number Total number of distinct individual |
||
482 | */ |
||
483 | View Code Duplication | public function getDifferentSosaCount(){ |
|
493 | |||
494 | /** |
||
495 | * Get the number of distinct Sosa individual up to a specific generation. |
||
496 | * |
||
497 | * @param number $gen Generation |
||
498 | * @return number Number of distinct Sosa individuals up to generation |
||
499 | */ |
||
500 | View Code Duplication | public function getDifferentSosaCountUpToGeneration($gen){ |
|
512 | |||
513 | /** |
||
514 | * Get an array of birth statistics for a specific generation |
||
515 | * Statistics are : |
||
516 | * - first : First birth year in generation |
||
517 | * - last : Last birth year in generation |
||
518 | * - avg : Average birth year |
||
519 | * |
||
520 | * @param number $gen Generation |
||
521 | * @return array Birth statistics array |
||
522 | */ |
||
523 | public function getStatsBirthYearInGeneration($gen){ |
||
537 | |||
538 | /** |
||
539 | * Get the mean generation time, based on a linear regression of birth years and generations |
||
540 | * |
||
541 | * @return number|NULL Mean generation time |
||
542 | */ |
||
543 | public function getMeanGenerationTime(){ |
||
566 | |||
567 | /** |
||
568 | * Return a computed array of statistics about the dispersion of ancestors across the ancestors |
||
569 | * at a specified generation. |
||
570 | * This statistics cannot be used for generations above 11, as it would cause a out of range in MySQL |
||
571 | * |
||
572 | * Format: |
||
573 | * - key : a base-2 representation of the ancestor at generation G for which exclusive ancestors have been found, |
||
574 | * -1 is used for shared ancestors |
||
575 | * For instance base2(0100) = base10(4) represent the maternal grand father |
||
576 | * - values: number of ancestors exclusively in the ancestors of the ancestor in key |
||
577 | * |
||
578 | * For instance a result at generation 3 could be : |
||
579 | * array ( -1 => 12 -> 12 ancestors are shared by the grand-parents |
||
580 | * base10(1) => 32 -> 32 ancestors are exclusive to the paternal grand-father |
||
581 | * base10(2) => 25 -> 25 ancestors are exclusive to the paternal grand-mother |
||
582 | * base10(4) => 12 -> 12 ancestors are exclusive to the maternal grand-father |
||
583 | * base10(8) => 30 -> 30 ancestors are exclusive to the maternal grand-mother |
||
584 | * ) |
||
585 | * |
||
586 | * @param int $gen Reference generation |
||
587 | * @return array |
||
588 | */ |
||
589 | public function getAncestorDispersionForGen($gen) { |
||
615 | |||
616 | |||
617 | } |
||
618 |
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.