---
title: "PHP 8.6: A more expressive, more capable PHP"
url: "https://joelbutcher.dev/writing/php-8-6-a-more-expressive-more-capable-php"
published: "2026-11-13T15:00:00+00:00"
tags: ["Laravel/PHP"]
description: "I look at partial function application, more secure defaults and the runtime improvements planned for PHP 8.6, with examples of how they could reduce boilerplate in everyday application code."
---

# PHP 8.6: A more expressive, more capable PHP

PHP has never been a language that stands still. Each release brings incremental improvements, but every so often a collection of changes arrives that feels like a meaningful shift in how we can write applications.

PHP 8.6 looks like one of those releases.

There isn’t one headline feature that completely changes the language. Instead, it brings together several improvements that make PHP more expressive, improve its foundations, and remove some of the friction developers have become accustomed to working around.

The feature I’m particularly excited about is **Partial Function Application (PFA)**. Combined with PHP’s existing first-class callables and the pipe operator introduced in PHP 8.5, it opens up some interesting possibilities for functional programming in everyday PHP.

There are also useful additions to the DateTime and Stream APIs, a handful of quality-of-life improvements, and some changes that make secure configuration the default rather than something developers need to remember to enable.

Let’s take a look at what’s coming.

## Partial Function Application: less boilerplate, better composition

If there’s one feature I’d encourage PHP developers to experiment with, it’s Partial Function Application.

PFA allows us to call a function with some arguments supplied and leave placeholders for the arguments we want to provide later. Rather than executing the function immediately, PHP returns a closure that can be invoked when the remaining arguments are available.

Consider a familiar example:

```php
$normalise = fn (string $value): string => strtolower(trim($value));
```

There’s nothing wrong with this. Arrow functions are concise, and PHP developers use them constantly when working with callbacks.

But what if we want to reuse existing functions without wrapping them in another closure?

PHP 8.6 allows us to do exactly that:

```php
$trim = trim(?);

$lowercase = strtolower(?);
```

The `?` indicates where an argument will be supplied later. These expressions produce callable closures, effectively giving us the same behaviour as writing the corresponding arrow functions ourselves.

This becomes particularly useful when working with higher-order functions such as `array_map()`:

```php
$messages = array_map(trim(?), $messages);
```

Previously, we’d typically write:

```php
$messages = array_map(
    fn (string $message): string => trim($message),
    $messages,
);
```

It’s a small difference in isolation, but it becomes much more significant when composing several operations or working with functions that accept multiple arguments.

### PFA with `Result`

This is where I think PFA gets genuinely interesting.

