Completed
Push — master ( e86a88...9f06ff )
by Boudry
04:59 queued 02:04
created

Vote   D

Complexity

Total Complexity 94

Size/Duplication

Total Lines 453
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 93.43%

Importance

Changes 0
Metric Value
wmc 94
lcom 1
cbo 6
dl 0
loc 453
ccs 199
cts 213
cp 0.9343
rs 4.8717
c 0
b 0
f 0

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