Passed
Branch master (73ca69)
by Boudry
03:33
created

Vote::getHashCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
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 4
        public function rewind() : void {
28 4
            $this->position = 1;
29 4
        }
30
31 4
        public function current() {
32 4
            return $this->getRanking()[$this->position];
33
        }
34
35 4
        public function key() : int {
36 4
            return $this->position;
37
        }
38
39 4
        public function next() : void {
40 4
            ++$this->position;
41 4
        }
42
43 4
        public function valid() : bool {
44 4
            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 4
    public function __construct ($ranking, $tags = null, $ownTimestamp = false)
60
    {
61 4
        $tagsFromString = null;
62
        // Vote Weight
63 4
        if (is_string($ranking)) :
64 3
            $is_voteWeight = mb_strpos($ranking, '^');
65 3
            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 3
            $is_voteTags = mb_strpos($ranking, '||');
76 3
            if ($is_voteTags !== false) :
77
                $tagsFromString = explode(',', trim( substr($ranking, 0, $is_voteTags) ));
78
                $ranking = substr($ranking, $is_voteTags + 2);
79
            endif;
80
        endif;
81
82 4
        $this->setRanking($ranking, $ownTimestamp);
83 4
        $this->addTags($tags);
84 4
        $this->addTags($tagsFromString);
85
86 4
        if (isset($weight)) :
87
            $this->setWeight($weight);
88
        endif;
89 4
    }
90
91
    public function __sleep () : array
92
    {
93
        $this->position = 1;
94
95
        return array_keys(get_object_vars($this));
96
    }
97
98
    public function __clone ()
99
    {
100
        $this->destroyAllLink();
101
        $this->setHashCode();
102
    }
103
104 4
    public function __toString () : string {
105
        
106 4
        if (empty($this->getTags())) :
107 4
            return $this->getSimpleRanking();
108
        else :
109 4
            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 4
    public function getRanking () : ?array
122
    {
123 4
        if (!empty($this->_ranking)) :
124 4
            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 4
    public function getTags () : array
137
    {
138 4
        return $this->_tags;
139
    }
140
141 4
    public function getTagsAsString () : string
142
    {
143 4
        return implode(',',$this->getTags());
144
    }
145
146
    public function getCreateTimestamp () : float
147
    {
148
        return $this->_ranking[0]['timestamp'];
149
    }
150
151 4
    public function getTimestamp () : float
152
    {
153 4
        return end($this->_ranking)['timestamp'];
154
    }
155
156
    public function countRankingCandidates () : int
157
    {
158
        return end($this->_ranking)['counter'];
159
    }
160
161 4
    public function getAllCandidates () : array
162
    {
163 4
        $list = [];
164
165 4
        foreach ($this->getRanking() as $rank) :
166 4
            foreach ($rank as $oneCandidate) :
167 4
                $list[] = $oneCandidate;
168
            endforeach;
169
        endforeach;
170
171 4
        return $list;
172
    }
173
174 4
    public function getContextualRanking (Election $election, bool $string = false) : array
175
    {
176 4
        if (!$this->haveLink($election)) :
177
            throw new CondorcetException(22);
178
        endif;
179
180 4
        $ranking = $this->getRanking();
181 4
        $present = $this->getAllCandidates();
182 4
        $newRanking = [];
183 4
        $candidates_list = $election->getCandidatesList(false);
184
185 4
        $nextRank = 1;
186 4
        $countContextualCandidate = 0;
187
188 4
        foreach ($ranking as $CandidatesInRanks) :
189 4
            foreach ($CandidatesInRanks as $candidate) :
190 4
                if ( $election->existCandidateId($candidate, true) ) :
191 4
                    $newRanking[$nextRank][] = $candidate;
192 4
                    $countContextualCandidate++;
193
                endif;
194
            endforeach;
195
196 4
            if (isset($newRanking[$nextRank])) :
197 4
                $nextRank++;
198
            endif;
199
        endforeach;
200
201 4
        if ($election->getImplicitRankingRule() && $countContextualCandidate < $election->countCandidates()) :
202 3
            $last_rank = [];
203 3
            foreach ($candidates_list as $oneCandidate) :
204 3
                if (!in_array($oneCandidate, $present, true)) :
205 3
                    $last_rank[] = $oneCandidate;
206
                endif;
207
            endforeach;
208
209 3
            $newRanking[] = $last_rank;
210
        endif;
211
212 4
        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 4
        return $newRanking;
221
    }
222
223 4
    public function getSimpleRanking (?Election $context = null) : string
224
    {
225 4
        $ranking = ($context) ? $this->getContextualRanking($context) : $this->getRanking();
226
227 4
        $simpleRanking = self::getRankingAsString($ranking);
228
229 4
        if ($this->_weight > 1 && ( ($context && $context->isVoteWeightIsAllowed()) || $context === null )  ) :
230
            $simpleRanking .= " ^".$this->getWeight();
231
        endif;
232
233 4
        return $simpleRanking;
234
    }
235
236 4
    public static function getRankingAsString (array $ranking) : string
237
    {
238 4
        foreach ($ranking as &$rank) :
239 4
            if (is_array($rank)) :
240 4
                sort($rank);
241 4
                $rank = implode(' = ',$rank);
242
            endif;
243
        endforeach;
244
245 4
        return implode(' > ', $ranking);
246
    }
247
248
249
    // SETTERS
250
251 4
    public function setRanking ($rankingCandidate, $ownTimestamp = false) : bool
252
    {
253
        // Timestamp
254 4
        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 4
        $candidateCounter = $this->formatRanking($rankingCandidate);
264
265 4
        $this->archiveRanking($rankingCandidate, $candidateCounter, $ownTimestamp);
266
267 4
        if (!empty($this->_link)) :
268
            try {
269 2
                foreach ($this->_link as &$link) :
270 2
                    $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 4
        $this->setHashCode();
282 4
        return true;
283
    }
284
285 4
        private function formatRanking (&$ranking) : int
286
        {
287 4
            if (is_string($ranking)) :
288 3
                $ranking = self::convertVoteInput($ranking);
289
            endif;
290
291 4
            if (!is_array($ranking)) :
292
                throw new CondorcetException(5);
293
            endif;
294
295 4
            $ranking = array_filter($ranking, function ($key) {
296 4
                return is_numeric($key);
297 4
            }, ARRAY_FILTER_USE_KEY);
298
299 4
            ksort($ranking);
300
            
301 4
            $i = 1; $vote_r = [];
302 4
            foreach ($ranking as &$value) :
303 4
                if ( !is_array($value) ) :
304 4
                    $vote_r[$i] = [$value];
305
                else :
306 4
                    $vote_r[$i] = $value;
307
                endif;
308
309 4
                $i++;
310
            endforeach;
311
312 4
            $ranking = $vote_r;
313
314 4
            $counter = 0;
315 4
            $list_candidate = [];
316 4
            foreach ($ranking as &$line) :
317 4
                foreach ($line as &$Candidate) :
318 4
                    if ( !($Candidate instanceof Candidate) ) :
319 4
                        $Candidate = new Candidate ($Candidate);
320 4
                        $Candidate->setProvisionalState(true);
321
                    endif;
322
323 4
                    $counter++;
324
325
                // Check Duplicate
326
327
                    // Check objet reference AND check candidates name
328 4
                    if (!in_array($Candidate, $list_candidate)) :
329 4
                        $list_candidate[] = $Candidate;
330
                    else : 
331 4
                        throw new CondorcetException(5);
332
                    endif;
333
334
                endforeach;
335
            endforeach;
336
337 4
            return $counter;
338
        }
339
340
        // From a string like 'A>B=C=H>G=T>Q'
341 3
        public static function convertVoteInput (string $formula) : array
342
        {
343 3
            $vote = explode('>', $formula);
344
345 3
            foreach ($vote as &$rank_vote) :
346 3
                $rank_vote = explode('=', $rank_vote);
347
348
                // Del space at start and end
349 3
                foreach ($rank_vote as &$value) :
350 3
                    $value = trim($value);
351
                endforeach;
352
            endforeach;
353
354 3
            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 4
    public function addTags ($tags) : bool
399
    {
400 4
        if (is_object($tags) || is_bool($tags)) :
401
            throw new CondorcetException(17);
402
        endif;
403
404 4
        $tags = self::tagsConvert($tags);
405
406 4
        if (empty($tags)) :
407 4
            return false;
408
        endif;
409
410
411 4
        foreach ($tags as $key => $tag) :
412 4
            if (is_numeric($tag)) :
413
                throw new CondorcetException(17);
414 4
            elseif (in_array($tag, $this->_tags, true)) :
415 4
                unset($tags[$key]);
416
            endif;
417
        endforeach;
418
419 4
        foreach ($tags as $tag) :
420 4
            $this->_tags[] = $tag;
421
        endforeach;
422
423 4
        $this->setHashCode();
424
425 4
        return true;
426
    }
427
428
    public function removeTags ($tags) : array
429
    {
430
        if (is_object($tags) || is_bool($tags)) :
431
            throw new CondorcetException(17);
432
        endif;
433
434
        $tags = self::tagsConvert($tags);
435
436
        if (empty($tags)) :
437
            return [];
438
        endif;
439
440
441
        $rm = [];
442
        foreach ($tags as $key => $tag) :
443
            $tagK = array_search($tag, $this->_tags, true);
444
445
            if ($tagK === false) :
446
                unset($tags[$key]);
447
            else :
448
                $rm[] = $this->_tags[$tagK];
449
                unset($this->_tags[$tagK]);
450
            endif;
451
        endforeach;
452
453
        $this->setHashCode();
454
        return $rm;
455
    }
456
457
    public function removeAllTags () : bool
458
    {
459
        $this->removeTags($this->getTags());
460
        return true;
461
    }
462
463 4
        public static function tagsConvert ($tags) : ?array
464
        {
465 4
            if (empty($tags)) :
466 4
                return null;
467
            endif;
468
469
            // Make Array
470 4
            if (!is_array($tags)) :
2 ignored issues
show
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
Coding Style introduced by
The visibility should be declared for property $tags.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

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

Loading history...
471 4
                $tags = explode(',', $tags);
472
            endif;
473
474
            // Trim tags
475 4
            foreach ($tags as $key => &$oneTag) :
2 ignored issues
show
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
Coding Style introduced by
The visibility should be declared for property $tags.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

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

Loading history...
476 4
                if (empty($oneTag) || is_object($oneTag) || is_bool($oneTag)) {unset($tags[$key]);
477
                    continue;
478
                }
479
480 4
                $oneTag = (!ctype_digit($oneTag)) ? trim($oneTag) : intval($oneTag);
2 ignored issues
show
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
Coding Style introduced by
The visibility should be declared for property $oneTag.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

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

Loading history...
481
            endforeach;
482
483 4
            return $tags;
1 ignored issue
show
Coding Style introduced by
The visibility should be declared for property $tags.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

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

Loading history...
484
        }
485
486 2
    public function getWeight () : int
487
    {
488 2
        return $this->_weight;
489
    }
490
491 2
    public function setWeight (int $newWeight) : int
492
    {
493 2
        if ($newWeight < 1) :
494
            throw new CondorcetException(26);
495
        endif;
496
497 2
        $this->_weight = $newWeight;
498
499 2
        if (!empty($this->_link)) :
500 2
            foreach ($this->_link as &$link) :
501 2
                $link->setStateToVote();
502
            endforeach;
503
        endif;
504
505 2
        $this->setHashCode();
506
507 2
        return $this->getWeight();
508
    }
509
510
/////////// INTERNAL ///////////
511
512 4
    private function archiveRanking ($ranking, int $counter, $ownTimestamp) : void
513
    {
514 4
        $this->_ranking[] = [   'ranking' => $ranking,
515 4
                                'timestamp' => ($ownTimestamp !== false) ? (float) $ownTimestamp : microtime(true),
516 4
                                'counter' => $counter   ];
517
518 4
        $this->rewind();
519 4
    }
520
521 4
    private function setHashCode () : string
522
    {
523 4
        return $this->_hashCode = hash('sha224', ((string) $this) . microtime(false));
524
    }
525
}
526