denprog / river-flow
Functional utilities for PHP 8.5: lazy Pipes, mbstring-aware Strings, and composition Utils with pipe operator support.
Installs: 2
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 0
Forks: 0
Open Issues: 7
pkg:composer/denprog/river-flow
Requires
- php: >=8.5
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.85.1
- pestphp/pest: ^4.0
- phpstan/phpstan: ^2.1.22
- phpstan/phpstan-strict-rules: ^2.0
- rector/rector: ^2.1.2
- spaze/phpstan-disallowed-calls: ^4.6.0
- dev-main
- v0.3.0
- v0.2.6
- v0.2.5
- v0.2.4
- v0.2.3
- v0.2.2
- v0.2.1
- v0.2.0
- v0.1.2
- v0.1.1
- v0.1.0
- dev-dependabot/github_actions/actions/upload-artifact-6
- dev-dependabot/github_actions/actions/checkout-6
- dev-dependabot/composer/pestphp/pest-4.1.3
- dev-dependabot/composer/rector/rector-2.2.7
- dev-dependabot/github_actions/actions/upload-artifact-5
- dev-dependabot/composer/friendsofphp/php-cs-fixer-3.89.1
- dev-dependabot/composer/phpstan/phpstan-2.1.31
- dev-dependabot/composer/phpstan/phpstan-strict-rules-2.0.7
This package is auto-updated.
Last update: 2025-12-15 10:22:05 UTC
README
Modern, strictly-typed functional utilities for PHP 8.5: lazy collection pipelines (Pipes), mbstring‑aware string helpers (Strings), and ergonomic composition tools (Utils). Designed for the PHP 8.5 pipe operator |> and built with rigorous QA (Pest, PHPStan max, Rector, CS).
Highlights
- Pipe operator first: idiomatic
|>pipelines, no external wrappers - Lazy + eager: predictable key behavior, memory‑friendly where it matters
- Strong typing: precise PHPDoc generics, PHPStan at max level
- Unicode aware:
Stringsuse mbstring when available - Ergonomics:
tap,identity,compose,pipe - Cross‑platform CI: Linux/macOS/Windows
Requirements
- PHP >= 8.5
- Composer 2
Install
composer require denprog/river-flow
Quickstart
<?php declare(strict_types=1); use function Denprog\RiverFlow\Pipes\{map, filter, toList}; use function Denprog\RiverFlow\Strings\{trim, toUpperCase}; $result = [10, 15, 20, 25, 30] |> filter(fn (int $n) => $n % 2 === 0) // [10, 20, 30] |> map(fn (int $n) => $n / 10) // [1, 2, 3] |> toList(); // [1, 2, 3] $text = " river flow " |> trim() |> toUpperCase(); // "RIVER FLOW"
Dual‑mode usage (direct and pipe‑friendly)
- Direct: pass data as the first argument, e.g.
toList([1,2,3]) - Curried / pipe‑friendly: call a function without the data argument to get a callable, then chain with
|>
use function Denprog\RiverFlow\Pipes\{map, filter, toList}; $res1 = toList(map(filter([1,2,3,4], fn($x)=>$x%2===0), fn($x)=>$x*10))); // [20, 40] $res2 = [1,2,3,4] |> filter(fn($x) => $x % 2 === 0) |> map(fn($x) => $x * 10) |> toList(); // [20, 40]
Other composition helpers (non‑pipe)
In addition to the |> operator, RiverFlow provides classic composition utilities in Utils which do not require pipes.
use function Denprog\RiverFlow\Utils\{compose, pipe}; $sum = fn (int $a, int $b): int => $a + $b; // right‑most may be variadic $inc = fn (int $x): int => $x + 1; $dbl = fn (int $x): int => $x * 2; $f = compose($dbl, $inc, $sum); // dbl(inc(sum(a,b))) assert($f(3, 4) === 16); $out = pipe(5, fn($x) => $x + 3, fn($x) => $x * 2, 'strval'); assert($out === '16');
Module snapshots
Pipes
use function Denprog\RiverFlow\Pipes\{filter, map, take, toList, toArray, flatten, uniq, groupBy, values, sortBy, zipWith, range, repeat, times, tail, init, scan, scanRight, partitionBy, distinctUntilChanged, intersperse, pairwise, countBy}; // Transform → filter → take → materialize $topSquares = [1,2,3,4,5,6,7,8,9] |> map(fn (int $n) => $n * $n) |> filter(fn (int $x) => $x % 2 === 0) |> take(3) |> toList(); // [4, 16, 36] // Flatten nested, uniquify $flatUnique = [[1,2], [2,3, [3,4]], 4] |> flatten(2) |> uniq() |> toList(); // [1,2,3,4] // Group, traverse group values and sort by size $byFirstLetter = ['apple','apricot','banana','blueberry','avocado'] |> groupBy(fn (string $s) => $s[0]) |> values() |> map(fn (array $xs) => $xs) |> toList() |> sortBy(fn (array $xs) => \count($xs)); // Zip in pipelines $zipped = [1, 2, 3] |> zipWith(['a','b'], ['X','Y','Z']) |> toList(); // [[1,'a','X'], [2,'b','Y']] // Numeric ranges and generation $nums = range(0, 5) |> toList(); // [0,1,2,3,4] // Infinite repetition capped with take $threes = repeat(3) |> take(4) |> toList(); // [3,3,3,3] // Produce values by index $squares = times(5, fn (int $i): int => $i * $i) |> toList(); // [0,1,4,9,16] $rest = ['a'=>1,'b'=>2,'c'=>3] |> tail() |> toArray(); // ['b'=>2,'c'=>3] $allButLast = ['x'=>10,'y'=>20,'z'=>30] |> init() |> toArray(); // ['x'=>10,'y'=>20] // Inclusive prefix and suffix scans (lazy, keys preserved) $prefix = [1, 2, 3, 4] |> scan(fn (?int $c, int $v) => ($c ?? 0) + $v, 0) |> toList(); // [1, 3, 6, 10] $suffix = [1, 2, 3] |> scanRight(fn (?int $c, int $v) => ($c ?? 0) + $v, 0) |> toList(); // [6, 5, 3] // Partition into contiguous groups by discriminator (lazy) $groups = ['ant', 'apple', 'bear', 'bob', 'cat'] |> partitionBy(fn (string $s) => $s[0]) |> toList(); // [[0=>'ant',1=>'apple'], [2=>'bear',3=>'bob'], [4=>'cat']] // Skip consecutive duplicates (preserves first keys of runs) $d = ['ant', 'apple', 'bear', 'bob', 'cat'] |> distinctUntilChanged(fn (string $s) => $s[0]) |> toArray(); // [0=>'ant', 2=>'bear', 4=>'cat'] // Intersperse a separator (keys discarded) $withPipes = ['a','b','c'] |> intersperse('|') |> toList(); // ['a','|','b','|','c'] // Pairwise (consecutive pairs) $pairs = [1,2,3] |> pairwise() |> toList(); // [[1,2],[2,3]] // Count by classifier (eager) $counts = ['apple','apricot','banana','blueberry','avocado'] |> countBy(fn (string $s) => $s[0]); // ['a' => 3, 'b' => 2]
Strings
use function Denprog\RiverFlow\Strings\{trim, replacePrefix, toLowerCase, toUpperCase, split, join, length}; $title = " River FLOW: Intro " |> trim() |> toLowerCase() |> replacePrefix('river ', 'river '); // "river flow: intro" $csv = ' foo | Bar |BAZ ' |> trim() |> toLowerCase() |> split('|') |> join(','); // "foo , bar ,baz" $n = ' Hello ' |> trim() |> toUpperCase() |> length(); // 5
Utils
use function Denprog\RiverFlow\Utils\{tap, identity}; use function Denprog\RiverFlow\Strings\{trim, toUpperCase}; $result = ' Hello ' |> trim() |> tap(fn (string $s) => error_log("after trim: $s")) |> toUpperCase() |> tap(fn (string $s) => error_log("after upper: $s")); // 'HELLO' $val = 10 |> identity() |> (fn (int $x) => $x + 5) |> (fn (int $x) => $x * 2); // 30
Documentation
Development
composer install # QA composer test # Pest composer analyse # PHPStan (max) composer cs:lint # PHP-CS-Fixer (dry-run) composer rector:check # Rector (dry-run) composer cs:fix # Apply PHP-CS-Fixer fixes composer rector:fix # Apply Rector refactors
Security
See SECURITY.md for our vulnerability disclosure policy.
Contributing
See CONTRIBUTING.md for setup, standards (PSR-12), and PR process.
Code of Conduct
This project adheres to the Contributor Covenant. See CODE_OF_CONDUCT.md.
Versioning & Changelog
We follow SemVer. See CHANGELOG.md for release notes.
License
MIT — see LICENSE.