By-reference foreach loops may now visit previously skipped elements after array compaction. Internal pointers on deleted elements now move to the next surviving element during copy-on-write.
?? and empty() on a magic property
no longer call __get() when
__isset() has materialized the
property by writing into the property table.
The freshly-written value is returned directly.
isset() applied directly to the property is unaffected;
nested forms such as isset($o->p['k']) follow the same
path as ??.
<?php
#[\AllowDynamicProperties]
class Example
{
public function __isset(string $name): bool
{
$this->$name = 'from __isset()';
return true;
}
public function __get(string $name): string
{
return 'from __get()';
}
}
$example = new Example();
var_dump($example->foo ?? 'default');
// As of PHP 8.6.0: string(14) "from __isset()"
// Prior to PHP 8.6.0: string(12) "from __get()"array_intersect() with at least two arrays now converts values to strings while scanning its inputs instead of during sort comparisons. This can change the number and order of conversion warnings and __toString() calls, which conversion exception is reached, and the result for stateful __toString() implementations. Argument types are validated before checking for empty arrays or converting values, so an invalid later argument can suppress conversion side effects from earlier arrays. Values are not converted if any input array is empty.
Form feed (\f) is now added to the default trimmed
characters of trim(), rtrim() and
ltrim().
<?php
var_dump(trim("\f hello \f"));
// As of PHP 8.6.0: string(5) "hello"
// Prior to PHP 8.6.0: string(9) "\f hello \f"The following functions now raise a ValueError when an invalid flag or mode value is passed:
mode)case)flags)sorting_order)
The following functions now raise a
ValueError when the given argument contains
NUL bytes:
extension_filename)prefix)string)cwd)The following functions now raise a ValueError for out-of-range values instead of silently clamping them or allowing them to overflow:
decimals
is outside the integer range.
seconds is
greater than UINT_MAX, or
UINT_MAX / 1000 on Windows.
microseconds is
greater than UINT_MAX.
linkinfo() now raises a
ValueError when the
path argument is empty.
setlocale() now raises a
ValueError when a locale name contains
NUL bytes, instead of silently truncating it.
Arrays are now accepted only for the locales
argument.
Passing an array as a later variadic locale argument now throws a
TypeError.
Passing any additional locale arguments when
locales is an array now throws an
ArgumentCountError.
The following functions now raise a
ValueError when the
filename argument contains NUL
bytes:
base_convert(), bindec(), hexdec() and octdec() now raise a notice when they cannot precisely convert the given number.
unpack() now reads a < or
> immediately following a format code as an
endianness modifier rather than as the first character of the element
name.
Formats such as "s<value" now produce the key
"value" instead of "<value", and
"C>name" raises a
ValueError because the C
format code accepts no endianness modifier.
A name starting with these characters is unaffected when a repeater
precedes it, as in "s1<value".
See also the
new
endianness modifiers.
<?php
var_dump(unpack('s<value', "\x01\x00"));
// As of PHP 8.6.0: ["value" => 1]
// Prior to PHP 8.6.0: ["<value" => 1]It is no longer possible to clone variant objects, because the cloning behavior was ill-defined.
The callback registered with CURLOPT_READFUNCTION now
throws a ValueError when returning an
integer other than 0,
CURL_READFUNC_ABORT or
CURL_READFUNC_PAUSE.
Properties previously documented as read-only (e.g.
DOMNode::$nodeType,
DOMDocument::$xmlEncoding,
DOMEntity::$actualEncoding,
DOMEntity::$encoding,
DOMEntity::$version) are now declared with
asymmetric
visibility (public private(set)).
Attempts to write to them from outside the class now raise
Cannot modify private(set) property class::$prop from global scope
instead of the prior readonly modification error.
ReflectionProperty::isWritable() also reports these
properties accurately.
Array access on Dom\DtdNamedNodeMap objects now
returns null for negative integer indexes instead of returning the first
node, and raises a ValueError when the
integer index is greater than PHP_INT_MAX instead of
overflowing to a smaller index.
ftp_nb_fget(), ftp_nb_fput(),
ftp_nb_get() and ftp_nb_put() now
throw an Error when the connection is
already transferring, instead of emitting a warning and returning false.
ftp_close() already throws on the same condition.
imagesetstyle(), imagefilter() and imagecrop() filter the types and values of their array arguments and raise a TypeError or ValueError accordingly.
imageaffinematrixget() now enforces the documented
array|float type for
the options parameter, including the corresponding
weak and strict typing behavior.
gmp_fact() now throws a
ValueError if num
does not fit into an unsigned long.
gmp_pow(), gmp_binomial(), gmp_root() and gmp_rootrem() now throw a ValueError if their second argument does not fit into an unsigned long.
The shift (<<, >>) and
exponentiation (**) operators on
GMP objects now throw a
ValueError when GMP
right operands are outside the unsigned long range, instead of silently
truncating them.
GMP integer string parsing now throws a
ValueError for strings containing
NUL bytes, instead of silently truncating them.
The message of the DivisionByZeroError
raised by gmp_powm() on modulo by zero now includes the
function name and argument index (modulus).
Passing a non-stringable object as a time zone to Intl APIs that accept time zone objects or strings now raises a TypeError instead of an Error.
IntlIterator::current() now returns null when
called before the iterator is positioned, or after the iterator becomes
invalid, instead of exposing an undefined value.
IntlBreakIterator::getLocale() now raises a
ValueError when the type is neither
Locale::ACTUAL_LOCALE nor
Locale::VALID_LOCALE instead of returning false.
MessageFormatter::parse() and
MessageFormatter::parseMessage() now return
PHP_INT_MIN as int, rather than
float, on 64-bit platforms when parsing integer values.
The type parameter of
IntlBreakIterator::getPartsIterator() has been
changed from string to int to match the
underlying implementation.
UConverter::transcode() now rejects
from_subst and to_subst option values
longer than 127 bytes instead of silently truncating the length before
passing it to ICU.
ResourceBundle::get() and
resourcebundle_get() now report fallback-disabled
resource lookups with
without fallback to locale
instead of the malformed
without fallback from to locale
.
IntlDateFormatter::parse()/datefmt_parse()
and
IntlDateFormatter::localtime()/datefmt_localtime()
now raise a TypeError when the
offset argument is not of type int
instead of silently converting the value.
Collator::sort(),
collator_sort(),
Collator::asort(), and
collator_asort() now report UTF-8/UTF-16 conversion
failures during comparison through the intl error mechanism and return
false.
With intl.use_exceptions
enabled, these failures throw an IntlException.
Previously, these paths emitted a warning and compared the value as an
empty string.
Unicode data tables have been updated to Unicode 18.0.
pcntl_alarm() now raises a
ValueError if the
seconds argument is lower than zero or greater
than the platform's UINT_MAX.
pcntl_exec() now raises a
ValueError if the
args argument is not a list array.
preg_grep() now returns false instead of a partial
array when a PCRE execution error occurs (e.g. malformed
UTF-8 input with
the u modifier).
This is consistent with the other preg_* functions.
When pg_fetch_object() is given a non-empty
constructor_args for a class without a constructor,
the ValueError now refers to the
constructor_args argument instead of
class.
Errors raised when the requested class is not instantiable (abstract,
interface, enum) now surface before the row is fetched.
Phar::mungServer() now raises a ValueError when an invalid argument value is passed instead of being silently ignored.
Phar::addEmptyDir() now rejects
"/.phar" paths in addition to ".phar"
paths, and raises the same
BadMethodCallException for attempts to
create the reserved magic ".phar" directory through
that form.
Non-magic names that merely share the ".phar" prefix
are now treated as ordinary directories.
Files are only automatically interpreted as Phar archives when included if
".phar" occurs as an extension in the filename component
of their paths.
Previously, it could occur in a directory name or as part of an extension
such as ".pharma".
Setting session.cookie_path,
session.cookie_domain, or
session.cache_limiter to a
value containing NUL bytes now emits a warning and
leaves the setting unchanged.
Previously, NUL bytes were silently accepted: for
cookie_path and cookie_domain this
caused the SAPI to drop the
Set-Cookie header; for cache_limiter
the value was silently truncated at the NUL byte.
session_module_name() now throws a
ValueError if name
is a string containing NUL bytes.
session_encode() now returns an empty string instead of
false for empty sessions.
It only returns false now when the session data could not be encoded.
This mainly happens with the default serialization handler if a key
contains the pipe (|) character.
When session.lazy_write is
enabled and a session handler implements
SessionUpdateTimestampHandlerInterface,
sessions that were read as empty and remain empty at write time will now
trigger SessionUpdateTimestampHandlerInterface::updateTimestamp()
instead of SessionHandlerInterface::write().
Previously, SessionHandlerInterface::write() was
always called for empty sessions because
session_encode() returned false, bypassing the
lazy_write comparison.
Custom session handlers that rely on
SessionHandlerInterface::write() being called with
empty data (e.g. to destroy the session) should implement the same logic
in their SessionUpdateTimestampHandlerInterface::updateTimestamp()
method.
The defaults of three session INI settings have changed to provide secure behavior out of the box:
1 (was 0).
Strict mode rejects uninitialized session IDs, mitigating session
fixation.
Custom session handlers that previously relied on accepting externally
supplied IDs without a corresponding storage entry must either implement
SessionUpdateTimestampHandlerInterface::validateId()
and SessionIdInterface::create_sid(), or
explicitly set this to 0.
1 (was 0).
Session cookies are no longer accessible to JavaScript via
document.cookie.
Applications that read the session cookie from JavaScript must
explicitly set this to 0.
"Lax" (was unset).
Session cookies are no longer sent on cross-site requests other than
top-level navigations using safe HTTP methods.
Applications that depend on session cookies being sent on cross-site
POST submissions must explicitly set this to "None"
(and also set
session.cookie_secure
to 1).
SessionHandler::validateId() has been added and
delegates to the configured save handler.
A subclass that declares SessionHandler::validateId()
without a return type now emits a deprecation notice for the tentative
bool return type.
A subclass that overrides SessionHandler::open()
without calling parent::open() keeps its previous behavior
and emits a warning when an ID is validated.
shmop_open() now raises a
ValueError when the
key argument is outside the platform's
key_t range instead of passing a truncated key to the
operating system.
SimpleXMLElement::__construct() now raises a
ValueError when the
data argument contains NUL
bytes, matching simplexml_load_file().
With dataIsURL set, it previously truncated the
path at the first NUL byte.
Without it, the string went to libxml, which with default options rejects
a NUL on current versions but accepts the truncated
document on older ones and under LIBXML_RECOVER.
The "classmap" option of
SoapClient and SoapServer
now rejects arrays containing integer keys.
Previously, sparse integer-keyed and mixed-keyed arrays could be accepted.
SoapClient now throws a
TypeError for non-array
"classmap" options and a
ValueError for arrays containing integer
keys, also when the "exceptions" option is disabled.
WSDL/XML Schema parsing now rejects out-of-range integer values for
occurrence constraints and integer restriction facets.
Negative minOccurs and maxOccurs
values are rejected as well.
SOAP encoding errors now report the affected type or failing operation in
the error message instead of the generic
Encoding: Violation of encoding rules
message.
Code that compares the exact message may need to be updated.
socket_set_option() with
SO_ATTACH_REUSEPORT_CBPF now requires an
int value and a
level of SOL_SOCKET.
Any other value type throws a TypeError
instead of being coerced, and any other level raises a warning and
returns false.
socket_set_option() with
SO_ATTACH_REUSEPORT_CBPF and a
value of 0 now detaches the
reuseport filter through SO_DETACH_REUSEPORT_BPF.
It previously used SO_DETACH_BPF, an alias of
SO_DETACH_FILTER, which left the reuseport program
attached.
The password-hashing functions sodium_crypto_pwhash(),
sodium_crypto_pwhash_str(),
sodium_crypto_pwhash_scryptsalsa208sha256() and
sodium_crypto_pwhash_scryptsalsa208sha256_str() now
throw a ValueError instead of a
SodiumException when an argument is out of
range, such as an opslimit or
memlimit below the documented minimum.
SodiumException is still thrown for
internal libsodium failures.
SplObjectStorage::getHash() implementations may no longer mutate any SplObjectStorage instance. Attempting to do so now throws an Error.
SplFileObject::next() now advances the stream when no prior SplFileObject::current() call has cached a line. A subsequent SplFileObject::current() call returns the new line rather than the previous one.
SplFileObject::fgets() no longer caches the returned line for subsequent SplFileObject::current() calls. SplFileObject::current() now re-reads from the current stream position instead of returning the line SplFileObject::fgets() just returned.
SplFileObject::next() past EOF no longer increments SplFileObject::key() without bound. SplFileObject::seek() past EOF now produces the same SplFileObject::key() value as SplTempFileObject; the two previously returned different values.
DirectoryIterator::key() now returns int|string, and DirectoryIterator::current() returns string|SplFileInfo|static.
shm_attach() now raises a
ValueError when the
key argument is outside the platform's
key_t range instead of passing a truncated key to the
operating system.
Uri\WhatWg\Url::__construct() now sets
softErrors to an empty array when URL processing
throws an exception, instead of preserving its value.
URL validation errors remain available in the
Uri\WhatWg\InvalidUrlException::$errors property.
Uri\WhatWg\Url::__construct(),
Uri\WhatWg\Url::parse(), the
Uri\WhatWg\Url::with*() methods, and
Uri\WhatWg\UrlBuilder::build() now report
validation errors in the order in which they are detected.
This reverses the order of multiple errors compared to PHP 8.5.
ZipArchive::extractTo() now raises a
TypeError for the
files argument if one or more of the entries is not
a string.
ZipArchive::addGlob() and
ZipArchive::addPattern() now raise a
TypeError when the
"remove_all_path" option is not of type
bool, or when the "comp_method",
"comp_flags", or "enc_method"
options are not of type int (numeric strings are coerced),
instead of emitting a warning.
Serializing or unserializing a ZipArchive now throws an Exception. A subclass can override __serialize() and __unserialize() to round-trip through ZipArchive::closeString() and ZipArchive::openString().
deflate_init() now raises a
TypeError when the value for option
"level", "memory",
"window", or "strategy" is not of
type int.
inflate_init() now raises a
TypeError when the value for option
"window" is not of type int.