Completed
Push — master ( 9ee175...c01904 )
by Boudry
02:34
created

Vote::removeCandidates()   D

Complexity

Conditions 9
Paths 25

Size

Total Lines 38
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 9.081

Importance

Changes 0
Metric Value
dl 0
loc 38
ccs 18
cts 20
cp 0.9
rs 4.909
c 0
b 0
f 0
cc 9
eloc 28
nc 25
nop 1
crap 9.081
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 105
        public function rewind() : void {
28 105
            $this->position = 1;
29 105
        }
30
31 98
        public function current() {
32 98
            return $this->getRanking()[$this->position];
33
        }
34
35 98
        public function key() : int {
36 98
            return $this->position;
37
        }
38
39 98
        public function next() : void {
40 98
            ++$this->position;
41 98
        }
42
43 98
        public function valid() : bool {
44 98
            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 105
    public function __construct ($ranking, $tags = null, $ownTimestamp = false)
60
    {
61 105
        $tagsFromString = null;
62
        // Vote Weight
63 105
        if (is_string($ranking)) :
64 89
            $is_voteWeight = mb_strpos($ranking, '^');
65 89
            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 89
            $is_voteTags = mb_strpos($ranking, '||');
76 89
            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 105
        $this->setRanking($ranking, $ownTimestamp);
83 105
        $this->addTags($tags);
84 105
        $this->addTags($tagsFromString);
85
86 105
        if (isset($weight)) :
87
            $this->setWeight($weight);
88
        endif;
89 105
    }
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 105
    public function __toString () : string {
105
        
106 105
        if (empty($this->getTags())) :
107 105
            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 105
    public function getRanking () : ?array
122
    {
123 105
        if (!empty($this->_ranking)) :
124 105
            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 105
    public function getTags () : array
137
    {
138 105
        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 86
    public function getAllCandidates () : array
162
    {
163 86
        $list = [];
164
165 86
        foreach ($this->getRanking() as $rank) :
166 86
            foreach ($rank as $oneCandidate) :
167 86
                $list[] = $oneCandidate;
168
            endforeach;
169
        endforeach;
170
171 86
        return $list;
172
    }
173
174 85
    public function getContextualRanking (Election $election, bool $string = false) : array
175
    {
176 85
        if (!$this->haveLink($election)) :
177
            throw new CondorcetException(22);
178
        endif;
179
180 85
        $ranking = $this->getRanking();
181 85
        $present = $this->getAllCandidates();
182 85
        $newRanking = [];
183 85
        $candidates_list = $election->getCandidatesList(false);
184
185 85
        $nextRank = 1;
186 85
        $countContextualCandidate = 0;
187
188 85
        foreach ($ranking as $CandidatesInRanks) :
189 85
            foreach ($CandidatesInRanks as $candidate) :
190 85
                if ( $election->existCandidateId($candidate, true) ) :
191 85
                    $newRanking[$nextRank][] = $candidate;
192 85
                    $countContextualCandidate++;
193
                endif;
194
            endforeach;
195
196 85
            if (isset($newRanking[$nextRank])) :
197 85
                $nextRank++;
198
            endif;
199
        endforeach;
200
201 85
        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 85
        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 85
        return $newRanking;
221
    }
222
223 105
    public function getSimpleRanking (?Election $context = null) : string
224
    {
225 105
        $ranking = ($context) ? $this->getContextualRanking($context) : $this->getRanking();
226
227 105
        $simpleRanking = self::getRankingAsString($ranking);
228
229 105
        if ($this->_weight > 1 && ( ($context && $context->isVoteWeightIsAllowed()) || $context === null )  ) :
230 2
            $simpleRanking .= " ^".$this->getWeight();
231
        endif;
232
233 105
        return $simpleRanking;
234
    }
235
236 105
    public static function getRankingAsString (array $ranking) : string
237
    {
238 105
        foreach ($ranking as &$rank) :
239 102
            if (is_array($rank)) :
240 102
                sort($rank);
241 102
                $rank = implode(' = ',$rank);
242
            endif;
243
        endforeach;
244
245 105
        return implode(' > ', $ranking);
246
    }
247
248
249
    // SETTERS
250
251 105
    public function setRanking ($rankingCandidate, $ownTimestamp = false) : bool
252
    {
253
        // Timestamp
254 105
        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 105
        $candidateCounter = $this->formatRanking($rankingCandidate);
264
265 105
        $this->archiveRanking($rankingCandidate, $candidateCounter, $ownTimestamp);
266
267 105
        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 105
        $this->setHashCode();
282 105
        return true;
283
    }
284
285 105
        private function formatRanking (&$ranking) : int
286
        {
287 105
            if (is_string($ranking)) :
288 90
                $ranking = self::convertVoteInput($ranking);
289
            endif;
290
291 105
            if (!is_array($ranking)) :
292
                throw new CondorcetException(5);
293
            endif;
294
295 105
            $ranking = array_filter($ranking, function ($key) {
296 102
                return is_numeric($key);
297 105
            }, ARRAY_FILTER_USE_KEY);
298
299 105
            ksort($ranking);
300
            
301 105
            $i = 1; $vote_r = [];
302 105
            foreach ($ranking as &$value) :
303 102
                if ( !is_array($value) ) :
304 25
                    $vote_r[$i] = [$value];
305
                else :
306 93
                    $vote_r[$i] = $value;
307
                endif;
308
309 102
                $i++;
310
            endforeach;
311
312 105
            $ranking = $vote_r;
313
314 105
            $counter = 0;
315 105
            $list_candidate = [];
316 105
            foreach ($ranking as &$line) :
317 102
                foreach ($line as &$Candidate) :
318 102
                    if ( !($Candidate instanceof Candidate) ) :
319 92
                        $Candidate = new Candidate ($Candidate);
320 92
                        $Candidate->setProvisionalState(true);
321
                    endif;
322
323 102
                    $counter++;
324
325
                // Check Duplicate
326
327
                    // Check objet reference AND check candidates name
328 102
                    if (!in_array($Candidate, $list_candidate)) :
329 102
                        $list_candidate[] = $Candidate;
330
                    else : 
331 102
                        throw new CondorcetException(5);
332
                    endif;
333
334
                endforeach;
335
            endforeach;
336
337 105
            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 105
    public function addTags ($tags) : bool
399
    {
400 105
        if (is_object($tags) || is_bool($tags)) :
401
            throw new CondorcetException(17);
402
        endif;
403
404 105
        $tags = self::tagsConvert($tags);
405
406 105
        if (empty($tags)) :
407 105
            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 105
        public static function tagsConvert ($tags) : ?array
464
        {
465 105
            if (empty($tags)) :
466 105
                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 105
    private function archiveRanking ($ranking, int $counter, $ownTimestamp) : void
513
    {
514 105
        $this->_ranking[] = [   'ranking' => $ranking,
515 105
                                'timestamp' => ($ownTimestamp !== false) ? (float) $ownTimestamp : microtime(true),
516 105
                                'counter' => $counter   ];
517
518 105
        $this->rewind();
519 105
    }
520
521 105
    private function setHashCode () : string
522
    {
523 105
        return $this->_hashCode = hash('sha224', ((string) $this) . microtime(false));
524
    }
525
}
526