1
|
|
|
<?php |
|
|
|
|
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\Vote; |
17
|
|
|
use Condorcet\DataManager\VotesManager; |
18
|
|
|
use Condorcet\DataManager\DataHandlerDrivers\DataHandlerDriverInterface; |
19
|
|
|
use Condorcet\ElectionProcess\CandidatesProcess; |
20
|
|
|
use Condorcet\ElectionProcess\ResultsProcess; |
21
|
|
|
use Condorcet\ElectionProcess\VotesProcess; |
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
|
|
|
// Mechanics |
60
|
|
|
protected $_State = 1; // 1 = Add Candidates / 2 = Voting / 3 = Some result have been computing |
61
|
|
|
protected $_timer; |
62
|
|
|
|
63
|
|
|
// Params |
64
|
|
|
protected $_ImplicitRanking = true; |
65
|
|
|
protected $_VoteWeightRule = false; |
66
|
|
|
|
67
|
|
|
////// |
68
|
|
|
|
69
|
111 |
|
public function __construct () |
70
|
|
|
{ |
71
|
111 |
|
$this->_Candidates = []; |
72
|
111 |
|
$this->_Votes = new VotesManager ($this); |
73
|
111 |
|
$this->_timer = new Timer_Manager; |
74
|
111 |
|
} |
75
|
|
|
|
76
|
1 |
|
public function __destruct () |
77
|
|
|
{ |
78
|
1 |
|
$this->destroyAllLink(); |
79
|
1 |
|
} |
80
|
|
|
|
81
|
2 |
|
public function __sleep () : array |
82
|
|
|
{ |
83
|
|
|
// Don't include others data |
84
|
|
|
$include = [ |
85
|
2 |
|
'_Candidates', |
86
|
|
|
'_Votes', |
87
|
|
|
|
88
|
|
|
'_i_CandidateId', |
89
|
|
|
'_State', |
90
|
|
|
'_objectVersion', |
91
|
|
|
'_ignoreStaticMaxVote', |
92
|
|
|
|
93
|
|
|
'_ImplicitRanking', |
94
|
|
|
'_VoteWeightRule', |
95
|
|
|
|
96
|
|
|
'_Pairwise', |
97
|
|
|
'_Calculator', |
98
|
|
|
]; |
99
|
|
|
|
100
|
2 |
|
!self::$_checksumMode && array_push($include, '_timer'); |
101
|
|
|
|
102
|
2 |
|
return $include; |
103
|
|
|
} |
104
|
|
|
|
105
|
1 |
|
public function __wakeup () |
106
|
|
|
{ |
107
|
1 |
|
if ( version_compare($this->getObjectVersion('MAJOR'),Condorcet::getVersion('MAJOR'),'!=') ) : |
108
|
|
|
throw new CondorcetException(11, 'Your object version is '.$this->getObjectVersion().' but the class engine version is '.Condorcet::getVersion('ENV')); |
109
|
|
|
endif; |
110
|
1 |
|
} |
111
|
|
|
|
112
|
1 |
|
public function __clone () |
113
|
|
|
{ |
114
|
1 |
|
$this->_Votes = clone $this->_Votes; |
115
|
1 |
|
$this->_Votes->setElection($this); |
116
|
1 |
|
$this->registerAllLinks(); |
117
|
|
|
|
118
|
1 |
|
$this->_timer = clone $this->_timer; |
119
|
|
|
|
120
|
1 |
|
if ($this->_Pairwise !== null) : |
121
|
1 |
|
$this->_Pairwise = clone $this->_Pairwise; |
122
|
1 |
|
$this->_Pairwise->setElection($this); |
123
|
|
|
endif; |
124
|
1 |
|
} |
125
|
|
|
|
126
|
|
|
|
127
|
|
|
/////////// TIMER & CHECKSUM /////////// |
128
|
|
|
|
129
|
2 |
|
public function getGlobalTimer (bool $float = false) { |
130
|
2 |
|
return $this->_timer->getGlobalTimer($float); |
131
|
|
|
} |
132
|
|
|
|
133
|
2 |
|
public function getLastTimer (bool $float = false) { |
134
|
2 |
|
return $this->_timer->getLastTimer($float); |
135
|
|
|
} |
136
|
|
|
|
137
|
80 |
|
public function getTimerManager () : Timer_Manager { |
138
|
80 |
|
return $this->_timer; |
139
|
|
|
} |
140
|
|
|
|
141
|
1 |
|
public function getChecksum () : string |
142
|
|
|
{ |
143
|
1 |
|
self::$_checksumMode = true; |
144
|
|
|
|
145
|
1 |
|
$r = hash_init('sha256'); |
146
|
|
|
|
147
|
1 |
|
foreach ($this->_Candidates as $value) : |
148
|
1 |
|
hash_update($r, (string) $value); |
149
|
|
|
endforeach; |
150
|
|
|
|
151
|
1 |
|
foreach ($this->_Votes as $value) : |
152
|
1 |
|
hash_update($r, (string) $value); |
153
|
|
|
endforeach; |
154
|
|
|
|
155
|
1 |
|
$this->_Pairwise !== null |
156
|
1 |
|
&& hash_update($r,serialize($this->_Pairwise->getExplicitPairwise())); |
157
|
|
|
|
158
|
1 |
|
hash_update($r, $this->getObjectVersion('major')); |
159
|
|
|
|
160
|
1 |
|
self::$_checksumMode = false; |
161
|
|
|
|
162
|
1 |
|
return hash_final($r); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
|
166
|
|
|
/////////// LINKS REGULATION /////////// |
167
|
|
|
|
168
|
1 |
|
protected function registerAllLinks () : void |
169
|
|
|
{ |
170
|
1 |
|
foreach ($this->_Candidates as $value) : |
171
|
1 |
|
$value->registerLink($this); |
172
|
|
|
endforeach; |
173
|
|
|
|
174
|
1 |
|
if ($this->_State > 1) : |
175
|
1 |
|
foreach ($this->_Votes as $value) : |
176
|
1 |
|
$value->registerLink($this); |
177
|
|
|
endforeach; |
178
|
|
|
endif; |
179
|
1 |
|
} |
180
|
|
|
|
181
|
1 |
|
protected function destroyAllLink () : void |
182
|
|
|
{ |
183
|
1 |
|
foreach ($this->_Candidates as $value) : |
184
|
1 |
|
$value->destroyLink($this); |
185
|
|
|
endforeach; |
186
|
|
|
|
187
|
1 |
|
if ($this->_State > 1) : |
188
|
1 |
|
foreach ($this->_Votes as $value) : |
189
|
1 |
|
$value->destroyLink($this); |
190
|
|
|
endforeach; |
191
|
|
|
endif; |
192
|
1 |
|
} |
193
|
|
|
|
194
|
|
|
|
195
|
|
|
/////////// IMPLICIT RANKING & VOTE WEIGHT /////////// |
196
|
|
|
|
197
|
86 |
|
public function getImplicitRankingRule () : bool |
198
|
|
|
{ |
199
|
86 |
|
return $this->_ImplicitRanking; |
200
|
|
|
} |
201
|
|
|
|
202
|
3 |
|
public function setImplicitRanking (bool $rule = true) : bool |
203
|
|
|
{ |
204
|
3 |
|
$this->_ImplicitRanking = $rule; |
205
|
3 |
|
$this->cleanupResult(); |
206
|
3 |
|
return $this->getImplicitRankingRule(); |
207
|
|
|
} |
208
|
|
|
|
209
|
82 |
|
public function isVoteWeightIsAllowed () : bool |
210
|
|
|
{ |
211
|
82 |
|
return $this->_VoteWeightRule; |
212
|
|
|
} |
213
|
|
|
|
214
|
1 |
|
public function allowVoteWeight (bool $rule = true) : bool |
215
|
|
|
{ |
216
|
1 |
|
$this->_VoteWeightRule = $rule; |
217
|
1 |
|
$this->cleanupResult(); |
218
|
1 |
|
return $this->isVoteWeightIsAllowed(); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
|
222
|
|
|
/////////// LARGE ELECTION MODE /////////// |
223
|
|
|
|
224
|
3 |
|
public function setExternalDataHandler (DataHandlerDriverInterface $driver) : bool |
225
|
|
|
{ |
226
|
3 |
|
if (!$this->_Votes->isUsingHandler()) : |
227
|
3 |
|
$this->_Votes->importHandler($driver); |
228
|
3 |
|
return true; |
229
|
|
|
else : |
230
|
|
|
throw new CondorcetException(24); |
231
|
|
|
endif; |
232
|
|
|
} |
233
|
|
|
|
234
|
1 |
|
public function removeExternalDataHandler () : bool |
235
|
|
|
{ |
236
|
1 |
|
if ($this->_Votes->isUsingHandler()) : |
237
|
1 |
|
$this->_Votes->closeHandler(); |
238
|
1 |
|
return true; |
239
|
|
|
else : |
240
|
|
|
throw new CondorcetException(23); |
241
|
|
|
endif; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
|
245
|
|
|
/////////// STATE /////////// |
246
|
|
|
|
247
|
|
|
// Close the candidate config, be ready for voting (optional) |
248
|
98 |
|
public function setStateToVote () : bool |
249
|
|
|
{ |
250
|
98 |
|
if ( $this->_State === 1 ) : |
251
|
98 |
|
if (empty($this->_Candidates)) : |
252
|
|
|
throw new CondorcetException(20); |
253
|
|
|
endif; |
254
|
|
|
|
255
|
98 |
|
$this->_State = 2; |
256
|
|
|
|
257
|
|
|
// If voting continues after a first set of results |
258
|
86 |
|
elseif ( $this->_State > 2 ) : |
259
|
5 |
|
$this->cleanupResult(); |
260
|
|
|
endif; |
261
|
|
|
|
262
|
98 |
|
return true; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
// Prepare to compute results & caching system |
266
|
80 |
|
protected function prepareResult () : bool |
267
|
|
|
{ |
268
|
80 |
|
if ($this->_State > 2) : |
269
|
74 |
|
return false; |
270
|
80 |
|
elseif ($this->_State === 2) : |
271
|
80 |
|
$this->cleanupResult(); |
272
|
|
|
|
273
|
|
|
// Do Pairewise |
274
|
80 |
|
$this->makePairwise(); |
275
|
|
|
|
276
|
|
|
// Change state to result |
277
|
80 |
|
$this->_State = 3; |
278
|
|
|
|
279
|
|
|
// Return |
280
|
80 |
|
return true; |
281
|
|
|
else : |
282
|
|
|
throw new CondorcetException(6); |
283
|
|
|
endif; |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
|
287
|
|
|
/////////// CANDIDATES /////////// |
288
|
|
|
|
289
|
|
|
use CandidatesProcess; |
290
|
|
|
|
291
|
|
|
|
292
|
|
|
/////////// VOTING /////////// |
293
|
|
|
|
294
|
|
|
use VotesProcess; |
295
|
|
|
|
296
|
|
|
|
297
|
|
|
/////////// RESULTS /////////// |
298
|
|
|
|
299
|
|
|
use ResultsProcess; |
300
|
|
|
|
301
|
|
|
} |
302
|
|
|
|
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.