
11 minutes read
PHP 8.5 is coming soon: 15 new features and changes
Table of contents
- → Introduction
- → PHP 8.5 release date and schedule
- → How to install PHP 8.5 (Homebrew and Docker)
-
→
New in PHP 8.5
- → Pipe operator (|>)
- → URI extension (RFC 3986 and WHATWG URL)
- → array_first and array_last
- → Fatal error backtraces (fatal_error_backtraces)
- → Clone with v2
- → Handler introspection (get_error_handler/get_exception_handler)
- → Intl updates (list formatter, RTL check, NumberFormatter updates)
- → grapheme_levenshtein
- → Attributes on constants
- → Final property promotion
- → Closures and first-class callables in constant expressions
- → cURL: curl_multi_get_handles
- → See only the settings you changed with php –ini=diff
- → The new PHP_BUILD_DATE constant
- → Marking return values as important (#[\NoDiscard])
- → Under the hood
- → What else is coming in 8.5
- → Conclusion
Introduction
PHP is an open-source project. Knowing what’s going on in PHP 8.5 only takes a minute of research. For instance, this page lists all the accepted RFCs for different versions of PHP, including PHP 8.5. In this post, I highlight PHP 8.5 features you’ll actually use.
PHP 8.5 release date and schedule
General Availability (GA) is planned for November 20, 2025, according to the preparation tasks list. It will be tested through three alpha releases, three betas, and four release candidates. You can also track the schedule on PHP.Watch’s PHP 8.5 page.
Date | Release of PHP 8.5 |
---|---|
Jul 03 2025 | Alpha 1 |
Jul 17 2025 | Alpha 2 |
Jul 31 2025 | Alpha 3 |
Aug 12 2025 | Feature freeze |
Aug 14 2025 | Beta 1 |
Aug 28 2025 | Beta 2 |
Sep 11 2025 | Beta 3 |
Sep 25 2025 | RC1 |
Oct 09 2025 | RC2 |
Oct 23 2025 | RC3 |
Nov 06 2025 | RC4 |
Nov 20 2025 | GA |
How to install PHP 8.5 (Homebrew and Docker)
If you’re on macOS and want to test the PHP 8.5 release early, Homebrew makes it easy. Bottles are available, and these builds track 8.5.0-dev (nightlies) until GA. The safest way to avoid conflicts with Homebrew core is to use the dedicated tap and fully qualified formula. The tap’s status is documented in the tap README.
- Install the Homebrew package manager if it’s not done already.
- Run
brew update
to make sure Homebrew and the formulae are up to date. - Add the PHP tap:
brew tap shivammathur/php
. - Install PHP 8.5 from the tap:
brew install shivammathur/php/php@8.5
. - Link it so the
php
shim targets 8.5:brew link --overwrite --force shivammathur/php/php@8.5
(this overwrites thephp
shim).
Tip if you have multiple PHP versions installed: run brew unlink php
(or another version like php@8.3
) before linking 8.5; later run brew link
for the one you want active.
If you prefer not to touch your Homebrew PHP, you can test using the official PHP Docker images.
If you want to learn more about how to install PHP on your Mac, I wrote something for you: PHP for Mac: get started fast using Laravel Valet
New in PHP 8.5
Pipe operator (|>)
The PHP 8.5 pipe operator (|>
) is my favorite of the PHP 8.5 features. It passes the result of the left expression as the first argument to the callable on the right, letting you write readable, left-to-right pipelines.
A type-safe example that stays string-only until the end:
$length = " Hello, World! " |> trim(...) |> strtoupper(...) |> htmlentities(...) |> strlen(...);
Rules to remember for the PHP 8.5 pipe operator:
- Each callable in the chain must accept the piped value as its first parameter and have at most one required parameter.
- You can pipe into closures, arrow functions, first-class callables, array callables, and invokable objects.
- Built-ins that accept no parameters cannot be used directly: wrap them in a callable that accepts one parameter, for example
(|> (fn ($_): string => phpversion()))
inside a chain. - Precedence is left to right. When mixing with math, comparisons, null coalescing (??), and the ternary operator, use parentheses to force the order you intend.
- Gotchas: wrap arrow-function steps in parentheses when used in a chain; by-reference callables are not allowed on the right-hand side.
A quick example showing an arrow function in a chain:
$result = " a " |> trim(...) |> (fn ($s) => $s . "!") |> strlen(...);
Learn more: the pipe operator overview on PHP.Watch and the RFC on wiki.php.net.
URI extension (RFC 3986 and WHATWG URL)
PHP 8.5 includes a new, always-available URI extension with standards-compliant parsers for both RFC 3986 and the WHATWG URL standard. You get immutable value objects, withers for safe changes, normalization, and exceptions for invalid input.
use Uri\Rfc3986\Uri; $u = new Uri('https://example.com/a/../b?x=1#frag'); $u = $u->withPath('/b'); echo (string) $u; // https://example.com/b?x=1#frag
Overview and background: The PHP Foundation: the PHP 8.5 URI extension and the URI extension RFC.
array_first and array_last
Two small helpers you’ll actually use: array_first
and array_last
return the first and last element of an array.
Let’s say you have an array with inconsistent keys:
$numbers = [10, 20, 30]; unset($numbers[0]); array_first($numbers); // 20 array_last($numbers); // 30;
$numbers[0]
doesn’t exist anymore and you have can’t know it in advance. And obviously, you usually never know which key is the last one of an array, so array_last()
’s usefulness is immediately obvious.
Learn more: RFC: array_first and array_last.
Fatal error backtraces (fatal_error_backtraces)
Debugging gets easier with fatal error backtraces. With the new fatal_error_backtraces
INI (defaults to On in both development and production), PHP shows a stack trace for fatal errors and respects #[\SensitiveParameter]
and zend.exception_ignore_args
.
; php.ini fatal_error_backtraces = 1
Details: PHP.Watch: fatal error backtraces and the RFC.
Clone with v2
PHP 8.5 upgrades clone
so you can pass a second array argument to adjust properties on the cloned object. This works with readonly
, honors __clone
, and respects property hooks. It is a nice quality-of-life win for immutable patterns. Learn more: clone with v2 RFC.
Handler introspection (get_error_handler/get_exception_handler)
New functions get_error_handler()
and get_exception_handler()
let you introspect the current handlers at runtime.
set_error_handler(fn () => true); var_dump(get_error_handler()); restore_error_handler(); set_exception_handler(fn (Throwable $e) => null); var_dump(get_exception_handler()); restore_exception_handler();
Learn more: RFC for handler introspection and the PHP.Watch overview.
Intl updates (list formatter, RTL check, NumberFormatter updates)
Internationalization gets a nice bump in PHP 8.5: IntlListFormatter
formats human-readable lists; locale_is_right_to_left()
and Locale::isRightToLeft()
help with layout for right-to-left scripts; NumberFormatter
adds compact decimal formats; and Locale::addLikelySubtags()
/::minimizeSubtags()
help normalize locale tags.
$lf = new IntlListFormatter('en', IntlListFormatter::TYPE_CONJUNCTION); echo $lf->format(['apples', 'bananas', 'oranges']); // apples, bananas, and oranges $isRtl = locale_is_right_to_left('ar'); // true
Learn more: IntlListFormatter and RTL checks on PHP.Watch. Also see the 8.5 alpha notes for compact decimals and locale helpers in PHP.Watch’s 8.5 alpha 1 summary.
grapheme_levenshtein
grapheme_levenshtein
computes a Unicode-aware Levenshtein distance that understands grapheme clusters (what users perceive as characters), which is perfect for strings like “café”.
grapheme_levenshtein('café', 'cafe'); // 1
Learn more: [PHP.Watch explainer](PHP 8.5 RFC: Grapheme cluster for levenshtein, grapheme_levenshtein function).
Attributes on constants
PHP 8.5 lets you add attributes directly to const
declarations. The main constraint: one constant per statement.
#[\MyAttribute] const EXAMPLE = 1; #[\MyAttribute] const A = 1; // OK // const A = 1, B = 2; // Not allowed with attributes
You can reflect these with ReflectionClassConstant::getAttributes()
or ReflectionConstant::getAttributes()
. The built-in #[\Deprecated]
works on constants too, so you can mark constants as deprecated and surface warnings.
Learn more: RFC: attributes on constants.
Final property promotion
Final property promotion rounds out the work started in 8.4 with final properties and property hooks. In PHP 8.5 you can mark a promoted property as final
directly in the constructor, preventing redeclaration or overrides in child classes. Visibility is optional when using final
(defaults to public
), and you can combine this with property hooks.
Before PHP 8.5, you had to declare the final property in the class body, then assign it in the constructor:
class User { final public readonly string $name; public function __construct(string $name) { $this->name = $name; } }
With PHP 8.5, you can promote and finalize the property in one place:
class User { public function __construct( final public readonly string $name ) {} }
Learn more: RFC: final property promotion.
Closures and first-class callables in constant expressions
PHP 8.5 allows static closures in constant expressions and also allows first-class callables, which complements the closures-in-constants feature. Both are supported now, with a few restrictions.
class Foo { // Static closure as a constant public const UPPER = static function (string $v): string { return strtoupper($v); }; // First-class callable as a constant public const LOWER = 'strtolower'(...); }
Notes:
- Closures must be
static
. No variable capture (use ($x)
) or arrow functions (they auto-capture) are allowed in constant expressions. - First-class callables like
'strtolower'(...)
orBar::baz(...)
are allowed. (Closures are covered by the separate “closures in constant expressions” RFC.)
Learn more: Closures in constant expressions (RFC) and first-class callables in constants on PHP.Watch.
cURL: curl_multi_get_handles
curl_multi_get_handles
lets you retrieve all easy handles attached to a multi handle, which is useful for debugging and housekeeping.
$mh = curl_multi_init(); $ch = curl_init('https://example.com'); curl_multi_add_handle($mh, $ch); $handles = curl_multi_get_handles($mh); // array of CURLHandle
Learn more: PHP.Watch note.
See only the settings you changed with php –ini=diff
A small but powerful CLI addition: php --ini=diff
prints only INI settings that differ from PHP’s built-in defaults, which is great for quick diagnostics.
php --ini=diff
Example output:
Non-default INI settings: html_errors: "1" -> "0" implicit_flush: "0" -> "1" max_execution_time: "30" -> "0"
Ops note: a new max_memory_limit
INI can cap memory_limit
at the system level (INI_SYSTEM). See PHP.Watch: max_memory_limit.
The new PHP_BUILD_DATE constant
There’s a new PHP_BUILD_DATE
constant that exposes the timestamp of the binary you’re running, which helps when comparing nightlies or deployment artifacts.
echo PHP_BUILD_DATE; // e.g., 2025-08-31
Learn more: PHP.Watch: PHP_BUILD_DATE.
Marking return values as important (#[\NoDiscard])
#[\NoDiscard]
marks functions or methods whose return values must not be ignored, helping developers spot silent bugs.
#[\NoDiscard("processing might fail for individual items")] function bulk_process(array $items): array { /* ... */ } bulk_process($items); // Warning. $results = bulk_process($items); // OK. (void) bulk_process($items); // Explicitly ignore (suppresses the warning).
Engine details: ignoring a #[\NoDiscard]
return value emits E_WARNING
for internal functions and E_USER_WARNING
for userland; functions that return void
/never
, and magic methods that must be void
, cannot use it. Tip: the (void)
cast is a statement that explicitly discards a value.
Learn more: Marking return value as important (RFC).
Under the hood
- OPcache is no longer optional: it is always compiled in from PHP 8.5 onward, while enablement remains controlled by INI settings. This affects packaging and “what is included by default” in many environments. See the OPcache required RFC.
- A new
max_memory_limit
INI capsmemory_limit
at the system level (INI_SYSTEM), which helps lock down production defaults. See PHP.Watch: max_memory_limit.
What else is coming in 8.5
- DOM in the 8.5 line:
outerHTML
,insertAdjacentHTML
, andchildren
. See alpha 1 notes. - Smaller items worth noting: CHIPS/partitioned cookies;
#[Deprecated]
support for traits;#[Override]
allowed on properties;FILTER_THROW_ON_FAILURE
; locale support for case-insensitive grapheme functions; and the accepted RFC for persistent cURL share handles. See the PHP 8.5 RFC list.
Conclusion
The pipe operator (|>
) steals the spotlight in PHP 8.5, making code cleaner and more readable. The new URI extension gives PHP a modern, standards-based URL/URI API, and clone
with v2 improves immutable workflows. Constants also get love with support for attributes, static closures, and first-class callables. Combined with final property promotion and #[\NoDiscard]
, PHP 8.5 is shaping up to be a strong release. You can explore more proposals and accepted changes on the PHP 8.5 RFC list.
Did you like this article? Then, keep learning:
- Learn about features and release timeline of PHP 8.4, the previous PHP version
- A quick Mac install guide for PHP using Laravel Valet, complements PHP 8.5 install tips
- Compare upgrading guides for Laravel since it's often paired with PHP, useful context
- Explore new features in Laravel 10, often used with PHP, complements PHP 8.5 updates
- Step-by-step Laravel 11 upgrade guide, valuable for developers tracking framework and PHP versions
- Overview of Laravel 11 release features, to keep PHP Laravel pairing in sync
- Early look at Laravel 12, aligns with PHP 8.5 era and upcoming development
- Discover a new PHP 8.3 feature related to attributes, related to PHP 8.5 attribute improvements
- Stay updated with PHP 8.3 changes, good before PHP 8.5 upgrade
- An overview of upcoming major PHP 9.0 changes, good for future-facing readers
0 comments