The most useful place to try a new language feature is a small piece of code with an obvious result. PHP 8.5’s pipe operator fits that approach: take a value through a few transformations and make the sequence easy to read. You can adopt it in one formatter without redesigning an application.
The PHP manual on functional operators defines |> as passing the value on its left to the callable on its right. The expression evaluates to the returned value. The operator requires PHP 8.5 or later.
Imagine an import containing internal reference labels. Your product rule says those labels are ASCII, surrounding whitespace is ignored, and literal spaces become hyphens. Express that precise rule instead of calling this a universal slug generator.
function normalizeReference(string $reference): string
{
return $reference
|> trim(...)
|> strtolower(...)
|> (fn (string $value): string => str_replace(' ', '-', $value));
}
$result = normalizeReference(' RELEASE NOTES ');
// release-notestrim(...) and strtolower(...) create first-class callables. The closure supplies the additional arguments that str_replace needs while accepting the piped string. Writing str_replace with two fixed arguments followed by an ellipsis is not supported partial application syntax.
For the language rules behind those callable expressions, see first-class callable syntax. Arrow functions must be parenthesized when used in a pipe; the parentheses are required syntax.
The example intentionally has narrow behavior. It does not collapse repeated spaces, transliterate accented names, guarantee uniqueness, or protect a URL that has already been published. Those are separate requirements. An imported reference, a display label, and a public article slug may begin with the same input while needing different rules.
Write the expected outputs first. For this normalizer, test a padded string, an empty string, a string containing two spaces, and an already normalized reference. The two-space case should produce two hyphens under the stated rule. If that is wrong for the product, change the rule deliberately and name the operation accordingly.
A good pipeline lets a reader explain the type at every boundary. In this example each step takes a string and returns a string. A database write, a notification, or a method returning a success flag would change the meaning of the next step and deserves an explicit statement of its own.
Intermediate variables remain useful when you need to inspect an intermediate result, branch on it, or attach a domain name to it. A pipeline is helpful when the sequence itself is the story. There is no benefit in replacing a clear two-line function simply to increase the amount of new syntax in a project.
An import screen may accept a simple comma-separated list of internal tags. Here the contract allows literal commas as separators, trims surrounding whitespace, and removes empty entries. This is deliberately not a CSV parser: quoted commas and escaped values require a proper CSV reader. Name that restriction in the interface so users can predict the result.
/** @return list<string> */
function parseTags(string $input): array
{
return $input
|> (fn (string $value): array => explode(',', $value))
|> (fn (array $tags): array => array_map(trim(...), $tags))
|> (fn (array $tags): array => array_filter(
$tags,
fn (string $tag): bool => $tag !== '',
))
|> array_values(...);
}
$tags = parseTags(' PHP, , Laravel, 0 ');
// ['PHP', 'Laravel', '0']The explicit filter keeps the string zero, which could be a valid tag. The final array_values call gives the result consecutive numeric keys after filtering. That matters when encoding a list as JSON or passing it to code that relies on a list contract. We retain the original case and duplicates here; deciding whether PHP and php represent the same tag belongs to the import rules.
Notice the changing type: the first step converts a string to an array, and subsequent steps keep an array. Type annotations help a reader follow that transition. For a larger transformation, named functions with precise array documentation are easier to reuse and analyze than a long series of inline closures.
Pipes also work with a function that throws when input is invalid. The example below accepts a non-empty reference after trimming. It does not claim that non-empty input is safe for HTML, suitable for a database key, or authorized for the current user. Those concerns still need their own checks at the appropriate boundaries.
function requireReference(string $value): string
{
if ($value === '') {
throw new InvalidArgumentException('A reference is required.');
}
return $value;
}
$reference = ' RELEASE '
|> trim(...)
|> requireReference(...)
|> strtolower(...);
// releaseUse ordinary try/catch at the caller when the application needs to convert this exception into an import error. A pipeline should not silently turn invalid data into an empty string merely so later steps can continue. In a batch import, include the row number in the reported error and let the existing transaction policy decide whether other rows can be saved.
$cases = [
[' RELEASE NOTES ', 'release-notes'],
['', ''],
['TWO SPACES', 'two--spaces'],
['already-normalized', 'already-normalized'],
];
foreach ($cases as [$input, $expected]) {
if (normalizeReference($input) !== $expected) {
throw new RuntimeException('Unexpected normalized reference.');
}
}Place this after normalizeReference in a scratch PHP 8.5 file and run it from the command line. It intentionally uses explicit comparisons so it does not depend on runtime assertion settings. For production code, move the same cases into your normal test suite and add examples taken from actual imports. A useful test documents a business decision, such as preserving repeated spaces as repeated hyphens.
When investigating an unexpected tag, temporarily name the parsed, trimmed, and filtered arrays and inspect each value in your debugger. The important information is where the business rule diverges from the input, not whether every step remains on a single expression. Once the issue is understood, choose the representation that leaves that rule clearest for the next maintainer. Avoid inserting logging callbacks that return a boolean or nothing: the following step would receive that return value instead of the data you intended to inspect.
A PHP file containing this operator cannot be parsed by PHP 8.4. Before merging it, check the interpreter used by the web process, command line, queue workers, scheduled jobs, and CI. A successful browser request does not establish what interpreter a separate worker service is running.
For a shared library, increasing the minimum PHP version is a compatibility decision for consumers. For an application, record the runtime requirement in Composer and validate the release on the deployment environment. Once those conditions are satisfied, use pipes where they make a concrete transformation easier to follow.
The feature is listed in the official PHP 8.5 migration guide. This article uses the shipped 8.5 operator; proposals for additional pipe syntax need their own release verification.