PHP 8.5: New Features and Upgrade Guide
Table of contents
PHP 8.5 was released on November 20, 2025, and if you maintain a long-lived PHP codebase (as I do with Magento projects), the question is no longer "what is new?" but "is it time to upgrade, and what will break?". In this post I go through the PHP 8.5 features I will actually use, and the deprecations you should grep for before flipping the version in your Dockerfile.
The pipe operator
The headline feature is the pipe operator |>, which chains callables from left to right without intermediary variables. The official example turns a title into a slug:
$title = ' PHP 8.5 Released ';
$slug = $title
|> trim(...)
|> (fn($str) => str_replace(' ', '-', $str))
|> (fn($str) => str_replace('.', '', $str))
|> strtolower(...);
// "php-85-released"
I like it for data-massaging code, where nested calls like strtolower(str_replace(...)) read inside-out. Use it for short, linear transformations; if you need three arrow functions on every line, a small named function is still clearer.
Clone with and immutable objects
clone() now accepts an array of property updates, which finally makes the "with-er" pattern painless on readonly classes:
readonly class Color
{
public function __construct(
public int $red,
public int $green,
public int $blue,
public int $alpha = 255,
) {}
public function withAlpha(int $alpha): self
{
return clone($this, ['alpha' => $alpha]);
}
}
Value objects (prices, addresses, DTOs) are everywhere in e-commerce code, and this removes a lot of constructor-copying boilerplate.
Small additions that add up
array_first()andarray_last()return the first or last value, ornullfor an empty array. No morereset()/end()tricks that mutate the internal pointer.- The URI extension parses and modifies URIs following RFC 3986 and WHATWG URL standards, with objects such as
Uri\Rfc3986\Uri, instead of the quirks ofparse_url(). #[\NoDiscard]warns when a return value is ignored; cast to(void)to say it is intentional. Handy for methods returning a result object you must check.- Fatal errors now include a backtrace, which makes "maximum execution time exceeded" far easier to debug in production logs.
- Persistent cURL share handles (
curl_share_init_persistent()) can reuse connections across requests, useful for code calling the same APIs constantly.
Deprecations to fix first
Deprecations are what turn an upgrade into a bad afternoon, so check these before anything else:
- The backtick operator (alias of
shell_exec()) is deprecated. - Non-canonical casts
(boolean),(integer),(double)and(binary)are deprecated; use(bool),(int),(float),(string). - Using
nullas an array offset or inarray_key_exists()is deprecated; use an empty string. - Ending a
casewith a semicolon instead of a colon is deprecated. __sleep()and__wakeup()are soft-deprecated in favour of__serialize()and__unserialize().- The
disable_classesINI setting has been removed. - New warnings appear when casting
NAN, when destructuring non-array values, and when casting floats that cannot be represented as an integer.
Most of these can be found with a quick search. Old third-party modules are the usual offenders, particularly the casts and null offsets.
My upgrade checklist
- Read the official PHP 8.5 migration guide.
- Check that your framework and every Composer dependency declare support for 8.5 (
composer why-not php 8.5is a quick start). - Run your test suite and static analysis on an 8.5 container with
error_reporting = E_ALLso deprecations are visible. - Fix deprecations in your own code first, then update or patch dependencies.
- Deploy to staging, watch the logs for a few days, then production.
Conclusion
PHP 8.5 is not a revolution, but the pipe operator, clone with, array_first()/array_last() and better fatal-error backtraces all remove small daily frictions. The upgrade cost is mostly deprecation clean-up, and that is cheap if you start early. If you want a broader view of where the language came from, see my post on the evolution of PHP.
Sources: php.net: PHP 8.5 release announcement, Phoronix: PHP 8.5 Released.