PHP 8.6 changes how many functions handle invalid arguments. Code that once returned a partial result, changed a value, or ignored an error may now throw a ValueError or TypeError.

These changes are different from deprecations. A deprecation warns about code that may break in a future version. The changes below can affect an app as soon as it runs on PHP 8.6.

NUL Bytes Now Raise a ValueError

The largest group covers strings that contain a NUL byte. Some functions used to cut off the string at that byte. In PHP 8.6, they throw a ValueError instead.

Every one of these filesystem functions now raises a ValueError when $filename contains a NUL byte:

  • fileperms(), fileinode(), filesize(), fileowner(), filegroup()
  • fileatime(), filemtime(), filectime(), filetype()
  • is_writable(), is_readable(), is_executable()
  • is_file(), is_dir(), is_link()
  • file_exists(), lstat(), stat()

The same rule applies to the first argument of getenv() and putenv(). It also covers the filename in dl(), the prefix in openlog(), the input to parse_str(), the $cwd argument of proc_open(), and locale names passed to setlocale().

SimpleXMLElement::__construct() now rejects NUL bytes in $data. GMP integer parsing also throws instead of cutting off the string.

Session settings behave a little differently. A NUL byte in session.cookie_path, session.cookie_domain, or session.cache_limiter causes a warning, and PHP leaves the setting unchanged. Before PHP 8.6, a NUL byte in the path or domain could cause the SAPI to drop the Set-Cookie header.

This can affect code that passes user input to filesystem functions without checking it first. For example, file_exists($_GET['file']) can now throw when the value contains a NUL byte.

Invalid Flags and Modes Stop Being Ignored

Several functions now throw a ValueError when a mode or flag is not allowed:

  • array_filter() on an invalid $mode
  • array_change_key_case() on an invalid $case
  • pathinfo() on an invalid $flag
  • scandir() on an invalid $sorting_order
  • posix_access() on invalid $flags, posix_mkfifo() on invalid $permissions
  • Phar::mungServer() on an invalid argument value

PHP 8.6 also checks values that are outside the supported number range:

  • sleep() when $seconds exceeds the platform limit, and usleep() above UINT_MAX
  • number_format() when $decimals falls outside the integer range, instead of clamping large values
  • shmop_open() and shm_attach() when $key is outside the platform's key_t range
  • pcntl_alarm() below zero or above UINT_MAX, and pcntl_exec() when $args isn't a list
  • GMP power and shift operators when the right operand is outside unsigned long range

Static analysis may find some of these problems when the invalid value is written as a literal or constant.

preg_grep() Returns false Instead of a Partial Array

When a PCRE error happens during a search, preg_grep() now returns false. One example is malformed UTF-8 used with the /u modifier. Earlier versions could return only the matches found before the error.

Code that passes the result straight into foreach should check for false first:

$matches = preg_grep($pattern, $lines);

if ($matches === false) {
    // Handle the PCRE error.
}

trim() Strips One More Character

trim(), rtrim(), and ltrim() now include form feed (\f) in their default character list, per the trim form feed RFC.

This matters for formats that use form feeds as page separators. Pass an explicit character list if the form feed must remain in the string.

DOM Read-Only Properties Changed Their Error

Properties documented as @readonly on DOM classes, including DOMNode::$nodeType, DOMDocument::$xmlEncoding, and DOMEntity::$actualEncoding, are now declared with asymmetric visibility as public private(set).

Writing to one of these properties from outside the class still fails. The error now says Cannot modify private(set) property instead of showing the old readonly error. Code or tests that check the exact message may need an update. ReflectionProperty::isWritable() now reports these properties correctly.

Array access on Dom\DtdNamedNodeMap also tightened up. A negative integer index returns null instead of the first node, and an index above INT_MAX raises a ValueError instead of overflowing into a smaller index.

SplFileObject Iterates Differently

SplFileObject has four changes that may affect iteration results:

  • next() now advances the stream when no earlier current() call cached a line, so the following current() returns the new line instead of the previous one
  • fgets() no longer caches its return value for later current() calls, so current() re-reads from the stream position
  • next() past EOF no longer increments key() without bound
  • seek() past EOF now produces the same key() value as SplTempFileObject, where the two used to disagree

Review code that mixes fgets() with the iterator methods on the same SplFileObject. Code that depends on the old cache behavior may skip or repeat a line after upgrading.

Other Changes to Check

  • array_intersect() with two or more arrays converts values to strings while scanning inputs instead of during sort comparisons. That changes how many conversion warnings you get, the order they arrive in, and results for stateful __toString() implementations.
  • ?? and empty() on a magic property no longer call __get() when __isset() already materialised the property by writing into the property table. The written value comes back directly. isset() is unaffected.
  • ZipArchive::extractTo() raises a TypeError for non-string entries in the files argument, and addGlob() and addPattern() raise one for wrongly typed options instead of emitting a warning.
  • deflate_init() and inflate_init() raise a TypeError when option values aren't integers.
  • SOAP rejects classmap arrays containing integer keys, and encoding errors now name the affected type instead of the generic violation message.
  • sodium_crypto_pwhash() and friends throw ValueError instead of SodiumException for out of range arguments. SodiumException still covers internal libsodium failures.
  • Intl tightened several signatures, including a TypeError for non-stringable time zone objects and for a non-int offset to IntlDateFormatter::parse().
  • Variant objects in COM can no longer be cloned.

How to Find Problems

Many of these changes appear only at runtime with a certain input. Static analysis can find some invalid literal arguments, but it may not know when a string will contain a NUL byte.

Run the test suite on a PHP 8.6 pre-release build with error_reporting(E_ALL). Review each new ValueError and TypeError, and test the paths that handle filenames, modes, flags, and external data.

The PHP 8.6 deprecations only warn in PHP 8.6. The backward-incompatible changes in this article can break code now, so they should be checked first.

Read More

The php-src UPGRADING file contains the full list and may change as PHP 8.6 development continues. For new language features, see the PHP 8.6 feature roundup and the confirmed PHP 8.6 tag.