PHPackages                             stann/stream - 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. stann/stream

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

stann/stream
============

Pipe-native stream processing for PHP 8.5+ — lazy, curried, zero-dependency collection functions designed for the |&gt; pipe operator.

v1.1.2(3mo ago)21.7k↑285.7%MITPHPPHP &gt;=8.5CI passing

Since Feb 19Pushed 3mo agoCompare

[ Source](https://github.com/stannapp/stream-php)[ Packagist](https://packagist.org/packages/stann/stream)[ RSS](/packages/stann-stream/feed)WikiDiscussions main Synced 2d ago

READMEChangelog (4)Dependencies (6)Versions (5)Used By (0)

Stream
======

[](#stream)

Pipe-native stream functions for PHP 8.5+. Curried, lazy, zero-wrapper.

```
use function Stann\Stream\{filter, map, take, toArray};

$result = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    |> filter(fn(int $n) => $n % 2 === 0)
    |> map(fn(int $n) => $n * 10)
    |> take(3)
    |> toArray();

// [20, 40, 60]
```

Philosophy
----------

[](#philosophy)

No `Collection` object. No method chaining. Just **pure functions** designed for the PHP 8.5 pipe operator (`|>`).

Each function is **curried**: it takes its configuration and returns a `Closure` that accepts an `iterable`. The pipe operator does the wiring.

```
data |> transform(config) |> transform(config) |> terminator(config)

```

- **Data-last** — like Ramda (JS) or Elixir pipes
- **Lazy by default** — transformations return Generators, nothing executes until consumed
- **Zero dependency** — just PHP 8.5

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

[](#installation)

```
composer require stann/stream
```

Requires PHP 8.5+ (for the pipe operator `|>`).

Quick Start
-----------

[](#quick-start)

```
use function Stann\Stream\{filter, map, take, toArray};

// Simple pipeline
$emails = $users
    |> filter(fn(User $u) => $u->isActive())
    |> map(fn(User $u) => $u->email)
    |> map(trim(...))
    |> toArray();

// Lazy evaluation — only 100 elements processed, not all
$result = $hugeDataset
    |> filter(fn($row) => $row['status'] === 'ok')
    |> map(fn($row) => transform($row))
    |> take(100)
    |> toArray();
```

API
---

[](#api)

Full documentation with signatures and examples: **[docs/API.md](docs/API.md)**

### Transformations

[](#transformations)

Lazy (Generator-based) unless noted as blocking.

- [`map`](docs/API.md#map) — Apply a callback to each element
- [`filter`](docs/API.md#filter) — Keep elements matching a predicate (without callback: remove falsy values)
- [`flatMap`](docs/API.md#flatmap) — Map then flatten one level
- [`flatten`](docs/API.md#flatten) — Flatten one level of nested iterables
- [`take`](docs/API.md#take) — Take the first N elements
- [`takeWhile`](docs/API.md#takewhile) — Take while predicate holds
- [`skip`](docs/API.md#skip) — Skip the first N elements
- [`skipWhile`](docs/API.md#skipwhile) — Skip while predicate holds
- [`chunk`](docs/API.md#chunk) — Split into fixed-size chunks
- [`groupBy`](docs/API.md#groupby) — Group by key function *(blocking)*
- [`sortBy`](docs/API.md#sortby) — Sort by key function *(blocking)*
- [`unique`](docs/API.md#unique) — Remove duplicates
- [`zip`](docs/API.md#zip) — Combine two iterables into pairs
- [`concat`](docs/API.md#concat) — Append another iterable
- [`enumerate`](docs/API.md#enumerate) — Pair elements with their index
- [`scan`](docs/API.md#scan) — Running fold (intermediate values)
- [`reverse`](docs/API.md#reverse) — Reverse elements *(blocking)*
- [`keys`](docs/API.md#keys) — Extract keys
- [`values`](docs/API.md#values) — Extract values
- [`pluck`](docs/API.md#pluck) — Extract a property/key from each element
- [`tap`](docs/API.md#tap) — Side effect without altering the stream

### Terminators

[](#terminators)

Consume the iterable and return a final value.

- [`toArray`](docs/API.md#toarray) — Convert to array
- [`reduce`](docs/API.md#reduce) — Fold into a single value
- [`first`](docs/API.md#first) — First element (optionally matching predicate)
- [`last`](docs/API.md#last) — Last element (optionally matching predicate)
- [`count`](docs/API.md#count) — Count elements
- [`sum`](docs/API.md#sum) — Sum elements
- [`min`](docs/API.md#min) — Minimum element
- [`max`](docs/API.md#max) — Maximum element
- [`join`](docs/API.md#join) — Join into a string
- [`contains`](docs/API.md#contains) — Check if value exists
- [`every`](docs/API.md#every) — All match predicate?
- [`some`](docs/API.md#some) — Any match predicate?
- [`partition`](docs/API.md#partition) — Split into two arrays by predicate
- [`each`](docs/API.md#each) — Consume with side effect (void)

Patterns
--------

[](#patterns)

### Pagination

[](#pagination)

```
$page3 = $items
    |> sortBy(fn(Item $i) => $i->name)
    |> skip(($page - 1) * $perPage)
    |> take($perPage)
    |> toArray();
```

### Batch Processing

[](#batch-processing)

```
$items
    |> chunk(50)
    |> map(fn(array $batch) => processBatch($batch))
    |> flatMap(fn(array $results) => $results)
    |> toArray();
```

### Aggregation

[](#aggregation)

```
$byCountry = $customers
    |> filter(fn(Customer $c) => $c->revenue > 1000)
    |> groupBy(fn(Customer $c) => $c->country)
    |> map(fn(array $group) => $group |> sum(fn($c) => $c->revenue));
```

### Price Calculation

[](#price-calculation)

```
$total = $prices
    |> zip($quantities)
    |> map(fn(array $pair) => $pair[0] * $pair[1])
    |> sum();
```

### Running Totals

[](#running-totals)

```
$runningTotal = $transactions
    |> map(fn(Transaction $t) => $t->amount)
    |> scan(fn(float $acc, float $v) => $acc + $v, 0.0)
    |> toArray();
```

Extending with Custom Steps
---------------------------

[](#extending-with-custom-steps)

The pipe is open by design. Any function that takes an `iterable` and returns an `iterable` (or a final value) fits right in — no interface to implement, no class to extend.

### Without parameters — use `(...)`

[](#without-parameters--use-)

```
function removeNulls(iterable $items): Generator {
    foreach ($items as $value) {
        if ($value !== null) {
            yield $value;
        }
    }
}

$data |> removeNulls(...) |> map(fn($v) => $v * 2) |> toArray();
```

### With parameters — use currying

[](#with-parameters--use-currying)

```
function olderThan(int $minAge): Closure {
    return static function (iterable $items) use ($minAge): Generator {
        foreach ($items as $user) {
            if ($user->age >= $minAge) {
                yield $user;
            }
        }
    };
}

$users
    |> olderThan(18)
    |> filter(fn(User $u) => $u->isActive())
    |> map(fn(User $u) => $u->email)
    |> map(trim(...))
    |> toArray();
```

Both approaches mix naturally with the library's functions. The only rule: the pipe expects a **single-argument callable** (`iterable → something`).

How It Works
------------

[](#how-it-works)

Every function follows the same pattern:

```
function map(callable $fn): Closure
{
    return static function (iterable $items) use ($fn): Generator {
        foreach ($items as $key => $value) {
            yield $key => $fn($value, $key);
        }
    };
}
```

1. **Takes configuration** (the callback, size, etc.)
2. **Returns a Closure** that accepts `iterable`
3. The pipe operator passes the data

This is **currying** — you fix the transformation, and the pipe injects the data.

### Lazy vs Blocking

[](#lazy-vs-blocking)

Lazy (Generator)Blocking (array)`map`, `filter`, `flatMap`, `flatten`, `take`, `takeWhile` `skip`, `skipWhile`, `chunk`, `zip`, `concat`, `enumerate` `scan`, `unique`, `keys`, `values`, `pluck`, `tap``sortBy`, `groupBy`, `reverse`Blocking operations need all data upfront (you can't sort without seeing everything). Lazy operations process elements one by one.

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

[](#development)

```
composer install
composer test          # PHPUnit
composer phpstan       # Static analysis (level 8)
composer cs-check      # Code style check
composer cs-fix        # Auto-fix code style
```

License
-------

[](#license)

MIT

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance78

Regular maintenance activity

Popularity24

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity55

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

Every ~6 days

Total

4

Last Release

117d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/647a717d67e0f2742e4cb778effba9fb33d1f762dc57e39f3955772655363267?d=identicon)[camillebaronnet](/maintainers/camillebaronnet)

---

Top Contributors

[![camillebaronnet](https://avatars.githubusercontent.com/u/1884268?v=4)](https://github.com/camillebaronnet "camillebaronnet (6 commits)")

---

Tags

collectioncurryingdata-processingfunctional-programminggeneratorgeneratorslazyphpphp85pipe-operatorstreams

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/stann-stream/health.svg)

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

###  Alternatives

[typo3/cms-setup

TYPO3 CMS Setup - Allows users to edit a limited set of options for their user profile, including preferred language, their name and email address.

179.1M143](/packages/typo3-cms-setup)

PHPackages © 2026

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