New in PHP 8.5
Written on 2025-11-17Join over 14k subscribers on my mailing list. I write about PHP news, share programming content from across the web, keep you up to date about what's happening on this blog, my work on Tempest, and more.
You can subscribe by sending an email to brendt@stitcher.io.
Hi
It's only a couple more days before PHP 8.5 gets released, and keeping up good habits, I've just published my what's new in PHP 8.5 blogpost.
You might not be able to read it in full right now, so let me share my highlights with you in this email.
The pipe operator
In PHP 8.5, we get a 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 think there's a lot of potential here, and I hope the pipe operator will get some more improvements over time. I've done a deep-dive into this subject, 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.
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.
(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();
I think it's a nice feature, but I'm sad that PHP is still going with runtime checks for features that should be done at compile time with static analysis. The same happened with #[Override] in PHP 8.3, and I'm not sure PHP will ever be able to change its mindset.
These are my highlights, but there is a lot more to say about this release, you can read all about it here. All in all, I think it's again a solid release with some very nice features (the pipe operator and closure changes especially). I'm looking forward to implementing PHP 8.5 in Tempest soon!
In closing: I'll stream together with Nuno on November 20th (the actual release day). You can find more info here, and I hope to see all of you on stream!
Have a good week!
Brent