Passed
Push — TEST/ScrutinizerPHPAnalysisEng... ( bc3f13...c9e065 )
by Boudry
03:23
created

Vote::rewind()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 2
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
crap 1
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 106
        public function rewind() : void {
28 106
            $this->position = 1;
29 106
        }
30
31 99
        public function current() {
32 99
            return $this->getRanking()[$this->position];
33
        }
34
35 99
        public function key() : int {
36 99
            return $this->position;
37
        }
38
39 99
        public function next() : void {
40 99
            ++$this->position;
41 99
        }
42
43 99
        public function valid() : bool {
44 99
            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 106
    public function __construct ($ranking, $tags = null, $ownTimestamp = false)
60
    {
61 106
        $tagsFromString = null;
62
        // Vote Weight
63 106
        if (is_string($ranking)) :
64 89
            $is_voteWeight = mb_strpos($ranking, '^');
65 89
            if ($is_voteWeight !== false) :
1 ignored issue
show
introduced by
The condition $is_voteWeight !== false can never be false.
Loading history...
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 89
            $is_voteTags = mb_strpos($ranking, '||');
76 89
            if ($is_voteTags !== false) :
1 ignored issue
show
introduced by
The condition $is_voteTags !== false can never be false.
Loading history...
77 2
                $tagsFromString = explode(',', trim( substr($ranking, 0, $is_voteTags) ));
78 2
                $ranking = substr($ranking, $is_voteTags + 2);
79
            endif;
80
        endif;
81
82 106
        $this->setRanking($ranking, $ownTimestamp);
83 106
        $this->addTags($tags);
84 106
        $this->addTags($tagsFromString);
85
86 106
        if (isset($weight)) :
87
            $this->setWeight($weight);
88
        endif;
89 106
    }
90
91 2
    public function __sleep () : array
92
    {
93 2
        $this->position = 1;
94
95 2
        return array_keys(get_object_vars($this));
96
    }
97
98 2
    public function __clone ()
99
    {
100 2
        $this->destroyAllLink();
101 2
        $this->setHashCode();
102 2
    }
103
104 106
    public function __toString () : string {
105
        
106 106
        if (empty($this->getTags())) :
107 106
            return $this->getSimpleRanking();
108
        else :
109 11
            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 106
    public function getRanking () : ?array
122
    {
123 106
        if (!empty($this->_ranking)) :
124 106
            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 106
    public function getTags () : array
137
    {
138 106
        return $this->_tags;
139
    }
140
141 11
    public function getTagsAsString () : string
142
    {
143 11
        return implode(',',$this->getTags());
144
    }
145
146 1
    public function getCreateTimestamp () : float
147
    {
148 1
        return $this->_ranking[0]['timestamp'];
149
    }
150
151 91
    public function getTimestamp () : float
152
    {
153 91
        return end($this->_ranking)['timestamp'];
154
    }
155
156
    public function countRankingCandidates () : int
157
    {
158
        return end($this->_ranking)['counter'];
159
    }
160
161 87
    public function getAllCandidates () : array
162
    {
163 87
        $list = [];
164
165 87
        foreach ($this->getRanking() as $rank) :
166 87
            foreach ($rank as $oneCandidate) :
167 87
                $list[] = $oneCandidate;
168
            endforeach;
169
        endforeach;
170
171 87
        return $list;
172
    }
173
174 86
    public function getContextualRanking (Election $election, bool $string = false) : array
175
    {
176 86
        if (!$this->haveLink($election)) :
177
            throw new CondorcetException(22);
178
        endif;
179
180 86
        $ranking = $this->getRanking();
181 86
        $present = $this->getAllCandidates();
182 86
        $newRanking = [];
183 86
        $candidates_list = $election->getCandidatesList(false);
184
185 86
        $nextRank = 1;
186 86
        $countContextualCandidate = 0;
187
188 86
        foreach ($ranking as $CandidatesInRanks) :
189 86
            foreach ($CandidatesInRanks as $candidate) :
190 86
                if ( $election->existCandidateId($candidate, true) ) :
191 86
                    $newRanking[$nextRank][] = $candidate;
192 86
                    $countContextualCandidate++;
193
                endif;
194
            endforeach;
195
196 86
            if (isset($newRanking[$nextRank])) :
197 86
                $nextRank++;
198
            endif;
199
        endforeach;
200
201 86
        if ($election->getImplicitRankingRule() && $countContextualCandidate < $election->countCandidates()) :
202 43
            $last_rank = [];
203 43
            foreach ($candidates_list as $oneCandidate) :
204 43
                if (!in_array($oneCandidate, $present, true)) :
205 43
                    $last_rank[] = $oneCandidate;
206
                endif;
207
            endforeach;
208
209 43
            $newRanking[] = $last_rank;
210
        endif;
211
212 86
        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 86
        return $newRanking;
221
    }
222
223 106
    public function getSimpleRanking (?Election $context = null) : string
224
    {
225 106
        $ranking = ($context) ? $this->getContextualRanking($context) : $this->getRanking();
226
227 106
        $simpleRanking = self::getRankingAsString($ranking);
1 ignored issue
show
Bug introduced by
It seems like $ranking can also be of type null; however, parameter $ranking of Condorcet\Vote::getRankingAsString() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

227
        $simpleRanking = self::getRankingAsString(/** @scrutinizer ignore-type */ $ranking);
Loading history...
228
229 106
        if ($this->_weight > 1 && ( ($context && $context->isVoteWeightIsAllowed()) || $context === null )  ) :
230 2
            $simpleRanking .= " ^".$this->getWeight();
231
        endif;
232
233 106
        return $simpleRanking;
234
    }
235
236 106
    public static function getRankingAsString (array $ranking) : string
237
    {
238 106
        foreach ($ranking as &$rank) :
239 103
            if (is_array($rank)) :
240 103
                sort($rank);
241 103
                $rank = implode(' = ',$rank);
242
            endif;
243
        endforeach;
244
245 106
        return implode(' > ', $ranking);
246
    }
247
248
249
    // SETTERS
250
251 106
    public function setRanking ($rankingCandidate, $ownTimestamp = false) : bool
252
    {
253
        // Timestamp
254 106
        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 106
        $candidateCounter = $this->formatRanking($rankingCandidate);
264
265 106
        $this->archiveRanking($rankingCandidate, $candidateCounter, $ownTimestamp);
266
267 106
        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 106
        $this->setHashCode();
282 106
        return true;
283
    }
284
285 106
        private function formatRanking (&$ranking) : int
286
        {
287 106
            if (is_string($ranking)) :
288 90
                $ranking = self::convertVoteInput($ranking);
289
            endif;
290
291 106
            if (!is_array($ranking)) :
292
                throw new CondorcetException(5);
293
            endif;
294
295 106
            $ranking = array_filter($ranking, function ($key) {
296 103
                return is_numeric($key);
297 106
            }, ARRAY_FILTER_USE_KEY);
298
299 106
            ksort($ranking);
300
            
301 106
            $i = 1; $vote_r = [];
302 106
            foreach ($ranking as &$value) :
303 103
                if ( !is_array($value) ) :
304 26
                    $vote_r[$i] = [$value];
305
                else :
306 93
                    $vote_r[$i] = $value;
307
                endif;
308
309 103
                $i++;
310
            endforeach;
311
312 106
            $ranking = $vote_r;
313
314 106
            $counter = 0;
315 106
            $list_candidate = [];
316 106
            foreach ($ranking as &$line) :
317 103
                foreach ($line as &$Candidate) :
318 103
                    if ( !($Candidate instanceof Candidate) ) :
319 92
                        $Candidate = new Candidate ($Candidate);
320 92
                        $Candidate->setProvisionalState(true);
321
                    endif;
322
323 103
                    $counter++;
324
325
                // Check Duplicate
326
327
                    // Check objet reference AND check candidates name
328 103
                    if (!in_array($Candidate, $list_candidate)) :
329 103
                        $list_candidate[] = $Candidate;
330
                    else : 
331 103
                        throw new CondorcetException(5);
332
                    endif;
333
334
                endforeach;
335
            endforeach;
336
337 106
            return $counter;
338
        }
339
340
        // From a string like 'A>B=C=H>G=T>Q'
341 90
        public static function convertVoteInput (string $formula) : array
342
        {
343 90
            $vote = explode('>', $formula);
344
345 90
            foreach ($vote as &$rank_vote) :
346 90
                $rank_vote = explode('=', $rank_vote);
347
348
                // Del space at start and end
349 90
                foreach ($rank_vote as &$value) :
350 90
                    $value = trim($value);
351
                endforeach;
352
            endforeach;
353
354 90
            return $vote;
355
        }
356
357
358 1
    public function removeCandidates (array $candidatesList) : bool
359
    {
360 1
        $ranking = $this->getRanking();
361
362 1
        if ($ranking === null) :
363
            return false;
364
        endif;
365
366 1
        $rankingCandidate = $this->getAllCandidates();
367
368 1
        $canRemove = false;
369 1
        foreach ($candidatesList as $oneCandidate) :
370 1
            if (in_array($oneCandidate, $rankingCandidate, false)) :
371 1
                $canRemove = true;
372 1
                break;
373
            endif;
374
        endforeach;
375
376 1
        if (!$canRemove) :
377
            return false;
378
        endif;
379
380 1
        foreach ($ranking as $rankingKey => &$rank) :
381 1
            foreach ($rank as $oneRankKey => $oneRankValue) :
382 1
                if (in_array($oneRankValue, $candidatesList, false)) :
383 1
                    unset($rank[$oneRankKey]);
384
                endif;
385
            endforeach;
386
387 1
            if (empty($rank)) :
388 1
                unset($ranking[$rankingKey]);
389
            endif;
390
        endforeach;
391
392 1
        $this->setRanking($ranking);
393
394 1
        return true;
395
    }
396
397
398 106
    public function addTags ($tags) : bool
399
    {
400 106
        if (is_object($tags) || is_bool($tags)) :
401
            throw new CondorcetException(17);
402
        endif;
403
404 106
        $tags = self::tagsConvert($tags);
405
406 106
        if (empty($tags)) :
407 106
            return false;
408
        endif;
409
410
411 11
        foreach ($tags as $key => $tag) :
412 11
            if (is_numeric($tag)) :
413
                throw new CondorcetException(17);
414 11
            elseif (in_array($tag, $this->_tags, true)) :
415 11
                unset($tags[$key]);
416
            endif;
417
        endforeach;
418
419 11
        foreach ($tags as $tag) :
420 11
            $this->_tags[] = $tag;
421
        endforeach;
422
423 11
        $this->setHashCode();
424
425 11
        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 106
        public static function tagsConvert ($tags) : ?array
464
        {
465 106
            if (empty($tags)) :
466 106
                return null;
467
            endif;
468
469
            // Make Array
470 11
            if (!is_array($tags)) :
471 11
                $tags = explode(',', $tags);
472
            endif;
473
474
            // Trim tags
475 11
            foreach ($tags as $key => &$oneTag) :
476 11
                if (empty($oneTag) || is_object($oneTag) || is_bool($oneTag)) {unset($tags[$key]);
477 1
                    continue;
478
                }
479
480 11
                $oneTag = (!ctype_digit($oneTag)) ? trim($oneTag) : intval($oneTag);
481
            endforeach;
482
483 11
            return $tags;
484
        }
485
486 70
    public function getWeight () : int
487
    {
488 70
        return $this->_weight;
489
    }
490
491 70
    public function setWeight (int $newWeight) : int
492
    {
493 70
        if ($newWeight < 1) :
494
            throw new CondorcetException(26);
495
        endif;
496
497 70
        $this->_weight = $newWeight;
498
499 70
        if (!empty($this->_link)) :
500 70
            foreach ($this->_link as &$link) :
501 70
                $link->setStateToVote();
502
            endforeach;
503
        endif;
504
505 70
        $this->setHashCode();
506
507 70
        return $this->getWeight();
508
    }
509
510
/////////// INTERNAL ///////////
511
512 106
    private function archiveRanking ($ranking, int $counter, $ownTimestamp) : void
513
    {
514 106
        $this->_ranking[] = [   'ranking' => $ranking,
515 106
                                'timestamp' => ($ownTimestamp !== false) ? (float) $ownTimestamp : microtime(true),
516 106
                                'counter' => $counter   ];
517
518 106
        $this->rewind();
519 106
    }
520
521 106
    private function setHashCode () : string
522
    {
523 106
        return $this->_hashCode = hash('sha224', ((string) $this) . microtime(false));
524
    }
525
}
526