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 $words = " the QUICK brown fox " |> trim(...) |> strtolower(...) |> (fn($s) => explode(' ', $s)) |> (fn($w) => array_map(ucfirst(...), $w)) |> (fn($w) => implode(' ', $w)); echo $words, "\n";
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 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";
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 #[\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";
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 $scores = ['ana' => 82, 'ben' => 91, 'cleo' => 77]; echo array_first($scores), " ", array_last($scores), "\n"; var_dump(array_first([]));
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 $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'));
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 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"; }
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()andget_exception_handler()return the current handlers.- New
PHP_BUILD_DATEconstant andmax_memory_limitini 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 $n = (integer) "42"; $on = (boolean) 1; echo $n, " ", var_export($on, true), "\n";
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
| Feature | Example | Lesson |
|---|---|---|
| Property hooks | public string $email { set => strtolower($value); } | 35 |
| Asymmetric visibility | public private(set) float $balance; | 35 |
new without parentheses | new Cart()->add($item) | 31 |
| Array find functions | array_find, array_find_key, array_any, array_all | 23 |
| Multibyte trim and case | mb_trim, mb_ucfirst, mb_lcfirst | 19 |
#[\Deprecated] | Mark your own functions as deprecated | — |
BcMath\Number | Exact decimal arithmetic with operators | 8 |
| HTML5 parser | Dom\HTMLDocument::createFromString($html) | — |
| Lazy objects | Objects initialised on first use (for frameworks and ORMs) | — |
Catching up: PHP 8.0–8.3
| Version | Key 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_ALLto 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.
learning-php-85-pipes-clones--more learning-php-85-pipes-clones--more bool(true)