News

What's New in PHP 8.6

Published
What's New in PHP 8.6 image

PHP's annual release is just around the corner! PHP 8.6 will be released on November 19, 2026. PHP 8.6 is in beta now, with a feature freeze on September 22 and the first release candidate two days later:

Release Timeline

The PHP 8.6 preparation page has the following timeline for the release:

  • Alpha 1 through Alpha 3: July 2 to July 30, 2026
  • Beta 1 (soft feature freeze): August 13, 2026
  • Beta 3: September 10, 2026
  • Feature freeze: September 22, 2026
  • RC 1: September 24, 2026
  • RC 4: November 5, 2026
  • GA: November 19, 2026

The release managers are Daniel Scherzer, Matteo Beccati, and Joe Ferguson.

Partial Function Application

Partial function application (PFA) lets you call a function with some arguments filled in and get back a closure that takes the rest. The ? placeholder marks a single argument to fill later, and ... stands in for all remaining arguments:

$makeSlug = str_replace(' ', '-', ?);
 
$makeSlug('Hello World'); // Hello-World
 
$titles = array_map(strtolower(?), $titles);

The resulting closure keeps the parameter names, types, and defaults of the original function. Arguments you supply are evaluated when the partial is created, not when it is called. A follow-up RFC settled that every ? placeholder becomes a required parameter on the closure, even if the original parameter was optional.

An earlier version of this proposal was declined in 2021. The v2 RFC passed 33 to 0.

The clamp() Function

The new clamp() function returns the value if it falls within the bounds, or the nearest bound if it does not:

clamp(10, min: 0, max: 100); // 10
clamp(101, min: 0, max: 100); // 100
clamp(-1, min: 0, max: 100); // 0

It works with any comparable type, including strings and DateTime objects. Passing a $min greater than $max throws a ValueError. This replaces the common min(max($value, $min), $max) pattern, which is easy to get backwards.

A Duration Class

PHP 8.6 adds Time\Duration, a final readonly class that represents a stopwatch-style length of time with nanosecond precision. It has factory methods for each unit, arithmetic methods, and comparison support:

use Time\Duration;
 
$oneSecond = Duration::fromSeconds(1);
$halfSecond = $oneSecond->divideBy(2);
$total = $oneSecond->add($halfSecond);
 
$delay = Duration::fromMilliseconds(100)->multiplyBy(2 ** $attempt);
 
$total > $delay; // comparison operators work

Durations can also be built from an ISO 8601 duration string. The class is intended as a shared type that core functions and the new polling API can accept instead of loose integers and floats. See the Duration RFC for the full method list.

Readonly Property Defaults

Starting in PHP 8.6, readonly properties can have a default value. Before 8.6 this was a compile-time error, which made it awkward to satisfy get-only interface properties from PHP 8.4 with a fixed value:

final readonly class CreateBooksTable implements Migration
{
public string $name = '2026_01_01_create_books_table';
}

The RFC only removes the restriction. Readonly semantics are unchanged, so the property still cannot be reassigned after initialization.

DocComments for Function Parameters

Doc comments can now sit directly on a parameter, and ReflectionParameter::getDocComment() returns them. This avoids repeating the parameter name in a @param tag above the function:

function search(
/** Terms to search for in the database */
string $query,
/** Maximum number of entries to return */
int $limit = 10,
): array {
// ...
}

Static analyzers and IDEs can read the comment from the parameter itself. See the parameter DocComments RFC.

A Polling API

The new Io\Poll namespace gives PHP a unified interface to platform polling mechanisms: epoll on Linux, kqueue on BSD and macOS, event ports on Solaris, and WSAPoll on Windows. It replaces stream_select() for anyone building event loops or async runtimes in userland:

use Io\Poll\{Context, Event, StreamPollHandle};
 
$poll = new Context();
$server = stream_socket_server('tcp://0.0.0.0:8080');
stream_set_blocking($server, false);
 
$poll->add(new StreamPollHandle($server), [Event::Read], ['type' => 'server']);
 
while (true) {
foreach ($poll->wait(1) as $watcher) {
if ($watcher->hasTriggered(Event::Read)) {
// accept the connection
}
}
}

The RFC states the main motivation is internal, such as signal handling and FPM improvements, with userland async frameworks as the secondary audience. See the Polling API RFC.

SortDirection Enum

A global SortDirection enum with Ascending and Descending cases is now built in. Nothing in core accepts it yet. The RFC proposes it as a shared type so libraries stop defining their own, with query builders given as the example:

$query->orderBy('created_at', SortDirection::Descending);

Enums Can Implement __debugInfo()

Enums were not allowed to define most magic methods when they shipped in 8.1. PHP 8.6 lifts that restriction for __debugInfo(), since it needs no state, so var_dump() output can be customized:

enum Status: int
{
case Ok = 200;
 
public function __debugInfo(): array
{
return [__CLASS__ . '::' . $this->name . ' = ' . $this->value];
}
}

Stream Error Handling

Streams get a unified error model. A new error_mode context option chooses between the current warnings, exceptions, or silence, and stream_last_errors() returns structured StreamError objects for the last operation:

$context = stream_context_create([
'stream' => ['error_mode' => StreamErrorMode::Exception],
]);
 
try {
$stream = fopen('/nonexistent/file.txt', 'r', false, $context);
} catch (StreamException $e) {
foreach ($e->getErrors() as $error) {
echo $error->code->name . ': ' . $error->message;
}
}

The Stream Error Handling RFC defines more than 50 semantic error codes in a StreamErrorCode enum.

URI Extension Follow-ups

The URI extension that shipped in PHP 8.5 gains builder classes, so you can assemble a URI without creating intermediate objects for each component:

$uri = new Uri\Rfc3986\UriBuilder()
->setScheme('https')
->setHost('example.com')
->setPath('/foo/bar')
->build();

The follow-up RFC also adds getUriType() and getHostType() methods, plus percent-encoding functions for individual URI components.

Secure Session Defaults

Three php.ini session defaults change for new installations:

Setting Old default New default
session.use_strict_mode 0 1
session.cookie_httponly 0 1
session.cookie_samesite unset Lax

Laravel manages its own session cookies, so most apps are unaffected. Apps that use native PHP sessions and rely on the old defaults should check the session defaults RFC.

Learn More

Paul Redmond photo

Staff writer at Laravel News. Full stack web developer and author.

Sponsored

masteringlaravel logo
Laravel Code Review

Get expert guidance in a few days with a Laravel code review

Visit Laravel Code Review

The latest

View all →
Laravel Vet: Review Composer Code Before It Installs image

Laravel Vet: Review Composer Code Before It Installs

Read article
Building EasyReply: How We Used Laravel to Unify Customer Support image

Building EasyReply: How We Used Laravel to Unify Customer Support

Read article
PostgreSQL Monitoring and Schema Linting for Laravel with Vacuum image

PostgreSQL Monitoring and Schema Linting for Laravel with Vacuum

Read article
PayZephyr: One Payment API for Stripe, Paystack, and PayPal image

PayZephyr: One Payment API for Stripe, Paystack, and PayPal

Read article
Bifrost Turns One With AI Builds and New Workflows image

Bifrost Turns One With AI Builds and New Workflows

Read article
Preview Blade Templates in macOS Finder with Quick Blade image

Preview Blade Templates in macOS Finder with Quick Blade

Read article