What’s New in PHP 8.5? (Release Date + Must-Know Features)
At the time of this writing, PHP 8.5 is in the release candidate cycle, so we can finally start to discuss what’s new in the next release of PHP. In this video, we’ll discuss the timeline for the release and discuss some new features and changes we can expect to see.
Overall
At the time of this recording, PHP 8.5 is scheduled to be released on November 20, 2025, after it has gone through the alpha, beta, and release candidate phases. It’s always possible the date may change, as we would rather have them release working software than something with a bug in it. This happened with the 8.2 release when a critical bug was found just before the release.
What’s included in this release will make it easier for us to develop and maintain our code. Because this is a point release, it’s not expected to be a painful upgrade, but plan accordingly, as this isn’t a given.
I have two disclaimers before we go any further. This video was made using beta release 3 on the official Docker image (php:8.5.0beta3-cli if you want to try at home), but functionality may change between now and the actual release. I also can not stress enough do not install this in production yet. Sometime after the release candidates, I’ll start including it in my automated tests so we can see if anything breaks, but I’ll wait until the first patch (which will be 8.5.1 in this case) before we start installing it in ANY production environments.
If you’re interested in trying it now, there are Docker images available and instructions for compiling it directly from the source.
Let’s talk about some of the things that have been added. I should point out that this is my personal selection of features that I personally think are going to make my life better, and others may feel differently.
Pipe Operator (|>)
In PHP code, it’s common to have a block of several functions that operate on the output of the previous functions:
$result = [" scott", "@", "podplanner.online "];
$result = implode($result);
$result = strtolower($result);
$result = trim($result);
// output: scott@podplanner.online
echo $result;
PHP 8.5 introduces a new operator called the pipe operator (|>) that allows chaining multiple callables. It does so by passing the return value of the “left” callable to the “right” callable as if they were connected together like a pipe:
$result = [" scott", "@", "podplanner.online "]
|> implode(...)
|> strtolower(...)
|> trim(...);
// output: scott@podplanner.online
echo $result;
The thing I find most interesting about this comes from one of the lines in the RFC for the pipe operator
The current implementation works entirely at the compiler level, and effectively transforms the first example above into the second at compile time. The result is that pipe itself has virtually no runtime overhead.
There are some limitations, like we can only use callables that accept one required parameter, and we can’t use callables that pass by reference.
That being said, to me, this is one of those features that are going to slowly take over sections of my codebase to make them just a little bit easier to read, and hopefully Rector will be able to find these and swap them out automatically.
#[\NoDiscard] Attribute
There’s a class of bug I like to call “stepping on a rake” bugs. “Stepping on a rake” bugs are where you know better but manage to make a bone-headed mistake over and over again.
For example, the code below is used to calculate the tax for a purchase, but we forgot to store the result. It’s especially annoying if that function is making an “expensive” call to something like an external service because it’s just wasting CPU cycles and maybe extra fees.
function calculateSalesTax(int $price): int
{
return getLocalTaxRateViaTheInternet() * $price;
}
calculateSalesTax(100);
PHP 8.5 is adding the #[\NoDiscard]
attribute, which will allow us to flag that we don’t want to discard the result, and if we accidentally forget to store it into a variable, PHP will raise a warning.
#[\NoDiscard]
function calculateSalesTax(int $price): int
{
return getLocalTaxRateViaTheInternet() * $price;
}
// Warning: The return value of function calculateSalesTax() should either be used
// or intentionally ignored by casting it as (void) in /app/test.php on line 10
calculateSalesTax(100);
// valid
$tax = calculateSalesTax(100);
Hopefully, our Static Code Analysis tools or a code review will catch the fact that we didn’t use the $tax
variable, but this is a huge step forward in preventing us from forgetting to use the return value.
I’m curious to see how this will work in practice, if people will be scattering these everywhere, or if it gets used sparingly. I’m hoping for the former.
https://wiki.php.net/rfc/marking_return_value_as_important
Clone()
As a fan of value objects, I really like to mark my class and properties read-only when possible so we can reduce bugs. The hard part is that if you want to change just one property of an object, you need to make sure you copy over all the properties like the setFirst()
function below.
readonly class FullName
{
public function __construct(public string $first, public string $last)
{
// ..
}
public function setFirst(string $newFirst): FullName
{
return new FullName($newFirst, $this->last);
}
}
$fullName = (new FullName("Scott", "Keck-Warren"))
->setFirst("Alice");
In this case, there are only two properties, so it’s not too much work, but adding additional properties requires us to maintain additional steps, which is always error-prone and annoying. It sure would be nice if we could just clone the object and update just the fields we want.
PHP 8.5 alters the clone logic so we can do just that by making it a function call with a second parameter, with what we want to override:
readonly class FullName
{
public function __construct(public string $first, public string $last)
{
// ..
}
public function setFirst(string $newFirst): FullName
{
return clone($this, [
"first" => $newFirst
]);
}
}
$fullName = (new FullName("Scott", "Keck-Warren"))
->setFirst("Alice");
Again, I’m expecting this to become part of my regular usage as it will future-proof some of my more complex value objects.
https://wiki.php.net/rfc/clone_with_v2
array_first() and array_last()
As I go through the list of new features, sometimes I read one and realize how obvious it is that we need something, and I’m shocked that it doesn’t exist. PHP 8.5 adds two functions for retrieving the first and last values of an array. They are array_first()
and array_last()
respectively, and they work more or less like you would expect them to.
$users = ["Alice", "Avery", "Scott", "Steph"];
// "Alice"
echo array_first($users), PHP_EOL;
// "Steph"
echo array_last($users), PHP_EOL;
They also work with associative arrays to return the first and last keys:
$person = ["name" => "Scott", "City" => "Saginaw"];
// "Scott"
echo array_first($person), PHP_EOL;
// "Saginaw"
echo array_last($person), PHP_EOL;
If you have an empty array, they will both return null
:
$empty = [];
// NULL
var_dump(array_first($empty));
// NULL
var_dump(array_last($empty));
https://wiki.php.net/rfc/array_first_last
Show Only Modified Settings
One of the more annoying things that can, and does happen, is that settings get changed in production environments without supporting documentation being updated. So when you try to move an application from one server to another, you always have to copy over the whole php.ini file and just pray that you’re not missing a new default setting somewhere.
PHP 8.5 adds the --ini=diff
command line flag that will display any of the settings in our ini file that are different from the default settings:
php --ini=diff
Non-default INI settings:
html_errors: "1" -> "0"
implicit_flush: "0" -> "1"
max_execution_time: "30" -> "0"
As someone who co-manages some servers, having this in our toolkit is going to make server migrations
Error Backtraces
Before PHP 8.5, errors do not provide a backtrace, which can make it a challenge to determine what exactly is wrong with your code.
For example, let’s look at this example modified from the RFC.
set_time_limit(1);
function recurse() {
foreach (range(0, 100000) as $i) {
foreach (range(0, 100000) as $i) {
// nothing
}
}
recurse();
}
recurse();
In PHP 8.4, we’ll get the following less-than-helpful error:
Fatal error: Maximum execution time of 1 second exceeded in /app/test.php on line 5
Maximum execution time errors are always fun because the point at which the script dies can be non-deterministic, so you’re always fighting to figure out what’s causing the problem.
However, in PHP 8.5, we’ll get a stack trace:
Fatal error: Maximum execution time of 1 second exceeded in /app/test.php on line 6
Stack trace:
#0 /app/test.php(6): range(0, 100000)
#1 /app/test.php(13): recurse()
#2 {main}
This is one of those changes that seems simple, like no big deal, but is going to save a ton of time debugging problems.
https://wiki.php.net/rfc/error_backtraces_v2
Lightning Round
As usual, there are too many features to go over in depth in one of my videos, but there are some others I want to mention.
There’s a new PHP_BUILD_DATE constant that will tell us when the build of PHP we’re using was built.
// my version: Sep 12 2025 23:51:45
echo PHP_BUILD_DATE;
We can now add the #[\Deprecated]
attribute to traits.
#[\Deprecated]
trait User {
public function hi() {
}
}
// Deprecated: Trait User used by ConcreteUser is deprecated in /app/test.php on line 9
class ConcreteUser
{
use User;
}
new ConcreteUser();
We can now add the #[\Override]
attribute to properties.
// Fatal error: ConcreteUser::$id has #[\Override] attribute,
// but no matching parent property exists in /app/test.php on line 7
class ConcreteUser extends User
{
#[\Override]
public int $id;
}
new ConcreteUser();
Finally, the OPcache extension is no longer optional and is going to be statically compiled into PHP like ext/date, ext/hash, ext/pcre, ext/random, ext/standard, and others. This is going to be a huge win, we’ll always have it available.
General Deprecations
As usual, there are some deprecations that might get removed in PHP 9.0. You can check out the full list on the RFC (https://wiki.php.net/rfc/deprecations_php_8_5). I’m not as excited about this batch because they’re not features I use often, but I’m sure a lot of people are going to be annoyed they can’t use back ticks to run console commands anymore.
What You Need To Know
- PHP 8.5 is set for release on November 20, 2025
- Major changes include the pipe operator, the #[\NoDiscard] attribute, and new version of clone().
Leave a comment
Use the form below to leave a comment: