What's new in PHP 8.5
Written on 2025-11-20PHP 8.5 was released on November 20, 2025. It includes the pipe operator, clone with, a new URI parser, and more.
The pipe operator
PHP 8.5 introduces the new pipe operator that makes chaining output from one function to another a lot easier. Instead of deeply nested function calls like this:
$input = ' Some kind of string. '; $output = strtolower( str_replace(['.', '/', '…'], '', str_replace(' ', '-', trim($input) ) ) );
You can now write this:
$output = $input |> trim(...) |> (fn (string $string) => str_replace(' ', '-', $string)) |> (fn (string $string) => str_replace(['.', '/', '…'], '', $string)) |> strtolower(...);
I've done a deep-dive into this new operator, and you can read about it here.
Clone with
There's now a way to assign new values to cloned objects while cloning them:
final class Book { public function __construct( public string $title, public string $description, ) {} public function withTitle(string $title): self { return clone($this, [ 'title' => $title, ]); } }
I think this is a great feature. The only thing I find unfortunate is that it doesn't work when cloning readonly properties from the outside (which I think is a common use case). To do so, you have to specifically reset the propery's write access to public(set). I explained the problem here.
(void) cast and #[NoDiscard]
You can now mark a function with the #[NoDiscard] attribute, indicating that its return value must be used. If nothing happens with that return value, a warning will be triggered.
#[NoDiscard("you must use this return value, it's very important.")] function foo(): string { return 'hi'; } // Warning: // The return value of function foo() is expected to be consumed, // you must use this return value, it's very important. foo(); // This is ok: $string = foo();
The warning can still be surpressed by using the new (void) cast:
(void) foo();
Closure improvements
Closures and first-class callables can now be used in constant expressions. In practice this means you'll be able to define closures in attributes, which is an incredible new feature:
#[SkipDiscovery(static function (Container $container): bool { return ! $container->get(Application::class) instanceof ConsoleApplication; })] final class BlogPostEventHandlers { /* … */ }
Note that these kinds of closures must always be explicitly marked as static, since they aren't attached to a $this scope. They also cannot access variables from the outside scope with use.
Backtraces for fatal errors
A small but awesome change: fatal errors will now include backtraces.
Fatal error: Maximum execution time of 1 second exceeded in example.php on line 6
Stack trace:
#0 example.php(6): usleep(100000)
#1 example.php(7): recurse()
#2 example.php(7): recurse()
#3 example.php(7): recurse()
#4 example.php(7): recurse()
#5 example.php(7): recurse()
#6 example.php(7): recurse()
#7 example.php(7): recurse()
#8 example.php(7): recurse()
#9 example.php(7): recurse()
#10 example.php(10): recurse()
#11 {main}
Added array_first() and array_last()
Perhaps a bit overdue (array_key_first() and array_key_last() were added in PHP 7.3), but we finally get built-in functions to get the first and last elements from arrays! So instead of writing this:
$first = $array[array_key_first($array)] ?? null;
You can now write this:
$first = array_first($array);
URI parsing
There's a brand new URI implemention that makes working with URIs a lot easier:
use Uri\Rfc3986\Uri; $uri = new Uri('https://tempestphp.com/2.x/getting-started/introduction'); $uri->getHost(); $uri->getScheme(); $uri->getPort(); // …
The #[DelayedTargetValidation] attribute
Some built-in attributes (like #[Override]) are validated at compile-time rather than at runtime when being called via reflection. The #[DelayedTargetValidation] allows you to postpone that validation to a runtime:
class Child extends Base { #[DelayedTargetValidation] #[Override] public const NAME = 'Child'; // Note that this is an example, you cannot currently add #[Override] on constants }
This attribute is added to manage backwards compatibility issues. You can read a concrete example here.
Smaller changes
- Asymmetric visibility support for static properties
- Added support for attributes on compile-time non-class constants
- Constructor property promotion can now be used for final properties
#[\Override]can now be applied to properties- Added Dom\Element::$outerHTML
- The Exif extension now supports HEIF and HEIC images
- There's a new
FILTER_THROW_ON_FAILUREflag when callingfilter_var()
Deprecations and breaking changes
- Non-standard cast names like
(boolean)and(integer)are deprecated - Backticks as an alias for
shell_exec()are deprecated - Constant redeclaration has been deprecated
- The
disabled_classesini setting has been removed - You can find all breaking changes and deprecations here
Those are the features and changes that stand out for PHP 8.5; you can find the whole list of everything that's changed over here.
What are your thoughts about PHP 8.5? You can leave them in the comments below!

I'm pretty bummed about the backtick depreciation. Using backticks made it really easy to write little one-off shell scripts in PHP that were just a little too complex for bash. I talked to Crell about it on Mastodon and he said essentially "We're running out of keyboard characters and are trying to claw some back." I found the argument uncompelling.