PHP Architect logo

Want to check out an issue? Sign up to receive a special offer.

Stop Writing PHP Like This (8.6 Fixes It)

Posted by on September 23, 2026

This is how PHP 8.6 lets you write a string transformation, with no closures and no temp variables.

$name = 'Scott Keck-Warren';
$fixed = $name
    |> str_replace('Scott', 'Alice', ?)
    |> strtolower(...);

// string(17) "alice keck-warren"
var_dump($fixed);

It’s going to be released November 19, and I’ll also show you a deprecation that’s almost certainly in your codebase right now, and the one-line grep to find it.

Overall

At the time of this being published, PHP 8.6 is scheduled for release on November 19, 2026, after it has gone through the alpha, beta, and release candidate phases.

I have two disclaimers before we go any further.

This video was made using beta release 3 on the official Docker image (php:8.6.0beta3-cli if you want to try at home), but functionality may change between now and the actual release.

This video also represents my selection of features that I think are going to make my life better. Tell me which one I should have covered in the comments.

Partial Function Application

Partial Function Application (PFA) is a computer science term that just refers to the process of setting up a function with only some of its required parameters so you can pass the others at a later time.

I’m sure that’s perfectly clear, but maybe an example will help. Pre-8.6, we might have something like the following:

$result = array_map(static fn(string $string): string => str_replace('Scott', 'Alice', $string), $names);

It’s a little hard to read but not impossible, but that’s less than ideal for us when our goal is to make our code as readable as possible.

With PFAs, we can do something like the following:

$names = ['Scott Keck-Warren'];
$renameScott = str_replace('Scott', 'Alice', ?);

//Output: array(1) {
//  [0]=>
//  string(17) "Alice Keck-Warren"
//}
var_dump(array_map($renameScott, $names));

The “?” is the “argument placeholder” symbol, and it’s telling the engine to replace that variable with the one that’s being sent. We can also use “…” to signify zero or more parameters.

The amazing part about this is that we can leverage the pipe operator (|>) that was added in PHP 8.5 and PFAs to make some consistent code:

$name = 'Scott Keck-Warren';
$fixed = $name
    |> str_replace('Scott', 'Alice', ?)
    |> strtolower(...);

// string(17) "alice keck-warren"
var_dump($fixed);

This is going to make our code a lot easier to read and maintain, especially if tools like RectorPHP continue to clean it up for us.

RFC link for more information: https://wiki.php.net/rfc/partial_function_application_v2

clamp()

It’s common to see code that makes sure a number doesn’t exceed a maximum or minimum. I’ve written something like the following more times than I would like to admit:


$percent = -5;
if ($percent < 0) {
    $percent = 0;
}

if ($percent > 100) {
    $percent = 100;
}

// percent == 0

It’s annoying because it’s both so simple and so small; refactoring it into a helper function isn’t always at the top of your priority list (unless you’re me and live for that kind of thing).

PHP 8.6 is adding the clamp() function, which will “wrap” this for us:

// percent == 0
$percent = clamp(-5, 0, 100);

I think my favorite more practical example of this is getting the current page from the end user and making sure it falls in the range of valid pages:

$page = clamp((int)($_GET["page"] ?? 1), 1, $totalPages);

There’s also a built-in range check, so you can’t mix up the max and min values.

// Warning: Uncaught ValueError: clamp(): 
// Argument #2 ($min) must be smaller than
// or equal to argument #3 ($max)
$percent = clamp(-5, 100, 0);

I’m looking forward to being able to use this and might even add a polyfill to my pre-8.6 projects, knowing it will be added at some point.

RFC link for more information: https://wiki.php.net/rfc/clamp_v2

SortDirection Enum

Enums are one of my favorite features added in the PHP 8.1 release because they gave us a powerful way to create variables that could only be a “set” of values. Something I’m still preaching to today.

One of the amazing parts about enums is that we can create them in user land, but the core language can also add them to “ext/standard” (or any other part of the language) and improve the existing language features.

My favorite example of this (which I’ve taken from the RFC but it’s clearly a problem) is that a lot of built-in functions like array_multisort() take integers to determine the sort direction. array_multisort() takes the integer constants SORT_ASC and SORT_DESC, but there’s nothing preventing me from passing it 42.

This change adds a new enum to the global namespace:

enum SortDirection {
    case Ascending;
    case Descending;
}

We can use it now in our code instead of passing around integers (yuck):

function sortMe(array $values, int $direction) {
    // logic
}

function sortMeBetter(array $values, SortDirection $direction) {
    // logic
}

$values = ["scott", "alice", "daisy"];

//what does 1 mean here?
$sorted = sortMe($values, 1);

// oh, we're sorting ascending
$sortedBetter = sortMeBetter($values, SortDirection::Ascending);

Based on the “Future Scope” section of the RFC, I’m guessing we’ll be seeing it used in internal functions.

Leveraging the enum in PHP’s standard library (e.g. for scandir()’s second parameter). This can happen without an RFC when it’s just widening the type to an enum.

After the internals team has implemented the use of the SortDirection enum, it’s going to make it easier to detect typos using SCA tools like PHPstan.

RFC link for more information: https://wiki.php.net/rfc/sort_direction_enum

