crell/fp
Crell/Fp is a lightweight functional programming toolkit for PHP, offering pipelines, function composition, currying, and immutable collection helpers like map, filter, reduce, and groupBy. It brings concise, expressive functional patterns to everyday PHP code.
Install with Composer:
composer require crell/fp
Requires PHP 8.0+ (the package leans on first-class callable syntax and match). There are no service providers, config files, or facades — it's a set of namespaced functions meant to be imported directly:
use function Crell\fp\pipe;
use function Crell\fp\compose;
use function Crell\fp\partial;
First place to look: the package README and src/ for the exact function inventory (the surface area is intentionally small). The canonical first use case is replacing a nest of intermediate variables or deeply nested calls with a readable pipe():
use function Crell\fp\pipe;
$total = pipe(
$request->input('items'),
fn (array $items) => array_filter($items, fn ($i) => $i['qty'] > 0),
fn (array $items) => array_map(fn ($i) => $i['price'] * $i['qty'], $items),
'array_sum',
);
If your codebase already reads top-to-bottom, this is the smallest possible win: one import, one call, immediate readability improvement. Everything else in the package (compose, partial, arity helpers) builds on the same idea.
Pipe for step-by-step transforms. pipe($value, $fn1, $fn2, ...) threads the result of each callable into the next. Use it in controllers, form requests, and jobs where you'd otherwise chain temporary variables. Each callable receives exactly one argument, so model multi-value state as an array or a small DTO.
Compose for reusable pipelines. Where pipe() executes immediately, compose() (right-to-left, like mathematical composition) returns a new callable. Store these as class constants or container bindings so a validation/normalization pipeline can be reused across entry points:
private const NORMALIZE = ... // compose(fn, fn, fn)
Because composed callables are just closures, you can inject them into other services, queue payloads, or call them from Blade components.
Partial application for dependency injection. partial() lets you pin leading arguments to a service method so it fits a one-argument pipe step. This is the idiomatic bridge between object-oriented Laravel services and the functional pipeline:
pipe(
$order,
partial($this->taxCalculator->apply(...), $rate),
fn (Order $o) => $this->repo->save($o),
);
Arity helpers for adapters. When piping into array_map/array_filter (which pass extra arguments), use the arity adapter so your single-arg callable isn't broken by the extra keys. Pair this with pipe() inside a Laravel Collection::pipe() for a hybrid style that keeps the rest of the team comfortable:
$collection->pipe(fn ($c) => collect(pipe($c->all(), ...)));
Testing and TDD. A composed pipeline is a pure callable; unit-test it by invoking it with fixtures, no container required. This is the biggest day-to-day payoff — heavy transformation logic leaves your controllers and lives in testable functions.
Layering with Laravel. Resolve the functions at the top of the file with use function. There's no auto-discovery, so nothing leaks into the global namespace. Feature-test the pipeline from the HTTP boundary and unit-test the composed callable in isolation.
files entry. If you fork or copy snippets, keep that wiring intact or nothing resolves. There's no new Something() to instantiate — this is a flat function library.pipe() runs left-to-right; compose() runs right-to-left. Pick one convention per project and stick to it — mixing them in a single file is the #1 source of "why is this reversed" bugs.null or void mid-pipeline, the next callable breaks — keep transformations pure and side effects at the end of the chain.function ($x) { dump($x); return $x; } step in temporarily; it's the fastest way to see where a transformation went sideways. Remove before committing, or wrap it behind app()->isLocal().Either, Maybe, or monad machinery — this is pipelining and composition glue. Don't try to build a pure functional layer on top of it for a typical Laravel app; use it where it improves local readability and drop back to regular PHP elsewhere.use function it alongside — that's the intended extension path.How can I help you explore Laravel packages today?