The PHP team just released PHP 8.6.0 RC2, and it's the first release candidate for PHP 8.6. There is no RC1. The release managers tagged one, found a mistake in it, and tagged RC2 instead. Release candidates come after the feature freeze, so from here the work is bug fixes on the way to general availability on November 19.

Here's what's in RC2:

  • A fix so fclose(), file_put_contents(), and copy() return false when flushing the stream fails
  • A warning from unserialize(''), which used to return false with no warning at all
  • Renamed cases on the new URI host type enums, from IPv4 to IpV4
  • A comparison handler and an unserialization fix for Time\Duration
  • Unicode 18.0 data tables in MBString, and file 5.48 in Fileinfo
  • Memory safety fixes across DOM, Intl, PDO, SPL, SQLite, and Readline

What Happened to RC1

Daniel Scherzer, one of the three PHP 8.6 release managers, explained it on his blog. The first release candidate is when the team creates the PHP-8.6 branch, and part of that step is bumping the internal API numbers (ZEND_MODULE_API_NO, ZEND_EXTENSION_API_NO, and PHP_API_VERSION). Extensions check those numbers to know which PHP they were built for.

The other two release managers, Matteo Beccati and Joe Ferguson, use a Docker container to automate part of the build. The script had a bug that scheduled the bump for the last release candidate instead of the first. So the php-8.6.0RC1 tag went out with the wrong API numbers, and the team tagged a corrected RC2 rather than reuse the number. Scherzer says he'd reviewed the same script and missed the bug too, and that they handled it the way he would have.

Nothing here affects your code. If you went looking for RC1 downloads and couldn't find them, that's why.

Changes Worth Testing

Two fixes change what existing code sees. fclose(), file_put_contents(), and copy() now return false when flushing or closing the stream fails. Before, those failures were silently ignored, and there was no way to tell a failed write from a good one (bug #60110). If your code treats a false from these as impossible, it's worth a look.

unserialize('') still returns false, but now with the same warning any other bad input gets:

unserialize('');
// Warning: unserialize(): Error at offset 0 of 0 bytes

The old behavior made an empty string indistinguishable from serialize(false), which is b:0; (GH-23780).

If you've written code against the betas using the URI extension's getHostType(), the enum cases changed names to follow PHP's naming policy and the RFC (GH-23851):

// Beta 3
Uri\Rfc3986\UriHostType::IPv4;

// RC2
Uri\Rfc3986\UriHostType::IpV4;

The same goes for IpV6, IpVFuture, and the matching cases on Uri\WhatWg\UrlHostType. These enums are new in 8.6, so no released code depends on the old names.

The Time\Duration class picked up a comparison handler, so you can compare two durations directly, and a fix for unserializing it.

The Road to GA

Here is the full PHP 8.6 timetable. Releases are tagged on a Tuesday and ship on the Thursday, and the release managers can add or drop releases as development goes.

Date Release
July 2 Alpha 1
July 16 Alpha 2
July 30 Alpha 3
August 11 All RFCs targeting 8.6 should be merged
August 13 Beta 1 and soft feature freeze
August 27 Beta 2
September 10 Beta 3
September 22 Hard feature freeze
September 24 RC1 (skipped)
September 24 RC2
October 8 RC3
October 22 RC4
November 5 RC5
November 19 General availability

This is the point to run your test suite against 8.6 if you haven't already. The team asks you not to run it in production, and to report anything you find on GitHub Issues. For everything landing in 8.6, from partial function application to the new session defaults, see our PHP 8.6 version page.

References