What's New in PHP 8.5

PHP 8.5 was released in November 2025. This lesson collects its new features in one place, followed by the highlights of PHP 8.4 and a table of the 8.0–8.3 features used throughout this course — handy if you learned PHP years ago, or maintain code written for older versions.

The pipe operator

|> passes the value on its left into the callable on its right, so a chain of transformations reads from top to bottom (lesson 31):

PHP
<?php

$words = "  the QUICK brown fox  "
    |> trim(...)
    |> strtolower(...)
    |> (fn($s) => explode(' ', $s))
    |> (fn($w) => array_map(ucfirst(...), $w))
    |> (fn($w) => implode(' ', $w));

echo $words, "\n";
Output
The Quick Brown Fox

Clone with new property values

clone() now accepts an array of properties to change on the copy, which makes "wither" methods on immutable objects a one-liner (lesson 41):

PHP
<?php

final readonly class Color
{
    public function __construct(public int $r, public int $g, public int $b, public float $alpha = 1.0) {}

    public function withAlpha(float $alpha): static
    {
        return clone($this, ['alpha' => $alpha]);
    }
}

$red = new Color(255, 0, 0);
$faded = $red->withAlpha(0.5);
echo "$red->alpha → $faded->alpha\n";
Output
1 → 0.5

#[\NoDiscard]

Marks a function whose return value must not be ignored — typically one that returns a result or error instead of throwing. Ignoring it produces a warning; casting to (void) discards it on purpose:

PHP
<?php

#[\NoDiscard("the new list is returned; the original is unchanged")]
function withItem(array $list, string $item): array
{
    return [...$list, $item];
}

$cart = ['pen'];
withItem($cart, 'notebook');            // bug: result thrown away
(void) withItem($cart, 'stapler');      // explicitly ignored, no warning
$cart = withItem($cart, 'notebook');
echo implode(', ', $cart), "\n";
Output
Warning: The return value of function withItem() should either be used or intentionally ignored by casting it as (void), the new list is returned; the original is unchanged in main.php on line 10
pen, notebook

array_first() and array_last()

PHP
<?php

$scores = ['ana' => 82, 'ben' => 91, 'cleo' => 77];

echo array_first($scores), " ", array_last($scores), "\n";
var_dump(array_first([]));
Output
82 77
NULL

They return null for an empty array and, unlike reset() and end(), don't touch the array's internal pointer.

A standards-compliant URI parser

The new URI extension parses URLs according to RFC 3986 or the WHATWG URL standard used by browsers, replacing the many quirks of parse_url(). It can also resolve relative links:

PHP
<?php

$uri = Uri\Rfc3986\Uri::parse('https://shop.example.com:8443/products/42?ref=mail#reviews');
echo $uri->getHost(), " | ", $uri->getPort(), " | ", $uri->getPath(), " | ", $uri->getQuery(), "\n";

$base = new Uri\Rfc3986\Uri('https://example.com/docs/guide/intro');
echo $base->resolve('../api?v=2')->toString(), "\n";

$url = new Uri\WhatWg\Url('HTTPS://EXAMPLE.com/a/./b/../c');
echo $url->toAsciiString(), "\n";

var_dump(Uri\Rfc3986\Uri::parse('not a uri at all'));
Output
shop.example.com | 8443 | /products/42 | ref=mail
https://example.com/docs/api?v=2
https://example.com/a/c
NULL

Closures and callables in constant expressions

Static closures and first-class callables can now appear in constants, default values and attribute arguments:

PHP
<?php

final class Validators
{
    public const array RULES = [
        'name' => static function (string $v): bool { return strlen($v) >= 2; },
        'email' => static function (string $v): bool { return filter_var($v, FILTER_VALIDATE_EMAIL) !== false; },
    ];
}

foreach (['name' => 'A', 'email' => 'ana@example.com'] as $field => $value) {
    echo $field, ": ", (Validators::RULES[$field])($value) ? "ok" : "invalid", "\n";
}
Output
name: invalid
email: ok

Smaller additions

  • #[\Override] can now be applied to properties as well as methods.
  • Fatal errors (such as exceeding the memory limit) now include a backtrace, making them much easier to diagnose.
  • get_error_handler() and get_exception_handler() return the current handlers.
  • New PHP_BUILD_DATE constant and max_memory_limit ini setting.
  • Attributes can be placed on constants, and constructor-promoted properties can be final.

Deprecations in 8.5

Deprecated features still work but print a notice, and will be removed in a future major version:

PHP
<?php

$n = (integer) "42";
$on = (boolean) 1;
echo $n, " ", var_export($on, true), "\n";
Output
Deprecated: Non-canonical cast (integer) is deprecated, use the (int) cast instead in main.php on line 3

Deprecated: Non-canonical cast (boolean) is deprecated, use the (bool) cast instead in main.php on line 4
42 true

Also deprecated: the backtick operator for running shell commands (use shell_exec()), using null as an array offset, and the __sleep()/__wakeup() methods in favour of __serialize()/__unserialize().

PHP 8.4 highlights

FeatureExampleLesson
Property hookspublic string $email { set => strtolower($value); }35
Asymmetric visibilitypublic private(set) float $balance;35
new without parenthesesnew Cart()->add($item)31
Array find functionsarray_find, array_find_key, array_any, array_all23
Multibyte trim and casemb_trim, mb_ucfirst, mb_lcfirst19
#[\Deprecated]Mark your own functions as deprecated—
BcMath\NumberExact decimal arithmetic with operators8
HTML5 parserDom\HTMLDocument::createFromString($html)—
Lazy objectsObjects initialised on first use (for frameworks and ORMs)—

Catching up: PHP 8.0–8.3

VersionKey features
8.0 (2020)Named arguments, match, nullsafe ?->, constructor promotion, union types, attributes, str_contains, saner string-number comparison, JIT
8.1 (2021)Enums, readonly properties, first-class callables f(...), never, new in initialisers, fibers, array_is_list
8.2 (2022)Readonly classes, DNF types, true/false/null types, the Random extension, deprecated dynamic properties
8.3 (2023)Typed class constants, #[\Override], json_validate, dynamic class constant fetch, mb_str_pad

Upgrading

  • Read the official migration guide for each version on php.net — it lists every backward-incompatible change.
  • Run your test suite on the new version, and turn on E_ALL to see deprecation notices before they become errors.
  • Tools such as Rector can rewrite code automatically to use newer syntax and replace deprecated features.
  • Check that your Composer dependencies support the new version (composer why-not php 8.5).

PHP 8.6 is due in November 2026. Each release receives two years of active support and two more years of security fixes, so PHP 8.5 is supported until the end of 2029.

Try it yourself

Rewrite this nested call using the pipe operator, then add a step that limits the result to 20 characters.

Try it yourself · main.php
Edit the code, copy it into main.php, then run it with php main.php (PHP 8.5). How to install PHP
Output
learning-php-85-pipes-clones--more
learning-php-85-pipes-clones--more
bool(true)