Discover the main new features in PHP 8.5

Marcos Marcolin β€’ November 21, 2025 β€’ 5 min read

php php85 phprfc

What'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.

Property improvements

New functions and internal API

#[\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

Engine and syntax adjustments

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

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!

Share: