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 VotesProcess 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 VotesProcess, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 19 | trait VotesProcess  | 
            ||
| 20 | { | 
            ||
| 21 | |||
| 22 | /////////// CONSTRUCTOR ///////////  | 
            ||
| 23 | |||
| 24 | // Data and global options  | 
            ||
| 25 | protected $_Votes; // Votes list  | 
            ||
| 26 | |||
| 27 | |||
| 28 | /////////// VOTES LIST ///////////  | 
            ||
| 29 | |||
| 30 | // How many votes are registered ?  | 
            ||
| 31 | 12 | public function countVotes ($tag = null, bool $with = true) : int  | 
            |
| 35 | |||
| 36 | 1 | public function countInvalidVoteWithConstraints () : int  | 
            |
| 40 | |||
| 41 | 1 | public function countValidVoteWithConstraints () : int  | 
            |
| 45 | |||
| 46 | // Sum votes weight  | 
            ||
| 47 | 2 | public function sumVotesWeight () : int  | 
            |
| 51 | |||
| 52 | 6 | public function sumValidVotesWeightWithConstraints () : int  | 
            |
| 56 | |||
| 57 | // Get the votes registered list  | 
            ||
| 58 | 11 | public function getVotesList ($tag = null, bool $with = true) : array  | 
            |
| 62 | |||
| 63 | 5 | public function getVotesListAsString () : string  | 
            |
| 67 | |||
| 68 | 102 |     public function getVotesManager () : VotesManager { | 
            |
| 71 | |||
| 72 | 2 | public function getVotesListGenerator ($tag = null, bool $with = true) : \Generator  | 
            |
| 76 | |||
| 77 | 9 |     public function getVoteKey (Vote $vote) { | 
            |
| 80 | |||
| 81 | |||
| 82 | /////////// ADD & REMOVE VOTE ///////////  | 
            ||
| 83 | |||
| 84 | // Add a single vote. Array key is the rank, each candidate in a rank are separate by ',' It is not necessary to register the last rank.  | 
            ||
| 85 | 126 | public function addVote ($vote, $tag = null) : Vote  | 
            |
| 98 | |||
| 99 | 7 | public function prepareUpdateVote (Vote $existVote) : void  | 
            |
| 100 |     { | 
            ||
| 101 | 7 | $this->_Votes->UpdateAndResetComputing($this->getVoteKey($existVote),2);  | 
            |
| 102 | 7 | }  | 
            |
| 103 | |||
| 104 | 7 | public function finishUpdateVote (Vote $existVote) : void  | 
            |
| 105 |     { | 
            ||
| 106 | 7 | $this->_Votes->UpdateAndResetComputing($this->getVoteKey($existVote),1);  | 
            |
| 107 | |||
| 108 | 7 | if ($this->_Votes->isUsingHandler()) :  | 
            |
| 109 | 1 | $this->_Votes[$this->getVoteKey($existVote)] = $existVote;  | 
            |
| 110 | endif;  | 
            ||
| 111 | 7 | }  | 
            |
| 112 | |||
| 113 | 126 | public function checkVoteCandidate (Vote $vote) : bool  | 
            |
| 114 |     { | 
            ||
| 115 | 126 | $linkCount = $vote->countLinks();  | 
            |
| 116 | 126 | $links = $vote->getLinks();  | 
            |
| 117 | |||
| 118 | 126 | $mirror = $vote->getRanking();  | 
            |
| 119 | 126 | $change = false;  | 
            |
| 120 | 126 | foreach ($vote as $rank => $choice) :  | 
            |
| 121 | 126 | foreach ($choice as $choiceKey => $candidate) :  | 
            |
| 122 | 126 | if ( !$this->isRegisteredCandidate($candidate, true) ) :  | 
            |
| 
                                                                                                    
                        
                         | 
                |||
| 123 | 109 | if ($candidate->getProvisionalState() && $this->isRegisteredCandidate($candidate, false)) :  | 
            |
| 124 | 108 | if ( $linkCount === 0 || ($linkCount === 1 && reset($links) === $this) ) :  | 
            |
| 125 | 108 | $mirror[$rank][$choiceKey] = $this->_Candidates[$this->getCandidateKey((string) $candidate)];  | 
            |
| 126 | 108 | $change = true;  | 
            |
| 127 | else :  | 
            ||
| 128 | 126 | return false;  | 
            |
| 129 | endif;  | 
            ||
| 130 | endif;  | 
            ||
| 131 | endif;  | 
            ||
| 132 | endforeach;  | 
            ||
| 133 | endforeach;  | 
            ||
| 134 | |||
| 135 | 126 | if ($change) :  | 
            |
| 136 | 108 | $vote->setRanking( $mirror,  | 
            |
| 137 | 108 | ( abs($vote->getTimestamp() - microtime(true)) > 0.5 ) ? ($vote->getTimestamp() + 0.001) : null  | 
            |
| 138 | );  | 
            ||
| 139 | endif;  | 
            ||
| 140 | |||
| 141 | 126 | return true;  | 
            |
| 142 | }  | 
            ||
| 143 | |||
| 144 | // Write a new vote  | 
            ||
| 145 | 126 | protected function registerVote (Vote $vote, $tag = null) : Vote  | 
            |
| 146 |     { | 
            ||
| 147 | // Vote identifiant  | 
            ||
| 148 | 126 | $vote->addTags($tag);  | 
            |
| 149 | |||
| 150 | // Register  | 
            ||
| 151 |         try { | 
            ||
| 152 | 126 | $vote->registerLink($this);  | 
            |
| 153 | 126 | $this->_Votes[] = $vote;  | 
            |
| 154 | 1 |         } catch (CondorcetException $e) { | 
            |
| 155 | // Security : Check if vote object not already register  | 
            ||
| 156 | 1 | throw new CondorcetException(31);  | 
            |
| 157 | }  | 
            ||
| 158 | |||
| 159 | 126 | return $vote;  | 
            |
| 160 | }  | 
            ||
| 161 | |||
| 162 | 5 | public function removeVote ($in, bool $with = true) : array  | 
            |
| 163 |     {     | 
            ||
| 164 | 5 | $rem = [];  | 
            |
| 165 | |||
| 166 | 5 | if ($in instanceof Vote) :  | 
            |
| 167 | 4 | $key = $this->getVoteKey($in);  | 
            |
| 168 | 4 | if ($key !== false) :  | 
            |
| 169 | 4 | $deletedVote = $this->_Votes[$key];  | 
            |
| 170 | 4 | $rem[] = $deletedVote;  | 
            |
| 171 | |||
| 172 | 4 | unset($this->_Votes[$key]);  | 
            |
| 173 | |||
| 174 | 4 | $deletedVote->destroyLink($this);  | 
            |
| 175 | endif;  | 
            ||
| 176 | else :  | 
            ||
| 177 | // Prepare Tags  | 
            ||
| 178 | 3 | $tag = VoteUtil::tagsConvert($in);  | 
            |
| 179 | |||
| 180 | // Deleting  | 
            ||
| 181 | 3 | foreach ($this->getVotesList($tag, $with) as $key => $value) :  | 
            |
| 182 | 3 | $deletedVote = $this->_Votes[$key];  | 
            |
| 183 | 3 | $rem[] = $deletedVote;  | 
            |
| 184 | |||
| 185 | 3 | unset($this->_Votes[$key]);  | 
            |
| 186 | |||
| 187 | 3 | $deletedVote->destroyLink($this);  | 
            |
| 188 | endforeach;  | 
            ||
| 189 | |||
| 190 | endif;  | 
            ||
| 191 | |||
| 192 | 5 | return $rem;  | 
            |
| 193 | }  | 
            ||
| 194 | |||
| 195 | |||
| 196 | /////////// PARSE VOTE ///////////  | 
            ||
| 197 | |||
| 198 | // Return the well formated vote to use.  | 
            ||
| 199 | 126 | protected function prepareVoteInput (&$vote, $tag = null) : void  | 
            |
| 210 | |||
| 211 | 2 | public function jsonVotes (string $input) : array  | 
            |
| 248 | |||
| 249 | 89 | public function parseVotes (string $input, bool $isFile = false) : array  | 
            |
| 304 | |||
| 305 | }  | 
            ||
| 306 | 
This check looks for methods that are used by a trait but not required by it.
To illustrate, let’s look at the following code example
The trait
Idableprovides a methodequalsIdthat in turn relies on the methodgetId(). If this method does not exist on a class mixing in this trait, the method will fail.Adding the
getId()as an abstract method to the trait will make sure it is available.