Discover the main new features in PHP 8.5
Marcos Marcolin β’ November 21, 2025 β’ 5 min read
php php85 phprfcWhat's new in PHP 8.5 π
As has been tradition over the last decade, PHP gets a new version every year, and now it's PHP 8.5's turn.
If you follow this blog or my LinkedIn, you've probably already seen some of these new features, since I usually cover the most important RFCs and changes throughout the year.
In this post, I bring a summary of the main improvements in 8.5. I picked out what makes the biggest difference day-to-day, since a lot was developed throughout the year to make the language simpler, faster, and more consistent.
URI extension: parsing and manipulating URLs with a modern API - RFC
The new URI extension now ships enabled by default in PHP 8.5. It provides dedicated APIs for parsing and modifying
URIs and URLs following the RFC 3986 and WHATWG URL standards.
The implementation guarantees much more consistent parsing than parse_url, which has always had limitations and
inconsistent behavior.
PHP 8.4 and earlier
$components = parse_url('https://php.net/releases/8.4/en.php');
var_dump($components['host']);
// string(7) "php.net"
PHP 8.5
use Uri\Rfc3986\Uri;
$uri = new Uri('https://php.net/releases/8.5/en.php');
var_dump($uri->getHost());
// string(7) "php.net"
Pipe Operator: cleaner, more readable chaining - RFC
The pipe operator makes it easier to chain functions without creating intermediate variables and without nesting calls. The flow reads top to bottom, making it much easier to read, maintain, and extend.
PHP 8.4 and earlier
$title = ' PHP 8.5 Released ';
$slug = strtolower(
str_replace('.', '',
str_replace(' ', '-',
trim($title)
)
)
);
var_dump($slug);
// string(14) "php-85-released"
PHP 8.5
$title = ' PHP 8.5 Released ';
$slug = $title
|> trim(...)
|> (fn($str) => str_replace(' ', '-', $str))
|> (fn($str) => str_replace('.', '', $str))
|> strtolower(...);
var_dump($slug);
// string(14) "php-85-released"
You can check out my full explanation at this link.
Clone With: modify properties right in the clone - RFC
PHP 8.5 introduced the ability to change properties during cloning, by passing an associative array to the
clone() function.
This simplifies the "with" method pattern, very common in readonly classes, eliminating repetitive code to
rebuild objects.
PHP 8.4 and earlier
readonly class UserProfile
{
public function __construct(
public string $name,
public string $email,
public string $role = 'user',
) {}
public function withRole(string $role): self
{
$data = get_object_vars($this);
$data['role'] = $role;
return new self(...$data);
}
}
$profile = new UserProfile('Marcos', 'marcos@example.com');
$admin = $profile->withRole('admin');
PHP 8.5
readonly class UserProfile
{
public function __construct(
public string $name,
public string $email,
public string $role = 'user',
) {}
public function withRole(string $role): self
{
return clone($this, [
'role' => $role,
]);
}
}
$profile = new UserProfile('Marcos', 'marcolindev@gmail.com');
$admin = $profile->withRole('admin');
#[\NoDiscard] attribute: make sure the return value is used - RFC
The #[\NoDiscard] attribute lets you mark functions whose return value shouldn't be ignored. If the return value
isn't used, PHP will emit a warning. This increases the safety of APIs where the return value is essential, avoiding
silent bugs.
If you really want to discard the value, you can use the (void) cast to make that intent explicit.
PHP 8.4 and earlier
function getPhpVersion(): string
{
return 'PHP 8.4';
}
getPhpVersion(); // No warning
PHP 8.5
#[\NoDiscard]
function getPhpVersion(): string
{
return 'PHP 8.5';
}
getPhpVersion();
// Warning: The return value of function getPhpVersion() should either be used or intentionally ignored
You can check out my full explanation at this link.
Closures and first-class callables in constant expressions - RFC - RFC
PHP 8.5 now allows the use of static closures and first-class callables in constant expressions. This includes
attribute parameters, default values of properties and parameters, as well as constants inside a class.
This feature opens the door to more expressive, reusable configurations, without needing helper functions or extra blocks.
PHP 8.4 and earlier
class Example
{
const HANDLER = fn() => 'test'; // Fatal error
}
PHP 8.5
class Example
{
const HANDLER = (static fn() => 'test');
public function run(): string
{
return (self::HANDLER)();
}
}
$e = new Example();
var_dump($e->run());
// string(4) "test"
You can check out my full explanation at this link.
Persistent cURL Share Handles - RFC - RFC
PHP 8.5 introduced curl_share_init_persistent(), which creates persistent share handles.
Unlike curl_share_init(), these handles aren't destroyed at the end of the request.
If another handle with the same options already exists, it will be reused, avoiding initialization costs and
improving the performance of repeated requests.
PHP 8.4 and earlier
$sh = curl_share_init();
curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
$ch = curl_init('https://php.net/');
curl_setopt($ch, CURLOPT_SHARE, $sh);
curl_exec($ch);
PHP 8.5
$sh = curl_share_init_persistent([
CURL_LOCK_DATA_DNS,
CURL_LOCK_DATA_CONNECT,
]);
$ch = curl_init('https://php.net/');
curl_setopt($ch, CURLOPT_SHARE, $sh);
// This can now reuse the connection from a previous SAPI request
curl_exec($ch);
array_first() and array_last() functions - RFC
PHP 8.5 got two new functions for working with arrays: array_first() and array_last().
They return, respectively, the first and last value of the array. If the array is empty, they return null, which
is very handy when combined with the ?? operator.
PHP 8.4 and earlier
$lastEvent = $events === []
? null
: $events[array_key_last($events)];
PHP 8.5
$lastEvent = array_last($events);
You can check out my full explanation at this link.
Additional features and improvements
Besides the main features in PHP 8.5, several smaller improvements were added to make development safer, more consistent, and more predictable.
Backtrace on fatal errors
Fatal errors, like exceeding maximum execution time, now display a backtrace to make investigation easier.
More flexible attributes
Attributes can now be applied to constants.
- The
#[\Override]attribute can also now be used on properties. - The
#[\Deprecated]attribute now works on traits and constants.
Property improvements
- Static properties now support asymmetric visibility.
- Constructor-promoted properties can be marked as final.
New functions and internal API
- New
Closure::getCurrent()method, useful for recursion in closures. setcookie()andsetrawcookie()now accept the partitioned key.- New functions:
get_error_handler()andget_exception_handler(). - New
grapheme_levenshtein()function for multibyte strings. - New DOM methods:
Dom\Element::getElementsByClassName()andDom\Element::insertAdjacentHTML().
#[\DelayedTargetValidation] attribute
Using the #[\DelayedTargetValidation] attribute lets you suppress compile-time errors for attributes applied to invalid targets, useful for extensions and internal attributes.
Deprecations and backwards-compatibility breaks
Syntax and operators
- The backtick operator as an alias for
shell_exec()has been deprecated. - Non-canonical casts like
(boolean),(integer),(double), and(binary)have also been deprecated. Use(bool),(int),(float), and(string).
Engine and syntax adjustments
- The
disable_classesINI directive has been removed. - Terminating a
casewith a semicolon has been deprecated. - Using
nullas an array index, or when callingarray_key_exists(), is now deprecated. Use an empty string. - It's no longer possible to use
arrayandcallableas alias names inclass_alias().
Serialization and lifecycle
The __sleep() and __wakeup() magic methods have been softly deprecated. The recommended approach is to use
__serialize() and __unserialize().
Conversions and additional warnings
- A warning is now emitted when converting
NANto another type. - Destructuring non-array values (except
null) using[]orlist()now emits a warning. - Conversions of
floats(orstringsthat look likefloats) tointthat no longer fit the type now also raise a warning.
Final thoughts
In my view, PHP 8.5 reinforces just how consistently the language keeps evolving. The new features are useful in day-to-day work, solve real problems, and make code clearer and more modern.
Following the language's core continues to be something I really enjoy. Even without understanding every internal detail, you can see the care put into performance, safety, and runtime quality.
And honestly, I don't need to get into that old debate about what PHP "is or isn't." The language keeps delivering, keeps growing, and remains extremely relevant on its own merits.
You can check out all the changes on the
official PHP site.
Until next time!