Passed
Branch dev-2.0 (faa14c)
by Boudry
02:42
created

Vote   F

Complexity

Total Complexity 92

Size/Duplication

Total Lines 454
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 93.84%

Importance

Changes 0
Metric Value
dl 0
loc 454
ccs 198
cts 211
cp 0.9384
rs 2
c 0
b 0
f 0
wmc 92
lcom 1
cbo 7

31 Methods

Rating   Name   Duplication   Size   Complexity  
A rewind() 0 3 1
A current() 0 3 1
A key() 0 3 1
A next() 0 3 1
A valid() 0 3 1
B __construct() 0 35 6
A __sleep() 0 6 1
A __clone() 0 5 1
A __toString() 0 8 2
A getHashCode() 0 3 1
A getRanking() 0 8 2
A getHistory() 0 4 1
A getTags() 0 4 1
A getTagsAsString() 0 4 1
A getCreateTimestamp() 0 4 1
A getTimestamp() 0 4 1
A countRankingCandidates() 0 4 1
A getAllCandidates() 0 12 3
B getContextualRanking() 0 40 10
A getContextualRankingAsString() 0 4 1
A getSimpleRanking() 0 12 6
B setRanking() 0 31 7
B formatRanking() 0 54 9
B removeCandidates() 0 38 9
B addTags() 0 29 8
B removeTags() 0 28 6
A removeAllTags() 0 5 1
A getWeight() 0 4 1
A setWeight() 0 18 4
A archiveRanking() 0 8 2
A setHashCode() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Vote 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 Vote, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
    Condorcet PHP - Election manager and results calculator.
4
    Designed for the Condorcet method. Integrating a large number of algorithms extending Condorcet. Expandable for all types of voting systems.
5
6
    By Julien Boudry and contributors - MIT LICENSE (Please read LICENSE.txt)
7
    https://github.com/julien-boudry/Condorcet
