PHP 8.5 Pipe Operator: Clean Function Chaining in One Line
You've written trim(strtolower(strip_tags($input))) more times than you'd like to admit. It works, but it reads right-to-left — the opposite of how the transformation actually flows. PHP 8.5 ships the |> pipe operator to fix that.
PHP 8.5 Pipe Operator Syntax
The operator passes the value on the left as the first argument to the callable on the right:
$result = $input |> trim(...) |> strtolower(...);
The (...) syntax is PHP's first-class callable syntax — it creates a closure from the function reference so the pipe has something to invoke. Without it you'd be handing the pipe a string like "trim", which won't fly here.
Chain as many callables as you like:
$slug = ' PHP 8.5 Released '
|> trim(...)
|> strtolower(...)
|> (fn(string $str) => str_replace(' ', '-', $str));
// "php-8.5-released"
Notice the arrow function is wrapped in parentheses. That's required — without them the arrow function's body captures everything to the end of the expression and PHP throws a parse error. Always wrap fn() steps.
Before and After
A real example: sanitising user input before storing it.
Before — nested:
$clean = trim(strtolower(strip_tags($userInput)));
Reads inside-out. You have to scan inward to find where the transformation starts.
Before — intermediate variables:
$stripped = strip_tags($userInput);
$lower = strtolower($stripped);
$clean = trim($lower);
Readable, but the variable names are throwaway noise when all you care about is the final result.
After — php function chaining with the pipe operator:
$clean = $userInput
|> strip_tags(...)
|> strtolower(...)
|> trim(...);
Left-to-right. Each step is explicit. No intermediary names to invent.
Using Arrow Functions in the PHP 8.5 Pipe Operator
When a function requires more than one argument, wrap it in an arrow function:
$price = $rawInput
|> (fn(string $v) => (float) $v) // cast string to float
|> (fn(float $v) => round($v, 2)) // round to 2 decimal places
|> (fn(float $v) => number_format($v, 2)); // format as currency string
// "12.50"
I prefer extracting closures when the pipeline gets long — it keeps each step readable without going full named function:
$toFloat = fn(string $v) => (float) $v;
$round = fn(float $v) => round($v, 2);
$format = fn(float $v) => number_format($v, 2);
$price = $rawInput |> $toFloat |> $round |> $format;
Static methods and instance methods work too:
$result = $value
|> MyTransformer::sanitise(...)
|> $formatter->format(...);
Gotchas and Edge Cases
Single argument only. The pipe always passes one value, period. Functions that require multiple arguments — str_replace, substr, preg_replace — must be wrapped in a closure. There is no partial application syntax in PHP 8.5. It may arrive in 8.6.
Arrow functions need parentheses. Without them you get a parse error:
// Bad — parse error, arrow fn captures the rest of the expression
$result = $value |> fn($v) => strtolower($v) |> trim(...);
// Good
$result = $value |> (fn($v) => strtolower($v)) |> trim(...);
Void callables produce null. If any step in the chain has a void return type, the next callable receives null. Don't pipe through side-effect functions (logging, dispatching events) mid-chain.
No short-circuit. The pipe operator doesn't stop on null or false. If you need null-safe handling, you still need the nullsafe operator ?-> or an explicit guard closure.
PHP 8.5 only. Using |> on PHP 8.4 or earlier is a parse error. Add a platform constraint to composer.json if you ship this to a shared codebase:
{
"require": {
"php": ">=8.5"
}
}
Not method chaining. The pipe operator passes values through callables. It's not the same as fluent method chaining on an object ($query->where()->orderBy()). They solve similar readability problems in different contexts.
Wrapping Up
Reach for |> when you have a linear transformation sequence and want the code to read in execution order. For longer pipelines where many steps need extra arguments, a dedicated package like league/pipeline is still worth a look — the closure wrapping can get noisy at scale. But for everyday string and value transformations, the PHP 8.5 pipe operator is a clean, zero-dependency fit.
The pipe operator pairs especially well with PHP readonly classes as value objects — you can pipe raw primitives through a transformation chain and produce a fully-validated, immutable value object at the end. For the broader picture of modern PHP language features and where they fit in a production Laravel stack, The Complete Laravel Developer Toolchain for 2026 covers PHP 8.4 and 8.5 features alongside deployment, testing, and monitoring tooling.
Steven is a software engineer with a passion for building scalable web applications. He enjoys sharing his knowledge through articles and tutorials.