The PHP team just released PHP 8.6.0 RC3 on October 8. It's the second release candidate for PHP 8.6, since RC1 was skipped. Release candidates only carry bug fixes, and a few in this one undo regressions that 8.6 itself introduced. The biggest one silenced every warning raised inside a fiber.

Here's what's in RC3:

  • Fibers no longer start with error_reporting set to 0 when no php.ini sets it
  • hrtime() goes back to CLOCK_MONOTONIC, after the switch to the raw clock made it run fast on some machines
  • The faster compiled path for array_map() is now off in strict_types=1 files
  • OPcache no longer folds $cond ? -0.0 : 0.0 into a single -0.0
  • The built-in web server stops leaking a file descriptor on every HEAD request
  • A long list of PDO, OpenSSL, Intl, and memory safety fixes

What's New

Fibers No Longer Hide Errors

In the 8.6 betas and RC2, a new Fiber started with error_reporting set to 0 whenever the error_reporting INI directive had no value. That's the default when PHP runs without a php.ini, which includes the official Docker images and php -n. Every warning and notice raised inside the fiber disappeared, while code outside it reported errors as usual.

Here's the reproduction from the bug report:

echo error_reporting(), "\n";

(new Fiber(function () {
    echo error_reporting(), "\n";
    echo $undefined;
    echo "after\n";
}))->start();

On RC2 with php -n, the fiber printed 0, and the undefined variable warning never showed up. PHP 8.5 prints 30719 both times and raises the warning. Gina Peter Banyard fixed it in RC3. A fiber now starts with the configured error level, the same E_ALL default you get outside it, and an @ around the code that starts a fiber still doesn't leak into the fiber's body.

If you run async code on the 8.6 betas (anything built on Revolt, AMPHP, or another fiber-based event loop), it's worth re-running your test suite on RC3. Warnings that were hidden before will show up now.

Issue: GH-23921

hrtime() Goes Back to CLOCK_MONOTONIC

PHP 8.6 had switched the POSIX high resolution timer to CLOCK_MONOTONIC_RAW (GH-19221), to keep NTP adjustments out of hrtime() measurements. The catch is that the raw clock is never corrected, so it ticks at whatever frequency the hardware actually runs at. Nicolas Grekas measured a one-second sleep under WSL2:

Version microtime() hrtime()
8.5 1.000083 s 1.000086 s
8.6 before the fix 1.000072 s 1.038898 s
8.6 RC3 1.000158 s 1.000163 s

That's hrtime() running about 4% fast. He also pointed out that the kernel only serves CLOCK_MONOTONIC_RAW through the vDSO since Linux 5.3, so on older systems like RHEL 8 every call costs a syscall. RC3 goes back to CLOCK_MONOTONIC, which matches 8.5. If you benchmarked anything with hrtime() on the betas, rerun it.

PR: GH-23790

array_map() and strict_types

PHP 8.6 compiles some array_map() calls differently to make them faster. In a strict_types=1 file, that new code path type checked the callback's arguments, which array_map() never did before:

declare(strict_types=1);

array_map(trim(...), [1]);
// 8.5: runs fine
// 8.6 RC2: TypeError: trim(): Argument #1 ($string) must be of type string, int given

Tim Düsterhus turned the faster path off for strict_types=1 files, so this code behaves the way it does on 8.5. Arnaud Le Blanc also fixed it for calls with a non-literal function or non-literal arguments.

Issue: GH-23882 PR: GH-23889

Signed Zero Under OPcache

With OPcache on, the optimizer turned $cond ? -0.0 : 0.0 into a plain -0.0 and dropped the condition. Code that depends on the sign of zero, like getting INF or -INF from 1.0 / $zero, gave a different answer with OPcache on than with it off. That's a hard bug to spot, because development setups often run without OPcache and production almost always has it on.

Issue: GH-24063

Other Fixes Worth Noting

The built-in web server leaked a file descriptor on every HEAD request for a static file (GH-23764). The reporter found that a monitoring probe hitting php -S every 40 seconds would run it out of descriptors in about 11 hours. The bug goes back to PHP 8.2. RC3 also fixes a crash when a client resets before the server accepts it, and a truncated error page after a partial socket write.

Ilia Alshanetsky wrote a large share of the fixes in this release, including a run of PDO fixes: PDOStatement::execute() no longer leaves the previous result available after a failed execution, setFetchMode() applies its changes all at once, and a use-after-free from a bound parameter's __toString() is gone. PDO_DBLIB now reports errors from beginTransaction(), commit(), rollBack(), and lastInsertId() instead of dropping them.

Elsewhere, pcntl_signal_dispatch() stops dropping queued signals when a handler throws, the grapheme search functions in Intl return the right substrings around emoji and other supplementary characters, and the bundled Lexbor URL parser picked up nine upstream patches. There are memory safety fixes across BZ2, FFI, SOAP, SimpleXML, Tidy, and the JIT as well.

Upgrade Notes

openssl_sign() now uses OpenSSL's default salt length for RSA-PSS signatures instead of the maximum. If you verify those signatures with code that expects a specific salt length, test it against RC3.

RC3's UPGRADING file also lists three deprecations that passed in the August deprecation vote: using let or is as the name of a class, function, or constant, and using _ as a constant name or use alias. These aren't new in RC3, since RC2 already raised the notices. If your tests pull in Hamcrest, its global is() function is one to watch.

The Road to GA

Here's what's left on the PHP 8.6 timetable:

Date Release
October 8 RC3
October 22 RC4
November 5 RC5
November 19 General availability

The team asks you not to run RC3 in production, and to report anything you find on GitHub Issues. For everything landing in 8.6, see our PHP 8.6 version page.

References