Discover the main new features in PHP 8.6
Marcos Marcolin • August 17, 2026 • 7 min read
PHP PHP 8.6 PHP RFCWhat's new in PHP 8.6 🐘
PHP 8.6 is still under development and testing, with a planned release for the second half of November 2026. The information in this post may change until then, as RFCs get voted on and implemented.
If you follow this blog or my LinkedIn, you've probably already seen some of these new features, since I usually cover the most important RFCs and changes throughout the year.
In this post, I bring a summary of the main improvements in 8.6, based on the RFCs already accepted. I picked out what makes the biggest difference day-to-day, since a lot is developed throughout the year to make the language simpler, faster, and more consistent. I'll keep updating this post as new RFCs get voted on and implemented.
Contents
- Polling API: a native foundation for async I/O
- Partial Function Application: filling in arguments ahead of time
- The
clamp()function: restricting values to a range - More secure session defaults
- Additional features and improvements
- Deprecations and backwards-compatibility breaks
- Final thoughts
Polling API: a native foundation for async I/O - RFC
PHP 8.6 brings the new Io\Poll namespace, with a native polling API for I/O multiplexing, promising better
performance than the traditional stream_select(). The idea is to offer a common foundation that low-level
libraries, such as ReactPHP and Amp, can use to manage multiple streams, sockets, and other pollable resources.
PHP 8.5 and earlier
$read = [$server];
$write = $except = [];
stream_select($read, $write, $except, 1);
foreach ($read as $stream) {
// handle the read event
}
PHP 8.6
use Io\Poll\{Context, Event};
$server = stream_socket_server('tcp://0.0.0.0:8080');
stream_set_blocking($server, false);
$poll = new Context();
$poll->add(new StreamPollHandle($server), [Event::Read], ['type' => 'server']);
while (true) {
foreach ($poll->wait(timeoutSeconds: 1) as $watcher) {
if ($watcher->getData()['type'] === 'server' && $watcher->hasTriggered(Event::Read)) {
$client = stream_socket_accept($server);
$poll->add(new StreamPollHandle($client), [Event::Read], ['type' => 'client']);
}
}
}
For now, PHP 8.6 ships the foundation of the API. More specific handles, such as SocketPollHandle,
CurlPollHandle, TimerHandle, and SignalHandle, are left for future versions.
Partial Function Application: filling in arguments ahead of time - main RFC - companion RFC
PHP 8.6 introduces partial function application, allowing you to create closures from a function call by filling in
some arguments and leaving others for later. This is done with the ? (single argument) and ... (remaining
arguments, variadic-style) placeholders. A companion RFC made the behavior more predictable: every ? placeholder
now always generates a required parameter in the resulting closure.
This feature is a natural extension of the first-class callable syntax added in PHP 8.1, and it pairs really well with the pipe operator introduced in PHP 8.5.
PHP 8.5 and earlier
function convertCurrency(float $amount, string $from, string $to): float { /* ... */ }
$toBRL = fn (float $amount, string $from): float => convertCurrency($amount, $from, 'BRL');
PHP 8.6
function convertCurrency(float $amount, string $from, string $to): float { /* ... */ }
$toBRL = convertCurrency(?, ?, 'BRL');
$toBRL(100.0, 'USD');
// Equivalent to: fn(float $amount, string $from): float => convertCurrency($amount, $from, 'BRL');
$prices = [19.90, 249.00, 5.50];
$pricesInBRL = array_map(convertCurrency(?, 'USD', 'BRL'), $prices);
Worth noting that constructors aren't supported: you can't create a partial closure from a new expression. The
reason, according to the RFC itself, is that constructors are invoked indirectly by the engine, which would make
supporting them notably more work, and the use cases for it are rare, especially now that PHP 8.4 has lazy objects.
The clamp() function: restricting values to a range - RFC
PHP 8.6 gets the native clamp() function, which restricts a value to a minimum and maximum range. If the value is
outside the range, it returns the nearest bound; otherwise, it returns the value itself. It's such a common case
that it always ended up as some combination of min() and max() scattered across the codebase.
PHP 8.5 and earlier
$zoom = max(50, min(200, $zoom));
PHP 8.6
$zoom = clamp($zoom, min: 50, max: 200);
clamp(30, min: 50, max: 200); // 50
clamp(120, min: 50, max: 200); // 120
clamp(250, min: 50, max: 200); // 200
More secure session defaults - RFC
Three ext/session directives had security implications, but shipped with default values that left applications
unnecessarily exposed. PHP 8.6 changes these defaults so new installs are more secure out of the box, with no code
changes needed for the common case.
session.use_strict_mode: from0to1, mitigating session fixation attacks.session.cookie_httponly: from0to1, making it harder to access the session cookie via XSS.session.cookie_samesite: from empty toLax, mitigating CSRF attacks.
Additional features and improvements
Besides the main features in PHP 8.6, several smaller improvements were added to make development safer, more consistent, and more predictable.
Default values for readonly properties
readonly properties can now have a default value, which is especially useful combined with the interface
properties introduced in PHP 8.4, letting you declare fixed metadata without needing a constructor:
final readonly class InvoiceExporter implements ExporterBlueprint
{
public string $name = 'Invoice Exporter';
public string $format = 'pdf';
}
New Time\Duration class
Class that represents a fixed, context-free time span (unlike DateInterval), useful for timeouts, delays, and
measurements. It ships with methods like Duration::fromSeconds(), add(), and divideBy().
#[\Override] now also for class constants - RFC
The #[\Override] attribute, which already worked on methods and properties, can now also be used on class
constants, helping guarantee that a constant is really overriding one from the parent class.
Writing to properties of objects referenced by constants - RFC
It's now possible to change properties of an object held in a class constant, while keeping the constant itself immutable (that is, the reference to the object can't be swapped, only its internal state):
const BACKING = new stdClass();
class C
{
const O = BACKING;
}
C::O->prop = 42;
var_dump(C::O->prop);
// int(42)
More expressive enums: SortDirection and custom debug output - RFC - RFC
PHP 8.6 brings a native SortDirection enum, with the cases Ascending and Descending, meant for frameworks and
libraries to use instead of loose strings when sorting data:
$query->orderBy('created_at', SortDirection::Descending);
Enums can also now implement __debugInfo(), just like regular classes, customizing the output of var_dump():
enum OrderStatus: string
{
case Paid = 'paid';
public function __debugInfo(): array
{
return [self::class . '::' . $this->name => $this->value];
}
}
var_dump(OrderStatus::Paid);
// enum(OrderStatus::Paid) (1) {
// ["OrderStatus::Paid"]=>
// string(4) "paid"
// }
New in Reflection, enums, and functions
- New
ReflectionProperty::isReadable()andisWriteable()methods, to check property accessibility. - Doc comments can now be applied directly to function parameters.
- Internal optimizations let the engine reuse stateless closures (like simple arrow functions) across calls, reducing allocations with no code changes required.
- Type errors and other fatal errors now display the arguments received in the call, not just the expected types, making debugging a lot easier.
- Other small additions: a configurable limit on chained filters in
filter_var(), TLS session resumption support for streams, new locale methods onLocale,grapheme_strrev(), andmysqli_quote_string().
Deprecations and backwards-compatibility breaks
Syntax and reserved words
- The
list()construct has been deprecated; use the short array syntax[...]instead. - Returning a value from inside a
finallyblock has been deprecated. - Returning a value from
__construct()or__destruct()has also been deprecated. let,namespace(as a class constant),in,out,inout,is,readonly(as a function name), and_(as a constant, compile-time alias, or function name) are now deprecated as identifiers, reserved for future language features such as pattern matching and block scoping.
Form feed is now whitespace in trim()
The trim(), ltrim(), and rtrim() functions now also strip the form feed character (\f, ASCII 12) by default,
alongside space, tab, newline, carriage return, and NUL. This is a backwards-compatibility break: code that relied
on preserving this character at the edges of a string needs to be reviewed.
Objects passed to functions that expect arrays
Passing objects to functions that expect an array, such as array_walk(), array_walk_recursive(),
deflate_init(), inflate_init(), the zlib and bzip2 stream filters, mb_convert_variables(), and
http_build_query(), is now deprecated. Use get_object_vars() to convert the object before passing it in.
Deprecated functions and methods
is_double(),is_integer(), andis_long()(aliases foris_float()andis_int()), plusdoubleval()(an alias forfloatval()).strcoll()and theSORT_LOCALE_STRINGflag, tied to locale-sensitive string comparison.metaphone().- The
$case_insensitiveparameter ofdefine(). - Passing a string to
is_a()andis_subclass_of()when the$allow_stringparameter isfalse. - Several
Reflectionand SPL methods, such asReflectionProperty::setValue()/setRawValue()with incorrect types,ReflectionMethod::invoke()/invokeArgs()with objects for static methods,ArrayIteratormethods inherited fromArrayObject,spl_classes(),spl_object_hash(), and the CSV methods onSplFileObject. mysqli_stmt_init(),mysqli_get_charset(), and usingsession_set_save_handler()without thecreate_sid()andvalidateId()methods.- The Oniguruma library, used by the
mb_ereg*functions, has entered end-of-maintenance; these functions keep working for now, but should be replaced with PCRE (preg_*) in the coming years.
Final thoughts
In my view, PHP 8.6 reinforces just how consistently the language keeps evolving. The new features are useful in day-to-day work, solve real problems, and make code clearer and more modern.
This post will keep getting updated as new RFCs get voted on and accepted until the official release. You can follow the schedule and all the RFCs on the official PHP site.
Until next time!