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_reportingset to0when nophp.inisets it hrtime()goes back toCLOCK_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 instrict_types=1files - OPcache no longer folds
$cond ? -0.0 : 0.0into a single-0.0 - The built-in web server stops leaking a file descriptor on every
HEADrequest - 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.
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.