What's new in PHP 8.5
Written on 2025-11-14PHP 8.5 will be released on November 20, 2025. It'll include 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, ]); } }
While definitely a great feature, it's unfortunate that it doesn't work with cloning readonly properties (which I believe to be a very common use case). I wrote about 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 exception 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 excplitly 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 attributes like #[Override] or #[NoDiscard] can cause compile-time errors when used incorrectly. For example, when adding #[NoDiscard] to a function that returns void:
// Fatal error: A void function does not return a value, but #[NoDiscard] requires a return value #[NoDiscard] function foo(): void {}
Adding the new #[DelayedTargetValidation] to this function will supress the compile-time error, and will only trigger it if the attribute is being retrieved via reflection:
#[DelayedTargetValidation] #[NoDiscard] function foo(): void {}
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!
