robertgdev/ds-vector-polyfill

Ds\Vector wrapper with native array fallback

Maintainers

Package info

github.com/robertgdev/ds-vector-polyfill

pkg:composer/robertgdev/ds-vector-polyfill

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-07-20 22:07 UTC

This package is auto-updated.

Last update: 2026-07-20 22:09:56 UTC


README

A Ds\Vector wrapper with native PHP array fallback — write against one API, run anywhere.

Use Case

Ds\Vector from the PHP Data Structures extension is a fast, memory-efficient sequential container. But ext-ds isn't always available: shared hosts, CI runners, and minimal Docker images often omit it.

VectorPolyfill provides the full Ds\Vector API (33 methods) through a single interface backed by Ds\Vector when the extension is loaded, or a plain PHP array when it isn't. Your code doesn't change — the backend is selected once at factory time.

Tradeoffs: Vector vs Array

Property Ds\Vector backend Array fallback
get / set / push / pop O(1) — direct buffer index O(1)
shift / unshift / insert / remove O(n) — C-level memmove O(n) — PHP array_splice
Memory for large lists ~25% less than PHP arrays Standard PHP array overhead
Type safety None (same as PHP) None
sort / sorted Native Ds implementation sort() / usort()
foreach iteration Native Traversable ArrayIterator
allocate() / capacity() Actual buffer hint (useful) capacity() = count(); allocate() is a no-op
Installation requirement pecl install ds Zero dependencies

Benchmark: 1 million random ints

Operation Ds\Vector Array
Memory (series ready) 15.34 MB 0.66 MB
Memory (peak total) 30.68 MB 40.66 MB
append 0.002 ms 0.002 ms
random access 0.006 ms 0.005 ms
first 0.001 ms 0.001 ms
last 0.001 ms 0.002 ms
remove (end) 0.002 ms 10.171 ms
sort 216.958 ms 231.515 ms
sum 2.642 ms 1.910 ms
filter (>50%) 32.620 ms 55.990 ms
map (*2) 32.438 ms 30.559 ms

The array series ready appears small because the 1M-element $values array is allocated outside the measurement window and passed to createForced, which stores it directly — no copy. The Vector path is built element-by-element via append and reports 15.34 MB. Peak memory tells the real story: Ds\Vector uses ~25% less memory (30.68 MB vs 40.66 MB).

The most dramatic difference is remove (end): removing the last element from a 1M-item Vector is O(1) (just decrements the internal size counter). On a PHP array, removing the 999,999th element requires rebuilding the internal hash table from scratch — over 10 ms vs 0.002 ms. Both backends are comparable for read operations, sort, and map. Vector pulls ahead on filter thanks to its more efficient iterator.

If you control the deployment environment, install ext-ds for the performance and memory wins. If you distribute code to others or run on constrained environments, the array fallback ensures your code works identically.

Installation

composer require robertgdev/ds-vector-polyfill

For best performance, also install the Ds extension:

pecl install ds

Quick Start

use RobertGDev\DsVectorPolyfill\VectorPolyfill;

$series = VectorPolyfill::create(['a', 'b', 'c']);

$series->append('d');
$series->first();  // 'a'
$series->last();   // 'd'
$series->get(1);   // 'b'
$series->remove(2); // 'c'
$series->contains('b'); // true

// Immutable operations return new instances
$filtered = $series->filter(fn ($v) => $v !== 'b');
$mapped   = $series->map(fn ($v) => strtoupper($v));
$merged   = $series->merge(['x', 'y']);

// Iteration
foreach ($series as $value) {
    echo $value;
}

// Countable
count($series);   // 3

// JSON
json_encode($series);  // ["a","b","d"]

Forcing a Backend

Tests (or edge cases) can force a specific backend:

use RobertGDev\DsVectorPolyfill\VectorPolyfill;

$vectorSeries = VectorPolyfill::createForced([1, 2, 3], VectorPolyfill::BACKEND_VECTOR);
$arraySeries  = VectorPolyfill::createForced([1, 2, 3], VectorPolyfill::BACKEND_ARRAY);

VectorPolyfill::create() auto-detects the best available backend.

API Reference

All methods match Ds\Vector semantics exactly. Return types, exception types, and behavior are identical across both backends.

Factory

Method Signature
create() VectorPolyfill::create(iterable $values = []): self
createForced() VectorPolyfill::createForced(iterable $values, string $backend): self

Capacity

Method Returns Description
allocate(int $capacity): void Pre-allocate memory (no-op on array)
capacity(): int int Current capacity (count() on array)

Mutation (in-place)

Method Returns Description
append(mixed $value): void Add value to end
set(int $index, mixed $value): void Overwrite value at index
insert(int $index, mixed ...$values): void Insert values at index
remove(int $index): mixed Removed value Remove and return by index
pop(): mixed Last value Remove and return last
shift(): mixed First value Remove and return first
unshift(mixed ...$values): void Prepend values
push(mixed ...$values): void Append values (see append)
apply(callable $callback): void Update all values in place
clear(): void Remove all values
reverse(): void Reverse in place
rotate(int $rotations): void Rotate by N positions
sort(?callable $comparator = null): void Sort in place

Read

Method Returns Description
get(int $index): mixed Value Throws OutOfRangeException if OOB
first(): mixed First value or null Returns null on empty
last(): mixed Last value or null Returns null on empty
count(): int Count Also implements Countable
isEmpty(): bool bool True when empty
contains(mixed ...$values): bool bool True if all values present
find(mixed $value): mixed Index or null Returns null when not found
toArray(): array Plain PHP array Returns a copy
sum(): int|float Sum Returns 0 on empty
join(?string $glue = null): string Joined string No glue = concatenation
capacity(): int Capacity

Immutable (return new instance)

All of these return a new VectorPolyfill; the original is unchanged.

Method Returns Description
copy(): self Shallow copy Independent clone
filter(?callable $callback = null): self Filtered series Null callback removes falsy
map(callable $callback): self Mapped series
slice(int $index, ?int $length = null): self Slice
sorted(?callable $comparator = null): self Sorted copy
reversed(): self Reversed copy
merge(iterable $values): self Merged series Original unchanged

Reduction

Method Signature
reduce(callable $callback, mixed $initial = null): mixed

Serialization

Method Description
jsonSerialize(): array Implements JsonSerializable — use with json_encode()

Iteration

Implements IteratorAggregate — supports foreach ($series as $value).

Interfaces implemented

  • VectorPolyfillInterface<TValue> — the full API contract
  • IteratorAggregate — foreach iteration
  • Countablecount()
  • JsonSerializablejson_encode()

Exception Policy

Exception Thrown by
OutOfRangeException get(), set(), remove(), insert() with invalid index
RuntimeException pop(), shift() on empty (array backend)
UnderflowException pop(), shift() on empty (vector backend; wrapped to match)

PHP Version

Requires PHP >= 8.3.

License

MIT