Passed
Push — dev-1.6.x ( 95af89...c638bc )
by Boudry
04:31
created

Election::getCandidateObjectByName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 3
nop 1
crap 3
1
<?php
0 ignored issues
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 27 and the first side effect is on line 117.

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\Condorcet;
13
use Condorcet\CondorcetException;
14
use Condorcet\CondorcetVersion;
15
use Condorcet\Result;
16
use Condorcet\CondorcetUtil;
17
use Condorcet\Vote;
18
use Condorcet\DataManager\VotesManager;
19
use Condorcet\DataManager\DataHandlerDrivers\DataHandlerDriverInterface;
20
use Condorcet\ElectionProcess\CandidatesManager;
21
use Condorcet\ElectionProcess\ResultManager;
22
use Condorcet\ElectionProcess\VoteUtil;
23
use Condorcet\Timer\Manager as Timer_Manager;
24
25
26
// Base Condorcet class
27
class Election
28
{
29
30
/////////// PROPERTIES ///////////
31
32
    public const MAX_LENGTH_CANDIDATE_ID = 30; // Max length for candidate identifiant string
33
34
    protected static $_maxParseIteration = null;
35
    protected static $_maxVoteNumber = null;
36
    protected static $_checksumMode = false;
37
38
/////////// STATICS METHODS ///////////
39
40
    // Change max parse iteration
41 1
    public static function setMaxParseIteration (?int $value) : ?int
42
    {
43 1
        self::$_maxParseIteration = $value;
44 1
        return self::$_maxParseIteration;
45
    }
46
47
    // Change max vote number
48 1
    public static function setMaxVoteNumber (?int $value) : ?int
49
    {
50 1
        self::$_maxVoteNumber = $value;
51 1
        return self::$_maxVoteNumber;
52
    }
53
54
55
/////////// CONSTRUCTOR ///////////
56
57
    use CondorcetVersion;
58
59
    // Data and global options
60
    protected $_Votes; // Votes list
61
62
    // Mechanics
63
    protected $_State = 1; // 1 = Add Candidates / 2 = Voting / 3 = Some result have been computing
64
    protected $_timer;
65
    protected $_nextVoteTag = 0;
66
    protected $_ignoreStaticMaxVote = false;
67
68
    // Params
69
    protected $_ImplicitRanking = true;
70
    protected $_VoteWeightRule = false;
71
72
    // Result
73
    protected $_ResultManager;
74
75
        //////
76
77 111
    public function __construct ()
78
    {
79 111
        $this->_Candidates = [];
80 111
        $this->_Votes = new VotesManager ($this);
81 111
        $this->_timer = new Timer_Manager;
82 111
    }
83
84 1
    public function __destruct ()
85
    {
86 1
        $this->destroyAllLink();
87 1
    }
88
89 2
    public function __sleep () : array
90
    {
91
        // Don't include others data
92
        $include = [
93 2
            '_Candidates',
94
            '_Votes',
95
96
            '_i_CandidateId',
97
            '_State',
98
            '_nextVoteTag',
99
            '_objectVersion',
100
            '_ignoreStaticMaxVote',
101
102
            '_ImplicitRanking',
103
            '_VoteWeightRule',
104
105
            '_ResultManager'
106
        ];
107
108 2
        !self::$_checksumMode && array_push($include, '_timer');
109
110 2
        return $include;
111
    }
112
113 1
    public function __wakeup ()
114
    {
115 1
        if ( version_compare($this->getObjectVersion('MAJOR'),Condorcet::getVersion('MAJOR'),'!=') ) :
116
            throw new CondorcetException(11, 'Your object version is '.$this->getObjectVersion().' but the class engine version is '.Condorcet::getVersion('ENV'));
117
        endif;
118 1
    }
119
120 1
    public function __clone ()
121
    {
122 1
        $this->_Votes = clone $this->_Votes;
123 1
        $this->_Votes->setElection($this);      
124 1
        $this->registerAllLinks();
125
126 1
        $this->_timer = clone $this->_timer;
127
128 1
        if ($this->_ResultManager !== null) :
129 1
            $this->_ResultManager = clone $this->_ResultManager;
130 1
            $this->_ResultManager->setElection($this);
131
        endif;
132 1
    }
133
134
135
/////////// INTERNAL GENERIC REGULATION ///////////
136
137
138 1
    protected function registerAllLinks () : void
139
    {
140 1
        foreach ($this->_Candidates as $value) :
141 1
            $value->registerLink($this);
142
        endforeach;
143
144 1
        if ($this->_State > 1) :
145 1
            foreach ($this->_Votes as $value) :
146 1
                $value->registerLink($this);
147
            endforeach;
148
        endif;
149 1
    }
150
151 1
    protected function destroyAllLink () : void
152
    {
153 1
        foreach ($this->_Candidates as $value) :
154 1
            $value->destroyLink($this);
155
        endforeach;
156
157 1
        if ($this->_State > 1) :
158 1
            foreach ($this->_Votes as $value) :
159 1
                $value->destroyLink($this);
160
            endforeach;
161
        endif;
162 1
    }
163
164
        //////
165
166
167 2
    public function getGlobalTimer (bool $float = false) {
168 2
        return $this->_timer->getGlobalTimer($float);
169
    }
170
171 2
    public function getLastTimer (bool $float = false) {
172 2
        return $this->_timer->getLastTimer($float);
173
    }
174
175 80
    public function getTimerManager () : Timer_Manager {
176 80
        return $this->_timer;
177
    }
178
179 1
    public function getChecksum () : string
180
    {
181 1
        self::$_checksumMode = true;
182
183 1
        $r = hash_init('sha256');
184
185 1
        foreach ($this->_Candidates as $value) :
186 1
            hash_update($r, (string) $value);
187
        endforeach;
188
189 1
        foreach ($this->_Votes as $value) :
190 1
            hash_update($r, (string) $value);
191
        endforeach;
192
193 1
        $this->_ResultManager !== null
194 1
            && hash_update($r,serialize($this->_ResultManager->getPairwise()->getExplicitPairwise()));
195
196 1
        hash_update($r, $this->getObjectVersion('major'));
197
198 1
        self::$_checksumMode = false;
199
200 1
        return hash_final($r);
201
    }
202
203 1
    public function ignoreMaxVote (bool $state = true) : bool
204
    {
205 1
        return $this->_ignoreStaticMaxVote = $state;
206
    }
207
208 86
    public function getImplicitRankingRule () : bool
209
    {
210 86
        return $this->_ImplicitRanking;
211
    }
212
213 3
    public function setImplicitRanking (bool $rule = true) : bool
214
    {
215 3
        $this->_ImplicitRanking = $rule;
216 3
        $this->cleanupResult();
217 3
        return $this->getImplicitRankingRule();
218
    }
219
220 82
    public function isVoteWeightIsAllowed () : bool
221
    {
222 82
        return $this->_VoteWeightRule;
223
    }
224
225 1
    public function allowVoteWeight (bool $rule = true) : bool
226
    {
227 1
        $this->_VoteWeightRule = $rule;
228 1
        $this->cleanupResult();
229 1
        return $this->isVoteWeightIsAllowed();
230
    }
231
232
233
/////////// CANDIDATES ///////////
234
235
    use CandidatesManager;
236
237
/////////// VOTING ///////////
238
239
240
    // Close the candidate config, be ready for voting (optional)
241 98
    public function setStateToVote () : bool
242
    {
243 98
        if ( $this->_State === 1 ) :
244 98
                if (empty($this->_Candidates)) :
245
                    throw new CondorcetException(20);
246
                endif;
247
248 98
                $this->_State = 2;
249
250
        // If voting continues after a first set of results
251 86
        elseif ( $this->_State > 2 ) :
252 5
                $this->cleanupResult();
253
        endif;
254
255 98
        return true;
256
    }
257
258
259
    // Add a single vote. Array key is the rank, each candidate in a rank are separate by ',' It is not necessary to register the last rank.
260 98
    public function addVote ($vote, $tag = null) : Vote
261
    {
262 98
        $this->prepareVoteInput($vote, $tag);
263
264
        // Check Max Vote Count
265 98
        if ( self::$_maxVoteNumber !== null && !$this->_ignoreStaticMaxVote && $this->countVotes() >= self::$_maxVoteNumber ) :
266 1
            throw new CondorcetException(16, self::$_maxVoteNumber);
267
        endif;
268
269
270
        // Register vote
271 98
        return $this->registerVote($vote, $tag); // Return the vote object
272
    }
273
274
        // return True or throw an Exception
275 5
        public function prepareModifyVote (Vote $existVote)
276
        {
277
            try {
278 5
                $this->prepareVoteInput($existVote);
279 5
                $this->setStateToVote();
280
            }
281
            catch (\Exception $e) {
282
                throw $e;
283
            }
284 5
        }
285
286
        // Return the well formated vote to use.
287 98
        protected function prepareVoteInput (&$vote, $tag = null) : void
288
        {
289 98
            if (!($vote instanceof Vote)) :
290 82
                $vote = new Vote ($vote, $tag);
291
            endif;
292
293
            // Check array format && Make checkVoteCandidate
294 98
            if ( !$this->checkVoteCandidate($vote) ) :
295
                throw new CondorcetException(5);
296
            endif;
297 98
        }
298
299
300 98
        public function checkVoteCandidate (Vote $vote) : bool
301
        {
302 98
            $linkCount = $vote->countLinks();
303 98
            $links = $vote->getLinks();
304
305 98
            $mirror = $vote->getRanking();
306 98
            $change = false;
307 98
            foreach ($vote as $rank => $choice) :
308 98
                foreach ($choice as $choiceKey => $candidate) :
309 98
                    if ( !$this->existCandidateId($candidate, true) ) :
310 91
                        if ($candidate->getProvisionalState() && $this->existCandidateId($candidate, false)) :
311 90
                            if ( $linkCount === 0 || ($linkCount === 1 && reset($links) === $this) ) :
312 90
                                $mirror[$rank][$choiceKey] = $this->_Candidates[$this->getCandidateKey((string) $candidate)];
313 90
                                $change = true;
314
                            else :
315 98
                                return false;
316
                            endif;
317
                        endif;
318
                    endif;
319
                endforeach;
320
            endforeach;
321
322 98
            if ($change) :
323 90
                $vote->setRanking(
324 90
                                    $mirror,
325 90
                                    ( abs($vote->getTimestamp() - microtime(true)) > 0.5 ) ? ($vote->getTimestamp() + 0.001) : null
326
                );
327
            endif;
328
329 98
            return true;
330
        }
331
332
        // Write a new vote
333 98
        protected function registerVote (Vote $vote, $tag = null) : Vote
334
        {
335
            // Vote identifiant
336 98
            $vote->addTags($tag);
337
            
338
            // Register
339
            try {
340 98
                $vote->registerLink($this);
341 98
                $this->_Votes[] = $vote;
342
            } catch (CondorcetException $e) {
343
                // Security : Check if vote object not already register
344
                throw new CondorcetException(6,'Vote object already registred');
345
            }
346
347 98
            return $vote;
348
        }
349
350
351 5
    public function removeVote ($in, bool $with = true) : array
352
    {    
353 5
        $rem = [];
354
355 5
        if ($in instanceof Vote) :
356 4
            $key = $this->getVoteKey($in);
357 4
            if ($key !== false) :
358 4
                $this->_Votes[$key]->destroyLink($this);
359
360 4
                $rem[] = $this->_Votes[$key];
361
362 4
                unset($this->_Votes[$key]);
363
            endif;
364
        else :
365
            // Prepare Tags
366 3
            $tag = VoteUtil::tagsConvert($in);
367
368
            // Deleting
369 3
            foreach ($this->getVotesList($tag, $with) as $key => $value) :
370 3
                $this->_Votes[$key]->destroyLink($this);
371
372 3
                $rem[] = $this->_Votes[$key];
373
374 3
                unset($this->_Votes[$key]);
375
            endforeach;
376
377
        endif;
378
379 5
        return $rem;
380
    }
381
382
383 2
    public function jsonVotes (string $input)
384
    {
385 2
        $input = CondorcetUtil::prepareJson($input);
386 1
        if ($input === false) :
387
            return $input;
388
        endif;
389
390
            //////
391
392 1
        $adding = [];
393
394 1
        foreach ($input as $record) :
395 1
            if (empty($record['vote'])) :
396 1
                continue;
397
            endif;
398
399 1
            $tags = (!isset($record['tag'])) ? null : $record['tag'];
400 1
            $multi = (!isset($record['multi'])) ? 1 : $record['multi'];
401
402 1
            for ($i = 0; $i < $multi; $i++) :
403 1
                if (self::$_maxParseIteration !== null && $this->countVotes() >= self::$_maxParseIteration) :
404
                    throw new CondorcetException(12, self::$_maxParseIteration);
405
                endif;
406
407
                try {
408 1
                    $adding[] = $this->addVote($record['vote'], $tags);
409
                } catch (\Exception $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
410
            endfor;
411
        endforeach;
412
413 1
        return $adding;
414
    }
415
416 70
    public function parseVotes (string $input, bool $allowFile = true)
417
    {
418 70
        $input = CondorcetUtil::prepareParse($input, $allowFile);
419 70
        if ($input === false) :
420
            return $input;
421
        endif;
422
423
        // Check each lines
424 70
        $adding = [];
425 70
        foreach ($input as $line) :
426
            // Empty Line
427 70
            if (empty($line)) :
428 63
                continue;
429
            endif;
430
431
            // Multiples
432 70
            $is_multiple = mb_strpos($line, '*');
433 70
            if ($is_multiple !== false) :
434 63
                $multiple = trim( substr($line, $is_multiple + 1) );
435
436
                // Errors
437 63
                if ( !is_numeric($multiple) ) :
438
                    throw new CondorcetException(13, null);
439
                endif;
440
441 63
                $multiple = intval($multiple);
442
443
                // Reformat line
444 63
                $line = substr($line, 0, $is_multiple);
445
            else :
446 9
                $multiple = 1;
447
            endif;
448
449
            // Vote Weight
450 70
            $is_voteWeight = mb_strpos($line, '^');
451 70
            if ($is_voteWeight !== false) :
452 2
                $weight = trim( substr($line, $is_voteWeight + 1) );
453
454
                // Errors
455 2
                if ( !is_numeric($weight) ) :
456
                    throw new CondorcetException(13, null);
457
                endif;
458
459 2
                $weight = intval($weight);
460
461
                // Reformat line
462 2
                $line = substr($line, 0, $is_voteWeight);
463
            else :
464 69
                $weight = 1;
465
            endif;
466
467
            // Tags + vote
468 70
            if (mb_strpos($line, '||') !== false) :
469 4
                $data = explode('||', $line);
470
471 4
                $vote = $data[1];
472 4
                $tags = $data[0];
473
            // Vote without tags
474
            else :
475 69
                $vote = $line;
476 69
                $tags = null;
477
            endif;
478
479
            // addVote
480 70
            for ($i = 0; $i < $multiple; $i++) :
481 70
                if (self::$_maxParseIteration !== null && count($adding) >= self::$_maxParseIteration) :
482 1
                    throw new CondorcetException(12, self::$_maxParseIteration);
483
                endif;
484
485
                try {
486 70
                    $adding[] = ($newVote = $this->addVote($vote, $tags));
487 70
                    $newVote->setWeight($weight);
488 1
                } catch (CondorcetException $e) {}
1 ignored issue
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
489
            endfor;
490
        endforeach;
491
492 70
        return $adding;
493
    }
494
495
496
    //:: LARGE ELECTION MODE :://
497
498 3
    public function setExternalDataHandler (DataHandlerDriverInterface $driver) : bool
499
    {
500 3
        if (!$this->_Votes->isUsingHandler()) :
501 3
            $this->_Votes->importHandler($driver);
502 3
            return true;
503
        else :
504
            throw new CondorcetException(24);
505
        endif;
506
    }
507
508 1
    public function removeExternalDataHandler () : bool
509
    {
510 1
        if ($this->_Votes->isUsingHandler()) :
511 1
            $this->_Votes->closeHandler();
512 1
            return true;
513
        else :
514
            throw new CondorcetException(23);
515
        endif;
516
    }
517
518
519
    //:: VOTING TOOLS :://
520
521
    // How many votes are registered ?
522 10
    public function countVotes ($tag = null, bool $with = true) : int
523
    {
524 10
        return $this->_Votes->countVotes(VoteUtil::tagsConvert($tag),$with);
525
    }
526
527
    // Get the votes registered list
528 9
    public function getVotesList ($tag = null, bool $with = true) : array
529
    {
530 9
        return $this->_Votes->getVotesList(VoteUtil::tagsConvert($tag), $with);
531
    }
532
533 4
    public function getVotesListAsString () : string
534
    {
535 4
        return $this->_Votes->getVotesListAsString();
536
    }
537
538 81
    public function getVotesManager () : VotesManager {
539 81
        return $this->_Votes;
540
    }
541
542 4
    public function getVoteKey (Vote $vote) {
543 4
        return $this->_Votes->getVoteKey($vote);
544
    }
545
546
547
/////////// RESULTS ///////////
548
549
    //:: PUBLIC FUNCTIONS :://
550
551
    // Generic function for default result with ability to change default object method
552 48
    public function getResult ($method = true, array $options = []) : Result
553
    {
554 48
        $this->prepareResult();
555
556 48
        return $this->_ResultManager->getResult($method,$options);
557
    }
558
559
560 78
    public function getWinner (?string $substitution = null)
561
    {
562 78
        $this->prepareResult();
563
564 78
        return $this->_ResultManager->getWinner($substitution);
565
    }
566
567
568 75
    public function getLoser (?string $substitution = null)
569
    {
570 75
        $this->prepareResult();
571
572 75
        return $this->_ResultManager->getLoser($substitution);
573
    }
574
575
576 1
    public function computeResult ($method = true) : void
577
    {
578 1
        $this->getResult($method);
579 1
    }
580
581
582
    //:: TOOLS FOR RESULT PROCESS :://
583
584
585
    // Prepare to compute results & caching system
586 80
    protected function prepareResult () : bool
587
    {
588 80
        if ($this->_State > 2) :
589 77
            return false;
590 80
        elseif ($this->_State === 2) :
591 80
            $this->cleanupResult();
592
593 80
            $this->_ResultManager = new ResultManager ($this);
594
595
            // Change state to result
596 80
            $this->_State = 3;
597
598
            // Return
599 80
            return true;
600
        else :
601
            throw new CondorcetException(6);
602
        endif;
603
    }
604
605
606
    // Cleanup results to compute again with new votes
607 80
    protected function cleanupResult () : void
608
    {
609
        // Reset state
610 80
        if ($this->_State > 2) : 
611 7
            $this->_State = 2;
612
        endif;
613
614
            //////
615
616 80
        $this->_ResultManager = null;
617 80
    }
618
619
620
    //:: GET RAW DATA :://
621
622 77
    public function getPairwise (bool $explicit = true)
623
    {
624 77
        $this->prepareResult();
625
626 77
        $pairwise = $this->_ResultManager->getPairwise($explicit);
627
628 77
        return (!$explicit) ? $pairwise : $pairwise->getExplicitPairwise($explicit);
629
    }
630
631
}
632