While working on IndexNow in Laravel, I needed to clean a list of URLs before submitting it: remove empty values, deduplicate the list, and restore sequential numeric keys.
It was a small, ordinary transformation pipeline, but it became the first place where I used PHP 8.5's Pipe Operator |> as part of real production code rather than as a demo of new syntax.
The result reads in the same direction as the data changes: filter → unique → values.
By that point, ICanUp was already running on PHP 8.5, so I could evaluate the new syntax while building the application instead of experimenting with it in an isolated example.
The Real Use Case Appeared in My IndexNow Client
In my IndexNow implementation for automatically submitting URLs to Bing, the HTTP client receives an array of public URLs and builds the urlList sent to the external service.
Before making that request, I need to guarantee that the list contains no empty values or duplicates and that its indexes are sequential.
This was not a utility invented to demonstrate the Pipe Operator. The cleanup was already part of the IndexNow contract.
$urls = array_filter( $urls, static fn(mixed $url): bool => is_string($url) && $url !== '', ) |> array_unique(...) |> array_values(...);What I like about this fragment is that it barely needs to be translated into prose: filter the URLs, keep unique values, then reindex the array.
array_filter()removes everything that is not a non-empty stringarray_unique()removes duplicate URLsarray_values()restores sequential numeric keysThe result is a clean list ready for the
urlListJSON payload
What `|>` Actually Does in PHP 8.5
The Pipe Operator takes the result of the expression on its left and passes it to the callable on its right.
I can read the right-hand side as a function that PHP invokes with the previous result.
A simple pipe therefore does not create a new collection type or a special pipeline object. It is an expression provided by the language itself.
$result = $value |> transform(...); $result = transform($value);What the Same Code Looks Like Without the Pipe Operator
The same operation can be written with ordinary nested function calls, and it behaves exactly the same way.
$urls = array_values( array_unique( array_filter( $urls, static fn(mixed $url): bool => is_string($url) && $url !== '', ), ),);There is nothing technically wrong with this version. The difference is the reading order.
I first have to find array_filter(), then move outward to array_unique(), and finally reach the outer array_values() call.
As the number of transformations grows, nesting starts to hide the actual sequence of operations.
Temporary Variables Are Still a Good Alternative
$urls = array_filter( $urls, static fn(mixed $url): bool => is_string($url) && $url !== '',); $urls = array_unique($urls);$urls = array_values($urls);I do not consider this version worse either.
If I need to place a breakpoint between stages, log an intermediate value, or give each state a meaningful name, temporary variables may be clearer than a pipe.
For me, the question is not whether code can be rewritten with |>. The question is whether doing so makes the intent easier to read.
I use the Pipe Operator not to eliminate variables, but to make a sequence of transformations read in its natural direction.
Why This IndexNow Fragment Fits `|>` So Well
Each step is small and easy to understand
The result of one step naturally becomes the input of the next
The steps do not have separate business meanings that need their own names
I do not need the intermediate values after the pipeline finishes
The
filter → unique → valuessequence matters more than function-call nestingThe final result remains an ordinary PHP array
There is another detail in this example that makes the order of transformations important.
array_filter() preserves keys. array_unique() can also leave gaps after duplicate values are removed.
After filtering and deduplication, the array indexes are therefore not guaranteed to be 0, 1, 2, ...
Input: 0 => https://example.com/a1 => ''2 => https://example.com/a3 => https://example.com/b After array_filter(): 0 => https://example.com/a2 => https://example.com/a3 => https://example.com/b After array_unique(): 0 => https://example.com/a3 => https://example.com/b After array_values(): 0 => https://example.com/a1 => https://example.com/bFor IndexNow, this is not just about making an array dump look nicer.
The array is later passed to Laravel's HTTP client as urlList in a JSON payload. In PHP, an array with non-sequential numeric keys can be encoded as a JSON object with explicit keys instead of a JSON list.
The final array_values() therefore guarantees the data shape I want to send to the external API.
Why `array_filter()` Stays Before the Pipe
At first glance, it may seem cleaner to start the entire chain with $urls |> ....
But my array_filter() call needs both the input array and a callback containing the validation rule.
The simplest version was to keep that first call as a normal expression and pipe its result into the one-argument callable stages.
$urls = $urls |> (static fn(array $urls): array => array_filter( $urls, static fn(mixed $url): bool => is_string($url) && $url !== '', )) |> array_unique(...) |> array_values(...);This version also expresses a pipeline, but it introduces a wrapper closure just for the first step. In my case, starting with a normal `array_filter()` call was simpler.
My Second Real Example Was Even Smaller
Another natural place for `|>` appeared while extracting a slug candidate from a URL path.
private function pageSlugCandidate(string $url): ?string{ if ($this->isExternalUrl($url)) { return null; } $path = parse_url($url, PHP_URL_PATH); if (! is_string($path)) { return null; } $slug = rtrim($path, '/') |> basename(...) |> rawurldecode(...); return $slug !== '' ? $slug : null;}Here, the pipeline reads almost literally as a transformation path.
First I remove the trailing slash, then take the basename, and finally decode the resulting slug.
Short, pure transformations like this are where the new syntax feels most natural to me.
Where I Would Not Use the Pipe Operator
When each step has important domain meaning and deserves a separate name
When I need to inspect or log intermediate values
When most stages require wrapper closures just to adapt their signatures
When the chain becomes so long that several assignments would be easier to scan
When transformations have side effects and it is not obvious what value continues through the chain
When
|>is being added only because it is a new PHP 8.5 feature
$result = $input |> (fn($value) => transform($value, $config, $locale)) |> (fn($value) => validateValue($value, $rules, $context)) |> (fn($value) => formatOutput($value, $options, $timezone));If almost every line turns into a closure with additional dependencies, I am no longer getting the simplicity that made the pipeline attractive in the first place.
There Is One Obvious Boundary: the Runtime Must Be PHP 8.5
The Pipe Operator is PHP 8.5 syntax, so upgrading only my local PHP installation would not be enough.
CLI, PHP-FPM, CI runners, Docker workspaces, Composer platform requirements, and the production runtime all need to agree before |> enters the codebase.
Otherwise, readability is no longer the problem - the older interpreter simply cannot parse the new syntax.
{ "require": { "php": "^8.5" }}I covered the runtime migration itself separately in my article about the PHP 8.5, Laravel, Laradock, and Composer package upgrade.
`|>` and Laravel Pipeline Are Not the Same Thing
The names are similar, but I use these approaches at very different levels.
PHP's Pipe Operator is a small language-level expression for passing one value through callables.
Laravel Pipeline is useful for a more structured application flow where stages can be separate classes with their own responsibilities. I have already used that approach in a real commission calculation flow built with Laravel Pipeline.
Creating a Laravel Pipeline for three array functions would be excessive. At the other extreme, one long |> chain does not replace architecture for a complex domain flow.
Approach | When I use it | Main advantage |
|---|---|---|
Nested functions | A short, simple expression | Minimal syntax |
Temporary variables | Intermediate states or debugging matter | Maximum explicitness |
PHP |> | Small sequential transformations | Left-to-right readability |
Laravel Pipeline | Multiple application/domain stages | Structure and separate responsibilities |
After the Refactor, I Verify Behavior Rather Than Syntax
Non-string values never reach the final URL list.
Empty strings are removed.
Duplicate URLs are removed.
Keys are sequential again after filtering and deduplication.
An empty result does not trigger an HTTP request.
The final
urlListremains a JSON array.The IndexNow HTTP payload keeps the same functional behavior after the syntax refactor.
What I Kept for Next Time
|>works best for small sequential transformationsNot every nested call needs to be rewritten
Temporary variables remain a good solution, especially for debugging
A wrapper closure is not worth adding just to make everything look like one pipe chain
I need to think about data shape as well as data value between stages—as the final
array_values()in the JSON payload demonstratesNew PHP syntax should enter the codebase only after the entire runtime is aligned, not just the local environment
Conclusion
I like PHP 8.5's Pipe Operator not because it lets me write the same code with newer syntax.
In the right place, |> changes the direction in which I read the code. Instead of unpacking nested functions from the inside out, I see the data flow in the same order in which it actually executes.
My IndexNow URL cleanup is exactly that kind of case: filter → unique → values. The second rtrim → basename → rawurldecode example confirmed the same pattern.
I do not plan to turn every PHP function chain into a pipe. But for short transformation pipelines without unnecessary side effects, |> has already become a normal tool for me rather than just a PHP 8.5 novelty.
A good Pipe Operator does not make me think about the Pipe Operator. It simply lets me read the transformation in the right direction.



