Passed
Push — master ( 88b497...e86a88 )
by Boudry
03:07
created

Vote   D

Complexity

Total Complexity 94

Size/Duplication

Total Lines 453
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 91.55%

Importance

Changes 0
Metric Value
wmc 94
lcom 1
cbo 6
dl 0
loc 453
ccs 195
cts 213
cp 0.9155
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
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 20 and the first side effect is on line 81.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

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