PHP 8.5: URI parsing, pipe expressions, and constant-expression callbacks

Try three focused PHP 8.5 examples, understand their limits, and keep runtime adoption separate from syntax changes.
Adopt the runtime before changing the syntax
PHP 8.5 was released on 20 November 2025. Its additions include the URI extension, the pipe operator, and expanded constant expressions. These features solve different problems; adopting the runtime does not require rewriting existing code into the newest syntax.
First confirm that extensions, deployment images, dependency constraints, and analysis tools support the target runtime. Run the application test suite on that runtime before introducing syntax an older interpreter cannot parse. A rollback to PHP 8.4 will fail if the deployed source already contains a pipe expression.
The complete CLI files below were run on PHP 8.5.11 on 12 October 2026. Save each block under its displayed filename and run it with php followed by that path. The URI extension is built in. These examples test language behavior without network access; they do not verify an application upgrade or authorize a URL destination.
Choose URI parsing semantics explicitly
PHP 8.5 provides Uri\Rfc3986\Uri for RFC 3986 parsing and Uri\WhatWg\Url for WHATWG URL behavior. Pick the model required by the consuming protocol instead of assuming all parsers normalize ambiguous input identically.
The parser can accept a relative reference even when the application requires a scheme and host. The three fixtures below distinguish that policy rejection from malformed URI syntax. The valid input prints https example.test; /docs prints rejected: absolute URI required; https://[ prints rejected: invalid syntax.
<?php
declare(strict_types=1);
use Uri\InvalidUriException;
use Uri\Rfc3986\Uri;
foreach (['https://example.test/docs', '/docs', 'https://['] as $input) {
try {
$uri = new Uri($input);
} catch (InvalidUriException) {
echo "rejected: invalid syntax", PHP_EOL;
continue;
}
if ($uri->getScheme() === null || $uri->getHost() === null) {
echo "rejected: absolute URI required", PHP_EOL;
continue;
}
echo $uri->getScheme(), ' ', $uri->getHost(), PHP_EOL;
}
Parsing does not authorize a destination
A syntactically valid URI is not necessarily safe to fetch or redirect to. Keep parsing separate from application rules about schemes, hosts, ports, credentials, redirects, and network destinations. The inspection script intentionally does not claim to implement an SSRF defense.
When replacing an existing parser, build fixtures from the formats your application accepts: relative references, percent encoding, fragments, and unexpected whitespace. Compare parsed components as well as serialized output. A silent normalization difference can change a cache key or a signature input.
Use pipes for a short, linear transformation
The pipe operator sends the value on its left as the single argument to a callable on its right. Additional parameters need a wrapper. It does not add automatic retries, asynchronous execution, or null handling.
This identifier function accepts ASCII input only. Its fixtures show both preserved repeated spaces and rejected non-ASCII text. It is intentionally unsuitable for Unicode slugs: lowercasing and transliteration need a separate policy for those inputs.
<?php
declare(strict_types=1);
function identifier(string $input): string
{
if (preg_match('/\A[A-Za-z0-9 _-]+\z/', $input) !== 1) {
throw new InvalidArgumentException('ASCII input required');
}
$value = $input
|> trim(...)
|> strtolower(...)
|> (static fn (string $text): string => str_replace(' ', '-', $text));
if ($value === '') {
throw new InvalidArgumentException('Identifier must not be empty');
}
return $value;
}
foreach ([' RELEASE NOTES ', 'TWO SPACES', ' ', 'café'] as $input) {
try {
echo identifier($input), PHP_EOL;
} catch (InvalidArgumentException $error) {
echo $error->getMessage(), PHP_EOL;
}
}
Keep constant-expression closures within their restrictions
PHP 8.5 allows closures and first-class callables in more constant-expression positions, and also permits casts there. A closure in a constant expression must be static and cannot capture variables; arrow functions are not supported in that position.
A default callback is a small use case that avoids a nullable callback plus a fallback assignment. This function returns only non-negative integers by default and rejects mixed input deliberately.
<?php
declare(strict_types=1);
function selectValues(
array $values,
Closure $accept = static function (int $value): bool {
return $value >= 0;
},
): array {
foreach ($values as $value) {
if (!is_int($value)) {
throw new InvalidArgumentException('Expected integer values');
}
}
return array_values(array_filter($values, $accept));
}
echo json_encode(selectValues([-2, 0, 3]), JSON_THROW_ON_ERROR), PHP_EOL;
try {
selectValues([0, '3']);
throw new RuntimeException('Mixed input was accepted');
} catch (InvalidArgumentException $error) {
echo $error->getMessage(), PHP_EOL;
}
Keep useful intermediate names
Pipes work well when each step consumes the previous result. If a transformation branches, performs I/O, or needs several values, named intermediate variables can make the failure path easier to review. Shorter source is not automatically clearer source.
Add the new constructs where they remove a concrete ambiguity. Verify their input and output contracts, then keep unrelated code stable during the runtime migration. The upgrade should leave a reviewer able to distinguish compatibility work from optional style changes.

