
9 minutes read
A peek at PHP 8.5's features and changes
Table of contents
- → Introduction
- → PHP 8.5 Release Date and Schedule
- → How to Install PHP 8.5 on macOS (Homebrew)
-
→
New in PHP 8.5
- → Pipe operator (|>)
- → array_first and array_last
- → Fatal error backtraces (fatal_error_backtraces)
- → 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
- → CLI: php –ini=diff
- → PHP_BUILD_DATE
- → Marking return values as important (#[\NoDiscard])
- → 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
PHP 8.5 will be released on November 20, 2025, according to the preparation tasks list. It will be tested through three alpha releases, three betas, and four release candidates.
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 |
Note: dates may shift slightly.
How to Install PHP 8.5 on macOS (Homebrew)
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.
- 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
.
Tip if you have multiple PHP versions installed: brew unlink php
(or another version like php@8.3
) before linking 8.5, and later brew link
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
What changed since 8.4?
- The new PHP 8.5 pipe operator makes readable, left-to-right function chaining possible.
array_first
/array_last
are built-in helpers for common array access.- Fatal error backtraces are now available by default for faster debugging.
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 with an arrow function that ignores the piped value, e.g.
fn ($_): string => phpversion()
. - Precedence is left-to-right. When mixing with math, comparisons, null coalescing (??), and the ternary operator, use parentheses to force the order you intend.
Learn more: the pipe operator overview on PHP.Watch and the RFC on wiki.php.net.
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, making code a little clearer.
$numbers = [10, 20, 30]; array_first($numbers); // 10 array_last($numbers); // 30
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 (on by default), 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.
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: 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
gets 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.
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.
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.
CLI: 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"
Learn more: PHP.Watch: php –ini=diff.
PHP_BUILD_DATE
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).
What else is coming in 8.5
Here’s a quick summary of additional PHP 8.5 features mentioned above so you can scan fast:
- PHP 8.5 pipe operator (
|>
) for readable pipelines. array_first
andarray_last
helpers.- Fatal error backtraces via
fatal_error_backtraces
(on by default). - Handler introspection with
get_error_handler()
/get_exception_handler()
. - Intl updates:
IntlListFormatter
, RTL checks, compact decimals, and new Locale helpers. - Unicode-aware
grapheme_levenshtein
. - Attributes on constants (one constant per statement).
- Final property promotion (final on promoted properties).
- Closures and first-class callables allowed in constant expressions.
- cURL:
curl_multi_get_handles
. - CLI:
php --ini=diff
. PHP_BUILD_DATE
constant.- Honorable mention for web devs: DOM additions like
outerHTML
andinsertAdjacentHTML
landed in the 8.5 line as well.
Conclusion
The pipe operator (|>
) definitely steals the spotlight in PHP 8.5, making code cleaner and more readable. 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 RFC index.
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