8
*/
9
declare(strict_types=1);
10
11
namespace CondorcetPHP\Condorcet;
12
13
14
use CondorcetPHP\Condorcet\ElectionProcess\VoteUtil;
15
16
17
class Vote implements \Iterator
18
{
19
    use Linkable, CondorcetVersion;
20
21
    // Implement Iterator
22
23
        private $position = 1;
24
25 138
        public function rewind() : void {
26 138
            $this->position = 1;
27 138
        }
28
29 126
        public function current() : array {
30 126
            return $this->getRanking()[$this->position];
31
        }
32
33 126
        public function key() : int {
34 126
            return $this->position;
35
        }
36
37 126
        public function next() : void {
38 126
            ++$this->position;
39 126
        }
40
41 126
        public function valid() : bool {
42 126
            return isset($this->getRanking()[$this->position]);
43
        }
44
45
    // Vote
46
47
    private $_ranking = [];
48
49
    private $_weight = 1;
50
51
    private $_tags = [];
52
53
    private $_hashCode;
54
55
        ///
56
57 138
    public function __construct ($ranking, $tags = null, ?float $ownTimestamp = null)
58
    {
59 138
        $tagsFromString = null;
60
        // Vote Weight
61 138
        if (is_string($ranking)) :
62 112
            $is_voteWeight = mb_strpos($ranking, '^');
63 112
            if ($is_voteWeight !== false) :
64 1
                $weight = trim( substr($ranking, $is_voteWeight + 1) );
65
66
                // Errors
67 1
                if ( !is_numeric($weight) ) :
68 1
                    throw new CondorcetException(13, null);
69
                endif;
70
71 1
                $weight = intval($weight);
72
73 1
                $ranking = substr($ranking, 0,$is_voteWeight);
74
75
            endif;
76
77 112
            $is_voteTags = mb_strpos($ranking, '||');
78 112
            if ($is_voteTags !== false) :
79 4
                $tagsFromString = explode(',', trim( substr($ranking, 0, $is_voteTags) ));
80 4
                $ranking = substr($ranking, $is_voteTags + 2);
81
            endif;
82
        endif;
83
84 138
        $this->setRanking($ranking, $ownTimestamp);
85 138
        $this->addTags($tags);
86 138
        $this->addTags($tagsFromString);
87
88 138
        if (isset($weight)) :
89 1
            $this->setWeight($weight);
90
        endif;
91 138
    }
92
93 2
    public function __sleep () : array
94
    {
95 2
        $this->position = 1;
96
97 2
        return array_keys(get_object_vars($this));
98
    }
99
100 2
    public function __clone ()
101
    {
102 2
        $this->destroyAllLink();
103 2
        $this->setHashCode();
104 2
    }
105
106 138
    public function __toString () : string {
107
        
108 138
        if (empty($this->getTags())) :
109 138
            return $this->getSimpleRanking();
110
        else :
111 15
            return $this->getTagsAsString().' || '.$this->getSimpleRanking();
112
        endif;
113
    }
114
115 1
    public function getHashCode () : string {
116 1
        return $this->_hashCode;
117
    }
118
119
        ///
120
121
    // GETTERS
122
123 138
    public function getRanking () : ?array
124
    {
125 138
        if (!empty($this->_ranking)) :
126 138
            return end($this->_ranking)['ranking'];
127
        else :
128
            return null;
129
        endif;
130
    }
131
132 3
    public function getHistory () : array
133
    {
134 3
        return $this->_ranking;
135
    }
136
137
138 138
    public function getTags () : array
139
    {
140 138
        return $this->_tags;
141
    }
142
143 15
    public function getTagsAsString () : string
144
    {
145 15
        return implode(',',$this->getTags());
146
    }
147
148 2
    public function getCreateTimestamp () : float
149
    {
150 2
        return $this->_ranking[0]['timestamp'];
151
    }
152
153 109
    public function getTimestamp () : float
154
    {
155 109
        return end($this->_ranking)['timestamp'];
156
    }
157
158 1
    public function countRankingCandidates () : int
159
    {
160 1
        return end($this->_ranking)['counter'];
161
    }
162
163 102
    public function getAllCandidates () : array
164
    {
165 102
        $list = [];
166
167 102
        foreach ($this->getRanking() as $rank) :
168 102
            foreach ($rank as $oneCandidate) :
169 102
                $list[] = $oneCandidate;
170
            endforeach;
171
        endforeach;
172
173 102
        return $list;
174
    }
175
176 101
    public function getContextualRanking (Election $election) : array
177
    {
178 101
        if (!$this->haveLink($election)) :
179
            throw new CondorcetException(22);
180
        endif;
181
182 101
        $ranking = $this->getRanking();
183 101
        $present = $this->getAllCandidates();
184 101
        $newRanking = [];
185 101
        $candidates_list = $election->getCandidatesList();
186
187 101
        $nextRank = 1;
188 101
        $countContextualCandidate = 0;
189
190 101
        foreach ($ranking as $CandidatesInRanks) :
191 101
            foreach ($CandidatesInRanks as $candidate) :
192 101
                if ( $election->isRegisteredCandidate($candidate, true) ) :
193 101
                    $newRanking[$nextRank][] = $candidate;
194 101
                    $countContextualCandidate++;
195
                endif;
196
            endforeach;
197
198 101
            if (isset($newRanking[$nextRank])) :
199 101
                $nextRank++;
200
            endif;
201
        endforeach;
202
203 101
        if ($election->getImplicitRankingRule() && $countContextualCandidate < $election->countCandidates()) :
204 45
            $last_rank = [];
205 45
            foreach ($candidates_list as $oneCandidate) :
206 45
                if (!in_array($oneCandidate, $present, true)) :
207 45
                    $last_rank[] = $oneCandidate;
208
                endif;
209
            endforeach;
210
211 45
            $newRanking[] = $last_rank;
212
        endif;
213
214 101
        return $newRanking;
215
    }
216
217 1
    public function getContextualRankingAsString (Election $election) : array
218
    {
219 1
        return CondorcetUtil::format($this->getContextualRanking($election),true);
220
    }
221
222 138
    public function getSimpleRanking (?Election $context = null) : string
223
    {
224 138
        $ranking = ($context) ? $this->getContextualRanking($context) : $this->getRanking();
225
226 138
        $simpleRanking = VoteUtil::getRankingAsString($ranking);
227
228 138
        if ($this->_weight > 1 && ( ($context && $context->isVoteWeightIsAllowed()) || $context === null )  ) :
229 6
            $simpleRanking .= " ^".$this->getWeight();
230
        endif;
231
232 138
        return $simpleRanking;
233
    }
234
235
236
    // SETTERS
237
238 138
    public function setRanking ($rankingCandidate, ?float $ownTimestamp = null) : bool
239
    {
240
        // Timestamp
241 138
        if ($ownTimestamp !== null) :
242 8
            if (!empty($this->_ranking) && $this->getTimestamp() >= $ownTimestamp) :
243
                throw new CondorcetException(21);
244
            endif;
245
        endif;
246
247
        // Ranking
248 138
        $candidateCounter = $this->formatRanking($rankingCandidate);
249
250 138
        $this->archiveRanking($rankingCandidate, $candidateCounter, $ownTimestamp);
251
252 138
        if (!empty($this->_link)) :
253
            try {
254 6
                foreach ($this->_link as &$link) :
255 6
                    $link->prepareModifyVote($this);
256
                endforeach;
257
            }
258
            catch (CondorcetException $e)
259
            {                
260
                array_pop($this->_ranking);
261
262
                throw new CondorcetException(18);
263
            }
264
        endif;
265
266 138
        $this->setHashCode();
267 138
        return true;
268
    }
269
270 138
        private function formatRanking (&$ranking) : int
271
        {
272 138
            if (is_string($ranking)) :
273 113
                $ranking = VoteUtil::convertVoteInput($ranking);
274
            endif;
275
276 138
            if (!is_array($ranking)) :
277
                throw new CondorcetException(5);
278
            endif;
279
280
            $ranking = array_filter($ranking, function ($key) {
281 135
                return is_numeric($key);
282 138
            }, ARRAY_FILTER_USE_KEY);
283
284 138
            ksort($ranking);
285
            
286 138
            $i = 1; $vote_r = [];
287 138
            foreach ($ranking as &$value) :
288 135
                if ( !is_array($value) ) :
289 35
                    $vote_r[$i] = [$value];
290
                else :
291 116
                    $vote_r[$i] = $value;
292
                endif;
293
294 135
                $i++;
295
            endforeach;
296
297 138
            $ranking = $vote_r;
298
299 138
            $counter = 0;
300 138
            $list_candidate = [];
301 138
            foreach ($ranking as &$line) :
302 135
                foreach ($line as &$Candidate) :
303 135
                    if ( !($Candidate instanceof Candidate) ) :
304 115
                        $Candidate = new Candidate ($Candidate);
305 115
                        $Candidate->setProvisionalState(true);
306
                    endif;
307
308 135
                    $counter++;
309
310
                // Check Duplicate
311
312
                    // Check objet reference AND check candidates name
313 135
                    if (!in_array($Candidate, $list_candidate)) :
314 135
                        $list_candidate[] = $Candidate;
315
                    else : 
316 135
                        throw new CondorcetException(5);
317
                    endif;
318
319
                endforeach;
320
            endforeach;
321
322 138
            return $counter;
323
        }
324
325
326 1
    public function removeCandidates (array $candidatesList) : bool
327
    {
328 1
        $ranking = $this->getRanking();
329
330 1
        if ($ranking === null) :
331
            return false;
332
        endif;
333
334 1
        $rankingCandidate = $this->getAllCandidates();
335
336 1
        $canRemove = false;
337 1
        foreach ($candidatesList as $oneCandidate) :
338 1
            if (in_array($oneCandidate, $rankingCandidate, false)) :
339 1
                $canRemove = true;
340 1
                break;
341
            endif;
342
        endforeach;
343
344 1
        if (!$canRemove) :
345
            return false;
346
        endif;
347
348 1
        foreach ($ranking as $rankingKey => &$rank) :
349 1
            foreach ($rank as $oneRankKey => $oneRankValue) :
350 1
                if (in_array($oneRankValue, $candidatesList, false)) :
351 1
                    unset($rank[$oneRankKey]);
352
                endif;
353
            endforeach;
354
355 1
            if (empty($rank)) :
356 1
                unset($ranking[$rankingKey]);
357
            endif;
358
        endforeach;
359
360 1
        $this->setRanking($ranking);
361
362 1
        return true;
363
    }
364
365
366 138
    public function addTags ($tags) : bool
367
    {
368 138
        if (is_object($tags) || is_bool($tags)) :
369
            throw new CondorcetException(17);
370
        endif;
371
372 138
        $tags = VoteUtil::tagsConvert($tags);
373
374 138
        if (empty($tags)) :
375 138
            return false;
376
        endif;
377
378
379 15
        foreach ($tags as $key => $tag) :
380 15
            if (is_numeric($tag)) :
381
                throw new CondorcetException(17);
382 15
            elseif (in_array($tag, $this->_tags, true)) :
383 15
                unset($tags[$key]);
384
            endif;
385
        endforeach;
386
387 15
        foreach ($tags as $tag) :
388 15
            $this->_tags[] = $tag;
389
        endforeach;
390
391 15
        $this->setHashCode();
392
393 15
        return true;
394
    }
395
396 2
    public function removeTags ($tags) : array
397
    {
398 2
        if (is_object($tags) || is_bool($tags)) :
399
            throw new CondorcetException(17);
400
        endif;
401
402 2
        $tags = VoteUtil::tagsConvert($tags);
403
404 2
        if (empty($tags)) :
405 1
            return [];
406
        endif;
407
408
409 2
        $rm = [];
410 2
        foreach ($tags as $key => $tag) :
411 2
            $tagK = array_search($tag, $this->_tags, true);
412
413 2
            if ($tagK === false) :
414 1
                unset($tags[$key]);
415
            else :
416 2
                $rm[] = $this->_tags[$tagK];
417 2
                unset($this->_tags[$tagK]);
418
            endif;
419
        endforeach;
420
421 2
        $this->setHashCode();
422 2
        return $rm;
423
    }
424
425 2
    public function removeAllTags () : bool
426
    {
427 2
        $this->removeTags($this->getTags());
428 2
        return true;
429
    }
430
431 89
    public function getWeight () : int
432
    {
433 89
        return $this->_weight;
434
    }
435
436 89
    public function setWeight (int $newWeight) : int
437
    {
438 89
        if ($newWeight < 1) :
439
            throw new CondorcetException(26);
440
        endif;
441
442 89
        $this->_weight = $newWeight;
443
444 89
        if (!empty($this->_link)) :
445 1
            foreach ($this->_link as &$link) :
446 1
                $link->setStateToVote();
447
            endforeach;
448
        endif;
449
450 89
        $this->setHashCode();
451
452 89
        return $this->getWeight();
453
    }
454
455
/////////// INTERNAL ///////////
456
457 138
    private function archiveRanking ($ranking, int $counter, ?float $ownTimestamp) : void
458
    {
459 138
        $this->_ranking[] = [   'ranking' => $ranking,
460 138
                                'timestamp' => ($ownTimestamp !== null) ? $ownTimestamp : microtime(true),
461 138
                                'counter' => $counter   ];
462
463 138
        $this->rewind();
464 138
    }
465
466 138
    private function setHashCode () : string
467
    {
468 138
        return $this->_hashCode = hash('sha224', ((string) $this) . microtime(false));
469
    }
470
}
471