Readonly Property Defaults

The team keeps sharpening features they already shipped, like the SortDirection enum and now readonly property defaults.

PHP 8.1 allowed a property to be marked as read-only, but we couldn’t define it with a default:

final class CommunityCornerPodcast
{
    // ❌ Fatal error pre-8.6:
    // "Fatal error: Readonly property CommunityCornerPodcast::$feedSlug 
    // cannot have default value"
    public readonly string $feedSlug = 'community-corner-podcast';
}

This is by design because this is essentially a constant, and we have constant support, which is a better option than having a bunch of readonly constant values.

To get around this, we could make the property read-only and then initialize it in the constructor:

final class CommunityCornerPodcast
{
    public readonly string $feedSlug;

    public function __construct() {
     $this->feedSlug = 'community-corner-podcast';
    }
}

However, PHP 8.4 added property hooks with the ability to define getter and setter hooks on interfaces:

interface PublishesToFeed
{
    // this is read-only because we only allow a `get` and no `set`
    public string $feedSlug { get; }

    public function toRssItem(): RssItem;
}

We can implement this by overriding the property hook, but it requires redefining the whole property hook.

final class CommunityCornerPodcast implements PublishesToFeed
{
    public string $feedSlug { get => 'community-corner-podcast'; }
}

This reopened this debate because now there’s a need to be able to have read-only properties so we can fulfill the interface‘s contract while keeping it tighter.

final class CommunityCornerPodcast implements PublishesToFeed
{
    // Works 8.6+:
    public readonly string $feedSlug = 'community-corner-podcast';
}

I’m a supporter of value objects, and this is going to improve at least my value objects.

RFC link for more information: https://wiki.php.net/rfc/readonly_property_defaults

Session Security Defaults

My day job is all about making sure our code is as secure as possible (you too, right?), but part of having a good “Defense In Depth” strategy is bringing this to all layers. In PHP 8.6 we get a hand up by adding some new safe defaults:

  • session.use_strict_mode now 1 (blocks session fixation)
  • session.cookie_httponly now 1 (JS can’t read the session cookie)
  • session.cookie_samesite now Lax (mitigates CSRF)

Each of these can break a working app on upgrade. SameSite=Lax stops the session cookie on cross-site POSTs, so SSO and payment callbacks that POST back to you lose the session. httponly breaks any JavaScript that reads the session cookie. Strict mode rejects session IDs your app didn’t generate. Check all three before you flip the version.

The thing to take away is that when people say “PHP isn’t secure,” you can point them to how every release improves the security.

RFC link for more information: https://wiki.php.net/rfc/session_security_defaults

Lightning Round

This section contains changes that aren’t “headliners” but are still going to make our lives better:

Time\Duration

There’s a new Time\Duration class that comes with helpers for math:

// note that it's immutable
$delay = \Time\Duration::fromMilliseconds(100);
$delay = $delay->add(\Time\Duration::fromSeconds(1));
$delay = $delay->multiplyBy($attempt);

if ($delay > \Time\Duration::fromSeconds(10)) {
  // lock account
}

#[\Override] for class constants

PHP 8.3 allowed us to mark methods as #[\Override] to indicate that if the parent implementation’s name changes, so should the child’s. PHP 8.6 is giving us this with class constants.

interface Show {
    const FEED_FORMAT = 'rss';
}

class Podcast implements Show {
    #[\Override] // valid: overrides interface constant
    const FEED_FORMAT = 'rss-2.0';
}

Now if we change the parent’s name, we’ll get an error in PHP 8.6:

interface Show {
    const FEED_FORMAT_NAME = 'rss';
}

class Podcast implements Show {
    // Fatal error: Podcast::FEED_FORMAT has #[\Override] attribute, 
    // but no matching parent constant exists
    #[\Override] // valid: overrides interface constant
    const FEED_FORMAT = 'rss-2.0';
}

General Deprecations

Again, like all releases, there’s a general list of deprecated features that most likely will be going away in PHP 9.0, but we should get out ahead of as they’ll be filling our logs with errors when we update to 8.6 if we’re still using them.

The one I think most developers will hit is that is_double(), is_integer(), is_long(), and doubleval() are being deprecated, and instead we should use the standard is_int(), is_float(), and floatval(). Searching my “app” directory finds 150 of these, and I wasn’t even aware it was “wrong”.

If you want to search for them, use the following:

grep -rnE --include='*.php' '\b(is_integer|is_long|is_double|doubleval)\(' app/

This also deprecates returning a value from a finally block, a constructor, or a destructor. This is one I wasn’t even aware you could do, so hopefully I’m safe.

These aren’t urgent, but I would recommend running a tool like Rector or PHPStan to catch them before 9.0 so you can upgrade.

For a full list, see https://wiki.php.net/rfc/deprecations_php_8_6

What You Need To Know

  • PHP 8.6 is set for release on November 19, 2026
  • Major changes: partial function application, clamp(), the SortDirection enum, secure-by-default sessions
  • Some minor deprecations that might blow up your log until you get them fixed

 

Leave a comment

Use the form below to leave a comment:

 

Our Partners

Collaborating with industry leaders to bring you the best PHP resources and expertise