Completed
Push — master ( facd47...922b0c )
by Boudry
02:37
created

Vote::__construct()   B

Complexity

Conditions 6
Paths 11

Size

Total Lines 31
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 6.0052

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 18
cts 19
cp 0.9474
rs 8.439
c 0
b 0
f 0
cc 6
eloc 23
nc 11
nop 3
crap 6.0052
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 107
        public function rewind() : void {
28 107
            $this->position = 1;
29 107
        }
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 107
    public function __construct ($ranking, $tags = null, $ownTimestamp = false)
60
    {
61 107
        $tagsFromString = null;
62
        // Vote Weight
63 107
        if (is_string($ranking)) :
64 90
            $is_voteWeight = mb_strpos($ranking, '^');
65 90
            if ($is_voteWeight !== false) :
66 1
                $weight = intval( trim( substr($ranking, $is_voteWeight + 1) ) );
67 1
                $ranking = substr($ranking, 0,$is_voteWeight);
68
69
                // Errors
70 1
                if ( !is_numeric($weight) ) :
71
                    throw new CondorcetException(13, null);
72
                endif;
73
            endif;
74
75 90
            $is_voteTags = mb_strpos($ranking, '||');
76 90
            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 107
        $this->setRanking($ranking, $ownTimestamp);
83 107
        $this->addTags($tags);
84 107
        $this->addTags($tagsFromString);
85
86 107
        if (isset($weight)) :
87 1
            $this->setWeight($weight);
88
        endif;
89 107
    }
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 107
    public function __toString () : string {
105
        
106 107
        if (empty($this->getTags())) :
107 107
            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 107
    public function getRanking () : ?array
122
    {
123 107
        if (!empty($this->_ranking)) :
124 107
            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 107
    public function getTags () : array
137
    {
138 107
        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 107
    public function getSimpleRanking (?Election $context = null) : string
224
    {
225 107
        $ranking = ($context) ? $this->getContextualRanking($context) : $this->getRanking();
226
227 107
        $simpleRanking = self::getRankingAsString($ranking);
228
229 107
        if ($this->_weight > 1 && ( ($context && $context->isVoteWeightIsAllowed()) || $context === null )  ) :
230 3
            $simpleRanking .= " ^".$this->getWeight();
231
        endif;
232
233 107
        return $simpleRanking;
234
    }
235
236 107
    public static function getRankingAsString (array $ranking) : string
237
    {
238 107
        foreach ($ranking as &$rank) :
239 104
            if (is_array($rank)) :
240 104
                sort($rank);
241 104
                $rank = implode(' = ',$rank);
242
            endif;
243
        endforeach;
244
245 107
        return implode(' > ', $ranking);
246
    }
247
248
249
    // SETTERS
250
251 107
    public function setRanking ($rankingCandidate, $ownTimestamp = false) : bool
252
    {
253
        // Timestamp
254 107
        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 107
        $candidateCounter = $this->formatRanking($rankingCandidate);
264
265 107
        $this->archiveRanking($rankingCandidate, $candidateCounter, $ownTimestamp);
266
267 107
        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 107
        $this->setHashCode();
282 107
        return true;
283
    }
284
285 107
        private function formatRanking (&$ranking) : int
286
        {
287 107
            if (is_string($ranking)) :
288 91
                $ranking = self::convertVoteInput($ranking);
289
            endif;
290
291 107
            if (!is_array($ranking)) :
292
                throw new CondorcetException(5);
293
            endif;
294
295 107
            $ranking = array_filter($ranking, function ($key) {
296 104
                return is_numeric($key);
297 107
            }, ARRAY_FILTER_USE_KEY);
298
299 107
            ksort($ranking);
300
            
301 107
            $i = 1; $vote_r = [];
302 107
            foreach ($ranking as &$value) :
303 104
                if ( !is_array($value) ) :
304 26
                    $vote_r[$i] = [$value];
305
                else :
306 94
                    $vote_r[$i] = $value;
307
                endif;
308
309 104
                $i++;
310
            endforeach;
311
312 107
            $ranking = $vote_r;
313
314 107
            $counter = 0;
315 107
            $list_candidate = [];
316 107
            foreach ($ranking as &$line) :
317 104
                foreach ($line as &$Candidate) :
318 104
                    if ( !($Candidate instanceof Candidate) ) :
319 93
                        $Candidate = new Candidate ($Candidate);
320 93
                        $Candidate->setProvisionalState(true);
321
                    endif;
322
323 104
                    $counter++;
324
325
                // Check Duplicate
326
327
                    // Check objet reference AND check candidates name
328 104
                    if (!in_array($Candidate, $list_candidate)) :
329 104
                        $list_candidate[] = $Candidate;
330
                    else : 
331 104
                        throw new CondorcetException(5);
332
                    endif;
333
334
                endforeach;
335
            endforeach;
336
337 107
            return $counter;
338
        }
339
340
        // From a string like 'A>B=C=H>G=T>Q'
341 91
        public static function convertVoteInput (string $formula) : array
342
        {
343 91
            $vote = explode('>', $formula);
344
345 91
            foreach ($vote as &$rank_vote) :
346 91
                $rank_vote = explode('=', $rank_vote);
347
348
                // Del space at start and end
349 91
                foreach ($rank_vote as &$value) :
350 91
                    $value = trim($value);
351
                endforeach;
352
            endforeach;
353
354 91
            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 107
    public function addTags ($tags) : bool
399
    {
400 107
        if (is_object($tags) || is_bool($tags)) :
401
            throw new CondorcetException(17);
402
        endif;
403
404 107
        $tags = self::tagsConvert($tags);
405
406 107
        if (empty($tags)) :
407 107
            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 1
            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 1
                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 107
        public static function tagsConvert ($tags) : ?array
464
        {
465 107
            if (empty($tags)) :
466 107
                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 71
    public function getWeight () : int
487
    {
488 71
        return $this->_weight;
489
    }
490
491 71
    public function setWeight (int $newWeight) : int
492
    {
493 71
        if ($newWeight < 1) :
494
            throw new CondorcetException(26);
495
        endif;
496
497 71
        $this->_weight = $newWeight;
498
499 71
        if (!empty($this->_link)) :
500 70
            foreach ($this->_link as &$link) :
501 70
                $link->setStateToVote();
502
            endforeach;
503
        endif;
504
505 71
        $this->setHashCode();
506
507 71
        return $this->getWeight();
508
    }
509
510
/////////// INTERNAL ///////////
511
512 107
    private function archiveRanking ($ranking, int $counter, $ownTimestamp) : void
513
    {
514 107
        $this->_ranking[] = [   'ranking' => $ranking,
515 107
                                'timestamp' => ($ownTimestamp !== false) ? (float) $ownTimestamp : microtime(true),
516 107
                                'counter' => $counter   ];
517
518 107
        $this->rewind();
519 107
    }
520
521 107
    private function setHashCode () : string
522
    {
523 107
        return $this->_hashCode = hash('sha224', ((string) $this) . microtime(false));
524
    }
525
}
526