PHPackages                             dseguy/genie - 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. [Testing &amp; Quality](/categories/testing)
4. /
5. dseguy/genie

ActiveLibrary[Testing &amp; Quality](/categories/testing)

dseguy/genie
============

Systematic value generation for PHP: lazy, composable, fluent.

v0.5.0(1mo ago)20MITPHPPHP ^8.2

Since Jun 1Pushed 1mo agoCompare

[ Source](https://github.com/dseguy/genie)[ Packagist](https://packagist.org/packages/dseguy/genie)[ RSS](/packages/dseguy-genie/feed)WikiDiscussions main Synced 1w ago

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

genie — Systematic Value Generation for PHP
===========================================

[](#genie--systematic-value-generation-for-php)

A PHP 8.2+ library for generating all values in a domain using lazy, composable iterators.

**Use case:** when code needs to walk an entire value domain — all letters, all digit pairs, all 3-character strings — this library provides the building blocks to express that concisely and memory-efficiently.

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

[](#installation)

```
composer require exakat/generator
```

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

[](#requirements)

- PHP 8.2+
- No runtime dependencies

---

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

[](#quick-start)

```
use Dseguy\Generator\Letters;
use Dseguy\Generator\Digits;

foreach (Letters::lower()->merge(Digits::all()) as $value) {
    // a, b, …, z, 0, 1, …, 9
}
```

---

Primitive generators
--------------------

[](#primitive-generators)

Each primitive is a class with static factory methods. All generators are **lazy** — values are computed only when iterated.

### `Letters`

[](#letters)

Yields individual alphabetic characters.

Factory methodYields`Letters::lower()``a` … `z` (26 values)`Letters::upper()``A` … `Z` (26 values)`Letters::all()``a` … `z` then `A` … `Z` (52 values)```
foreach (Letters::upper() as $c) {
    // A, B, C, …, Z
}
```

### `Digits`

[](#digits)

Yields integers over a bounded range.

Factory methodYields`Digits::all()``0` … `9``Digits::range(int $start, int $end, int $step = 1)`integers from `$start` to `$end` inclusive, stepping by `$step````
foreach (Digits::range(1, 10, 2) as $n) {
    // 1, 3, 5, 7, 9
}
```

Throws `InvalidArgumentException` if `$start > $end` or `$step product(Booleans::values())
// yields: [1, true], [1, false], [2, true], …
```

### `FromCallable`

[](#fromcallable)

Wraps a **factory callable** that returns a `\Traversable` (generator, iterator, or iterator aggregate). The factory is invoked fresh on every iteration, making the source fully reusable and safe to use with all combinators.

Factory methodAcceptsYields`FromCallable::of(callable $factory)``callable(): \Traversable`every value produced by the traversable, keys discarded```
use Dseguy\Generator\FromCallable;

// Wrap a generator function
$source = FromCallable::of(fn() => (function () {
    yield 'x';
    yield 'y';
    yield 'z';
})());

// Wrap a database cursor or any lazy iterator
$rows = FromCallable::of(fn() => $db->query('SELECT id FROM items'));
foreach ($rows->filter(fn($row) => $row['id'] > 100) as $row) { … }
```

> **Warning — infinite sources:** a factory that yields forever is valid with `filter()`, `map()`, and `merge()`, but will loop forever or exhaust memory inside `product()` or `repeat()`. There is currently no way to detect an infinite source at construction time; support for this is planned in a future version.

### `Permutations`

[](#permutations)

Yields all ordered arrangements of `$length` **distinct** characters drawn from a charset. No character repeats within a single value.

Factory methodYields`Permutations::of(int $length, string $charset)`All permutations of exactly `$length` distinct chars from `$charset````
foreach (Permutations::of(2, 'abc') as $s) {
    // 'ab', 'ac', 'ba', 'bc', 'ca', 'cb' — 6 values
}

// All 2-char permutations over the alphabet: 26×25 = 650 values
$codes = Permutations::of(2, 'abcdefghijklmnopqrstuvwxyz');
```

Throws `InvalidArgumentException` if `$length merge(GeneratorInterface ...$others)` — Union

[](#-mergegeneratorinterface-others--union)

Chains generators end-to-end into a single sequence.

```
Letters::lower()->merge(Digits::all())
// yields: a, b, …, z, 0, 1, …, 9

Letters::lower()->merge(Letters::upper(), Digits::all())
// yields: a, …, z, A, …, Z, 0, …, 9
```

### `->product(GeneratorInterface ...$others)` — Cartesian product

[](#-productgeneratorinterface-others--cartesian-product)

Yields all combinations as flat arrays (one element per source generator).

```
Letters::upper()->product(Digits::all())
// yields: ['A', 0], ['A', 1], …, ['Z', 9] — 260 tuples

Letters::lower()->product(Digits::all())->product(Booleans::values())
// yields: ['a', 0, true], ['a', 0, false], ['a', 1, true], …
```

Arrays from nested `product()` or `repeat()` calls are **flattened** — values spread into the result rather than nest.

### `->filter(callable $predicate)` — Conditional filter

[](#-filtercallable-predicate--conditional-filter)

Excludes values for which `$predicate` returns falsy.

```
Digits::range(1, 100)->filter(fn($n) => $n % 2 === 0)
// yields: 2, 4, 6, …, 100

Letters::lower()->product(Digits::all())
    ->filter(fn($pair) => $pair[1] > 5)
// yields: ['a', 6], ['a', 7], …, ['z', 9]
```

### `->map(callable $transform)` — Transform

[](#-mapcallable-transform--transform)

Applies a function to every yielded value.

```
Letters::lower()->map(fn($c) => strtoupper($c))
// yields: A, B, …, Z

Letters::lower()->product(Digits::all())
    ->map(fn($pair) => implode('', $pair))
// yields: 'a0', 'a1', …, 'z9'
```

### `->repeat(int $n)` — Power (G^n)

[](#-repeatint-n--power-gn)

Yields all `$n`-length sequences, with repetition allowed.

```
Letters::lower()->repeat(2)
// yields: ['a','a'], ['a','b'], …, ['z','z'] — 676 tuples

Digits::all()->repeat(3)
// yields: [0,0,0], [0,0,1], …, [9,9,9] — 1000 tuples
```

Each yielded value is a flat array. Throws `InvalidArgumentException` if `$n  **`repeat()` vs `Permutations`:** `repeat()` allows the same value to appear multiple times in one tuple; `Permutations` does not.

---

Pragmatic usage examples
------------------------

[](#pragmatic-usage-examples)

Real-world scenarios where systematic value generation is useful.

### Password / token brute-force testing

[](#password--token-brute-force-testing)

Generate all possible PIN codes or short alphanumeric tokens to verify a rate-limiter or lockout policy in tests.

```
// All 4-digit PINs: 10^4 = 10 000 values
$pins = Digits::all()->repeat(4)
    ->map(fn($d) => implode('', $d));

foreach ($pins as $pin) {
    $response = $client->post('/login', ['pin' => $pin]);
    if ($response->status() === 429) {
        // lockout triggered — stop here
        break;
    }
}
```

```
// All 6-character alphanumeric tokens (case-insensitive)
$tokens = Letters::lower()->merge(Digits::all())->repeat(6)
    ->map(fn($chars) => implode('', $chars));
```

### Exhaustive unit test data providers

[](#exhaustive-unit-test-data-providers)

Feed a PHPUnit data provider with every combination of inputs rather than hand-picking a few.

```
// Test a validator against every (letter, digit) pair
public static function letterDigitPairs(): iterable
{
    return Letters::lower()->product(Digits::all())
        ->map(fn($pair) => [$pair[0], $pair[1]]);
}

/** @dataProvider letterDigitPairs */
public function test_accepts_alphanumeric(string $letter, int $digit): void
{
    $this->assertTrue(Validator::isAlphanumeric($letter . $digit));
}
```

```
// Test a function against all boolean/null combinations
public static function truthyInputs(): iterable
{
    return Booleans::withNull()->map(fn($b) => [$b]);
}
```

### Generating test fixtures

[](#generating-test-fixtures)

Populate a database or in-memory store with a systematic set of records.

```
// One user per letter of the alphabet
foreach (Letters::lower() as $initial) {
    User::factory()->create(['username' => "user_{$initial}"]);
}
```

```
// All combinations of role × status for permission matrix tests
$roles    = Collection::of(['admin', 'editor', 'viewer']);
$statuses = Collection::of(['active', 'suspended', 'pending']);

foreach ($roles->product($statuses) as [$role, $status]) {
    // test every role × status combination
}
```

### Exhaustive enum coverage in tests

[](#exhaustive-enum-coverage-in-tests)

Iterate every case of an enum in a data provider so no case is accidentally omitted from test coverage.

```
enum Status: string { case Active = 'active'; case Suspended = 'suspended'; case Pending = 'pending'; }

public static function allStatuses(): iterable
{
    return Enums::cases(Status::class)->map(fn($s) => [$s]);
}

/** @dataProvider allStatuses */
public function test_notification_is_sent_for_every_status(Status $status): void
{
    $this->assertTrue(Notifier::shouldNotify($status));
}
```

```
// Verify a mapping covers every backing value
foreach (Enums::values(Status::class) as $value) {
    $this->assertArrayHasKey($value, StatusLabel::MAP);
}
```

### Slug / identifier collision detection

[](#slug--identifier-collision-detection)

Verify that a slug generator produces unique output across all inputs.

```
$seen = [];
foreach (Letters::lower()->repeat(3)->map(fn($t) => implode('', $t)) as $trigram) {
    $slug = MySlugifier::slugify($trigram);
    assert(!isset($seen[$slug]), "Collision on: $trigram → $slug");
    $seen[$slug] = true;
}
```

### Scanning configuration ranges

[](#scanning-configuration-ranges)

Walk every valid port in a range, every HTTP status code, or every timeout value to verify system behaviour.

```
// Check that all privileged ports are refused
$privileged = Digits::range(1, 1023)->filter(fn($p) => !in_array($p, $allowList));
foreach ($privileged as $port) {
    $this->assertFalse($server->canBind($port));
}
```

```
// Verify all 5xx codes are handled
$serverErrors = Digits::range(500, 599);
foreach ($serverErrors as $code) {
    $this->assertInstanceOf(ServerException::class, $handler->handle($code));
}
```

### Character set validation

[](#character-set-validation)

Probe a sanitiser or encoder against every character in a defined alphabet.

```
// Every character that must survive HTML encoding unchanged
$safe = Letters::all()->merge(Digits::all());
foreach ($safe as $char) {
    $this->assertSame($char, htmlspecialchars($char));
}
```

```
// Detect which characters a legacy system rejects
$rejected = [];
foreach (Letters::all()->merge(Digits::all()) as $char) {
    if (!$legacySystem->accepts($char)) {
        $rejected[] = $char;
    }
}
```

### Generating SQL / query permutations

[](#generating-sql--query-permutations)

Build every variant of a parameterised query for fuzz-style integration tests.

```
// All ORDER BY direction × LIMIT combinations
$directions = ['ASC', 'DESC'];
$limits     = Digits::range(1, 5);   // 1 … 5

foreach ($limits as $limit) {
    foreach ($directions as $dir) {
        $results = $db->query("SELECT * FROM items ORDER BY name $dir LIMIT $limit");
        $this->assertCount($limit, $results);
    }
}
```

---

Terminal method
---------------

[](#terminal-method)

After composing a chain, call `->toIterator()` to obtain a plain `\Iterator` that can be assigned to a variable and passed around.

```
$candidates = Letters::lower()->merge(Digits::all())->toIterator();

foreach ($candidates as $value) {
    // a, b, …, z, 0, 1, …, 9
}
```

All generators also implement `\IteratorAggregate`, so they can be used directly in `foreach` without calling `->toIterator()`.

---

Composing chains
----------------

[](#composing-chains)

Combinators compose freely. Each step returns a new generator.

```
use Dseguy\Generator\Letters;
use Dseguy\Generator\Digits;
use Dseguy\Generator\Booleans;
use Dseguy\Generator\Permutations;

// All lowercase alphanumeric characters
$alphanumeric = Letters::lower()->merge(Digits::all());

// All uppercase letter + single digit pairs
$pairs = Letters::upper()->product(Digits::all());
foreach ($pairs as [$letter, $digit]) { … }

// All 3-letter lowercase trigrams (26^3 = 17 576 values)
$trigrams = Letters::lower()->repeat(3);
foreach ($trigrams as [$a, $b, $c]) { … }

// Even numbers from 1 to 50
$evens = Digits::range(1, 50)->filter(fn($n) => $n % 2 === 0);

// Letters mapped to their ASCII code
$ascii = Letters::all()->map(fn($c) => ord($c));

// All 2-char permutations over a-z, formatted as strings
$codes = Permutations::of(2, 'abcdefghijklmnopqrstuvwxyz')
    ->map(fn($s) => strtoupper($s));
```

---

API reference
-------------

[](#api-reference)

### Namespace

[](#namespace)

```
use Dseguy\Generator\Letters;
use Dseguy\Generator\Digits;
use Dseguy\Generator\Booleans;
use Dseguy\Generator\Enums;
use Dseguy\Generator\Collection;
use Dseguy\Generator\FromCallable;
use Dseguy\Generator\Permutations;
```

### Primitive generators

[](#primitive-generators-1)

ClassFactory methods`Letters``lower()`, `upper()`, `all()``Digits``all()`, `range(int $start, int $end, int $step = 1)``Booleans``values()`, `withNull()``Enums``cases(string $class)`, `names(string $class)`, `values(string $class)``Collection``of(array $items)``FromCallable``of(callable $factory)``Permutations``of(int $length, string $charset)`### `GeneratorInterface`

[](#generatorinterface)

All generators implement `GeneratorInterface`, which extends `\IteratorAggregate`.

MethodReturnsDescription`getIterator()``\Generator`Yields domain values`merge(...$others)``GeneratorInterface`Chain generators end-to-end`product(...$others)``GeneratorInterface`Cartesian product`filter($predicate)``GeneratorInterface`Filter by predicate`map($transform)``GeneratorInterface`Transform each value`repeat(int $n)``GeneratorInterface`All n-length sequences`toIterator()``\Iterator`Materialise the chain---

Design principles
-----------------

[](#design-principles)

- **Lazy by default** — every generator uses PHP `yield`; no value is computed until iterated.
- **Composable** — any generator can be used as input to any combinator.
- **Fluent API** — combinators chain directly on generator objects.
- **Deterministic** — fixed iteration order; generators can be re-iterated to produce the same sequence.
- **Fail fast** — invalid constructor arguments throw `InvalidArgumentException` immediately, never produce silent empty sequences.
- **No runtime dependencies** — pure PHP; only dev dependencies (PHPUnit, PHPStan).

---

Out of scope (v1)
-----------------

[](#out-of-scope-v1)

- Random / shuffled generation
- Weighted generators
- CSV / file-based value sources
- Output adapters (`->toArray()`, `->toCsv()`, `->echo()`)
- Framework integrations (Laravel, Symfony)

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance92

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity40

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 88.9% 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

Every ~4 days

Total

5

Last Release

37d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/15652256?v=4)[Exakat](/maintainers/exakat)[@exakat](https://github.com/exakat)

---

Top Contributors

[![christopheexakat](https://avatars.githubusercontent.com/u/33156062?v=4)](https://github.com/christopheexakat "christopheexakat (8 commits)")[![dseguy](https://avatars.githubusercontent.com/u/170418?v=4)](https://github.com/dseguy "dseguy (1 commits)")

---

Tags

testinggeneratoriteratorcombinatoricsvalues

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/dseguy-genie/health.svg)

```
[![Health](https://phpackages.com/badges/dseguy-genie/health.svg)](https://phpackages.com/packages/dseguy-genie)
```

###  Alternatives

[phpunit/phpunit

The PHP Unit Testing framework.

20.0k955.1M159.1k](/packages/phpunit-phpunit)[phpunit/php-code-coverage

Library that provides collection, processing, and rendering functionality for PHP code coverage information.

8.9k935.9M1.7k](/packages/phpunit-php-code-coverage)[mockery/mockery

Mockery is a simple yet flexible PHP mock object framework

10.7k526.2M27.5k](/packages/mockery-mockery)[phpunit/php-file-iterator

FilterIterator implementation that filters files based on a list of suffixes.

7.5k922.4M89](/packages/phpunit-php-file-iterator)[behat/behat

Scenario-oriented BDD framework for PHP

4.0k101.8M2.3k](/packages/behat-behat)[behat/mink

Browser controller/emulator abstraction for PHP

1.6k90.6M699](/packages/behat-mink)

PHPackages © 2026

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