Completed
Push — master ( 922505...49fe4c )
by Boudry
05:20
created

Vote::getContextualRanking()   C

Complexity

Conditions 13
Paths 29

Size

Total Lines 48
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 13.0085

Importance

Changes 0
Metric Value
dl 0
loc 48
ccs 26
cts 27
cp 0.963
rs 5.0877
c 0
b 0
f 0
cc 13
eloc 38
nc 29
nop 2
crap 13.0085

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 19 and the first side effect is on line 467.

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