spatie/piper
Piper is a pipe operator-first PHP utility library that ports Laravel’s array and string helpers into standalone functions. Chain filter, map, join, lower, replace, and more with the |> operator, mixing array and string primitives in one clean pipeline.
Architecture fit
Piper is a zero-coupling utility library: pure functions in, closures out, no service provider, no config, no container resolution, no facades. For a Laravel codebase that is architecturally the cheapest possible dependency to absorb — it adds no bootstrapping surface, no lifecycle hooks, and no global state. The design is a deliberate port of Collection/Stringable semantics onto primitives, so it slots in as a complement to Eloquent/Collection rather than a replacement: you exit the Collection world with ->all(), run the pipeline, and re-enter with Str::of() or collect() if the caller expects a fluent object.
The genuine architectural value is composability outside the framework context — named domain pipelines (format_winning_numbers(array $numbers): string) are pure functions of primitives, testable with plain assertions and no DB or container. That is a real win for jobs, console commands, mail/notification body builders, form request prepareForValidation, report row assembly, and CSV normalization.
Integration feasibility
Feasibility is gated entirely on one binary fact: PHP 8.5. The |> pipe operator does not exist below 8.5, so composer require spatie/piper will fail platform resolution outright. This is not a soft constraint you can work around with a polyfill. Four things must be verified in order before a decision:
php -v and config.platform.php in composer.json.|> , so they must run on 8.5.If PHP 8.5 is not already funded and shipping, this package is a no-go today regardless of its merits. If it is, installation is one command with zero registration.
Technical risk
sortByMany callable addition — nothing that signals a battle-tested API surface.use function group imports also silently fall back to the global namespace on omission — a class of bug that method chaining structurally cannot produce.PORT.md documents what was and wasn't ported.Key questions
collect() chain? Is the readability gain real enough to justify a second idiom in the codebase?app/Support/pipes.php seam, and is it reviewed like a public API?Stack fit
Strong for Laravel/PHP shops. The API is a 1:1 port of Collection and Stringable methods, so Laravel-fluent developers already know roughly 90% of the surface; the docs are for confirming names and checking which methods were ported. The natural home is app/Support/pipes.php for named domain pipes, plus targeted use in console commands, job handle payload shaping, mail/notification builders, form request normalization, and seeders/fixtures. It does not belong in models, Blade templates, config, or middleware.
Migration path There is no migration — this is strictly additive. The correct motion is a single-method rewrite, not a sweep:
collect() chain or a run of temp variables.The canonical before/after is a filter→map→values chain with nested string transforms. Good pilots: report/export builders, notification body assembly, CSV import normalization in a queued job, small artisan utilities. Bad pilots: hot loops, anything protected by LazyCollection, Blade.
Compatibility
use function-aware configuration, and the analyzer itself must run on 8.5.use function imports. Budget for it in the style guide.$values |> array_sum(...). Note this is a different form from |> array_sum(), which evaluates eagerly and breaks.Sequencing
config.platform.php, CI matrix (including analyzers), and deploy image. Stop here if any fail.collect() for hot paths, two-step transforms, and anything needing laziness.app/Support/pipes.php with the first two or three named domain pipes and custom curried helpers (round_to, limit_words, tap_pipe).Keep the Collection boundary deliberate: convert at the top and bottom of a pipe, once, explicitly — never mid-pipe.
Maintenance
Low ongoing effort for the library itself: MIT, no config, no migrations, no bootstrapping, no upgrade scripts. The maintenance burden that does exist is self-inflicted and lives in two places — the house style guide, and app/Support/pipes.php, which should be reviewed like a public API because it becomes the de facto extension surface. Dependency hygiene is trivial; the package has essentially no transitive footprint.
Support Community-only via Spatie's GitHub. No SLA, no commercial backing for this specific package. The mitigating factor is Spatie's institutional reputation — abandonment is unlikely to be abrupt — but the bus factor on a 57-star package with two external contributors is genuinely thin. Treat supportability as "we can vendor-fork this in an afternoon if needed," which is true here precisely because the code is small, pure, and MIT.
Scaling
Pipelines are eager: every stage builds a full intermediate array, and map adds one closure invocation per element on top of your callback. That is fine for reports, notifications, and per-request payload shaping at normal volumes. It is the wrong tool for a 500k-row import or any high-throughput hot path — benchmark against a plain foreach before use there, and reach for generators or LazyCollection regardless. Scale-out risk is therefore per-call-site, not systemic: the failure mode is one poorly chosen pipeline in a loop, not a library-wide degradation.
Failure modes
$x |> count() invokes count() with zero arguments and blows up; $x |> count(...) yields a closure. Same trap on dump(): |> dump() pipes null. Highest-frequency week-one bug — mitigate with a custom tap_pipe that guarantees pass-through.use function import fatals — unless a global of that name exists (your own helper, a polyfill, another package), in which case it silently does the wrong thing. Enforce explicit group imports in review and, ideally, in a lint rule.filter preserves keys exactly like Laravel's. Any filter followed by map-into-arrays, JSON encoding, or other key-sensitive stages needs values() in between.dd() between stages inside a single expression. The recovery procedure is to explode the pipeline into intermediate variables, isolate the bad stage, then re-collapse — worth documenting so it isn't rediscovered under incident pressure.Ramp-up
Fast for Laravel developers — the method names are already muscle memory, and the calling convention (config args in, closure out, subject via |>) takes one example to internalize. Realistic estimate: an afternoon to productive, one release cycle to fluent. The two things that need explicit teaching are the |> fn() vs |> fn(...) distinction and the Collection-boundary discipline; both should be in the style guide, not tribal knowledge. The dominant ramp-up cost is not learning Piper — it is the PHP 8.5 upgrade itself, which should be budgeted and sequenced separately and is a prerequisite regardless of whether Piper is adopted.
Verdict for planning purposes: conditional pilot. Fund the PHP 8.5 upgrade on its own merits, not for this library. If 8.5 lands anyway, run one pilot PR with static-analysis validation and a written house rule; defer any broader standardization until the package has a year of field data or internal dependents.
How can I help you explore Laravel packages today?