I’ve been experimenting with functional programming patterns in PHP using the [gosuperscript/monads](https://github.com/gosuperscript/monads-php) package. It provides `Option`, `Result`, `Lazy` and `Writer` types, giving us a nice way to compose operations while keeping error handling explicit.

Imagine we’re processing an email address supplied by a user.

We want to trim whitespace, normalise the casing, and validate the result. If validation fails, we want to return an error rather than continue processing an invalid value.

Using `Result`, we can express that as a pipeline:

```php
use function Superscript\Monads\Result\{Ok, Err};

function validateEmail(string $email): Result
{
    return filter_var($email, FILTER_VALIDATE_EMAIL)
        ? Ok($email)
        : Err('Invalid email address');
}

$result = Ok($input)
    ->map(fn ($email) => trim($email))
    ->map(fn ($email) => strtolower($email))
    ->andThen(fn ($email) => validateEmail($email));
```

Each operation receives the successful value from the previous step. If one returns an `Err`, subsequent `map()` and `andThen()` operations are skipped.

With PHP 8.6, we can remove the wrapper closures:

```php
$result = Ok($input)
    ->map(trim(?))
    ->map(strtolower(?))
    ->andThen(validateEmail(?));
```

That’s a much nicer expression of intent. We’re not interested in the mechanics of passing a value into `trim()` or `strtolower()`; we simply want to apply those functions to the value held by the Result.

This is the kind of improvement that makes functional composition feel much more natural in PHP.

### PFA with `Lazy`

PFA also works particularly well alongside lazy evaluation.

The `Lazy` type in `gosuperscript/monads` allows us to defer an expensive computation until its result is actually required. It also memoises the result, so subsequent evaluations don’t repeat the work.

Consider a report-generation function:

```php
function generateMonthlyReport(
    string $month,
    int $tenantId,
): Report {
    // Potentially expensive report generation...
}
```

We could create a lazy computation like this:

```php
use Superscript\Monads\Lazy\Lazy;

$report = Lazy::of(
    generateMonthlyReport($month, $tenantId, ...)
);
```

The trailing `...` here is part of PHP’s partial function application syntax. Because we’ve supplied the actual arguments and left the remaining argument list open, PHP creates a zero-argument callable.

`Lazy::of()` can hold that callable without executing the report generation immediately.

When we eventually need the report:

```php
$data = $report->evaluate();
```

The computation runs, and its result is cached for subsequent calls to evaluate().

This is a useful example of how PHP 8.6’s language features can complement existing libraries. PFA isn’t replacing `Lazy` or `Result`; it’s making the functions we pass into them easier to express.

For me, that’s the real value of this feature: less glue code between the abstractions we’re already using.

## `clamp()`: a small addition with everyday value

PHP 8.6 also introduces a native clamp() function.

Clamping a value means ensuring it falls within a specified range. If it’s below the minimum, it returns the minimum. If it’s above the maximum, it returns the maximum. Otherwise, it returns the original value.

Previously, we’d commonly implement this using min() and max():

```php
$quantity = max(1, min($quantity, 100));
```

PHP 8.6 makes the intention much clearer:

```php
$quantity = clamp($quantity, 1, 100);
```

This is useful in all sorts of everyday scenarios: pagination limits, volume controls, percentage calculations, configuration values and form input.

It also provides native validation around the bounds, rather than relying on developers to get the ordering right.

It’s not a transformative feature, but it’s a good example of PHP gradually absorbing common patterns that developers have otherwise had to implement themselves.

## `Time`\\`Duration`: a better way to represent elapsed time

PHP’s `DateTime` functionality is already one of its strengths, but there’s a distinction worth making between calendar-based intervals and elapsed time.

`DateInterval` is useful when dealing with calendar concepts: adding a month, moving a date forward by a week, or calculating a period between two dates.

But sometimes we simply want to represent a duration of time.

PHP 8.6 introduces `Time`\\`Duration`, providing a dedicated representation for durations with nanosecond precision.

That gives developers a more appropriate abstraction for elapsed-time calculations, rather than having to bend calendar-oriented date APIs to fit the problem.

This is particularly relevant when working with performance measurements, timeouts, scheduling, and systems that need to represent precise durations.

It’s also an encouraging sign of continued investment in PHP’s time and date APIs.

## Stream API improvements

The Stream API has been part of PHP for a long time. It’s used throughout the language, from file handling to network communication.

However, it’s fair to say that parts of the API have accumulated complexity over the years.

PHP 8.6 brings several improvements that help modernise this area.

### Polling API

One of the more interesting additions is a polling API.

Polling provides a mechanism for checking multiple events or resources and determining when they’re ready for further processing. It’s a useful building block for things such as timers, signal handling and asynchronous execution.

This isn’t a new async programming model for PHP applications, and developers shouldn’t mistake it for a complete replacement for existing event-loop libraries.

What it does provide is groundwork within PHP itself for more capable event-driven functionality in the future.

### Improved stream error handling

Stream errors haven’t always been particularly pleasant to work with. Depending on the operation, developers have had to deal with warnings, return values and error handling that can feel inconsistent.

PHP 8.6 introduces improvements to stream error handling, including more consistent error modes and exception-based handling.

The changes are opt-in, which is important for compatibility with existing applications.

I expect this to be particularly useful for library authors. Better error handling at the runtime level makes it easier to build predictable abstractions around network and file operations.

## TLS session resumption

PHP 8.6 also introduces TLS session resumption improvements.

Establishing a TLS connection involves a handshake, which adds overhead to network communication. Session resumption allows a subsequent connection to reuse previously established session information rather than always performing a full handshake.

For applications making repeated secure network requests, this can reduce connection overhead.

It’s not the sort of feature that will change how most developers write PHP, but it is a worthwhile runtime improvement.

## Security-conscious defaults

One of the most welcome changes in PHP 8.6 is the move towards more secure session defaults.

Historically, developers have needed to configure several session settings explicitly to get sensible security behaviour.

PHP 8.6 changes the defaults for three important settings:

|  |  |
| --- | --- |
| **Setting** | **New default** |
| session.use_strict_mode | 1 |
| session.cookie_httponly | 1 |
| session.cookie_samesite | Lax |

These changes help protect against session fixation, prevent JavaScript from reading the session cookie, and restrict when browsers send session cookies in cross-site requests.

This is a good direction for PHP.

Secure configuration should be the starting point, not an optional hardening exercise that every application team needs to remember to perform.

There is one important migration consideration: applications relying on cross-site POST requests carrying the session cookie, such as some SSO and SAML flows, may need explicit configuration.

As always, test authentication and payment flows carefully when upgrading.

## Smaller improvements worth knowing about

Not every useful change needs its own headline feature.

PHP 8.6 includes a collection of smaller improvements that should make everyday development a little more predictable.

### Readonly property defaults

Readonly properties can now have default values. This is particularly useful for immutable classes implementing get-only interface properties, where previously a constructor assignment was needed simply to establish a fixed value.

```php
final readonly class CsvExport
{
    public string $extension = 'csv';
    public string $contentType = 'text/csv';
}
```

Override attributes on class constants

The `#[\Override]` attribute can now be used with class constants, making it possible to explicitly identify constants that intentionally override inherited definitions.

### SortDirection

A new `SortDirection` enum provides a more consistent way to represent ascending and descending sort directions in application code.

### `mysqli_quote_string()`

The mysqli extension gains `mysqli_quote_string()`, providing a native quoting function for SQL string values.

## More useful error messages

An opt-in `error_include_args` setting allows function arguments to be included in error messages. This can make debugging easier, while respecting `#[\SensitiveParameter]` to avoid exposing sensitive values.

**Other behavioural changes**

There are also some changes worth checking during an upgrade:

- `trim()`, `ltrim()` and `rtrim()` now strip form-feed characters by default.
- Several functions now perform stricter checks around NULL bytes.
- `preg_grep()` returns false on a PCRE error rather than returning a partial result.
- Mbregex support is being removed, following the end of maintenance for its underlying Oniguruma dependency.

These aren’t necessarily changes you’ll notice immediately, but they are worth including in an upgrade checklist.

## What does PHP 8.6 mean for the future?

PHP 8.6 isn’t a release that attempts to reinvent the language. Instead, it feels like a release that is making PHP more capable at the things modern developers increasingly want to do.

PFA is the standout example. Combined with first-class callables and the pipe operator, it makes functional programming patterns more approachable without requiring developers to abandon the object-oriented approaches that PHP has always supported.

The Stream API improvements are similarly interesting. They don’t deliver a complete asynchronous runtime, but they improve the foundations on which future capabilities can be built.

And the smaller improvements matter too. Better defaults, more useful error messages, more expressive types and less boilerplate all contribute to a better developer experience.

I don’t think PHP needs to become another language to remain relevant. Its strength has always been its flexibility: it can support traditional object-oriented applications, functional programming, and everything in between.

PHP 8.6 continues that evolution.

With general availability currently scheduled for 19 November 2026, there’s still time to experiment with the release candidates, review compatibility, and start identifying where the new features could benefit existing applications.

My recommendation? Start with Partial Function Application. Find a few places where you’re currently writing trivial arrow functions just to adapt an existing function to a callback, and see how much cleaner they become.

It’s a small change in syntax, but it points towards a more expressive future for PHP.
