PHP 8.5 was released on 20 November 2025. Almost a year on, it is the version most new projects start on, and most major platforms now support it. Release notes list every feature with equal weight. This article covers the ones that have actually changed how we write code day to day, and the deprecations that come up during upgrades.

The pipe operator

The pipe operator |> passes the value on its left into the callable on its right. A chain of transformations now reads in the order it runs, rather than inside out.

// Before: read from the inside out
$slug = strtolower(str_replace('.', '', str_replace(' ', '-', trim($title))));

// PHP 8.5: read from top to bottom
$slug = $title
    |> trim(...)
    |> (fn($s) => str_replace(' ', '-', $s))
    |> (fn($s) => str_replace('.', '', $s))
    |> strtolower(...);

It works best for small, pure steps: cleaning input, formatting output, building a slug or a cache key. Each step must take exactly one argument, which is why the example wraps str_replace() in an arrow function. PHP 8.6 adds partial function application, which removes most of those wrappers.

Use it with some restraint. A pipe of eight anonymous functions is no easier to debug than nested calls. If a step needs a name, give it one.

Clone with

Immutable value objects used to need a with...() method that cloned the object and then worked around readonly to change one property. PHP 8.5 lets clone() take the properties to change:

readonly class Money
{
    public function __construct(
        public int $amount,
        public string $currency,
    ) {}

    public function withAmount(int $amount): self
    {
        return clone($this, ['amount' => $amount]);
    }
}

This is one of those features that removes boilerplate across a whole codebase. Value objects for prices, addresses, date ranges and configuration all get shorter.

The URI extension

For years PHP’s answer to URL handling was parse_url(), which follows no standard precisely and has well-known edge cases. That matters for security: when two parts of a system parse the same URL differently, attackers can sometimes slip a request past a check. Server-side request forgery bugs often start this way.

PHP 8.5 includes a URI extension that follows the actual standards: Uri\Rfc3986\Uri for RFC 3986 and Uri\WhatWg\Url for the WHATWG URL standard used by browsers.

use Uri\Rfc3986\Uri;

$uri = new Uri('https://pointofcode.com/blog/?page=2');
echo $uri->getHost(); // pointofcode.com

Our rule for new code is simple: if a URL comes from a user and decides where the server connects, parse it with the new classes, not parse_url().

array_first() and array_last()

Small, but you will use them every week:

$latest = array_last($events);   // null if the array is empty
$first  = array_first($results);

They replace reset() and end(), which move the array’s internal pointer, and the longer $a[array_key_last($a)] pattern with its empty-array check.

#[\NoDiscard]

Mark a function whose return value must not be ignored, and PHP emits a warning when a caller drops it:

#[\NoDiscard]
function saveOrder(Order $order): Result { /* ... */ }

saveOrder($order);         // Warning: the return value should be used
(void) saveOrder($order);  // Explicitly ignored, no warning

This is valuable for functions that report failure through their return value instead of an exception. A forgotten result check is a common source of silent data loss.

Fatal errors now include a backtrace

When a request hits the maximum execution time or runs out of memory, PHP 8.5 logs where it was, not just the file and line where it stopped. For anyone who has tried to diagnose a timeout in a large application from a one-line log entry, this alone justifies the upgrade.

Deprecations you will meet during upgrades

  • The backtick operator as a shortcut for shell_exec() is deprecated.
  • Non-standard cast names (boolean), (integer), (double) and (binary) are deprecated. Use (bool), (int), (float) and (string).
  • Using null as an array key, or in array_key_exists(), is deprecated.
  • Ending a case with a semicolon instead of a colon is deprecated.
  • __sleep() and __wakeup() are soft-deprecated in favour of __serialize() and __unserialize().
  • Casting a float that cannot be represented as an integer, or NAN, now emits a warning.
  • The disable_classes ini setting has been removed. Check hardened server configurations that set it.

The null array key change is the one that shows up most in real code, usually from a lookup like $map[$row['parent_id']] where the value can be null. The warnings point you straight at the bugs.

Is it time to move?

PHP 8.5 has security support until 31 December 2029, the longest of any current branch. For custom applications and new projects it is the right default. For platforms such as Magento or WordPress, follow the platform’s compatibility list and the support of your third-party extensions. Those decide the timing, not the PHP release date.

Sources