All functions live in the Stann\Stream namespace.
use function Stann\Stream\{filter, map, take, toArray};Every transformation follows the same pattern: takes configuration, returns a Closure that accepts an iterable. The pipe operator does the wiring.
data |> transform(config) |> transform(config) |> terminator(config)
All transformations return Closure(iterable): iterable. They are lazy (Generator-based) unless noted as blocking.
map(callable $fn): ClosureApplies $fn to each element. Receives ($value, $key). Preserves keys.
[1, 2, 3] |> map(fn(int $n) => $n * 10) |> toArray();
// [10, 20, 30]
['a' => 1, 'b' => 2] |> map(fn(int $v, string $k) => "{$k}:{$v}") |> toArray();
// ['a' => 'a:1', 'b' => 'b:2']filter(?callable $fn = null): ClosureKeeps elements where $fn returns true. Receives ($value, $key).
Without callback, removes falsy values (null, false, 0, "", []) — like array_filter().
[1, 2, 3, 4, 5] |> filter(fn(int $n) => $n > 3) |> toArray();
// [4, 5]
[1, 0, null, '', false, 2] |> filter() |> toArray();
// [1, 2]flatMap(callable $fn): ClosureMaps each element then flattens one level. $fn must return an iterable.
[[1, 2], [3, 4]] |> flatMap(fn(array $a) => $a) |> toArray();
// [1, 2, 3, 4]
[1, 2, 3] |> flatMap(fn(int $n) => [$n, $n * 10]) |> toArray();
// [1, 10, 2, 20, 3, 30]flatten(): ClosureFlattens one level of nested iterables. Scalars pass through untouched.
[[1, 2], [3], 4] |> flatten() |> toArray();
// [1, 2, 3, 4]take(int $n): ClosureTakes the first $n elements. Lazy — stops consuming the source after $n.
[1, 2, 3, 4, 5] |> take(3) |> toArray();
// [1, 2, 3]takeWhile(callable $fn): ClosureTakes elements as long as the predicate returns true. Stops at the first failure.
[1, 2, 3, 4, 5] |> takeWhile(fn(int $n) => $n < 4) |> toArray();
// [1, 2, 3]skip(int $n): ClosureSkips the first $n elements.
[1, 2, 3, 4, 5] |> skip(2) |> toArray();
// [3, 4, 5]skipWhile(callable $fn): ClosureSkips elements as long as the predicate returns true. Yields all remaining once it fails.
[1, 2, 3, 4, 5] |> skipWhile(fn(int $n) => $n < 3) |> toArray();
// [3, 4, 5]chunk(int $size): ClosureSplits into chunks of the given size. Yields arrays.
[1, 2, 3, 4, 5] |> chunk(2) |> toArray();
// [[1, 2], [3, 4], [5]]groupBy(callable $fn): ClosureGroups elements by the return value of $fn. Returns an associative array.
Blocking — must consume all elements.
$users |> groupBy(fn(User $u) => $u->country);
// ['FR' => [User, User], 'US' => [User]]sortBy(callable $fn, string $direction = 'asc'): ClosureSorts elements by the return value of $fn.
Blocking — must consume all elements.
[3, 1, 4, 1, 5] |> sortBy(fn(int $n) => $n);
// [1, 1, 3, 4, 5]
[3, 1, 5] |> sortBy(fn(int $n) => $n, 'desc');
// [5, 3, 1]unique(?callable $fn = null): ClosureRemoves duplicates. Optionally deduplicates by a key function.
[1, 2, 2, 3, 3] |> unique() |> toArray();
// [1, 2, 3]
$users |> unique(fn(User $u) => $u->email) |> toArray();zip(iterable $other): ClosureCombines two iterables element-by-element into [$a, $b] pairs. Stops at the shorter one.
[1, 2, 3] |> zip(['a', 'b', 'c']) |> toArray();
// [[1, 'a'], [2, 'b'], [3, 'c']]concat(iterable $other): ClosureAppends another iterable after the current one.
[1, 2, 3] |> concat([4, 5, 6]) |> toArray();
// [1, 2, 3, 4, 5, 6]enumerate(): ClosurePairs each element with its zero-based index. Yields [index, value] tuples.
['a', 'b', 'c'] |> enumerate() |> toArray();
// [[0, 'a'], [1, 'b'], [2, 'c']]scan(callable $fn, mixed $initial): ClosureLike reduce, but yields each intermediate accumulator value. Also called "running fold".
[1, 2, 3, 4] |> scan(fn(int $acc, int $v) => $acc + $v, 0) |> toArray();
// [1, 3, 6, 10]reverse(): ClosureReverses elements.
Blocking — must consume all elements.
[1, 2, 3] |> reverse();
// [3, 2, 1]keys(): ClosureYields keys from the iterable.
['a' => 1, 'b' => 2] |> keys() |> toArray();
// ['a', 'b']values(): ClosureYields values, discarding keys.
['a' => 1, 'b' => 2] |> values() |> toArray();
// [1, 2]pluck(string|int $key): ClosureExtracts a single property/key from each element. Works with arrays, ArrayAccess, and objects.
$users |> pluck('name') |> toArray();
// ['Alice', 'Bob', 'Charlie']tap(callable $fn): ClosureExecutes a side effect on each element without altering the stream. Lazy — nothing happens until consumed by a terminator.
$users
|> tap(fn(User $u) => $logger->info($u->name))
|> map(fn(User $u) => $u->email)
|> toArray();Terminators consume the iterable and return a final value (or void). They trigger evaluation of the entire lazy chain.
toArray(): ClosureConverts the iterable to an array. Preserves string keys, re-indexes integer keys.
[1, 2, 3] |> map(fn(int $n) => $n * 2) |> toArray();
// [2, 4, 6]reduce(callable $fn, mixed $initial = null): ClosureReduces to a single value. $fn receives ($accumulator, $value).
[1, 2, 3, 4] |> reduce(fn(int $acc, int $v) => $acc + $v, 0);
// 10first(?callable $predicate = null): ClosureReturns the first element, optionally matching a predicate. Returns null if empty.
[1, 2, 3] |> first();
// 1
[1, 2, 3, 4] |> first(fn(int $n) => $n > 2);
// 3last(?callable $predicate = null): ClosureReturns the last element, optionally matching a predicate. Returns null if empty.
[1, 2, 3] |> last();
// 3count(): ClosureCounts elements.
[1, 2, 3] |> count();
// 3sum(?callable $fn = null): ClosureSums elements, optionally extracting a numeric value.
[1, 2, 3] |> sum();
// 6
$orders |> sum(fn(Order $o) => $o->total);
// 1250.50min(?callable $fn = null): ClosureReturns the minimum element, optionally by a key function. Returns null on empty.
[3, 1, 4, 1, 5] |> min();
// 1
$users |> min(fn(User $u) => $u->age);
// User with lowest agemax(?callable $fn = null): ClosureReturns the maximum element, optionally by a key function. Returns null on empty.
[3, 1, 4, 1, 5] |> max();
// 5join(string $separator = ', '): ClosureJoins elements into a string.
['a', 'b', 'c'] |> join(' - ');
// 'a - b - c'contains(mixed $needle): ClosureChecks if the collection contains a value (strict comparison).
[1, 2, 3] |> contains(2);
// trueevery(callable $fn): ClosureReturns true if all elements satisfy the predicate. Short-circuits on first failure.
[2, 4, 6] |> every(fn(int $n) => $n % 2 === 0);
// truesome(callable $fn): ClosureReturns true if at least one element satisfies the predicate. Short-circuits on first match.
[1, 2, 3] |> some(fn(int $n) => $n === 2);
// truepartition(callable $fn): ClosureSplits elements into two arrays based on a predicate. Returns [matching, notMatching].
[$even, $odd] = [1, 2, 3, 4, 5] |> partition(fn(int $n) => $n % 2 === 0);
// $even = [2, 4], $odd = [1, 3, 5]each(callable $fn): ClosureConsumes the iterable, executing $fn on each element. Triggers evaluation of the entire lazy chain. Returns void.
$notifications
|> filter(fn(Notif $n) => !$n->isSent())
|> each(fn(Notif $n) => $mailer->send($n));