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

Election   F

Complexity

Total Complexity 164

Size/Duplication

Total Lines 906
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 92.49%

Importance

Changes 0
Metric Value
wmc 164
lcom 1
cbo 11
dl 0
loc 906
ccs 345
cts 373
cp 0.9249
rs 3.9999
c 0
b 0
f 0

55 Methods

Rating   Name   Duplication   Size   Complexity  
A setMaxParseIteration() 0 5 1
A setMaxVoteNumber() 0 5 1
A __construct() 0 6 1
A __destruct() 0 4 1
B __sleep() 0 24 2
B getResult() 0 46 6
A getWinner() 0 16 3
A getLoser() 0 16 3
A __wakeup() 0 6 2
A __clone() 0 13 2
A registerAllLinks() 0 12 4
A destroyAllLink() 0 12 4
A getGlobalTimer() 0 3 1
A getLastTimer() 0 3 1
A getTimerManager() 0 3 1
B getChecksum() 0 23 4
A ignoreMaxVote() 0 4 1
A getImplicitRankingRule() 0 4 1
A setImplicitRanking() 0 6 1
A isVoteWeightIsAllowed() 0 4 1
A allowVoteWeight() 0 6 1
D addCandidate() 0 39 10
A canAddCandidate() 0 4 1
B removeCandidate() 0 33 6
A jsonCandidates() 0 19 4
C parseCandidates() 0 29 8
A countCandidates() 0 4 1
A getCandidatesList() 0 14 3
A getCandidateKey() 0 8 2
A getCandidateId() 0 8 3
A existCandidateId() 0 4 2
A getCandidateObjectByName() 0 11 3
A setStateToVote() 0 16 4
A addVote() 0 13 4
A prepareModifyVote() 0 10 2
A prepareVoteInput() 0 11 3
C checkVoteCandidate() 0 31 11
A registerVote() 0 16 2
B removeVote() 0 30 4
D jsonVotes() 0 32 10
C parseVotes() 0 78 13
A setExternalDataHandler() 0 9 2
A removeExternalDataHandler() 0 9 2
A countVotes() 0 4 1
A getVotesList() 0 4 1
A getVotesListAsString() 0 4 1
A getVotesManager() 0 3 1
A getVoteKey() 0 3 1
A condorcetBasicSubstitution() 0 13 3
A computeResult() 0 4 1
A prepareResult() 0 19 3
A initResult() 0 6 2
A cleanupResult() 0 15 2
A formatResultOptions() 0 15 4
A getPairwise() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like Election often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Election, and based on these observations, apply Extract Interface, too.

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 26 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\Util;
17
use Condorcet\Vote;
18
use Condorcet\Algo\Pairwise;
19
use Condorcet\DataManager\VotesManager;
20
use Condorcet\DataManager\DataHandlerDrivers\DataHandlerDriverInterface;
21
use Condorcet\Timer\Chrono as Timer_Chrono;
22
use Condorcet\Timer\Manager as Timer_Manager;
23
24
25
// Base Condorcet class
26
class Election
27
{
28
29
/////////// PROPERTIES ///////////
30
31
    public const MAX_LENGTH_CANDIDATE_ID = 30; // Max length for candidate identifiant string
32
33
    protected static $_maxParseIteration = null;
34
    protected static $_maxVoteNumber = null;
35
    protected static $_checksumMode = false;
36
37
/////////// STATICS METHODS ///////////
38
39
    // Change max parse iteration
40 1
    public static function setMaxParseIteration (?int $value) : ?int
41
    {
42 1
        self::$_maxParseIteration = $value;
43 1
        return self::$_maxParseIteration;
44
    }
45
46
    // Change max vote number
47 1
    public static function setMaxVoteNumber (?int $value) : ?int
48
    {
49 1
        self::$_maxVoteNumber = $value;
50 1
        return self::$_maxVoteNumber;
51
    }
52
53
54
/////////// CONSTRUCTOR ///////////
55
56
    use CondorcetVersion;
57
58
    // Data and global options
59
    protected $_Candidates = []; // Candidate list
60
    protected $_Votes; // Votes list
61
62
    // Mechanics
63
    protected $_i_CandidateId = 'A';
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 110
    public function __construct ()
80
    {
81 110
        $this->_Candidates = [];
82 110
        $this->_Votes = new VotesManager ($this);
83 110
        $this->_timer = new Timer_Manager;
84 110
    }
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
239
    // Add a vote candidate before voting
240 105
    public function addCandidate ($candidate_id = null) : Candidate
241
    {
242
        // only if the vote has not started
243 105
        if ( $this->_State > 1 ) :
244
            throw new CondorcetException(2);
245
        endif;
246
247
        // Filter
248 105
        if ( is_bool($candidate_id) || is_array($candidate_id) || (is_object($candidate_id) && !($candidate_id instanceof Candidate)) ) :
249
            throw new CondorcetException(1, $candidate_id);
250
        endif;
251
252
253
        // Process
254 105
        if ( empty($candidate_id) ) :
255 5
            while ( !$this->canAddCandidate($this->_i_CandidateId) ) :
256 5
                $this->_i_CandidateId++;
257
            endwhile;
258
259 5
            $newCandidate = new Candidate($this->_i_CandidateId);
260
        else : // Try to add the candidate_id
261 102
            $newCandidate = ($candidate_id instanceof Candidate) ? $candidate_id : new Candidate ((string) $candidate_id);
262
263 102
            if ( !$this->canAddCandidate($newCandidate) ) :
264
                throw new CondorcetException(3,$candidate_id);
265
            endif;
266
        endif;
267
268
        // Register it
269 105
        $this->_Candidates[] = $newCandidate;
270
271
        // Linking
272 105
        $newCandidate->registerLink($this);
273
274
        // Disallow other candidate object name matching 
275 105
        $newCandidate->setProvisionalState(false);
276
277 105
        return $newCandidate;
278
    }
279
280 105
        public function canAddCandidate ($candidate_id) : bool
281
        {
282 105
            return !$this->existCandidateId($candidate_id, false);
283
        }
284
285
286
    // Destroy a register vote candidate before voting
287 3
    public function removeCandidate ($list) : array
288
    {
289
        // only if the vote has not started
290 3
        if ( $this->_State > 1 ) :
291
            throw new CondorcetException(2);
292
        endif;
293
294
        
295 3
        if ( !is_array($list) ) :
296 3
            $list = [$list];
297
        endif;
298
299 3
        foreach ($list as &$candidate_id) :
300 3
            $candidate_key = $this->getCandidateKey($candidate_id);
301
302 3
            if ( $candidate_key === false ) :
303
                throw new CondorcetException(4,$candidate_id);
304
            endif;
305
306 3
            $candidate_id = $candidate_key;
307
        endforeach;
308
309 3
        $rem = [];
310 3
        foreach ($list as $candidate_key) :
311 3
            $this->_Candidates[$candidate_key]->destroyLink($this);
312
313 3
            $rem[] = $this->_Candidates[$candidate_key];
314
315 3
            unset($this->_Candidates[$candidate_key]);
316
        endforeach;
317
318 3
        return $rem;
319
    }
320
321
322 1
    public function jsonCandidates (string $input)
323
    {
324 1
        $input = Util::prepareJson($input);
325 1
        if ($input === false) :
326
            return $input;
327
        endif;
328
329
            //////
330
331 1
        $adding = [];
332 1
        foreach ($input as $candidate) :
333
            try {
334 1
                $adding[] = $this->addCandidate($candidate);
335
            }
336 1
            catch (CondorcetException $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
337
        endforeach;
338
339 1
        return $adding;
340
    }
341
342 7
    public function parseCandidates (string $input, bool $allowFile = true)
343
    {
344 7
        $input = Util::prepareParse($input, $allowFile);
345 7
        if ($input === false) :
346
            return $input;
347
        endif;
348
349 7
        $adding = [];
350 7
        foreach ($input as $line) :
351
            // Empty Line
352 7
            if (empty($line)) :
353 1
                continue;
354
            endif;
355
356
            // addCandidate
357
            try {
358 7
                if (self::$_maxParseIteration !== null && count($adding) >= self::$_maxParseIteration) :
359
                    throw new CondorcetException(12, self::$_maxParseIteration);
360
                endif;
361
362 7
                $adding[] = $this->addCandidate($line);
363
            } catch (CondorcetException $e) {
364
                if ($e->getCode() === 12)
365 7
                    {throw $e;}
366
            }
367
        endforeach;
368
369 7
        return $adding;
370
    }
371
372
373
        //:: CANDIDATES TOOLS :://
374
375
        // Count registered candidates
376 87
        public function countCandidates () : int
377
        {
378 87
            return count($this->_Candidates);
379
        }
380
381
        // Get the list of registered CANDIDATES
382 88
        public function getCandidatesList (bool $stringMode = false) : array
383
        {
384 88
            if (!$stringMode) :
385 86
                return $this->_Candidates;
386
            else :
387 4
                $result = [];
388
389 4
                foreach ($this->_Candidates as $candidateKey => &$oneCandidate) :
390 4
                    $result[$candidateKey] = $oneCandidate->getName();
391
                endforeach;
392
393 4
                return $result;
394
            endif;
395
        }
396
397 93
        public function getCandidateKey ($candidate_id)
398
        {
399 93
            if ($candidate_id instanceof Candidate) :
400 80
                return array_search($candidate_id, $this->_Candidates, true);
401
            else:
402 91
                return array_search(trim((string) $candidate_id), $this->_Candidates, false);
403
            endif;
404
        }
405
406 77
        public function getCandidateId (int $candidate_key, bool $onlyName = false)
407
        {
408 77
            if (!array_key_exists($candidate_key, $this->_Candidates)) :
409
                return false;
410
            else :
411 77
                return ($onlyName) ? $this->_Candidates[$candidate_key]->getName() : $this->_Candidates[$candidate_key];
412
            endif;
413
        }
414
415 105
        public function existCandidateId ($candidate_id, bool $strict = true) : bool
416
        {
417 105
            return ($strict) ? in_array($candidate_id, $this->_Candidates, true) : in_array((string) $candidate_id, $this->_Candidates);
418
        }
419
420 3
        public function getCandidateObjectByName (string $s)
421
        {
422 3
            foreach ($this->_Candidates as $oneCandidate) :
423
424 3
                if ($oneCandidate->getName() === $s) :
425 3
                    return $oneCandidate;
426
                endif;
427
            endforeach;
428
429 1
            return false;
430
        }
431
432
433
434
/////////// VOTING ///////////
435
436
437
    // Close the candidate config, be ready for voting (optional)
438 98
    public function setStateToVote () : bool
439
    {
440 98
        if ( $this->_State === 1 ) :
441 98
                if (empty($this->_Candidates)) :
442
                    throw new CondorcetException(20);
443
                endif;
444
445 98
                $this->_State = 2;
446
447
        // If voting continues after a first set of results
448 86
        elseif ( $this->_State > 2 ) :
449 5
                $this->cleanupResult();
450
        endif;
451
452 98
        return true;
453
    }
454
455
456
    // 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.
457 98
    public function addVote ($vote, $tag = null) : Vote
458
    {
459 98
        $this->prepareVoteInput($vote, $tag);
460
461
        // Check Max Vote Count
462 98
        if ( self::$_maxVoteNumber !== null && !$this->_ignoreStaticMaxVote && $this->countVotes() >= self::$_maxVoteNumber ) :
463 1
            throw new CondorcetException(16, self::$_maxVoteNumber);
464
        endif;
465
466
467
        // Register vote
468 98
        return $this->registerVote($vote, $tag); // Return the vote object
469
    }
470
471
        // return True or throw an Exception
472 5
        public function prepareModifyVote (Vote $existVote)
473
        {
474
            try {
475 5
                $this->prepareVoteInput($existVote);
476 5
                $this->setStateToVote();
477
            }
478
            catch (\Exception $e) {
479
                throw $e;
480
            }
481 5
        }
482
483
        // Return the well formated vote to use.
484 98
        protected function prepareVoteInput (&$vote, $tag = null) : void
485
        {
486 98
            if (!($vote instanceof Vote)) :
487 82
                $vote = new Vote ($vote, $tag);
488
            endif;
489
490
            // Check array format && Make checkVoteCandidate
491 98
            if ( !$this->checkVoteCandidate($vote) ) :
492
                throw new CondorcetException(5);
493
            endif;
494 98
        }
495
496
497 98
        public function checkVoteCandidate (Vote $vote) : bool
498
        {
499 98
            $linkCount = $vote->countLinks();
500 98
            $links = $vote->getLinks();
501
502 98
            $mirror = $vote->getRanking();
503 98
            $change = false;
504 98
            foreach ($vote as $rank => $choice) :
505 98
                foreach ($choice as $choiceKey => $candidate) :
506 98
                    if ( !$this->existCandidateId($candidate, true) ) :
507 91
                        if ($candidate->getProvisionalState() && $this->existCandidateId($candidate, false)) :
508 90
                            if ( $linkCount === 0 || ($linkCount === 1 && reset($links) === $this) ) :
509 90
                                $mirror[$rank][$choiceKey] = $this->_Candidates[$this->getCandidateKey((string) $candidate)];
510 90
                                $change = true;
511
                            else :
512 98
                                return false;
513
                            endif;
514
                        endif;
515
                    endif;
516
                endforeach;
517
            endforeach;
518
519 98
            if ($change) :
520 90
                $vote->setRanking(
521 90
                                    $mirror,
522 90
                                    ( abs($vote->getTimestamp() - microtime(true)) > 0.5 ) ? ($vote->getTimestamp() + 0.001) : false
0 ignored issues
show
Bug introduced by
It seems like abs($vote->getTimestamp(...stamp() + 0.001 : false can also be of type double; however, Condorcet\Vote::setRanking() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
523
                );
524
            endif;
525
526 98
            return true;
527
        }
528
529
        // Write a new vote
530 98
        protected function registerVote (Vote $vote, $tag = null) : Vote
531
        {
532
            // Vote identifiant
533 98
            $vote->addTags($tag);
534
            
535
            // Register
536
            try {
537 98
                $vote->registerLink($this);
538 98
                $this->_Votes[] = $vote;
539
            } catch (CondorcetException $e) {
540
                // Security : Check if vote object not already register
541
                throw new CondorcetException(6,'Vote object already registred');
542
            }
543
544 98
            return $vote;
545
        }
546
547
548 5
    public function removeVote ($in, bool $with = true) : array
549
    {    
550 5
        $rem = [];
551
552 5
        if ($in instanceof Vote) :
553 4
            $key = $this->getVoteKey($in);
554 4
            if ($key !== false) :
555 4
                $this->_Votes[$key]->destroyLink($this);
556
557 4
                $rem[] = $this->_Votes[$key];
558
559 4
                unset($this->_Votes[$key]);
560
            endif;
561
        else :
562
            // Prepare Tags
563 3
            $tag = Vote::tagsConvert($in);
564
565
            // Deleting
566 3
            foreach ($this->getVotesList($tag, $with) as $key => $value) :
567 3
                $this->_Votes[$key]->destroyLink($this);
568
569 3
                $rem[] = $this->_Votes[$key];
570
571 3
                unset($this->_Votes[$key]);
572
            endforeach;
573
574
        endif;
575
576 5
        return $rem;
577
    }
578
579
580 2
    public function jsonVotes (string $input)
581
    {
582 2
        $input = Util::prepareJson($input);
583 1
        if ($input === false) :
584
            return $input;
585
        endif;
586
587
            //////
588
589 1
        $adding = [];
590
591 1
        foreach ($input as $record) :
592 1
            if (empty($record['vote'])) :
593 1
                continue;
594
            endif;
595
596 1
            $tags = (!isset($record['tag'])) ? null : $record['tag'];
597 1
            $multi = (!isset($record['multi'])) ? 1 : $record['multi'];
598
599 1
            for ($i = 0; $i < $multi; $i++) :
600 1
                if (self::$_maxParseIteration !== null && $this->countVotes() >= self::$_maxParseIteration) :
601
                    throw new CondorcetException(12, self::$_maxParseIteration);
602
                endif;
603
604
                try {
605 1
                    $adding[] = $this->addVote($record['vote'], $tags);
606
                } catch (\Exception $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
607
            endfor;
608
        endforeach;
609
610 1
        return $adding;
611
    }
612
613 70
    public function parseVotes (string $input, bool $allowFile = true)
614
    {
615 70
        $input = Util::prepareParse($input, $allowFile);
616 70
        if ($input === false) :
617
            return $input;
618
        endif;
619
620
        // Check each lines
621 70
        $adding = [];
622 70
        foreach ($input as $line) :
623
            // Empty Line
624 70
            if (empty($line)) :
625 63
                continue;
626
            endif;
627
628
            // Multiples
629 70
            $is_multiple = mb_strpos($line, '*');
630 70
            if ($is_multiple !== false) :
631 63
                $multiple = trim( substr($line, $is_multiple + 1) );
632
633
                // Errors
634 63
                if ( !is_numeric($multiple) ) :
635
                    throw new CondorcetException(13, null);
636
                endif;
637
638 63
                $multiple = intval($multiple);
639
640
                // Reformat line
641 63
                $line = substr($line, 0, $is_multiple);
642
            else :
643 9
                $multiple = 1;
644
            endif;
645
646
            // Vote Weight
647 70
            $is_voteWeight = mb_strpos($line, '^');
648 70
            if ($is_voteWeight !== false) :
649 2
                $weight = trim( substr($line, $is_voteWeight + 1) );
650
651
                // Errors
652 2
                if ( !is_numeric($weight) ) :
653
                    throw new CondorcetException(13, null);
654
                endif;
655
656 2
                $weight = intval($weight);
657
658
                // Reformat line
659 2
                $line = substr($line, 0, $is_voteWeight);
660
            else :
661 69
                $weight = 1;
662
            endif;
663
664
            // Tags + vote
665 70
            if (mb_strpos($line, '||') !== false) :
666 4
                $data = explode('||', $line);
667
668 4
                $vote = $data[1];
669 4
                $tags = $data[0];
670
            // Vote without tags
671
            else :
672 69
                $vote = $line;
673 69
                $tags = null;
674
            endif;
675
676
            // addVote
677 70
            for ($i = 0; $i < $multiple; $i++) :
678 70
                if (self::$_maxParseIteration !== null && count($adding) >= self::$_maxParseIteration) :
679 1
                    throw new CondorcetException(12, self::$_maxParseIteration);
680
                endif;
681
682
                try {
683 70
                    $adding[] = ($newVote = $this->addVote($vote, $tags));
684 70
                    $newVote->setWeight($weight);
685 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...
686
            endfor;
687
        endforeach;
688
689 70
        return $adding;
690
    }
691
692
693
    //:: LARGE ELECTION MODE :://
694
695 3
    public function setExternalDataHandler (DataHandlerDriverInterface $driver) : bool
696
    {
697 3
        if (!$this->_Votes->isUsingHandler()) :
698 3
            $this->_Votes->importHandler($driver);
699 3
            return true;
700
        else :
701
            throw new CondorcetException(24);
702
        endif;
703
    }
704
705 1
    public function removeExternalDataHandler () : bool
706
    {
707 1
        if ($this->_Votes->isUsingHandler()) :
708 1
            $this->_Votes->closeHandler();
709 1
            return true;
710
        else :
711
            throw new CondorcetException(23);
712
        endif;
713
    }
714
715
716
    //:: VOTING TOOLS :://
717
718
    // How many votes are registered ?
719 10
    public function countVotes ($tag = null, bool $with = true) : int
720
    {
721 10
        return $this->_Votes->countVotes(Vote::tagsConvert($tag),$with);
722
    }
723
724
    // Get the votes registered list
725 9
    public function getVotesList ($tag = null, bool $with = true) : array
726
    {
727 9
        return $this->_Votes->getVotesList(Vote::tagsConvert($tag), $with);
728
    }
729
730 4
    public function getVotesListAsString () : string
731
    {
732 4
        return $this->_Votes->getVotesListAsString();
733
    }
734
735 81
    public function getVotesManager () : VotesManager {
736 81
        return $this->_Votes;
737
    }
738
739 4
    public function getVoteKey (Vote $vote) {
740 4
        return $this->_Votes->getVoteKey($vote);
741
    }
742
743
744
/////////// RESULTS ///////////
745
746
747
    //:: PUBLIC FUNCTIONS :://
748
749
    // Generic function for default result with ability to change default object method
750 75
    public function getResult ($method = true, array $options = []) : Result
751
    {
752 75
        $options = $this->formatResultOptions($options);
753
754
        // Filter if tag is provided & return
755 75
        if ($options['%tagFilter']) :
756 2
            $chrono = new Timer_Chrono ($this->_timer, 'GetResult with filter');
757
758 2
            $filter = new self;
759
760 2
            foreach ($this->getCandidatesList() as $candidate) :
761 2
                $filter->addCandidate($candidate);
762
            endforeach;
763
764 2
            foreach ($this->getVotesList($options['tags'], $options['withTag']) as $vote) :
765 2
                $filter->addVote($vote);
766
            endforeach;
767
768 2
            unset($chrono);
769
770 2
            return $filter->getResult($method);
771
        endif;
772
773
            ////// Start //////
774
775
        // Prepare
776 75
        $this->prepareResult();
777
778
            //////
779
780 75
        $chrono = new Timer_Chrono ($this->_timer);
781
782 75
        if ($method === true) :
783 7
            $this->initResult(Condorcet::getDefaultMethod());
784 7
            $result = $this->_Calculator[Condorcet::getDefaultMethod()]->getResult();
785 71
        elseif ($method = Condorcet::isAuthMethod((string) $method)) :
786 71
            $this->initResult($method);
787 69
            $result = $this->_Calculator[$method]->getResult();
788
        else :
789
            throw new CondorcetException(8,$method);
790
        endif;
791
792 72
        $chrono->setRole('GetResult for '.$method);
793
794 72
        return $result;
795
    }
796
797
798 78
    public function getWinner (?string $substitution = null)
799
    {
800 78
        $algo = $this->condorcetBasicSubstitution($substitution);
801
802
            //////
803
804 78
        if ($algo === Condorcet::CONDORCET_BASIC_CLASS) :
805 76
            new Timer_Chrono ($this->_timer, 'GetWinner for CondorcetBasic');
806 76
            $this->initResult($algo);
807 76
            $result = $this->_Calculator[$algo]->getWinner();
808
809 76
            return ($result === null) ? null : $this->getCandidateId($result);
810
        else :
811 50
            return $this->getResult($algo)->getWinner();
812
        endif;
813
    }
814
815
816 75
    public function getLoser (?string $substitution = null)
817
    {
818 75
        $algo = $this->condorcetBasicSubstitution($substitution);
819
820
            //////
821
822 75
        if ($algo === Condorcet::CONDORCET_BASIC_CLASS) :
823 75
            new Timer_Chrono ($this->_timer, 'GetLoser for CondorcetBasic');
824 75
            $this->initResult($algo);
825 75
            $result = $this->_Calculator[$algo]->getLoser();
826
827 75
            return ($result === null) ? null : $this->getCandidateId($result);
828
        else :
829 2
            return $this->getResult($algo)->getLoser();
830
        endif;
831
    }
832
833 79
        protected function condorcetBasicSubstitution (?string $substitution) : string {
834 79
            if ( $substitution !== null ) :
835 50
                if ( Condorcet::isAuthMethod($substitution) ) :
836 50
                    $algo = $substitution;
837
                else :
838 50
                    throw new CondorcetException(9,$substitution);
839
                endif;
840
            else :
841 77
                $algo = Condorcet::CONDORCET_BASIC_CLASS;
842
            endif;
843
844 79
            return $algo;
845
        }
846
847
848 1
    public function computeResult ($method = true) : void
849
    {
850 1
        $this->getResult($method);
851 1
    }
852
853
854
    //:: TOOLS FOR RESULT PROCESS :://
855
856
857
    // Prepare to compute results & caching system
858 80
    protected function prepareResult () : bool
859
    {
860 80
        if ($this->_State > 2) :
861 74
            return false;
862 80
        elseif ($this->_State === 2) :
863 80
            $this->cleanupResult();
864
865
            // Do Pairewise
866 80
            $this->_Pairwise = new Pairwise ($this);
867
868
            // Change state to result
869 80
            $this->_State = 3;
870
871
            // Return
872 80
            return true;
873
        else :
874
            throw new CondorcetException(6);
875
        endif;
876
    }
877
878
879 80
    protected function initResult (string $class) : void
880
    {
881 80
        if ( !isset($this->_Calculator[$class]) ) :
882 80
            $this->_Calculator[$class] = new $class($this);
883
        endif;
884 78
    }
885
886
887
    // Cleanup results to compute again with new votes
888 80
    protected function cleanupResult () : void
889
    {
890
        // Reset state
891 80
        if ($this->_State > 2) : 
892 7
            $this->_State = 2;
893
        endif;
894
895
            //////
896
897
        // Clean pairwise
898 80
        $this->_Pairwise = null;
899
900
        // Algos
901 80
        $this->_Calculator = null;
902 80
    }
903
904
905 75
    protected function formatResultOptions (array $arg) : array
906
    {
907
        // About tag filter
908 75
        if (isset($arg['tags'])):
909 2
            $arg['%tagFilter'] = true;
910
911 2
            if ( !isset($arg['withTag']) || !is_bool($arg['withTag']) ) :
912 2
                $arg['withTag'] = true;
913
            endif;
914
        else:
915 75
            $arg['%tagFilter'] = false;
916
        endif;
917
918 75
        return $arg;
919
    }
920
921
922
    //:: GET RAW DATA :://
923
924 77
    public function getPairwise (bool $explicit = true)
925
    {
926 77
        $this->prepareResult();
927
928 77
        return (!$explicit) ? $this->_Pairwise : $this->_Pairwise->getExplicitPairwise();
929
    }
930
931
}
932