Generic, immutable, variance-aware collections for PHP, built on xphp monomorphization — real generics with declaration-site variance, compiled to concrete PHP classes.
Read-only interfaces (covariant in their element type, contravariant for Comparator):
| Interface | Description |
|---|---|
Collection<out E> |
A read-only collection (extends IteratorAggregate, Countable). |
OrderedCollection<out E> |
A Collection with positional order and index access. |
Set<out E> |
A Collection with no duplicate elements. |
Map<K, out V> |
A read-only key/value mapping (invariant key, covariant value). |
Entry<K, out V> |
A read-only key/value pair — the element of a Map. |
Tuple<out A, out B> |
A fully-covariant positional 2-tuple (first()/second()); returned by partition and produced element-wise by zip. |
Comparator<in T> |
A comparison strategy (contravariant). |
Immutable concrete classes (readonly):
| Class | Implements |
|---|---|
ImmutableList<out E> |
OrderedCollection<E> |
ImmutableSet<out E> |
Set<E> (identity de-duplication) |
ImmutableMap<K, out V> |
Map<K, V> (int|string keys) |
Pair<K, out V> |
Entry<K, V> |
Couple<out A, out B> |
Tuple<A, B> (readonly; component1()/component2() destructuring) |
Types are grouped by concept under XPHP\Collections: the root Collection/Comparator, then Lists\
(including the static ImmutableGrouping/ImmutableZipping/ImmutableReducing helpers), Sets\, Maps\
(which holds Map/Entry/ImmutableMap/Pair), and Tuples\ (Tuple/Couple), plus Support\ for the
internal base. The layout is frozen and additive-only —
new tiers and data structures are added, never relocated (see ADR-0006). A mutable, invariant tier
(MutableList, …) is planned but not part of this release.
The point of the library is sound variance:
// Covariance: an ImmutableList<Book> is usable where a Collection<Product> is expected.
function total(Collection::<Product> $items): int { return $items->count(); }
$books = new ImmutableList::<Book>(new Book('PHP'), new Book('Generics'));
total($books);
// Contravariance: a Comparator<Product> is usable where a Comparator<Book> is expected.
$sorted = $books->sortedWith(new ByName()); // ByName implements Comparator<Product>, usable hereThe library ships .xphp templates. Concrete classes are generated when you compile your own code
(which supplies the concrete type arguments) together with the library, via an xphp.json manifest.
See the example in examples/Demo.xphp. Locally:
make build # compile the library templates
make demo # compile + run the example
make test/unit # behaviour tests
make ci # check + build + tests + PHPStan (level 9) + mutation (100% MSI)Covariance imposes real constraints on the API (no add/remove; element-consuming operations keep the
element type via a method-level <S : E> turbofish or a fluent Comparator<E> member, not mixed), and a
few behaviours follow from PHP and from monomorphization. Higher-order operations (map, filter, fold, …)
take typed Closure(E $x): … callbacks — a wrong closure literal is caught at xphp check, while a plain
untyped fn ($x) stays accepted.
See docs/caveats.md. Design decisions are recorded under docs/adr/.
MIT.