Completed
Push — dev-1.6.x ( c638bc...048fe7 )
by Boudry
04:21
created

Election::getPairwise()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
crap 2
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 28 and the first side effect is on line 120.

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