Points d'extension
phil/pollLa bibliothèque a un seul port, Phil\Poll\Contract\CountingMethod. Une application ajoute une façon de dépouiller en
l'implémentant, et la déclare à côté de celles de la bibliothèque dans CountingMethods.
CountingMethod
interface CountingMethod
{
public function name(): string;
public function supports(BallotKind $kind): bool;
/**
* @param iterable<Ballot> $ballots
*/
public function count(PollRules $rules, iterable $ballots): Result;
}
name()est ce qu'un scrutin stocke pour être dépouillé par la méthode : de 1 à 64 lettres ASCII minuscules, chiffres ou_, unique parmi les méthodes de l'application.supports()dit quelles sortes de bulletins la méthode dépouille ;count()lanceInvalidPollpour les autres.count()lit les bulletins une seule fois — ils peuvent venir d'un générateur sur une table — et ne garde aucun état entre deux dépouillements : une seule instance sert ainsi chaque requête d'un worker de longue durée. Elle lanceInvalidBallotpour un choix qui n'est pas l'une des options des règles.
Un compte des premières préférences, pour des bulletins classés présentés comme une majorité simple :
use Phil\Poll\BallotKind;
use Phil\Poll\Contract\CountingMethod;
use Phil\Poll\Exception\InvalidBallot;
use Phil\Poll\Exception\InvalidPoll;
use Phil\Poll\PollRules;
use Phil\Poll\Result;
use Phil\Poll\Standing;
final readonly class FirstPreferences implements CountingMethod
{
public function name(): string
{
return 'first_preferences';
}
public function supports(BallotKind $kind): bool
{
return BallotKind::Ranked === $kind;
}
public function count(PollRules $rules, iterable $ballots): Result
{
if (!$this->supports($rules->kind)) {
throw InvalidPoll::because('First preferences are counted on ranked ballots.');
}
$scores = array_fill_keys($rules->options, 0);
$ballotCount = 0;
$abstentions = 0;
foreach ($ballots as $ballot) {
++$ballotCount;
if ($ballot->isAbstention()) {
++$abstentions;
continue;
}
$first = $ballot->choices[0];
$rules->has($first) || throw InvalidBallot::because(sprintf('"%s" is not an option.', $first));
++$scores[$first];
}
arsort($scores);
$standings = [];
$place = 0;
$previous = null;
foreach ($scores as $option => $score) {
$place = $score === $previous ? $place : \count($standings) + 1;
$previous = $score;
$standings[] = new Standing((string) $option, $score, $place);
}
return new Result($this->name(), $ballotCount, $abstentions, $standings);
}
}
use Phil\Poll\CountingMethods;
use Phil\Poll\Counting\Borda;
use Phil\Poll\Counting\ChoiceCount;
use Phil\Poll\Counting\InstantRunoff;
use Phil\Poll\Counting\Schulze;
$methods = new CountingMethods([new ChoiceCount(), new InstantRunoff(), new Borda(), new Schulze(), new FirstPreferences()]);
phil/poll-bundle déclare chaque service qui implémente CountingMethod à côté des méthodes de la bibliothèque.