php-standard-library/fun
Functional programming utilities for PHP: compose and pipe callables, decorate functions, and control execution (memoize, throttle, debounce, retry, etc.). Part of PHP Standard Library with focused, reusable helpers for cleaner functional-style code.
Installation Add the package via Composer:
composer require php-standard-library/fun
No additional configuration is required—it’s a drop-in utility library.
First Use Case: Function Composition Compose two functions into one:
use Fun\Compose;
$addFive = fn($x) => $x + 5;
$multiplyByTwo = fn($x) => $x * 2;
$composed = Compose::pipe($addFive, $multiplyByTwo);
echo $composed(3); // Output: 16 ( (3 + 5) * 2 )
Key Entry Points
Fun\Compose: For composing functions (e.g., pipe, tap).Fun\Control: For flow control (e.g., try, retry).Fun\Decorators: For wrapping functions (e.g., memoize, timeout).$pipeline = Compose::pipe(
fn($user) => $user->name,
fn($name) => strtoupper($name),
fn($name) => "Hello, {$name}"
);
echo $pipeline($user); // "Hello, JOHN"
tap to inspect/intercept values mid-pipeline:
$withLogging = Compose::pipe(
fn($data) => $data * 2,
fn($data) => tap($data, fn($val) => logger()->info("Value: {$val}"))
);
$safeDivide = Control::try(fn($a, $b) => $a / $b)
->otherwise(fn() => 0);
echo $safeDivide(10, 0); // 0
$retryable = Control::retry(fn() => riskyOperation(), 3);
$expensive = fn($input) => computeHeavyCalculation($input);
$memoized = Decorators::memoize($expensive);
$timeout = Decorators::timeout(fn() => slowTask(), 2);
$this->app->bind('pipeline.transform', fn() => Compose::pipe(...));
tap to modify requests/responses:
$middleware = fn($request) => tap($request, fn($req) => $req->merge(['logged' => true]));
Closure Scope Leaks
$this in class methods). Use use sparingly.static closures.Memoization Pitfalls
memoize caches based on all arguments. Uneven argument types (e.g., array vs string) may cause unexpected misses.json_encode for arrays) or use a custom cache key function.Timeout Behavior
timeout throws Fun\TimeoutException if the function exceeds the limit. Always wrap in a try-catch or use Control::try.Immutable Data Assumption
tap assume immutability. Side effects (e.g., modifying input objects) break pipelines.Compose::tap to log intermediate values:
$pipeline = Compose::pipe(
fn($x) => $x + 1,
fn($x) => tap($x, fn($v) => logger()->debug("Mid: {$v}")),
fn($x) => $x * 2
);
memoize) may not trigger computation until first use. Test with actual invocations.Compose by creating static methods:
Compose::static('batch', fn(...$fns) => fn($input) => array_reduce($fns, fn($acc, $fn) => $fn($acc), $input));
Control::retry logic (e.g., exponential backoff):
Control::retry(fn() => $operation, 3, fn($attempt) => sleep($attempt));
$decorated = Decorators::chain(
Decorators::memoize($func),
Decorators::timeout($func, 1)
);
How can I help you explore Laravel packages today?