PHPackages                             condorcet-vote/cef-writer - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. condorcet-vote/cef-writer

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

condorcet-vote/cef-writer
=========================

Streaming PHP writer that produces valid Condorcet Election Format (CEF) files line by line.

v1.0(2w ago)10MITPHPPHP ^8.5CI failing

Since May 24Pushed 2w agoCompare

[ Source](https://github.com/CondorcetVote/CEF-Writer)[ Packagist](https://packagist.org/packages/condorcet-vote/cef-writer)[ RSS](/packages/condorcet-vote-cef-writer/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (4)Versions (2)Used By (0)

CEF Writer
==========

[](#cef-writer)

A small PHP library that **streams** valid [Condorcet Election Format](https://github.com/CondorcetVote/CondorcetElectionFormat) (CEF) documents to a file or string with a friendly object API.

- Streaming: every `add*()` call writes one line immediately — nothing is buffered, nothing can be edited afterwards.
- Format-safe: the spec's syntactic rules (reserved characters, blank-ballot sentinel, single-line constraints, parameter-before-vote ordering) are enforced. Invalid input throws.
- Semantics-free on purpose: this library checks **format**, never **election logic** (it will, for example, happily let a vote reference a candidate that is not in `#/Candidates:`).
- Works with `\SplFileObject`, `\SplFileInfo`, a filesystem path, or a string passed **by reference**.

**[Full API reference](docs/readme.md)** — generated from the source with [PhpReference](https://github.com/julien-boudry/PhpReference).

Requirements
------------

[](#requirements)

- PHP **8.5** or later
- `ext-mbstring` (used for UTF-8 validation; bundled with PHP)

Installation
------------

[](#installation)

```
composer require condorcet-vote/cef-writer
```

Quick start
-----------

[](#quick-start)

```
use CondorcetVote\CefWriter\Cef;
use CondorcetVote\CefWriter\CommentLine;
use CondorcetVote\CefWriter\VoteLine;
use CondorcetVote\CefWriter\Parameter\CandidatesParameter;
use CondorcetVote\CefWriter\Parameter\ImplicitRankingParameter;
use CondorcetVote\CefWriter\Parameter\WeightAllowedParameter;

$cef = new Cef(file: '/tmp/election.cvotes');

$cef->addComment(new CommentLine('My beautiful election'));
$cef->addParameter(new CandidatesParameter(['Alice', 'Bob', 'Charlie']));
$cef->addParameter(new ImplicitRankingParameter(true));
$cef->addParameter(new WeightAllowedParameter(true));

$cef->addVote(new VoteLine(
    ranking: [['Alice'], ['Bob'], ['Charlie']],
    quantifier: 42,
));
$cef->addVote(new VoteLine(
    ranking: [['Charlie'], ['Alice', 'Bob']],
    weight: 7,
    quantifier: 8,
));
$cef->addVote(new VoteLine(ranking: [])); // blank ballot (/EMPTY_RANKING/)
```

produces:

```
# My beautiful election
#/Candidates: Alice ; Bob ; Charlie
#/Implicit Ranking: true
#/Weight Allowed: true

Alice > Bob > Charlie * 42
Charlie > Alice = Bob ^7 * 8
/EMPTY_RANKING/

```

Output targets
--------------

[](#output-targets)

The `Cef` constructor accepts **exactly one** of the following:

ArgumentTypeBehaviour`file: $path``string`Opened with mode `wb` (created/truncated).`file: $info``\SplFileInfo`Opened with mode `wb`.`file: $object``\SplFileObject` (must already be open)Used as-is.`string: $buf``string` passed **by reference**Each line is appended to the caller's variable.```
$buffer = '';
$cef = new Cef(string: $buffer);
$cef->addParameter(new CandidatesParameter(['A', 'B']));
echo $buffer; // "#/Candidates: A ; B\n"
```

`autoFormat`
------------

[](#autoformat)

`$cef->autoFormat` is a public `bool` (default `true`):

- `true` — writes the **readable** flavor of the spec: spaces around `>`, `=`, `;`, `,`, `||`, `^`, `*`; one blank line is inserted automatically between the parameter block and the first vote.
- `false` — writes the **compact** form with no optional whitespace and no auto blank line.

```
$cef->autoFormat = false;
$cef->addParameter(new CandidatesParameter(['A', 'B']));
$cef->addVote(new VoteLine([['A'], ['B']]));
// "#/Candidates:A;B\nA>B\n"
```

Building blocks
---------------

[](#building-blocks)

### Parameters

[](#parameters)

Each standard parameter has its own typed object. Custom parameters are supported via `CustomParameter`. The `StandardParameter` enum lists the exact spec names.

ClassCEF nameConstructor`CandidatesParameter``Candidates``array``NumberOfSeatsParameter``Number of Seats``int >= 1``ImplicitRankingParameter``Implicit Ranking``bool``VotingMethodsParameter``Voting Methods``array``WeightAllowedParameter``Weight Allowed``bool``CustomParameter`(free-form)`string $name, string $value`Parameters can only be added before the first vote — any later call throws `CefFormatException`.

### Vote lines

[](#vote-lines)

The typed way — build a `VoteLine` and pass it to `Cef::addVote()`:

```
new VoteLine(
    ranking:       [['Alice'], ['Bob', 'Charlie']], // [] => /EMPTY_RANKING/
    tags:          ['voter@example.com'],
    weight:        7,
    quantifier:    3,
    inlineComment: 'late ballot',
);
```

Each rank is itself a list of tied candidates. An empty top-level ranking emits the `/EMPTY_RANKING/` blank-ballot sentinel.

#### From a raw string — `VoteLine::fromString()`

[](#from-a-raw-string--votelinefromstring)

Parse a full CEF vote-line string into a `VoteLine` instance. Every component is optional except the ranking; both the relaxed (`A > B ^7 * 2`) and the compact (`A>B^7*2`) spacing flavors are accepted, plus the `/EMPTY_RANKING/`sentinel.

```
$cef->addVote(VoteLine::fromString('voter@example.com || Alice > Bob ^7 * 3 # late ballot'));
```

The string is parsed, every component is validated against the same rules as the constructor, and a `VoteLine` is returned. Throws `CefFormatException` on any malformed component.

#### Pre-validated raw lines — `Cef::addRawVoteLine()`

[](#pre-validated-raw-lines--cefaddrawvoteline)

When you already have ballots as text and want the fastest write path, `addRawVoteLine()` skips the `VoteLine` allocation while still enforcing the full CEF format:

```
$cef->addRawVoteLine('Alice > Bob = Charlie ^7 * 8');
```

It strips one trailing line terminator (`\r\n`, `\n`, `\r`), trims, rejects empty / multi-line / leading-`#` inputs, then runs `VoteLine::assertValidString()` for the same deep validation as `fromString()`. The `autoFormat` flag is **not** applied — what you pass is what gets written. About 1.8× faster than `addVote(VoteLine::fromString())`in practice.

#### Validation-only — `VoteLine::assertValidString()`

[](#validation-only--votelineassertvalidstring)

If you want to validate a vote-line string without allocating a `VoteLine`(e.g. to pre-flight user input before queueing it elsewhere), call the static `assertValidString()` — same pipeline as `fromString()`, no object returned, throws `CefFormatException` on any violation.

### Comments and blank lines

[](#comments-and-blank-lines)

```
$cef->addComment(new CommentLine('section divider'));
$cef->addCommentLine('shortcut — builds the CommentLine for you');
$cef->addEmptyLine();
```

Inline comments attached to vote lines live on `VoteLine::$inlineComment`. The CEF spec forbids inline comments on parameter lines, so the parameter classes intentionally do not expose one.

Errors
------

[](#errors)

Two top-level hierarchies, each for a different layer.

### Format &amp; input violations — `CefFormatException`

[](#format--input-violations--cefformatexception)

Base class for every specification or input violation. Catch this one to handle *any* format-related failure uniformly; catch a specific subclass below to branch on a kind of violation. Each message names the offending field and the rule that was broken.

SubclassThrown for`InvalidUtf8Exception`Byte sequence that does not decode as valid UTF-8.`ReservedCharacterException`One of the spec-reserved characters (`> = ; , # / * ^`), a `:` in a custom parameter name, `||` inside a tag, or a leading `#` on a raw vote line.`InvalidValueException`Empty required string, embedded line break, null byte, non-positive `weight` / `quantifier`, empty `#/Candidates:` or `#/Voting Methods:` list, or empty rank inside a ranking.`DuplicateCandidateException`Same candidate label appearing twice in `#/Candidates:` or anywhere inside a ranking (including across tied groups).`InvalidWriterStateException``Cef` constructed with neither a file nor a string target (or with both); parameter added after the first vote; vote-line string parsed without a ranking.All subclasses extend `CefFormatException`, which itself extends `\RuntimeException`.

### I/O failures — `CefWriteException`

[](#io-failures--cefwriteexception)

Thrown when writing to the underlying target (file or string buffer) fails — typically a closed handle, a read-only file, or a full disk. Distinct from `CefFormatException` because the cause is I/O, not your input. Extends `\RuntimeException`.

Development
-----------

[](#development)

```
composer install
vendor/bin/pest                                # run tests
vendor/bin/phpstan analyse                     # static analysis
vendor/bin/php-cs-fixer fix                    # apply lint
vendor/bin/php-cs-fixer fix --dry-run --diff   # check lint without writing
```

CI (`.github/workflows/ci.yml`) runs the test suite on Linux / Windows / macOS, plus a dedicated job with **JIT function mode** enabled, plus PHPStan and PHP CS Fixer.

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance97

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

16d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/4020317?v=4)[Julien Boudry](/maintainers/julien-boudry)[@julien-boudry](https://github.com/julien-boudry)

---

Top Contributors

[![julien-boudry](https://avatars.githubusercontent.com/u/4020317?v=4)](https://github.com/julien-boudry "julien-boudry (19 commits)")

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/condorcet-vote-cef-writer/health.svg)

```
[![Health](https://phpackages.com/badges/condorcet-vote-cef-writer/health.svg)](https://phpackages.com/packages/condorcet-vote-cef-writer)
```

###  Alternatives

[yunwuxin/think-cron

计划任务

16916.9k](/packages/yunwuxin-think-cron)[swissup/module-marketplace

One-Click modules and themes downloader/installer

18398.5k3](/packages/swissup-module-marketplace)[hiromi2424/transition

Transition component is a CakePHP component to help your transitional pages logic.

4561.9k](/packages/hiromi2424-transition)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
