Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

PHP Changed Behaviors is a catalog of the ways PHP’s behavior has changed from one version to the next.

Did you know that comparing a string to a number no longer works the way it used to? Or that constructors named after their class stopped being recognized? Or that array functions like array_sum() now reject non-numeric values instead of silently casting them?

For each behavior change, this catalog provides a short description, a “before” and “after” code example showing the difference, the PHP version where the change happened, and links to the resulting error messages and related static-analysis rules.

Some of these changes emit a clear deprecation or error message; others are silent, and only show up as a different result at runtime. Both kinds are tracked here, so that anyone upgrading a codebase - or reading someone else’s - can understand what actually changed and why.

This catalog doesn’t aim to replace the PHP manual or the official changelogs: it complements them by focusing specifically on backward-compatibility breaks and behavior changes, with runnable before/after examples.

Contributions

Contributions are welcomed, by submitting a PR to the repository

  • New or missing behavior changes to be added, preferably with a code example
  • Corrections to the PHP version where a change actually happened
  • Extra links to related error messages, RFCs or static-analysis rules
  • Checks on existing entries against current PHP versions

Per PHP version

9.0

8.6

8.5-

8.5

8.4

8.3

8.2

8.1

8.0

7.4

7.3

7.2

7.1

7.0

5.6

Silent changed behaviors

These changes do not emit any error. They are different between versions, but keep executing the task. They might be only detected by actual inspection of the result.

PHP Extensions

PHP Error Messages

array And callable Cannot Be Absolute

array and callable cannot be an absolute type, with the leading backslash. This was not the case until PHP 8.5, and is now in harmony with other types like int.

PHP code

<?php

function foo() : \array {
    return [];
}

print_r(foo());

?>

Before

PHP Fatal error:  Uncaught TypeError: foo(): Return value must be of type array, array returned

Fatal error: Uncaught TypeError: foo(): Return value must be of type array, array returned

After

PHP Fatal error:  Cannot use array as a type name as it is reserved

Fatal error: Cannot use array as a type name as it is reserved

PHP version change

This behavior changed in 8.5.

Error Messages

Extension

Array Has No Absolute Name

Classes may be used as type, with an optional leading \. This is not the case for scalar types, such as int or string, but it was the case for array. In PHP 8.5, it is now homogenized.

PHP code

<?php

function foo(\array $x = []) {}

?>

Before

PHP Fatal error:  Cannot use array as default value for parameter $x of type array

Fatal error: Cannot use array as default value for parameter $x of type array

After

PHP Fatal error:  Cannot use array as a type name as it is reserved

Fatal error: Cannot use array as a type name as it is reserved

PHP version change

This behavior changed in 8.5.

Error Messages

Extension

PHP 8.5 split the missing-abstract-method message by declaration kind

A class, interface, enum, or anonymous class left with an unimplemented abstract method (inherited from a parent class, or required by an implemented interface) was reported with one generic message: “%s %s contains %d abstract method%s and must therefore be declared abstract or implement the remaining method%s (…)”. Enums were the only exception: they already had a dedicated message, “%s %s must implement %d abstract method%s (…)”, but its wording incorrectly said “abstract private method%s” even for methods that were not private.

PHP 8.5 gives enums and anonymous classes their own dedicated message, since neither of them can be declared abstract to defer the missing implementation the way an ordinary class can. Anonymous classes now use the same “must implement N abstract method(s)” message enums already had, and the enum wording drops the incorrect “private” word. Ordinary named classes and interfaces keep using the original generic “contains … must therefore be declared abstract or implement the remaining method(s)” message.

A related, third message, “%s method %s::%s() must not be abstract”, is raised when a method is explicitly marked abstract inside an interface (redundant, since interface methods are implicitly abstract) or, since PHP 8.5, inside an enum (which can never be declared abstract). Before 8.5, writing abstract directly on an enum method instead fell through to the generic “must implement N abstract method(s)” message.

PHP code

<?php

interface Colorful {
    function color();
}

enum Suit implements Colorful {
    case Hearts;
}

?>

Before

Fatal error: Enum Suit must implement 1 abstract private method (Colorful::color)

After

Fatal error: Enum Suit must implement 1 abstract method (Colorful::color)

PHP version change

This behavior changed in 8.5.

Error Messages

Extension

No Abstract Private Method In Traits

It was not possible to have abstract private methods in a trait. There was a conflict between the abstract, which required a definition in a child, and private which prevented it.

This was resolved in PHP 8.0 and later.

PHP code

<?php

trait T { abstract private function foo() ;}

print_r(get_declared_traits());

?>

Before

PHP Fatal error:  Abstract function t::foo() cannot be declared private

Fatal error: Abstract function t::foo() cannot be declared private

After

Array
(
    [0] => t
)

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

Accessing Trait Resources Directly Is Not Allowed

It is not possible anymore to use traits just like a standalone class. As such, accessing methods, properties (and later constants) directly on the trait is not allowed anymore in PHP 8.1 and later. The feature might be removed in PHP 9.0.

Only static resources were accessible via the trait, as it is not possible to instantiate a trait without a class.

PHP code

<?php

trait t {
        static function foo() { print __METHOD__;}
        static $x = 'A';
}

echo T::foo();
echo T::$x;

?>

Before

t::fooA

After

PHP Deprecated:  Calling static trait method t::foo is deprecated, it should only be called on a class using the trait

Deprecated: Calling static trait method t::foo is deprecated, it should only be called on a class using the trait
t::fooPHP Deprecated:  Accessing static trait property t::$x is deprecated, it should only be accessed on a class using the trait

Deprecated: Accessing static trait property t::$x is deprecated, it should only be accessed on a class using the trait
A

PHP version change

This behavior was deprecated in 8.1.

This behavior changed in 9.0.

Error Messages

Extension

Analyzer

Alias Replace Class

When a class is defined before an alias, with use, it used to yield a fatal error. Since PHP 8.4, and when the alias is in a different block than the definition, it is possible to replace a class with another one.

While the fatal error has been removed, it now means that a class, local to a namespace, is not always described by its relative name. The class is still distinguisable with its absolute name.

PHP code

<?php

namespace A {
        class xBefore {}
}

namespace A {
    use y as xAfter;
    use y as xBefore;
    class y {}

  print get_class(new y);    
}


namespace A {
        class xAfter {}
}

?>

Before

PHP Fatal error:  Cannot use y as xBefore because the name is already in use s/aliasReplace.php on line 10

Fatal error: Cannot use y as xBefore because the name is already in use s/aliasReplace.php on line 10

After

A\y

PHP version change

This behavior changed in 8.4.

Error Messages

Extension

Anonymous Class Name Includes Extends

The fully qualified name of an anonymous class (sic) includes the parent class as the first part of the name, since PHP 8.0.

In previous versions, it only used class.

It also includes the mention anonymous, the name of the file and the line number. The parent name is the fully qualified name, so it includes the namespace, except for the initial backaslash (hence, here, it is not displayed).

PHP code

<?php

$object = new class() extends \Exception {};

echo get_class($object);

?>

Before

class@anonymous/codes/anonymousSignature.php:3

After

Exception@anonymous/codes/anonymousSignature.php:3

PHP version change

This behavior changed in 8.0.

Extension

array_key_exists() doesn’t work on objects

array_key_exists() used to accept arrays and objects, and worked on them indistinctly.

Since PHP 8.0, array_key_exists() only works on arrays. Objects must be converted to arrays before usage.

PHP code

<?php

var_dump(array_key_exists('a', (object) ['a' => 1]));

?>

Before

true

After

Fatal error

PHP version change

This behavior was deprecated in Using array_key_exists() on objects is deprecated. Use isset() or property_exists().

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

array_merge() And Variadic

array_merge() always needed at least one argument to execute. This means that using the variadic operator on an empty array yielded no argument, and then, an error.

Since PHP 7.4, array_merge() handles graciously the case of no arguments, by returning an empty array, and not more error.

This applies to array_merge() and array_merge_recursive().

PHP code

<?php

$array = [];

$array2 = array_merge(...$array);

print_r($array2);

?>

Before

PHP Warning:  array_merge() expects at least 1 parameter, 0 given

Warning: array_merge() expects at least 1 parameter, 0 given

After

Array
(
)

PHP version change

This behavior changed in 7.4.

Error Messages

Extension

Analyzer

Negative Automatic Index From Empty Array

When starting from an empty array and assigning an initial negative integer index, PHP used to continue assigning indices with 0, instead of the following negative number. It is fixed in PHP 8.3.

PHP code

<?php

$array = [];
$array[-2] = 'a';
$array[] = 'b';

print_r($array);

?>

Before

Array
(
    [-2] => a
    [0] => b
)

After

Array
(
    [-2] => a
    [-1] => b
)

PHP version change

This behavior changed in 8.3.

Extension

Automatic Index In Non Empty Array

When starting from an array whose maximum key is integer and negative, PHP used to continue assigning indices with 0, instead of the following negative number. It is fixed in PHP 8.0.

PHP code

<?php

$array = [
    -10 => 'a',
];
$array[] = 'b';

print_r($array);

?>

Before

Array
(
    [-10] => a
    [0] => b
)

After

Array
(
    [-10] => a
    [-9] => b
)

PHP version change

This behavior changed in 8.0.

See Also

Extension

Array Usage With String Initialisation

String and arrays share the same syntax when using integer index: it accesses one element in the array or string, to reading or modifying.

On the other hand, strings do not accept string as index. In older PHP versions, PHP would convert the string to an integer, and apply the operation. In PHP 8.0, it is now forbidden to use those index, unless the string can be converted to an integer.

PHP code

<?php

$s = 'abc';
$s[3] = 3;
$s['4'] = '4';
print $s;
$s['c'] = 3;

?>

Before

abc34PHP Warning:  Illegal string offset 'c'

Warning: Illegal string offset 'c'

After

abc34PHP Fatal error:  Uncaught TypeError: Cannot access offset of type string on string

Fatal error: Uncaught TypeError: Cannot access offset of type string on string

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

array_change_key_case() Validates Its Case Argument

array_change_key_case() accepts a second argument, either CASE_LOWER or CASE_UPPER. Until PHP 8.6, any other value was silently treated as CASE_LOWER. In PHP 8.6, an invalid value throws a ValueError.

PHP code

<?php

var_dump(array_change_key_case(['A' => 1], 99));

?>

Before

array(1) {
  [A]=>
  int(1)
}

After

array_change_key_case(): Argument #2 ($case) must be either CASE_LOWER or CASE_UPPER

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

array_filter() Validates Its Mode Argument

array_filter() accepts a third argument that selects which values are passed to the callback: ARRAY_FILTER_USE_VALUE, ARRAY_FILTER_USE_KEY, or ARRAY_FILTER_USE_BOTH. Until PHP 8.6, any other value was silently treated as 0 (ARRAY_FILTER_USE_VALUE). In PHP 8.6, an invalid mode throws a ValueError.

PHP code

<?php

var_dump(array_filter([1, 0, 2, null], fn($v) => true, 99));

?>

Before

array(4) {
  [0]=>
  int(1)
  [1]=>
  int(0)
  [2]=>
  int(2)
  [3]=>
  NULL
}

After

array_filter(): Argument #3 ($mode) must be one of ARRAY_FILTER_USE_VALUE, ARRAY_FILTER_USE_KEY, or ARRAY_FILTER_USE_BOTH

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

array_intersect() Converts Values While Scanning Inputs

array_intersect() compares values as strings. Until PHP 8.6, non-string values were converted to strings lazily, during the pairwise sort-based comparisons used internally. In PHP 8.6, all input arrays (once at least two are known to be non-empty) are scanned upfront and their values converted to strings before any comparison happens. This changes the number and order of conversion warnings and __toString() calls – fewer calls overall, since each value is now converted once instead of possibly several times during comparisons – and can change results for a stateful __toString() implementation.

PHP code

<?php

class Stateful {
    private static $calls = 0;
    public function __toString(): string {
        self::$calls++;
        echo "toString call #".self::$calls."\n";
        return 'x';
    }
}

$a = [new Stateful()];
$b = ['x'];
$c = ['x'];

$result = array_intersect($a, $b, $c);
var_dump(count($result));

?>

Before

toString call #1
toString call #2
int(1)

After

toString call #1
int(1)

PHP version change

This behavior changed in 8.6.

See Also

Extension

Several ArrayIterator Methods Are Deprecated

ArrayIterator exposes several methods inherited from its ArrayAccess, Countable and internal-flags implementation, such as getFlags(), setFlags(), asort() and ksort(). Until PHP 8.6, calling these methods worked silently. In PHP 8.6, calling ArrayIterator::getFlags(), ArrayIterator::setFlags(), ArrayIterator::asort() (and several sibling sort methods) each emit their own deprecation notice, while still behaving exactly as before.

PHP code

<?php

$it = new ArrayIterator(['b' => 2, 'a' => 1]);

var_dump($it->getFlags());
$it->setFlags(ArrayIterator::ARRAY_AS_PROPS);
var_dump($it->getFlags());
$it->asort();
print_r(iterator_to_array($it));

?>

Before

int(0)
int(2)
Array
(
    [a] => 1
    [b] => 2
)

After

PHP Deprecated:  Method ArrayIterator::getFlags() is deprecated since 8.6 in /codes/arrayIteratorMethodsDeprecated.php on line 5

Deprecated: Method ArrayIterator::getFlags() is deprecated since 8.6 in /codes/arrayIteratorMethodsDeprecated.php on line 5
int(0)
PHP Deprecated:  Method ArrayIterator::setFlags() is deprecated since 8.6 in /codes/arrayIteratorMethodsDeprecated.php on line 6

Deprecated: Method ArrayIterator::setFlags() is deprecated since 8.6 in /codes/arrayIteratorMethodsDeprecated.php on line 6
PHP Deprecated:  Method ArrayIterator::getFlags() is deprecated since 8.6 in /codes/arrayIteratorMethodsDeprecated.php on line 7

Deprecated: Method ArrayIterator::getFlags() is deprecated since 8.6 in /codes/arrayIteratorMethodsDeprecated.php on line 7
int(2)
PHP Deprecated:  Method ArrayIterator::asort() is deprecated since 8.6 in /codes/arrayIteratorMethodsDeprecated.php on line 8

Deprecated: Method ArrayIterator::asort() is deprecated since 8.6 in /codes/arrayIteratorMethodsDeprecated.php on line 8
Array
(
    [a] => 1
    [b] => 2
)

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

array_product() New Checks

array_product() used to cast the arguments to integers before executing the multiplications. Nowadays, the strange types raise a warning, as illustrated here with the array.

PHP code

<?php

print array_product([1, true, []]);

?>

Before

1

After

PHP Warning:  array_product(): Multiplication is not supported on type array

Warning: array_product(): Multiplication is not supported on type array
1

PHP version change

This behavior changed in 8.3.

Error Messages

Extension

array_sum() Checks Operands Thoroughly

array_sum() used to cast the arguments to integers before executing the additions. Nowadays, the strange types raise a warning, as illustrated here with the array.

PHP code

<?php

print array_sum([1, false, []]);

?>

Before

1

After

PHP Warning:  array_sum(): Addition is not supported on type array

Warning: array_sum(): Addition is not supported on type array
1

PHP version change

This behavior changed in 8.3.

See Also

Error Messages

Extension

Passing An Object To array_walk() Is Deprecated

array_walk() and array_walk_recursive() have always accepted an object as their first argument, walking its accessible public properties as if they were an array. Until PHP 8.6, this worked silently. In PHP 8.6, passing an object to either function emits a deprecation notice suggesting get_object_vars() first, even though the walk itself still happens and mutates the object’s properties exactly as before.

PHP code

<?php

class Point {
    public $x = 1;
    public $y = 2;
}

$p = new Point();
array_walk($p, function (&$value, $key) {
    $value = $value * 10;
});
var_dump($p);

?>

Before

object(Point)#1 (2) {
  ["x"]=>
  int(10)
  ["y"]=>
  int(20)
}

After

PHP Deprecated:  array_walk(): Passing an object for argument #1 $array to array_walk() is deprecated, call get_object_vars() first instead

Deprecated: array_walk(): Passing an object for argument #1 $array to array_walk() is deprecated, call get_object_vars() first instead
object(Point)#1 (2) {
  ["x"]=>
  int(10)
  ["y"]=>
  int(20)
}

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

assert Is Reserved Function

It is not possible to create a function named assert anymore in PHP 8.0. This applies to every scopes, global or custom.

Until PHP 8.0, it was possible.

PHP code

<?php

function assert() {}

?>

Before

PHP Deprecated:  Defining a custom assert() function is deprecated, as the function has special semantics

Deprecated: Defining a custom assert() function is deprecated, as the function has special semantics
PHP Fatal error:  Cannot redeclare assert()

Fatal error: Cannot redeclare assert()

After

PHP Fatal error:  Defining a custom assert() function is not allowed, as the function has special semantics

Fatal error: Defining a custom assert() function is not allowed, as the function has special semantics

PHP version change

This behavior was deprecated in 7.4.

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

assert() Throws Exception

assert() is the PHP native implementation of assertions. Until PHP 8.0, it would raise an error, while now, it throws an exception.

PHP code

<?php
// error handler function
function myErrorHandler($errno, $errstr, $errfile, $errline)
{
        print __METHOD__;

    return true;
}

set_error_handler('myErrorHandler');

try {
        assert(false);
} catch (\Error $e) {
        print $e->getMessage();
}

?>

Before

myErrorHandler

After

assert(false)

PHP version change

This behavior changed in 8.0.

Extension

Static Properties With Asymmetric Visibility

Asymmetric visibility was introduced in version 8.4. In that version, the asymmetric visibility was limited to non-static properties. In PHP 8.5, that feature is now extended to static properties.

PHP code

<?php

class x {
    public private(set) static int $a = 3;
}

?>

Before

PHP Fatal error:  Static property may not have asymmetric visibility

Fatal error: Static property may not have asymmetric visibility

After


PHP version change

This behavior changed in 8.5-.

Error Messages

Extension

Auto-initialization From Boolean

The auto-initialization is the conversion a boolean false or true, to an array, by using the array syntax on it.

When applied to a property, it may be impossible, given the type of that property. The warning message also appears if the type allow it: it is recommended to convert the property to an array before using the array syntax.

PHP code

<?php

class X {
    public bool $property = false;
    public bool|array $property2 = false;
}

$x = new X;
// Fatal error, as type doesn't allow it
$x->property[3] = 2;

// Deprecated error, as type allow it
$x->property2[4] = 5;

?>

Before

PHP Parse error:  syntax error

Parse error: syntax error

After

PHP Fatal error:  Uncaught TypeError: Cannot auto-initialize an array inside property X::$property of type bool

Fatal error: Uncaught TypeError: Cannot auto-initialize an array inside property X::$property of type bool

PHP version change

This behavior changed in 7.4.

Error Messages

Extension

Analyzer

Using __autoload() is deprecated

Defining the __autoload() function used to be the way to create a autoloading mechanism for classes, traits, interfaces and enumerations. This was later replaced by the spl_autoload_register() function, which allows adding and removing autoloading functions. Ever since, creating the __autoload() function is reported as deprecated, and the function is not used since PHP 8.0.

PHP code

<?php

function __autoload() {}

?>

Before

PHP Fatal error:  __autoload() is no longer supported, use spl_autoload_register() instead

Fatal error: __autoload() is no longer supported, use spl_autoload_register() instead

After

PHP Fatal error:  __autoload() is no longer supported, use spl_autoload_register() instead

Fatal error: __autoload() is no longer supported, use spl_autoload_register() instead

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Backed Enum Values Needed To Compile

The backed enums needed to be a completely processable at compile time. In particular, using other constants, global or class, was not possible.

In PHP 8.2 and later, this problem has be postponed to execution time. This means that, when the constant values in the expression are available at usage time, then it is OK.

Note that all the case expressions are checked at once, whatever the case, or constant used. If any constant expression is missing, even if it is not used, then PHP yields a fatal error. Autoload may play its part.

PHP code

<?php

const D = 1;

enum Foo: string {
    case A = '/' . D;
    case B = '/' . B;
    const C = 1;
}

Foo::A; // first actual usage of the case

?>

Before

PHP Fatal error:  Enum case value must be compile-time evaluatable 

Fatal error: Enum case value must be compile-time evaluatable 

After

PHP Fatal error:  Uncaught Error: Undefined constant B

Fatal error: Uncaught Error: Undefined constant B

PHP version change

This behavior changed in 8.3.

Error Messages

Extension

Back-tick Operator Is Deprecated

The back tick operator is deprecated, and will be removed in PHP 9.0. It should be replaced with a call to shell_exec(), which is the function equivalent. It may also be replaced with any other dedicated feature: for example, listing files in a directory may be replaced with a call to scandir().

PHP code

<?php

print `echo 'Hello'`;

?>

Before

Hello

After

PHP Deprecated:  The backtick (`) operator is deprecated, use shell_exec() instead

Deprecated: The backtick (`) operator is deprecated, use shell_exec() instead
Hello

PHP version change

This behavior was deprecated in 8.5.

This behavior changed in .

See Also

Error Messages

Extension

Analyzer

Base Conversion Functions Warn When Precision Is Lost

bindec(), octdec(), hexdec() and base_convert() all share the same internal conversion routine, which accumulates the result in a platform integer for as long as it fits, then switches to a float once the value would overflow. That switch already happened silently before PHP 8.6: a double only has 53 bits of mantissa, so digits beyond that are rounded away without any indication. In PHP 8.6, the switch to float now also emits an E_NOTICE, while the returned value is unchanged.

PHP code

<?php

var_dump(hexdec('FFFFFFFFFFFFFFFF'));

?>

Before

float(1.8446744073709552E+19)

After

PHP Notice:  Input number is larger than PHP_INT_MAX, precision has been lost in conversion in /codes/baseConversionPrecisionLossNotice.php on line 3

Notice: Input number is larger than PHP_INT_MAX, precision has been lost in conversion in /codes/baseConversionPrecisionLossNotice.php on line 3
float(1.8446744073709552E+19)

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

Bitshift And Concat Precedence

<< and >> and the dot, operators used to have the same priority. Thus, they used to be processed one after the other, from left to right.

In PHP 8.0, the bitshift has now the highest precedence, and will happen before the concatenation.

PHP code

<?php

echo 35 << 1 . '.' . 0 + 5;

?>

Before

70.5

After

2240

PHP version change

This behavior was deprecated in 7.4.

This behavior changed in 8.0.

Extension

Analyzer

Boolean Used As Array

Booleans, true and false are not an array, but it is possible to use the array syntax with it. The values are then always null, and since PHP 7.4, a warning is emitted.

PHP code

<?php

// var_dump(true[0]); is not a valid PHP syntax

const MY_CONSTANT = true;
var_dump(MY_CONSTANT[0]);

?>

Before

NULL

After

PHP Warning:  Trying to access array offset on true

Warning: Trying to access array offset on null
PHP

PHP version change

This behavior changed in 7.4.

Error Messages

Extension

Analyzer

Calling Non-Static Method Statically

Calling non-static method statically has been deprecated for a long time.

It should be noted that, inside a class, it is possible to statically call any methods of the same class. This is needed for edge cases such as parent::__construct(), where the constructor is never static.

PHP code

<?php

class x {
	function foo() {
		print __METHOD__;
	}
}

x::foo();

?>

Before

PHP Deprecated:  Non-static method x::foo() should not be called statically

Deprecated: Non-static method x::foo() should not be called statically
x::foo

After

PHP Fatal error:  Uncaught Error: Non-static method x::foo() cannot be called statically

Fatal error: Uncaught Error: Non-static method x::foo() cannot be called statically

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

Accessing Directly Properties In Trait

I was possible, though deprecated, to manipulate directly trait properties: the static properties.

Since traits only make sense as a part of a class, this operation is now forbidden.

Accessing static methods are also forbidden. Accessing trait constants is also forbidden, although constants in traits were introduced in PHP 8.3.

PHP code

<?php

trait T {
    public static $P = 1;
    
}

echo T::$P;

?>

Before

1

After

PHP Deprecated:  Accessing static trait property t::$P is deprecated, it should only be accessed on a class using the trait

Deprecated: Accessing static trait property t::$P is deprecated, it should only be accessed on a class using the trait
1

PHP version change

This behavior was deprecated in 8.0.

This behavior changed in 8.1.

Error Messages

Extension

Analyzer

Cannot Call Traits Methods Directly

Traits used to be called directly, like a class. In PHP 8.1, this feature has been removed. The methods, properties or constants of the trait must be called in the context of their host class.

PHP code

<?php

trait T {
    static function foo() { echo __METHOD__; }
}

echo T::foo();

?>

Before

t::foo

After

PHP Deprecated:  Calling static trait method t::foo is deprecated, it should only be called on a class using the trait

Deprecated: Calling static trait method t::foo is deprecated, it should only be called on a class using the trait
t::foo

PHP version change

This behavior was deprecated in 8.1.

This behavior changed in 9.0.

Error Messages

Extension

Analyzer

No Case With A Semi-colon

It was little known that one could use a semi-colon ; in a case entry, instead of a colon :. Both would act as a delimiter between the case value and the actualy case block.

Since PHP 8.5, it is forbidden.

PHP code

<?php

$a = 1;
switch ($a) {
    case 1; 
    echo 2;
     break;
}

?>

Before

2

After

PHP Deprecated:  Case statements followed by a semicolon (;) are deprecated, use a colon (:) instead

Deprecated: Case statements followed by a semicolon (;) are deprecated, use a colon (:) instead
2

PHP version change

This behavior changed in 8.5.

Error Messages

Extension

PHP Constants Are Not Case Insensitive

PHP allowed the creation of case-insensitive constants with the function define. That was the third parameter to be passed, with a default, and often ignored value of false.

Since PHP 8.0, case-insensitive constants are not possible anymore. Creating a constant with both const and define only leads to case-sensitive global constant.

As a reminder, accessing a non-existing constant is a Fatal error, so an error on the case in a global constant leads to it.

PHP code

<?php

define('A', 1, true);

echo a;
echo A;

?>

Before

11

After

PHP Warning:  define(): Argument #3 ($case_insensitive) is ignored since declaration of case-insensitive constants is no longer supported

Warning: define(): Argument #3 ($case_insensitive) is ignored since declaration of case-insensitive constants is no longer supported
PHP Fatal error:  Uncaught Error: Undefined constant a

Fatal error: Uncaught Error: Undefined constant a

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

Cast In Const

PHP accepts the cast operators in constant expressions. Until then, operators such as (int), (float), (string), (array) were not allowed.

While using a cast on a literal value is rather meaningless, static constant expressions may be build on top of other constants.

Then, (object) is still not accepted, along with (void).

PHP code

<?php

const S = 123s;
const C = (int) S;

class X {
    const C = (int) S;
}

echo C;

?>

Before

PHP Fatal error:  Constant expression contains invalid operations

Fatal error: Constant expression contains invalid operations

After

123

PHP version change

This behavior changed in 8.5.

Error Messages

Extension

Catch Without Variable

A catch clause doesn’t require a storing variable anymore. It may simply omit it. The exception is then caught, but not provided in the clause.

PHP code

<?php

try {
    throw new Exception('Error');
} catch (Exception) {
    print 'Exception caught';
}

?>

Before

Parse error: syntax error, unexpected ')', expecting '|' or variable (T_VARIABLE)

After

Exception caught

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

ceil() Strict Mode

ceil() doesn’t accept internal objects that can be converted to integer. This is the case for gmp and bcmath objects, as shown in the example. Since PHP 8.0, only integers and floats are allowed.

PHP code

<?php

$a = gmp_init(123456);

echo ceil($a);

?>

Before

123456

After

PHP Fatal error:  Uncaught TypeError: ceil(): Argument #1 ($num) must be of type int|float, GMP given

Fatal error: Uncaught TypeError: ceil(): Argument #1 ($num) must be of type int|float, GMP given

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

class_alias() Works On Internal Classes

class_alias() makes an alias for a class, an enumeration, an interface or a trait. Until PHP 8.3, it was only possible on custom structures.

PHP code

<?php

class_alias(stdClass::class, A::class);

var_dump(new A);

?>

Before

First argument of class_alias() must be a name of user defined class

After

object(stdClass)#1 (0) {
}

PHP version change

This behavior changed in 8.3.

See Also

Error Messages

Extension

::class On Object

The ::class operator provides the fully qualified name of the identifier or object. It used to be working only on identifier or names, but it also works on objects, via variables and properties: then, it provides the fully qualified name of the underlying class.

This is very convenient when the code needs to get a hold on the class, and only the object is provided.

PHP code

<?php

$a = new stdclass;
echo $a::class;

?>

Before

PHP Fatal error:  Cannot use ::class with dynamic class name

Fatal error: Cannot use ::class with dynamic class name

After

stdClass

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

Underscore Named Class

It is not possible to name a class _ underscore anymore.

This applies to classes, interfaces, traits and enumerations. It applies in every namespace.

It does not applies to functions, although that name is usually a native PHP function from the gettext extension. It also does not apply to constants, methods, class constants and variables.

It is still possible to name a class with a longer name, starting with an underscore.

The _ is being reserved for the future pattern matching feature of PHP.

PHP code

<?php

class _ {}

print get_class(new _);

?>

Before

_

After

PHP Deprecated:  Using "_" as a class name is deprecated since 8.4

Deprecated: Using "_" as a class name is deprecated since 8.4
_

PHP version change

This behavior changed in 8.4.

Error Messages

Extension

Analyzer

Clone A Constant

Cloning a constant was useless: this is the version where global constants could be initialized with an object.

The syntax has always been valid, but, at execution time, it would emit an error, as the constant could not be cloned.

PHP code

<?php

class C {}

const A = new C;

var_dump(clone A);

?>

Before

PHP Fatal error:  Constant expression contains invalid operations

Fatal error: Constant expression contains invalid operations

After

object(C)#2 (0) {
}

PHP version change

This behavior changed in 8.1.

Error Messages

Extension

Analyzer

Can Clone Readonly Properties

Readonly properties may be changed, both at constructor and cloning time, since PHP 8.3. Until then, once set, they could never be changed.

PHP code

<?php

class X {
	readonly int $property;
	readonly A $property2;
	
	function __construct(int $p) {
		$this->property = $p;
		$this->property2 = new A($p);
	}
	
	function __clone() {
		$this->property++; // clone used to change scalar
		$this->property2 = new A($this->property); // clone used to change object
	}
}

$x = new X;
$y = clone $x;

var_dump($y);

?>

Before

PHP Fatal error:  Uncaught Error: Cannot modify readonly property x::$p

Fatal error: Uncaught Error: Cannot modify readonly property x::$p

After

object(x)#2 (1) {
  ["p"]=>
  int(3)
}

PHP version change

This behavior changed in 8.3.

Error Messages

Extension

Analyzer

compact() Throws Notice On Missing Variable

compact() collects variables in an array. When trying to compact() variable that don’t exist, compact() now emits warnings to signal the missing variables. They might be removed or created.

Invalid variable names, such as numeric values, are also reported.

PHP code

<?php

$name = 'Tobias';
$age = 28;

// class error, where the variable is confused with its content
var_dump(compact($name, $age));

// valid usage
// var_dump(compact("name", 'age'));

?>

Before

PHP Warning:  compact(): Undefined variable $Tobias

Warning: compact(): Undefined variable $Tobias
array(0) {
}

After

PHP Warning:  compact(): Undefined variable $Tobias

Warning: compact(): Undefined variable $Tobias
PHP Warning:  compact(): Argument #2 must be string or array of strings, int given

Warning: compact(): Argument #2 must be string or array of strings, int given
array(0) {
}

PHP version change

This behavior changed in 8.1.

See Also

Error Messages

Extension

Analyzer

First-Class Callables Inside Constant Expressions Must Resolve To A Literal Method Name

PHP allows first-class callable syntax ((...)) to be used inside a constant expression, such as a global constant, a class constant, a static property default, a parameter default value, and so on, so that a static method can be turned into a Closure at compile time. To build that closure, the method name must be resolvable at compile time, which introduces two new, related compile errors for static method calls (X::method(...)):

‘Cannot use dynamic method name in constant expression’ is reported when the braced method-name expression cannot be folded down to a constant value at all – for example because it is an array, or depends on something not known at compile time.

‘Illegal method name’ is reported when the braced expression does resolve to a compile-time constant, but that value is not a legal method name, such as an integer.

The equivalent restrictions for a plain function name (rather than a static method) are reported as ‘Cannot use dynamic function name in constant expression’ and ‘Illegal function name’.

PHP code

<?php

class X {
    public static function foo() { return 1; }
}

const C1 = X::{[1, 2]}(...); // Cannot use dynamic method name in constant expression

const C2 = X::{0}(...); // Illegal method name

?>

Before


After

PHP Fatal error:  Cannot use dynamic method name in constant expression

PHP version change

This behavior changed in 8.5.

Error Messages

Extension

Interface Imported Constant Visibility Is Checked

Constant and methods visibility must be public when they are defined in an interface. When they are implemented in a class, they also need to be public. Until PHP 8.3, this was silently ignored, and made public.

PHP code

<?php

interface i {
    public const IPrivate   = 'private';
    public const IProtected = 'protected';
    public const IPublic    = 'public';
}

class x implements i {
    private const IPri = 1;
    protected const IPro = 2;
    public const IPub = 3;
}

echo x::IPrivate . PHP_EOL;
echo x::IProtected . PHP_EOL;
echo x::IPublic . PHP_EOL;

?>

Before

3

After

PHP Fatal error:  Access level to x::IPri must be public (as in interface i)

PHP version change

This behavior changed in 8.3.

Error Messages

Extension

Constants In Trait

Trait can define constants, like a class or an interface. Until PHP 8.3, constants cannot be set in traits, and end with a compilation error.

PHP code

<?php

trait T {
    const X = 1;
}

class X {
	use T;
}

echo X::X;

?>

Before

PHP Fatal error:  Traits cannot have constants

After

1

PHP version change

This behavior changed in 8.3.

Error Messages

Extension

Analyzer

Declaring A Class Constant Named namespace Is Deprecated

namespace is a reserved word in PHP, but a historical lexer exception allowed it to be used as the name of a class, interface, trait or enum constant, as well as a static property. Until PHP 8.6, declaring such a constant produced no warning. In PHP 8.6, declaring a constant called namespace emits a deprecation notice, reserving the word for a possible future ::namespace pseudo-constant, analogous to the existing ::class.

PHP code

<?php

class Foo {
    const NAMESPACE = 'bar';
}

echo Foo::NAMESPACE, "\n";

?>

Before

bar

After

PHP Deprecated:  Declaring class constant called 'namespace' is deprecated in /codes/constantNamespaceInClassDeprecated.php on line 4

Deprecated: Declaring class constant called 'namespace' is deprecated in /codes/constantNamespaceInClassDeprecated.php on line 4
bar

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

Making A Constructor A Generator Is Deprecated

Using yield anywhere in the body of __construct() turns it into a Generator function. Because a Generator’s body only starts running once it is iterated, and nothing iterates a constructor, the code inside such a constructor never runs when the object is created with new: initialization is silently skipped. Since this pattern is virtually always a mistake, PHP 8.6 deprecates making a constructor a Generator.

PHP code

<?php

class X {
    public function __construct() {
        yield 1;
    }
}

new X;

?>

Before


After

PHP Deprecated:  Making a constructor a Generator is deprecated in /codes/constructorAsGenerator.php on line 4

Deprecated: Making a constructor a Generator is deprecated in /codes/constructorAsGenerator.php on line 4

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in 8.6.

See Also

Error Messages

Extension

Returning A Value From A Constructor Is Deprecated

A constructor’s return value was always ignored by PHP, silently. In PHP 8.6, returning any value, other than not returning at all, from __construct() emits a deprecation notice. The same applies to __destruct().

PHP code

<?php

class x {
    public function __construct() {
        return 5;
    }
}

new x();
print "done\n";

?>

Before

done

After

PHP Deprecated:  Returning a value from a constructor is deprecated

Deprecated: Returning a value from a constructor is deprecated
done

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

Constants With Objects

Global constants are allowed to use an object, starting with PHP 8.1. The object must be instantiated with only constants values, like literals and other constants.

Class constant are not allowed to use the new keyword.

PHP code

<?php

const A = new stdclass();

var_dump(A);

?>

Before

PHP Fatal error:  Constant expression contains invalid operations

Fatal error: Constant expression contains invalid operations

After

object(stdClass)#1 (0) {
}

PHP version change

This behavior changed in 8.1.

Error Messages

Extension

Analyzer

Parameter Contravariance

PHP added the support of parameter type contravariance. Since PHP 7.4, the parameter type of a child class may be less strict than the one of the parent.

In PHP 7.3, the child method must have the same parameter type than the parent.

PHP code

<?php

interface I {}

interface J extends I {}

class X {
	function foo(j $a) {}
}

class Y extends X {
	function foo(i $a) {}
}

var_dump(new Y);

?>

Before

PHP Warning:  Declaration of y::foo(i $a) should be compatible with x::foo(j $a)

Warning: Declaration of y::foo(i $a) should be compatible with x::foo(j $a)
object(y)#1 (0) {
}

After

object(y)#1 (0) {
}

PHP version change

This behavior changed in 7.4.

Error Messages

Extension

convert_uuencode() Works On Empty Strings

convert_uuencode() returned false, aka error, when provided with an empty string.

Since PHP 8.0, it returns a valid encoded string, which may be decoded later.

PHP code

<?php

var_dump( convert_uuencode(''));

?>

Before

bool(false)

After

string(2) "\`
" 

PHP version change

This behavior changed in 8.0.

Extension

count() Must Count Countable

PHP used to count any kind of value. Most values would then be counted as one. This is not possible anymore in PHP 8.0. It requires an array or a countable object. This can be tested with is_countable.

PHP code

<?php

print count(3);

?>

Before

PHP Warning:  count(): Parameter must be an array or an object that implements Countable

Warning: count(): Parameter must be an array or an object that implements Countable
1

After

PHP Fatal error:  Uncaught TypeError: count(): Argument #1 ($value) must be of type Countable|array, int given

Fatal error: Uncaught TypeError: count(): Argument #1 ($value) must be of type Countable|array, int given

PHP version change

This behavior was deprecated in 7.2.

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

Returntype Covariance

PHP added the support of return type covariance. Since PHP 7.4, the return type of a child class may be more strict than the one of the parent.

In PHP 7.3, the child method must have the same return type than the parent.

PHP code

<?php

interface I {}

interface J extends I {}

class X {
	function foo() : I {
	
	}
}

class Y extends X {
	function foo() : J {
	
	}
}

var_dump(new Y);

?>

Before

PHP Fatal error:  Declaration of y::foo(): j must be compatible with x::foo(): i 

Fatal error: Declaration of y::foo(): j must be compatible with x::foo(): i 

After

object(y)#1 (0) {
}

PHP version change

This behavior changed in 7.4.

Error Messages

Extension

Creating Object On Null

It was possible to create an object just like a variable: simply by assigning a value to one property. The created object was of class stdClass, and could be used further.

In PHP 8.0, this is now a Fatal error. Later, undefined properties, also known as dynamic properties were deprecated, and will lead to a Fatal error in PHP 9.

PHP code

<?php

$x->a = 1;

print $x->a;

?>

Before

PHP Warning:  Creating default object from empty value 

Warning: Creating default object from empty value 
1

After

PHP Fatal error:  Uncaught Error: Attempt to assign property "a" on null 

Fatal error: Uncaught Error: Attempt to assign property "a" on null 

PHP version change

This behavior was deprecated in 7.3.

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

curl_close() No Longer Flushes The Cookie Jar

Calling curl_close() on a handle that used a cookie jar would immediately write the collected cookies to disk, since the underlying resource was destroyed right away. Since PHP 8.0, cURL handles are objects, and curl_close() no longer destroys the handle: it only becomes a no-op that keeps the handle usable, so the cookie jar file is not written anymore until the object is actually garbage collected. Code relying on curl_close() to flush cookies to disk should instead call curl_setopt($handle, CURLOPT_COOKIELIST, "FLUSH") before closing the handle. Since PHP 8.5, calling curl_close() at all raises a deprecation notice, as core has confirmed the function has had no effect since 8.0.

PHP code

<?php

$jar = tempnam(sys_get_temp_dir(), 'cookiejar');

$ch = curl_init('https://httpbin.org/cookies/set?test=value');
curl_setopt($ch, CURLOPT_COOKIEFILE, '');
curl_setopt($ch, CURLOPT_COOKIEJAR, $jar);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
curl_close($ch);

var_dump(filesize($jar) > 0);

unlink($jar);

?>

Before

bool(true)

After

bool(false)

PHP version change

This behavior was deprecated in 8.5.

This behavior changed in 8.0.

See Also

Error Messages

Extension

cUrl Moved Away From Resource

Curl functions have moved from resource to objects in PHP 8.0. Instead of returning a resource, it now returns a CurlHandle object. Checks based on is_resource() must be upgraded, and are now dead code.

PHP code

<?php

var_dump(curl_init('https://www.php.net'));

?>

Before

resource(4) of type (curl)

After

object(CurlHandle)#1 (0) {
}

PHP version change

This behavior changed in 8.0.

See Also

Extension

CURLOPT_READFUNCTION Callback Validates Its Return Value

A CURLOPT_READFUNCTION callback is expected to return the chunk of data to upload, the empty string, CURL_READFUNC_ABORT, or CURL_READFUNC_PAUSE. Until PHP 8.6, an invalid return value, such as an arbitrary integer, was passed straight to libcurl, which simply aborted the transfer without any diagnostic from PHP. In PHP 8.6, curl_exec() throws a ValueError when the callback returns something other than a string, CURL_READFUNC_ABORT, or CURL_READFUNC_PAUSE.

PHP code

<?php

$docroot = sys_get_temp_dir() . '/curlrf' . getmypid();
mkdir($docroot);
file_put_contents($docroot . '/upload.php', '<?php echo "ok";');

$port = 8391;
$server = proc_open(
    PHP_BINARY . ' -S 127.0.0.1:' . $port . ' -t ' . escapeshellarg($docroot),
    [1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
    $pipes
);
usleep(500000);

$ch = curl_init('http://127.0.0.1:' . $port . '/upload.php');
curl_setopt($ch, CURLOPT_UPLOAD, true);
curl_setopt($ch, CURLOPT_INFILESIZE, 5);
curl_setopt($ch, CURLOPT_READFUNCTION, function ($ch, $fh, $length) {
    return 999;
});
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);

try {
    $result = curl_exec($ch);
    var_dump($result);
} catch (\ValueError $e) {
    echo $e->getMessage(), "\n";
}

proc_terminate($server);
proc_close($server);
unlink($docroot . '/upload.php');
rmdir($docroot);

?>

Before

bool(false)

After

The CURLOPT_READFUNCTION callback must return a string or CURL_READFUNC_ABORT or CURL_READFUNC_PAUSE

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

Array Syntax With Curly Braces Are No More

Using the array syntax with curly braces used to yield a Fatal error, and a nice error message.

After PHP 8.4, it is downgraded to a simple syntax error.

PHP code

<?php

$x{3} = 2;

print_r($x);

?>

Before

PHP Fatal error:  Array and string offset access syntax with curly braces is no longer supported

Fatal error: Array and string offset access syntax with curly braces is no longer supported

After

PHP Parse error:  syntax error, unexpected token "\{" 

Parse error: syntax error, unexpected token "\{" 

PHP version change

This behavior changed in 8.4.

Error Messages

Extension

datetime With Multiple Signs

There can be only one sign character, when instantiating a DateTime object.

Until PHP 8.2, it was possible, though confusing, to use multiple sign + and -. This is considered a bad practice.

PHP code

<?php
$time = new \DateTimeImmutable("-+-1 year");

echo $time->format('Y/m/d H:i:s'), "\n";
?>

Before

2024/10/18 10:15:30

After

2022/10/18 10:15:30

PHP version change

This behavior changed in 8.2.

Extension

Magic Method __debugInfo() cannot return null

__debugInfo() is a magic method that returns an array with debug information. It gives a chance to the program to return any useful information, beyond the local properties. It also allows to remove any sensitive information, such as passwords or secrets. Since PHP 8.6, it is not possible to return NULL: the method method must return an array, albeit empty.

PHP code

<?php

class x {
    function __debugInfo() {
        return null;
    }
}

var_dump(new x);

?>

Before

object(x)#1 (0) {
}

After

PHP Deprecated:  Returning null from x::__debugInfo() is deprecated, return an empty array instead in /codes/debugInfoCannotReturnNull.php on line 9

Deprecated: Returning null from x::__debugInfo() is deprecated, return an empty array instead in /codes/debugInfoCannotReturnNull.php on line 9
object(x)#1 (0) {
}

PHP version change

This behavior changed in 8.5.

Error Messages

Extension

__debugInfo() Nullable Return Type Is Deprecated

The magic method __debugInfo() used to be freely typed to return ?array, since null and array were both acceptable return values. In PHP 8.6, simply declaring __debugInfo() with a nullable ?array return type is deprecated, regardless of what is actually returned: the return type should be made non-nullable, and an empty array should be returned instead of null.

PHP code

<?php

class x {
    public function __debugInfo(): ?array {
        return ['a' => 1];
    }
}

var_dump(new x());

?>

Before

object(x)#1 (1) {
  [a]=>
  int(1)
}

After

PHP Deprecated:  Returning null from x::__debugInfo() is deprecated, make the return type non-nullable and return an empty array instead

Deprecated: Returning null from x::__debugInfo() is deprecated, make the return type non-nullable and return an empty array instead
object(x)#1 (1) {
  [a]=>
  int(1)
}

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

Constant %s is deprecated

With new versions, PHP deprecates some constants. Mostly global constants, but also class constants, when needed.

8.4

  • SUNFUNCS_RET_TIMESTAMP

  • SUNFUNCS_RET_STRING

  • SUNFUNCS_RET_DOUBLE

8.3

  • ASSERT_ACTIVE

  • ASSERT_BAIL

  • ASSERT_CALLBACK

  • ASSERT_EXCEPTION

  • ASSERT_WARNING

PHP code

<?php

echo SUNFUNCS_RET_TIMESTAMP;

?>

Before

0

After

PHP Deprecated:  Constant SUNFUNCS_RET_TIMESTAMP is deprecated 

Deprecated: Constant SUNFUNCS_RET_TIMESTAMP is deprecated 
0

PHP version change

This behavior changed in 8.4.

Error Messages

Extension

Making A Destructor A Generator Is Deprecated

Using yield anywhere in the body of __destruct() turns it into a Generator function. A Generator only starts executing once something iterates it, but nothing iterates the return value of a destructor: PHP just calls it during garbage collection and discards the result. As a consequence, the body of such a destructor never actually runs, silently skipping any cleanup code it contains. Since this pattern is virtually always a mistake, PHP 8.6 deprecates making a destructor a Generator.

PHP code

<?php

class X {
    public function __destruct() {
        yield 1;
    }
}

$x = new X;
unset($x);

?>

Before


After

PHP Deprecated:  Making a destructor a Generator is deprecated in /codes/destructorAsGenerator.php on line 4

Deprecated: Making a destructor a Generator is deprecated in /codes/destructorAsGenerator.php on line 4

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in 8.6.

See Also

Error Messages

Extension

Destructuring Non Arrays

Destructuring non array values emits a warning in PHP 8.5. This applies to integers, floats, strings and booleans. objects emits a Fatal Error, as before. null values are not emitting any warning.

PHP code

<?php

[$a, $b] = 'abc';
[$a, $b] = 123;
[$a, $b] = true;
[$a, $b] = (object) [1,2];

[$a, $b] = null;  // OK

var_dump($a);

?>

Before

PHP Fatal error:  Uncaught Error: Cannot use object of type stdClass as array 

Fatal error: Uncaught Error: Cannot use object of type stdClass as array 

After

PHP Warning:  Cannot use string as array 

Warning: Cannot use string as array 
PHP Warning:  Cannot use string as array 

Warning: Cannot use string as array 
PHP Warning:  Cannot use int as array 

Warning: Cannot use int as array 
PHP Warning:  Cannot use int as array 

Warning: Cannot use int as array 
PHP Warning:  Cannot use bool as array 

Warning: Cannot use bool as array 
PHP Warning:  Cannot use bool as array 

Warning: Cannot use bool as array 
PHP Fatal error:  Uncaught Error: Cannot use object of type stdClass as array 

Fatal error: Uncaught Error: Cannot use object of type stdClass as array 

PHP version change

This behavior changed in 8.5.

Error Messages

Extension

Die And Exit As Functions

Die and Exit used to be language constructs, a special kind of PHP instructions. As such, they had special abilities and behaviors: in particular, it meant that they could not be called dynamically, with their name in a string. Since PHP 8.4, this is possible.

PHP code

<?php

	//Uncaught Error: Call to undefined function \exit()
    $s = 'exit';
    $s('Exit');

?>

Before

PHP Fatal error:  Uncaught Error: Call to undefined function exit()

After

Exit

PHP version change

This behavior changed in 8.4.

See Also

Error Messages

Extension

No More dir() With Null

When calling dir() with null as parameter, it defaulted to open again the last opened directory. Since PHP 8.1, this is not possible anymore.

PHP code

<?php

$a = dir('/tmp');

$b = dir(null);

var_dump($b);

?>

Before

bool(false)

After

PHP Deprecated:  dir(): Passing null to parameter #1 ($directory) of type string is deprecated

Deprecated: dir(): Passing null to parameter #1 ($directory) of type string is deprecated
bool(false)

PHP version change

This behavior changed in 8.1.

Error Messages

Extension

dl() Rejects NUL Bytes In The Extension Filename

dl() used to accept an extension filename containing a NUL byte and only reported the standard warning once enable_dl turned out to be off. In PHP 8.6, a NUL byte in the argument throws a ValueError before that check is even reached.

PHP code

<?php

try {
    var_dump(dl("foo\0bar"));
} catch (\ValueError $e) {
    echo $e->getMessage(), "\n";
}

?>

Before

PHP Warning:  dl(): Dynamically loaded extensions aren't enabled in /codes/dlNullByteValueError.php on line 4

Warning: dl(): Dynamically loaded extensions aren't enabled in /codes/dlNullByteValueError.php on line 4
bool(false)

After

dl(): Argument #1 ($extension_filename) must not contain any null bytes

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

${expression} is deprecated

The $\{} allowed the usage of an expression to be used as the name of a variable, inside de double quoted string. This feature was largely unknown and unused, so it is removed.

PHP code

<?php

$foo = 'bar';
$bar = 'xyz';
var_dump("foo is ${$foo}");

?>

Before

string(10) "foo is xyz" 

After

PHP Deprecated:  Using  (variable variables) in strings is deprecated, use {} instead

Deprecated: Using  (variable variables) in strings is deprecated, use {} instead
string(10) "foo is xyz" b

PHP version change

This behavior changed in 8.2.

Error Messages

Extension

DOM Readonly Properties Use Asymmetric Visibility

Properties previously documented as readonly, such as DOMNode::$nodeType, DOMDocument::$xmlEncoding, DOMEntity::$actualEncoding, $encoding and $version, are now declared with asymmetric visibility (public private(set)). Writing to them from outside the class still fails, but the error message and its wording changed.

PHP code

<?php

$doc = new DOMDocument();
$doc->loadXML('<root/>');

try {
    $doc->xmlEncoding = 'UTF-8';
} catch (\Error $e) {
    echo get_class($e), ': ', $e->getMessage(), "\n";
}

?>

Before

Error: Cannot modify readonly property DOMDocument::$xmlEncoding

After

Error: Cannot modify private(set) property DOMDocument::$xmlEncoding from global scope

PHP version change

This behavior changed in 8.6.

See Also

Extension

Dot And Bitshift Priority

The dot (concatenation) and bitshift (<< and >>) operators have a distinct priority in PHP

PHP code

<?php
echo 3 . 4 << 1;
?>

Before

68

After

38

PHP version change

This behavior was deprecated in The behavior of unparenthesized expressions containing both ‘.’ and ‘>>’/‘<<’ will change in PHP 8: ‘<<’/‘>>’ will take a higher precedence.

This behavior changed in 8.0.

See Also

Extension

Analyzer

Dot And Minus Changed Precedence

The dot (concatenation) and substraction - operators have a distinct priority in PHP 8.0. In particular, - has now precedence.

PHP code

<?php

echo 3 . 4 - 5;

?>

Before

PHP Deprecated:  The behavior of unparenthesized expressions containing both '.' and '+'/'-' will change in PHP 8: '+'/'-' will take a higher precedence

Deprecated: The behavior of unparenthesized expressions containing both '.' and '+'/'-' will change in PHP 8: '+'/'-' will take a higher precedence
29

After

3-1

PHP version change

This behavior was deprecated in 7.4.

This behavior changed in 8.0.

See Also

Error Messages

Extension

Analyzer

Dot And Plus Changed Precedence

The dot (concatenation) and addition + operators have a distinct priority in PHP 8.0. In particular, + has now precedence.

PHP code

<?php

echo 3 . 4 + 5;

?>

Before

PHP Deprecated:  The behavior of unparenthesized expressions containing both '.' and '+'/'-' will change in PHP 8: '+'/'-' will take a higher precedence

Deprecated: The behavior of unparenthesized expressions containing both '.' and '+'/'-' will change in PHP 8: '+'/'-' will take a higher precedence
39

After

39

PHP version change

This behavior was deprecated in 7.4.

This behavior changed in 8.0.

See Also

Error Messages

Extension

Analyzer

Duplicate Static Definition

PHP reports when the same static variable has been declared twice in the same context.

PHP code

<?php

namespace A { 
	function foo() {
        static $s;
        $s = 1;

        static $s;
        echo $s;
    }
}

?>

Before

11

After

Duplicate declaration of static variable $s

PHP version change

This behavior changed in 8.3.

Error Messages

Extension

Analyzer

Dynamic Class Constant

To access a constant value with its name in a string, one required the constant() function. constant('\A::'.$constantName).

In PHP 8.3, there is a dedicated syntax, to access those constants dynamically.

PHP code

<?php

class a {
	public const A = 1;
}

$b = 'A';

echo A::{$b};

?>

Before

PHP Parse error:  syntax error

After

1

PHP version change

This behavior changed in 8.3.

Error Messages

Extension

No Dynamic Properties By Default

Properties never required a definition before usage, just like variables. They could be added at any moment in any object.

In PHP 8.2, this is now a deprecated behavior. The property must be declared before usage. Visibility, type and default value are optional as before, so the requirement is to add the property in the class.

It is also possible to skip that warning by extending explicitly the stdClass; by adding the #[AllowDynamicProperties] attribute or by creating the magic property method __get or __set, depending on the usage.

PHP code

<?php

class x {} 

$x = new x;
$x->property = 1; 
echo $x->property;

?>

Before

1

After

PHP Deprecated:  Creation of dynamic property x::$p is deprecated

Deprecated: Creation of dynamic property x::$p is deprecated
1

PHP version change

This behavior was deprecated in 8.2.

This behavior changed in 9.0.

See Also

Error Messages

Extension

Analyzer

E_STRICT Is Deprecated

The PHP native constant E_STRICT is deprecated, and will be removed in PHP 9.0.

PHP code

<?php

var_dump(error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT));

?>

Before

int(32767)

After

PHP Deprecated:  Constant E_STRICT is deprecated

Deprecated: Constant E_STRICT is deprecated
int(30719)

PHP version change

This behavior was deprecated in 8.4.

This behavior changed in 9.0.

See Also

Error Messages

Extension

E_USER_ERROR Is Deprecated

The PHP native constant E_USER_ERROR is deprecated. It should not be used anymore with the trigger() function, nor anywhere else in the code. It shall be removed entirely in PHP 9.

PHP code

<?php

trigger_error('user error', E_USER_ERROR);

?>

Before

PHP Deprecated:  Passing E_USER_ERROR to trigger_error() is deprecated since 8.4, throw an exception or call exit with a string message instead

Deprecated: Passing E_USER_ERROR to trigger_error() is deprecated since 8.4, throw an exception or call exit with a string message instead
PHP Fatal error:  user error

Fatal error: user error

After

PHP Fatal error:  user error

Fatal error: user error

PHP version change

This behavior changed in 8.5.

Error Messages

Extension

Analyzer

each() Has Been Removed

The each() function has been deprecated in PHP 7.x, and removed in PHP 8.0. Use foreach() instead.

PHP code

<?php

$array = ['a' => 1];
list($a, $b) = each($array);

echo $a; 
echo $b;

?>

Before

PHP Deprecated:  The each() function is deprecated. This message will be suppressed on further calls

Deprecated: The each() function is deprecated. This message will be suppressed on further calls
a1

After

PHP Fatal error:  Uncaught Error: Call to undefined function each()

Fatal error: Uncaught Error: Call to undefined function each()

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

Enforcing Native PHP ReturnType

PHP did not enforce the return types in its own interfaces. Until PHP 8.1, incompatible signatures were allowed between them.

In PHP 8.1, such return type is now enforced. It should be set manually, or be temporarily suspended with the #[\ReturnTypeWillChange] attribute.

PHP code

<?php

class x implements iterator {
	function __construct() { echo __METHOD__; }
	public current() {}
}

new x;

?>

Before

x::__construct

After

PHP Deprecated:  Return type of x::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice

Deprecated: Return type of x::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice
x::__construct

PHP version change

This behavior was deprecated in 8.1.

This behavior changed in 9.0.

Error Messages

Extension

Analyzer

Enforcing Return Type With Spl Classes

The types of the methods that were related to SPL were not validated against their interfaced. It was only enforced for other native or custom interfaces.

In PHP 8.1, it is now enforced.

PHP code

<?php

class X extends LimitIterator {
	function __construct() { echo __METHOD__; }
	public function current() {}
}

new X();

?>

Before

x::__construct

After

PHP Deprecated:  Return type of x::current() should either be compatible with IteratorIterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice

Deprecated: Return type of x::current() should either be compatible with IteratorIterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice
x::__construct

PHP version change

This behavior changed in 8.1.

Error Messages

Extension

Enums May Now Define __debugInfo()

Declaring the magic method __debugInfo() on an enum used to be a compile-time fatal error, because enum cases were treated like other magic methods that make no sense on enums. In PHP 8.6, __debugInfo() is allowed on enums, so var_dump() can render a custom representation of an enum case instead of the default one.

PHP code

<?php

enum Suit {
    case Hearts;
    case Spades;

    public function __debugInfo(): array {
        return ['custom' => $this->name];
    }
}

var_dump(Suit::Hearts);

?>

Before

PHP Fatal error:  Enum Suit cannot include magic method __debugInfo in /codes/enumDebugInfo86.php on line 3
Stack trace:
#0 {main}

Fatal error: Enum Suit cannot include magic method __debugInfo in /codes/enumDebugInfo86.php on line 3
Stack trace:
#0 {main}

After

enum(Suit::Hearts) (1) {
  ["custom"]=>
  string(6) "Hearts" 
}

PHP version change

This behavior changed in 8.6.

See Also

Extension

eval() Without Try

The eval() command throws an error in case of unparsable PHP code. This error can be caught, to handle gracefully the situation, with a try-catch structure.

It is recommended to always use try-catch when dealing with eval().

It is possible to differentiate a parse error in the host code from a parse error in the eval() string with the error message: when it is in the eval() string, the error message mention eval: Parse error: syntax error, unexpected identifier "a" in file.ph : eval()'d code on line 1.

PHP code

<?php

try {
    eval('A = 1');
} catch (Error $e) {
    echo $e->getMessage();
}

?>

Before

Parse error: syntax error, unexpected '='

After

syntax error, unexpected token "="

PHP version change

This behavior changed in 7.0.

Extension

Analyzer

explode() Forbids Empty Strings

explode() doesn’t work on empty strings, as delimiter (first argument). It used to be a warning and a returned value of false, it is now a Fatal error.

PHP code

<?php

explode('', 'abc');

?>

Before

PHP Warning:  explode(): Empty delimiter

Warning: explode(): Empty delimiter

After

PHP Fatal error:  Uncaught ValueError: explode(): Argument #1 ($separator) cannot be empty

Fatal error: Uncaught ValueError: explode(): Argument #1 ($separator) cannot be empty

PHP version change

This behavior changed in 8.0.

See Also

Error Messages

Extension

Cannot Explode() Null

Null used to be a valid argument for explode(), used as an empty string. Nowadays, PHP requires an actual string to explode.

PHP code

<?php

var_dump(explode(';', null));

?>

Before

array(1) {
  [0]=>
  string(0) "" 
}

After

PHP Deprecated:  explode(): Passing null to parameter #2 ($string) of type string is deprecated 

Deprecated: explode(): Passing null to parameter #2 ($string) of type string is deprecated 
array(1) {
  [0]=>
  string(0) "" 
}

PHP version change

This behavior changed in 8.1.

Error Messages

Extension

Method export() in Reflection Is removed

The Reflection::export() static method was deprecated in PHP 7.4 and removed in 8.0.

PHP code

<?php

class A {}

$reflector = new ReflectionClass('A');

print Reflection::export($reflector, true);

?>

Before

PHP Deprecated:  Function Reflection::export() is deprecated 

Deprecated: Function Reflection::export() is deprecated 
Class [ <user> class A ] {
  @@ /exportReflection.php 3-3

  - Constants [0] {
  }

  - Static properties [0] {
  }

  - Static methods [0] {
  }

  - Properties [0] {
  }

  - Methods [0] {
  }
}

After

PHP Fatal error:  Uncaught Error: Call to undefined method Reflection::export() 

Fatal error: Uncaught Error: Call to undefined method Reflection::export() 

PHP version change

This behavior changed in 8.0.

Extension

Calling FFI::cast() statically is deprecated

cast(), new() and type() are instance methods of the object returned by FFI::cdef() or FFI::load(): each loaded C definition scope has its own set of types, so casting should happen on that specific instance. PHP used to allow calling these methods statically as FFI::cast(), in which case they implicitly operated on an anonymous, definition-less scope. Since PHP 8.3, this static shortcut is deprecated, because it is ambiguous about which set of C declarations is being used.

PHP code

<?php

$ffi = FFI::cdef('typedef struct { int x; int y; } point;');

$mem = $ffi->new('point');

$casted = FFI::cast('int', $mem);

var_dump($casted);

?>

Before

object(FFI\CData:int32)#3 (1) {
  [0]=>
  int(0)
}

After

PHP Deprecated:  Calling FFI::cast() statically is deprecated in /codes/ffiCastStaticDeprecated.php on line 7

Deprecated: Calling FFI::cast() statically is deprecated in /codes/ffiCastStaticDeprecated.php on line 7
object(FFI\CData:int32)#3 (1) {
  [0]=>
  int(0)
}

PHP version change

This behavior changed in 8.3.

Error Messages

Extension

Calling FFI::new() statically is deprecated

new(), cast() and type() are instance methods of the object returned by FFI::cdef() or FFI::load(): each loaded C definition scope has its own set of types, so allocating a new value should happen on that specific instance. PHP used to allow calling these methods statically as FFI::new(), in which case they implicitly operated on an anonymous, definition-less scope. Since PHP 8.3, this static shortcut is deprecated, because it is ambiguous about which set of C declarations is being used.

PHP code

<?php

FFI::cdef('typedef struct { int x; int y; } point;');

$mem = FFI::new('point');

var_dump($mem);

?>

Before

object(FFI\CData:struct <anonymous>)#2 (2) {
  ["x"]=>
  int(0)
  ["y"]=>
  int(0)
}

After

PHP Deprecated:  Calling FFI::new() statically is deprecated in /codes/ffiNewStaticDeprecated.php on line 5

Deprecated: Calling FFI::new() statically is deprecated in /codes/ffiNewStaticDeprecated.php on line 5
object(FFI\CData:struct <anonymous>)#2 (2) {
  ["x"]=>
  int(0)
  ["y"]=>
  int(0)
}

PHP version change

This behavior changed in 8.3.

Error Messages

Extension

Calling FFI::type() statically is deprecated

type(), cast() and new() are instance methods of the object returned by FFI::cdef() or FFI::load(): each loaded C definition scope has its own set of types, so resolving a type name should happen on that specific instance. PHP used to allow calling these methods statically as FFI::type(), in which case they implicitly operated on an anonymous, definition-less scope. Since PHP 8.3, this static shortcut is deprecated, because it is ambiguous about which set of C declarations is being used.

PHP code

<?php

FFI::cdef('typedef struct { int x; int y; } point;');

$type = FFI::type('point');

var_dump($type);

?>

Before

object(FFI\CType)#2 (0) {
}

After

PHP Deprecated:  Calling FFI::type() statically is deprecated in /codes/ffiTypeStaticDeprecated.php on line 5

Deprecated: Calling FFI::type() statically is deprecated in /codes/ffiTypeStaticDeprecated.php on line 5
object(FFI\CType)#2 (0) {
}

PHP version change

This behavior changed in 8.3.

Error Messages

Extension

file_get_contents() Needs A Real Path

file_get_contents() cannot work on an empty string. Until PHP 8.0, it would report it as a warning, and return false, keeping the execution. In PHP 8.0, it is now a Fatal error.

PHP code

<?php

var_dump(file_get_contents(""));

?>

Before

PHP Warning:  file_get_contents(): Filename cannot be empty 

Warning: file_get_contents(): Filename cannot be empty 
bool(false)

After

PHP Fatal error:  Uncaught ValueError: Path cannot be empty 

Fatal error: Uncaught ValueError: Path cannot be empty 

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Multiple File Functions Reject NUL Bytes In Filenames

Several filesystem functions, such as file_exists(), used to accept a filename containing a NUL byte; the string was silently truncated by the underlying C library call, or the argument was simply rejected with a warning and a NULL/false return value. In PHP 8.6, a NUL byte in the filename argument of these file functions throws a ValueError instead.

PHP code

<?php

try {
    var_dump(file_exists("/tmp/foo\0bar"));
} catch (\ValueError $e) {
    echo $e->getMessage(), "\n";
}

?>

Before

PHP Warning:  file_exists() expects parameter 1 to be a valid path, string given in /codes/fileFunctionsNullByteValueError.php on line 4

Warning: file_exists() expects parameter 1 to be a valid path, string given in /codes/fileFunctionsNullByteValueError.php on line 4
NULL

After

file_exists(): Argument #1 ($filename) must not contain any null bytes

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

FilessytemIterator Skips Dot Files

FilessytemIterator class used to list the current directory . and the parent directory ... Files starting with a dot were and are still listed.

In PHP 8.2, the dot files are not listed by default. At instantiation time, it is possible to have those file listed by using the FilesystemIterator::SKIP_DOTS option.

PHP code

<?php

// $dir is a path to a folder that contains 2 files:  a.txt and .b 
$it = new FilesystemIterator(dirname($dir), FilesystemIterator::CURRENT_AS_FILEINFO);
foreach ($it as $fileinfo) {
    echo $fileinfo->getFilename() . "\n";
}

?>

Before

.
..
a.txt
.b

After

.
..
a.txt
.b

PHP version change

This behavior changed in 8.1.

See Also

Extension

Long php://filter Chains Are Deprecated

A php://filter URL can chain several filters with |. Until PHP 8.6, any number of filters could be chained silently. In PHP 8.6, opening a php://filter URL with more than 16 chained filters emits a deprecation notice suggesting the max_filter_count stream context option or stream_filter_append() instead.

PHP code

<?php

$filters = str_repeat('string.toupper|', 20);
$handle = fopen('php://filter/read='.$filters.'/resource=php://memory', 'r');
var_dump($handle !== false);

?>

Before

bool(true)

After

PHP Deprecated:  Using more than 16 filters in a php://filter URL is deprecated, set this limit using the stream context option max_filter_count, or use stream_filter_append in /codes/filterChainCountDeprecated86.php on line 4

Deprecated: Using more than 16 filters in a php://filter URL is deprecated, set this limit using the stream context option max_filter_count, or use stream_filter_append in /codes/filterChainCountDeprecated86.php on line 4
bool(true)

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

Final Class Constants

Class constants can be made final, starting with PHP 8.2.

PHP code

<?php

class x {
	final public const A = 1;
}

echo x::A;

?>

Before

PHP Fatal error:  Cannot use 'final' as constant modifier 

After

1

PHP version change

This behavior changed in 8.1.

Error Messages

Extension

Final Method In Trait

Trait methods can be named final, when importing them as a trait alias. It was explicitly forbidden until PHP 8.3. This has nothing to do with the final keyword.

PHP code

<?php

trait t {
    function foo() {}
}

trait t2 {
    function foo() {}
}

class A {
        use t, t2 { t::foo as final; }
}
?>

Before


After


PHP version change

This behavior changed in 8.3.

Error Messages

Extension

Analyzer

Final Promoted Properties

Promoted properties could not be final. Since PHP 8.5, it is possible to use final in promoted properties definition.

PHP code

<?php

class x {
    function __construct( 
        public final int $i
    ) {}
}

var_dump(new x(1));

?>

Before

PHP Fatal error:  Cannot use the final modifier on a parameter

Fatal error: Cannot use the final modifier on a parameter

After

object(x)#1 (1) {
  ["i"]=>
  int(1)
}

PHP version change

This behavior changed in 8.5.

Error Messages

Extension

Finfo Moved Away From Resource

Finfo functions have moved from resource to objects. In PHP 8.1, instead of returning a resource, it now returns a finfo object. Checks based on is_resource() must be upgraded, and are now dead code.

PHP code

<?php

var_dump(finfo_open());

?>

Before

resource(4) of type (file_info)

After

object(finfo)#1 (0) {
}

PHP version change

This behavior changed in 8.1.

See Also

Extension

Float Used As Array

A float is not an array, but it is possible to use the array syntax with it. The values are then always null, and since PHP 7.4, a warning is emitted.

PHP code

<?php

$a = 45.6;
var_dump($a['dsds']);       // null
var_dump($a[1]);            // null
var_dump(((string) $a)[1]); // 5

?>

Before

NULL

After

PHP Warning:  Trying to access array offset on float

Warning: Trying to access array offset on float
NULL

PHP version change

This behavior changed in 7.4.

Error Messages

Extension

fputcsv() Needs Escape Parameter

fputcsv() used to have the $escape parameter with a default value. This is not the case anymore in PHP 8.4, where it is necessary to provide its value.

PHP code

<?php

$fp = fopen(':memory:', 'w');
var_dump(fputcsv($fp, [1,2,3]));

?>

Before

int(6)

After

PHP Deprecated:  fputcsv(): the $escape parameter must be provided as its default value will chang

Deprecated: fputcsv(): the $escape parameter must be provided as its default value will change
int(6)

PHP version change

This behavior was deprecated in 8.4.

This behavior changed in 8.4.

See Also

Error Messages

Extension

Analyzer

func_get_arg() Changed Behavior

func_get_arg() and func_get_args() used to report the calling value of the argument, until PHP 7.

Since PHP 7, it is reporting the value of the argument at calling time, which may have been modified by a previous instruction.

This code will display 1 in PHP 7, and 0 in PHP 5.

PHP code

<?php

function x($a) {
    print func_get_arg(0);  // 0 
    $a++;
    print func_get_arg(0);  // 1
}

x(0);
?>

Before

00

After

01

PHP version change

This behavior changed in 7.2.

Extension

Generators Don’t Return

Generators were not allowed to have return values. This feature was added in PHP 7.0, with the getReturn method.

PHP code

<?php

function foo() {
    yield 'a';
    
    return 2;
}

foreach(foo() as $a) {
    print $a.PHP_EOL;
}

?>

Before

PHP Fatal error:  Generators cannot return values using "return" 

Fatal error: Generators cannot return values using "return" 

After

a

PHP version change

This behavior changed in 7.0.

Error Messages

Extension

get_called_class() Cannot Be Called Outside A Class

get_called_class() generated a warning when called outside a class or an enumeration. Since PHP 8.0, it is a fatal error.

PHP code

<?php

var_dump(get_called_class());

?>

Before

PHP Warning:  get_called_class() called from outside a class

Warning: get_called_class() called from outside a class
bool(false)

After

PHP Fatal error:  Uncaught Error: get_called_class() must be called from within a class

Fatal error: Uncaught Error: get_called_class() must be called from within a class

PHP version change

This behavior was deprecated in 7.0.

This behavior changed in 8.0.

Error Messages

Extension

get_class() Needs An Argument

get_class() had a default behavior, where the current class would be returned when get_class() is called without arguments. This is now deprecated.

It is also deprecated for get_parent_class().

PHP code

<?php

class x {
        function foo() {
                echo get_class();
                echo get_parent_class();
        }
}

(new x)->foo();

?>

Before

x

After

Calling get_class() without arguments is deprecated

PHP version change

This behavior was deprecated in 8.3.

This behavior changed in 9.0.

Error Messages

Extension

Analyzer

get_defined_functions() Doesn’t Exclude Diabled Functions Anymore

get_defined_functions() used to have one parameter, called $exclude_disabled. It used to exclude functions appearing under the directive disabled_functions.

Since PHP 8.0, this parameter is not used anymore. It emits a warning since PHP 8.5.

PHP code

<?php

print_r(get_defined_functions(true));

?>

Before

Array
(
    [internal] => Array
        (
            [0] => exit
            [1] => die
            [2] => zend_version
            // many more functions
        )

    [user] => Array
        (
        )

)

After

PHP Deprecated:  get_defined_functions(): The $exclude_disabled parameter has no effect since PHP 8.0 in /codes/get_defined_functions.php on line 3

Deprecated: get_defined_functions(): The $exclude_disabled parameter has no effect since PHP 8.0 in /codes/get_defined_functions.php on line 3
Array
(
    [internal] => Array
        (
            [0] => clone
            [1] => exit
            [2] => die
            [3] => zend_version
            // many more functions
        )

    [user] => Array
        (
        )

)

PHP version change

This behavior changed in 8.5.

Extension

getenv() Rejects NUL Bytes In The Variable Name

getenv() used to accept a variable name containing a NUL byte, and simply returned false since no such variable can exist. In PHP 8.6, a NUL byte in the name argument throws a ValueError.

PHP code

<?php

var_dump(getenv("FOO\0BAR"));

?>

Before

bool(false)

After

getenv(): Argument #1 ($name) must not contain any null bytes

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

get_object_vars() Does Not Work On ArrayObject

ArrayObject used to export its properties as they were defined in the array. Since PHP 7.4, ArrayObject does not export any property anymore. They are still accessible via the normal property syntax, just not with get_object_vars() anymore.

PHP code

<?php

// Illustration courtesy of Doug Bierer
$obj = new ArrayObject(['A' => 1,'B' => 2,'C' => 3]);
var_dump($obj->getArrayCopy());
var_dump(get_object_vars($obj));
//var_dump((array) $obj);

?>

Before

array(3) {
  [A]=>
  int(1)
  [B]=>
  int(2)
  [C]=>
  int(3)
}
array(3) {
  [A]=>
  int(1)
  [B]=>
  int(2)
  [C]=>
  int(3)
}

After

array(3) {
  [A]=>
  int(1)
  [B]=>
  int(2)
  [C]=>
  int(3)
}
array(0) {
}

PHP version change

This behavior changed in 7.4.

Extension

No Dynamic Global Variables

Dynamic global variables were possible. This means that a variable, whose name is stored in another variable, could be dynamically used with the global keyword.

These notations are now dropped, in PHP 7.0, except for with the $\{ \} operator.

PHP code

<?php

// Valid in all PHP versions
global $normalGlobal;

// Forbidden in PHP 7
global $$variable->global ;

// Tolerated in PHP 7
global $\{$variable->global\}; 

?>

Before

1

After

PHP Parse error:  syntax error, unexpected token "->", expecting "," or ";" 

Parse error: syntax error, unexpected token "->", expecting "," or ";" 

PHP version change

This behavior changed in 5.6.

Error Messages

Extension

Appending To $GLOBALS

$GLOBALS is not a regular array: each of its entries is really a reference to a variable in the global scope, identified by its name. Appending to it with the empty-bracket operator, which would normally pick the next integer key, has no matching global variable to bind to. Before PHP 8.1, this meaningless operation was silently tolerated, and created a global variable literally named “0”. As of PHP 8.1, $GLOBALS is treated more strictly by the compiler, and this and several other previously-tolerated usages now raise a compile-time error instead.

PHP code

<?php

$GLOBALS[] = 'value';

?>

Before


After

PHP Fatal error:  Cannot append to $GLOBALS

PHP version change

This behavior changed in 8.1.

Error Messages

Extension

$GLOBALS Assignement

It is not possible to assign the $GLOBALS variable anymore. The individual values may still be assigned directly.

PHP code

<?php

$GLOBALS['a']  = 1;

$b = &$GLOBALS;
$b = array();

print_r($GLOBALS);

?>

Before

Array
(
)

After

PHP Fatal error:  Cannot acquire reference to $GLOBALS

PHP version change

This behavior changed in 8.1.

Error Messages

Extension

Analyzer

Copy Of $GLOBALS

Cpying $GLOBALS into another variable was made by reference: modifying the values in the copy was also modifying the original.

Since PHP 8.1, the copy is a copy by value: this means that changing something in the copy will not be changed in the original $GLOBALS variable.

PHP code

<?php
$a = 1;

$globals = $GLOBALS; // Ostensibly by-value copy
$globals['a'] = 2;
var_dump($a); // int(2)
?>

Before

int(2)

After

int(1)

PHP version change

This behavior changed in 8.1.

Extension

Analyzer

GMP Shift And Power Operators Deprecate Float Operands

The **, << and >> operators accept a float as the non-GMP operand when the other operand is a GMP object. Until PHP 8.6, a float such as 2.5 was silently truncated to an integer before being used as the exponent or shift amount, discarding its fractional part without warning. In PHP 8.6, a float that would lose precision when converted emits a deprecation notice, Implicit conversion from float 2.5 to int loses precision, before still truncating it the same way.

PHP code

<?php

var_dump(gmp_strval(gmp_init(2) ** 2.5));
var_dump(gmp_strval(gmp_init(8) << 2.5));

?>

Before

string(1) "4" 
string(2) "32" 

After

PHP Deprecated:  Implicit conversion from float 2.5 to int loses precision in /codes/gmpFloatOperandDeprecated.php on line 3

Deprecated: Implicit conversion from float 2.5 to int loses precision in /codes/gmpFloatOperandDeprecated.php on line 3
string(1) "4" 
PHP Deprecated:  Implicit conversion from float 2.5 to int loses precision in /codes/gmpFloatOperandDeprecated.php on line 4

Deprecated: Implicit conversion from float 2.5 to int loses precision in /codes/gmpFloatOperandDeprecated.php on line 4
string(2) "32" 

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

Several GMP Functions Validate Their Numeric Arguments

gmp_fact() accepts a GMP object as its $num argument. Until PHP 8.6, a value outside the range of an unsigned long was silently misinterpreted, so gmp_fact() returned an unrelated, incorrect result instead of failing. In PHP 8.6, such a value throws a ValueError. The same release also refines the ValueError messages already thrown by gmp_pow(), gmp_binomial(), gmp_root() and gmp_rootrem() for an out-of-range second argument ($exponent, $k or $nth), now stating the full valid range instead of only the lower bound.

PHP code

<?php

$huge = gmp_init('18446744073709551616');

try {
    var_dump(gmp_strval(gmp_fact($huge)));
} catch (\ValueError $e) {
    echo "ValueError: ".$e->getMessage()."\n";
}

?>

Before

string(1) "1" 

After

ValueError: gmp_fact(): Argument #1 ($num) must be between 0 and 18446744073709551615

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

gmp_powm() Reports More Detail On Modulo-By-Zero

gmp_powm() has thrown a DivisionByZeroError when its $modulus argument is zero since PHP 8.0; this is not new in PHP 8.6. Until PHP 8.6, the exception message was the generic Modulo by zero, giving no clue about which function or argument caused it. In PHP 8.6, the message is prefixed with the function name and the offending argument, becoming gmp_powm(): Argument #3 ($modulus) Modulo by zero.

PHP code

<?php

try {
    var_dump(gmp_strval(gmp_powm(2, 3, 0)));
} catch (\DivisionByZeroError $e) {
    echo "DivisionByZeroError: ".$e->getMessage()."\n";
}

?>

Before

DivisionByZeroError: Modulo by zero

After

DivisionByZeroError: gmp_powm(): Argument #3 ($modulus) Modulo by zero

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

GMP Shift Operators Validate A GMP Right Operand

The GMP shift operators (<< and >>) accept a right operand outside the range of a regular PHP integer when it is itself a GMP object. Until PHP 8.6, a right operand greater than the platform’s unsigned long maximum was silently truncated, producing an incorrect shift amount. In PHP 8.6, a right operand outside the unsigned long range throws a ValueError.

PHP code

<?php

$a = gmp_init(2);
$huge = gmp_init('18446744073709551616');

try {
    var_dump(gmp_strval($a << $huge));
} catch (\ValueError $e) {
    echo "ValueError: ".$e->getMessage()."\n";
}

?>

Before

string(1) "2" 

After

ValueError: Shift must be between 0 and 18446744073709551615

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

Heredoc Syntax In An Array

It was only possible to use the HEREDOC, and NOWDOC, syntax with a final semicolon. This meant it was impossible to use that syntax in an array, a list of arguments, or other context that do not finish the expression with a semicolon.

Since PHP 7.3, it is possible to use these syntaxes in more varied situations, such as method call, for example.

PHP code

<?php

$a = array(<<<HEREDOC
A
HEREDOC,
);

print_r($a);

?>

Before

PHP Parse error:  syntax error

Parse error: syntax error

After

Array
(
    [0] => A
)

PHP version change

This behavior changed in 7.3.

Error Messages

Extension

hexdec() Warns When The Result Loses Precision

hexdec(), bindec(), octdec() and base_convert() return a float when the converted number is too large to fit in a PHP integer. Until PHP 8.6, this silently lost precision, since a 64-bit float cannot represent every integer up to the represented magnitude exactly. In PHP 8.6, these functions raise a notice when the returned value cannot precisely represent the input number.

PHP code

<?php

var_dump(hexdec('FFFFFFFFFFFFFFFFFF'));

?>

Before

float(4.722366482869645E+21)

After

PHP Notice:  Input number is larger than PHP_INT_MAX, precision has been lost in conversion in /codes/hexdecImpreciseNotice86.php on line 3

Notice: Input number is larger than PHP_INT_MAX, precision has been lost in conversion in /codes/hexdecImpreciseNotice86.php on line 3
float(4.722366482869645E+21)

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

Default Values With htmlspecialchars()

The default values of htmlspecialchars() were changed in PHP 8.1. It was ENT_COMPAT and it is now replaced with ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401.

In particular, it means that ', single quote, is now converted in HTML entities.

PHP code

<?php

echo htmlspecialchars("'");

?>

Before

'

After

&#039;

PHP version change

This behavior changed in 8.1.

See Also

Extension

http_build_query() supports enumerations

http_build_query() accepted backed enumerations, and used to produce a query string with a b array, containing value and name. Since PHP 8.4, it is now using the string value of the case.

PHP code

<?php

enum E : string {
    case B = 'b';
}

print http_build_query(['a' => 'A', 'b' => e::B]);

?>

Before

a=A&b%5Bname%5D=B&b%5Bvalue%5D=b

After

a=A&b=b

PHP version change

This behavior changed in 8.4.

See Also

Extension

$http_response_header Is Deprecated

The $http_response_header PHP variable is deprecated. It should be replaced with a call to http_get_last_response_headers(), which is available since PHP 8.4.

PHP code

<?php

function get_contents() {
  file_get_contents("https://www.php.net/");
  var_dump($http_response_header); // variable is populated in the local scope
}
get_contents();

?>

Before

array(13) {
  [0]=>
  string(15) "HTTP/1.1 200 OK" 
  [1]=>
  string(17) "Server: myracloud" 
  [2]=>
  string(35) "Date: Wed, 22 Oct 2025 18:16:26 GMT" 
  [3]=>
  string(38) "Content-Type: text/html; charset=utf-8" 
  [4]=>
  string(17) "Connection: close" 
  [5]=>
  string(44) "Last-Modified: Wed, 22 Oct 2025 18:10:19 GMT" 
  [6]=>
  string(20) "Content-language: en" 
  [7]=>
  string(38) "Permissions-Policy: interest-cohort=()" 
  [8]=>
  string(27) "X-Frame-Options: SAMEORIGIN" 
  [9]=>
  string(114) "Set-Cookie: LAST_NEWS=1761156986; expires=Thu, 22 Oct 2026 18:16:26 GMT; Max-Age=31536000; path=/; domain=.php.net" 
  [10]=>
  string(47) "Link: <https://www.php.net/index>; rel=shorturl" 
  [11]=>
  string(38) "Expires: Wed, 22 Oct 2025 18:16:26 GMT" 
  [12]=>
  string(24) "Cache-Control: max-age=0" 
}

After

PHP Deprecated:  The predefined locally scoped $http_response_header variable is deprecated, call http_get_last_response_headers() instead

Deprecated: The predefined locally scoped $http_response_header variable is deprecated, call http_get_last_response_headers() instead
array(13) {
  [0]=>
  string(15) "HTTP/1.1 200 OK" 
  [1]=>
  string(17) "Server: myracloud" 
  [2]=>
  string(35) "Date: Wed, 22 Oct 2025 18:16:26 GMT" 
  [3]=>
  string(38) "Content-Type: text/html; charset=utf-8" 
  [4]=>
  string(17) "Connection: close" 
  [5]=>
  string(44) "Last-Modified: Wed, 22 Oct 2025 18:10:19 GMT" 
  [6]=>
  string(20) "Content-language: en" 
  [7]=>
  string(38) "Permissions-Policy: interest-cohort=()" 
  [8]=>
  string(27) "X-Frame-Options: SAMEORIGIN" 
  [9]=>
  string(114) "Set-Cookie: LAST_NEWS=1761156986; expires=Thu, 22 Oct 2026 18:16:26 GMT; Max-Age=31536000; path=/; domain=.php.net" 
  [10]=>
  string(47) "Link: <https://www.php.net/index>; rel=shorturl" 
  [11]=>
  string(38) "Expires: Wed, 22 Oct 2025 18:16:26 GMT" 
  [12]=>
  string(24) "Cache-Control: max-age=0" 
}

PHP version change

This behavior changed in 8.5.

Error Messages

Extension

http_build_query() With An Object Is Deprecated

http_build_query() accepts an object as its $data argument and builds the query string from its public properties. Until PHP 8.6, this was silently accepted. In PHP 8.6, passing an object emits a deprecation notice recommending get_object_vars() first; the resulting query string is unchanged.

PHP code

<?php

class Data {
    public $a = 1;
    public $b = 2;
}

var_dump(http_build_query(new Data()));

?>

Before

string(7) "a=1&b=2" 

After

PHP Deprecated:  http_build_query(): Passing an object for argument #1 $data to http_build_query() is deprecated, call get_object_vars() first instead in /codes/httpBuildQueryObjectDeprecated86.php on line 8

Deprecated: http_build_query(): Passing an object for argument #1 $data to http_build_query() is deprecated, call get_object_vars() first instead in /codes/httpBuildQueryObjectDeprecated86.php on line 8
string(7) "a=1&b=2" 

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

Implicit Array Key Conversion

Array keys accept only string and integer types. When providing a float, PHP used to convert it to an int. It still does, in PHP 8.1, though it now emits a deprecation warning.

PHP code

<?php

$a = [];
$a[15.5] = 2; // deprecated, as key value loses the 0.5 component
$a[15.0] = 3; // ok, as 15.0 == 15

print $a[15];

?>

Before

2

After

PHP Deprecated:  Implicit conversion from float 15.5 to int loses precision 

Deprecated: Implicit conversion from float 15.5 to int loses precision 
3

PHP version change

This behavior was deprecated in 8.1.

This behavior changed in 9.0.

Error Messages

Extension

Implicit Nullable

A typed argument with a default value of null was also implicitly nullable: it would accept null as a value. This is deprecated in PHP 8.4, and will be removed in PHP 9.0. It is recommended to make the nullable type explicit in the code.

That issue applies to arguments in methods and functions, but not on properties or returned values.

PHP code

<?php

function foo(int $a = null) {
	var_dump($a);
}

foo(1);
foo(null);

?>

Before

int(1)
NULL

After

PHP Deprecated:  foo(): Implicitly marking parameter $a as nullable is deprecated, the explicit nullable type must be used instead 

Deprecated: foo(): Implicitly marking parameter $a as nullable is deprecated, the explicit nullable type must be used instead 
int(1)
NULL

PHP version change

This behavior was deprecated in 8.4.

This behavior changed in 9.0.

Error Messages

Extension

Analyzer

implode() Arguments Order

It was possible to call implode() with a random order of argument : string first, or array first. PHP would figure out which one to use.

In PHP 8.0, it is now compulsory to put the string in the first place, as the types are checked. Or used named parameters.

PHP code

<?php

print_r(implode([1,2], '3'));

?>

Before

PHP Deprecated:  implode(): Passing glue string after array is deprecated. Swap the parameters 

Deprecated: implode(): Passing glue string after array is deprecated. Swap the parameters 
132

After

PHP Fatal error:  Uncaught TypeError: implode(): Argument #2 ($array) must be of type ?array, string given 

Fatal error: Uncaught TypeError: implode(): Argument #2 ($array) must be of type ?array, string given 

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

in_array() String Int Comparisons

The default comparison style of in_array() is the relaxed one. Hence, the behavior of that comparison changed in PHP 8.0, so does in_array().

By default, comparing strings and integers may not work as before. This is the case when the string doesn’t convert obviously to an integer.

PHP code

<?php

var_dump(in_array(' 1a', [ 1]));

?>

Before

bool(true)

After

bool(false)

PHP version change

This behavior changed in 8.0.

Extension

in_array() Doesn’t Confuse 0 And Empty String

in_array() makes a relaxed comparison of values in its arguments. When there are 0 and empty strings, those used to be considered identical in PHP 7 and they are now distinct in PHP 8.

This behavior change doesn’t impact calls to in_array() with the third argument strict_comparison. That feature is unchanged in PHP 8.

PHP code

<?php

var_dump(in_array('', [ 0]));

?>

Before

bool(true)

After

bool(false)

PHP version change

This behavior changed in 8.0.

See Also

Extension

Increment Non-alphanumeric

PHP has a string increment feature, where a string may be incremented by one, to its next ASCII character.

This does not apply to non-alphanumeric characters, such as space, semi-colon, etc. Until PHP 8.4, it was silent, and now, it is a warning.

PHP code

<?php

$a = ';';
++$a;

echo $a;

?>

Before

PHP Deprecated:  Increment on non-alphanumeric string is deprecated 

Deprecated: Increment on non-alphanumeric string is deprecated 
;

After

PHP Deprecated:  Increment on non-numeric string is deprecated, use str_increment() instead 

Deprecated: Increment on non-numeric string is deprecated, use str_increment() instead 

PHP version change

This behavior changed in 8.5.

Error Messages

Extension

Increment On Boolean Is Deprecated

Incrementing or decrementing a boolean value had no effect. In PHP 8.3, it is now a deprecation warning, and a message.

PHP code

<?php

$a = true;
$a++;

$b = false;
--$b;
echo $a, $b;

?>

Before

1

After

PHP Warning:  Increment on type bool has no effect, this will change in the next major version of PHP 

Warning: Increment on type bool has no effect, this will change in the next major version of PHP 
PHP Warning:  Decrement on type bool has no effect, this will change in the next major version of PHP 

Warning: Decrement on type bool has no effect, this will change in the next major version of PHP 
1

PHP version change

This behavior was deprecated in 8.3.

This behavior changed in 9.0.

Error Messages

Extension

Indirect Modification In Clone

__clone is used to apply modifications during the cloning operation. It could also be used to change the original object.

PHP code

<?php

class X {
    readonly public int $p;
    
    function foo() {
        $this->p = 2;
    }
    
    function __clone() {
        $ref = &$this->p;
    }
}

$x = new x;
clone $x;

?>

Before

PHP Parse error:  syntax error, unexpected identifier "readonly", expecting "function" or "const" 

Parse error: syntax error, unexpected identifier "readonly", expecting "function" or "const" 

After

PHP Fatal error:  Uncaught Error: Cannot indirectly modify readonly property X::$p

Fatal error: Uncaught Error: Cannot indirectly modify readonly property X::$p

PHP version change

This behavior changed in 8.1.

Error Messages

Extension

Inegality Comparisons

The loose comparisons, including =, between integers and strings have changed in PHP 8.0. Until now, a string was strictly superior to any integer, but was superior or egal to any integer.

Since PHP 8.0, strings are considered to be higher than integers. The comparison is consistent between the strict and inclusive comparison.

This also applies to float.

PHP code

<?php

var_dump(0 > 'a');
var_dump(0 >= 'a');

var_dump(0 < 'a');
var_dump(0 <= 'a');

?>

Before

bool(false)
bool(true)
bool(false)
bool(true)

After

bool(false)
bool(false)
bool(true)
bool(true)

PHP version change

This behavior changed in 8.0.

Extension

ini_get_all() Includes The Built-in Default Value

ini_get_all() used to return, for each directive, only the global_value, local_value and access keys. In PHP 8.6, a fourth key, builtin_default_value, is added, containing the value that is hard-coded in PHP itself, regardless of any php.ini or ini_set() change.

PHP code

<?php

$all = ini_get_all('core', true);
var_dump($all['precision']);

?>

Before

array(3) {
  [global_value]=>
  string(2) 14
  [local_value]=>
  string(2) 14
  [access]=>
  int(7)
}

After

array(4) {
  [global_value]=>
  string(2) 14
  [local_value]=>
  string(2) 14
  [builtin_default_value]=>
  string(2) 14
  [access]=>
  int(7)
}

PHP version change

This behavior changed in 8.6.

See Also

Extension

Init Readonly Properties In Child Class

Readonly properties used to be only assigned a value in their definition class. Even when they were protected, they could not be set in a child context.

In PHP 8.4, it is now possible.

On the other hand, initialisation in the global space is still forbidden.

PHP code

<?php

class x {
	protected readonly int $property;
}

class y extends x {
    function __construct() {
        $this->property = 5;
    }
}

$x = new y;
echo $x->property;

?>

Before

PHP Fatal error:  Uncaught Error: Cannot initialize readonly property x::$property from scope y

After

5

PHP version change

This behavior changed in 8.4.

Error Messages

Extension

instanceof Expect Objects

PHP used to report a fatal error when provided with a value that is not an object. After PHP 7.3, it would return false in such case, and not break the execution.

PHP code

<?php

var_dump(null instanceof Countable);

function foo() : ?X { /**/ }
var_dump(foo() instanceof Countable); // possible error when foo() returns null

?>

Before

PHP Fatal error:  instanceof expects an object instance, constant given 

Fatal error: instanceof expects an object instance, constant given 

After

bool(false)

PHP version change

This behavior changed in 7.3.

See Also

Error Messages

Extension

Integer Used As Array

An integer is not an array, but it is possible to use the array syntax with it. The values are then always null, and since PHP 7.4, a warning is emitted.

PHP code

<?php

var_dump(123[0]);

var_dump(1234['dsds']);

?>

Before

NULL

After

PHP Warning:  Trying to access array offset on int

Warning: Trying to access array offset on int
NULL

PHP version change

This behavior changed in 7.4.

Error Messages

Extension

Interface Constant Visibility Checks

PHP checks if the visibility of constants that are also part of an interface are all public. If the class constant, in the class, is not public, it is a Fatal Error. This was not checked until PHP 8.3.

PHP code

<?php

interface i {
        public const I = 1;
        public const J = 2;
}

class x implements i {
        private const I = 1;
        public const J = 2;
}

print x::J;
print x::I;
?>

Before

Cannot access private constant x::I

After

Access level to x::I must be public (as in interface i)

PHP version change

This behavior changed in 8.3.

Error Messages

Extension

Interpolated String Dereferencing

It was not possible to use a literal string as a variable for an array, or a class name, and access, respectively, index, properties, constant or methods. It was not possible for interpolated strings, which are strings that include another string.

In PHP 8, this is now possible.

PHP code

<?php

$bar = "abc";

echo "foo$bar"[0];
echo PHP_EOL
echo "foo$bar"::foo();

class fooabc{
    static function foo() {
        print __METHOD__;
    }
}

?>

Before

PHP Parse error:  syntax error, unexpected '[', expecting ';' or ',' 

After

f
fooabc::foo

PHP version change

This behavior changed in 8.0.

See Also

Error Messages

Extension

Analyzer

Calling Static Methods On Strings

The left operand of the :: operator for methods could not be a literal string, until PHP 8.0. It was not recognized as a valid syntax.

In PHP 8.0 and later, it is possible to build a class name in a string, then use it immediately in a method call.

It is also valid to access class constants and properties.

PHP code

<?php

$bar = abc;

echo foo$bar::foo();

class fooabc{
	static function foo() {
		print __METHOD__;
	}
}

?>

Before

PHP Parse error:  syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM), expecting ';' or ','

Parse error: syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM), expecting ';' or ','

After

fooabc::foo

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Integer Non-silent Conversion

When a string is converted into a integer, with problems, the notice was upgraded to a Warning. This raised level may end up filling logs.

This applies to all mathematical operations, such as multiplication, divisions, modulo, exponent, etc.

PHP code

<?php

print $a = 1 + '3a';

?>

Before

PHP Notice:  A non well formed numeric value encountered 

Notice: A non well formed numeric value encountered 
4

After

PHP Warning:  A non-numeric value encountered 

Warning: A non-numeric value encountered 
4

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Base Conversion Reports Invalid Characters

The base conversion functions, such as octdec(), base_convert(), binhex() or hexdex() used to ignore silently invalid characters. Invalid characters are the characters that do no belong to the base: for example, 2 or 3 in binary, or a in decimal, or g in hexadecimal.

The characters are still ignored, but they now raise a warning.

PHP code

<?php

print octdec('789');
print base_convert('123', 2, 10);
print bindec('a10');
print hexdec('defg');

?>

Before

7123567

After

PHP Deprecated:  Invalid characters passed for attempted conversion, these have been ignored

Deprecated: Invalid characters passed for attempted conversion, these have been ignored
7PHP Deprecated:  Invalid characters passed for attempted conversion, these have been ignored

Deprecated: Invalid characters passed for attempted conversion, these have been ignored
1PHP Deprecated:  Invalid characters passed for attempted conversion, these have been ignored

Deprecated: Invalid characters passed for attempted conversion, these have been ignored
2PHP Deprecated:  Invalid characters passed for attempted conversion, these have been ignored

Deprecated: Invalid characters passed for attempted conversion, these have been ignored
3567

PHP version change

This behavior changed in 7.4.

Error Messages

Extension

is_double(), is_long(), is_integer() And doubleval() Are Deprecated

is_double(), is_long(), is_integer() and doubleval() have always been pure aliases for is_float(), is_int(), is_int() and floatval() respectively. Until PHP 8.6, calling any of these aliases produced no diagnostic. In PHP 8.6, calling any of them emits a deprecation notice pointing to the canonical function name, even though the alias keeps working exactly as before.

PHP code

<?php

var_dump(is_double(1.5));
var_dump(is_long(1));
var_dump(is_integer(1));
var_dump(doubleval("1.5"));

?>

Before

bool(true)
bool(true)
bool(true)
float(1.5)

After

PHP Deprecated:  Function is_double() is deprecated since 8.6, use is_float() instead

Deprecated: Function is_double() is deprecated since 8.6, use is_float() instead
bool(true)
PHP Deprecated:  Function is_long() is deprecated since 8.6, use is_int() instead

Deprecated: Function is_long() is deprecated since 8.6, use is_int() instead
bool(true)
PHP Deprecated:  Function is_integer() is deprecated since 8.6, use is_int() instead

Deprecated: Function is_integer() is deprecated since 8.6, use is_int() instead
bool(true)
PHP Deprecated:  Function doubleval() is deprecated since 8.6, use floatval() instead

Deprecated: Function doubleval() is deprecated since 8.6, use floatval() instead
float(1.5)

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

isset() On Constants

It was not possible to use isset() on a constant. PHP mistook it with an expression, and stopped the execution with a Fatal error.

Since PHP 7.0, it is possible to use isset() with a constant, in particular with the array syntax or the object syntax. Still, isset() should not be used to check the existence of the constant: rather, there is the native function defined().

PHP code

<?php
const X = [1,2,3];

if (isset(X[4])) {
    echo 'set';
} else {
    echo 'not set';
}
?>

Before

PHP Fatal error:  Cannot use isset() on the result of an expression (you can use "null !== expression" instead) 

Fatal error: Cannot use isset() on the result of an expression (you can use "null !== expression" instead) 

After

not set

PHP version change

This behavior changed in 7.0.

Error Messages

Extension

iterator_count() Also Count Arrays

The PHP native function used to accept only iterators. Since PHP 8.1, arrays are also welcomed.

PHP code

<?php

print iterator_count([1,2,3]);

?>

Before

Uncaught TypeError: iterator_count(): Argument #1 ($iterator) must be of type Traversable, array given

After

3

PHP version change

This behavior changed in 8.2.

Error Messages

Extension

jsonSerialize Must Have Return Type

PHP used to not enforce the type compatibility between a native interface and its custom implementation. This was for backward compatibility, and it is now over: PHP checks for type compatibility.

If making the returntype mixed or compatible is not possible at the moment, it is possible to use the ReturnTypeWillChange attribute to avoid this error message until it is actually fixed.

This affects all PHP native interfaces, and jsonSerialize is the most frequent to be reported.

PHP code

<?php

class x implements JsonSerializable {
	function __construct() { echo __METHOD__; }
	function jsonSerialize() {}
}

new x;

?>

Before

x::__construct

After

PHP Deprecated:  Return type of x::jsonSerialize() should either be compatible with JsonSerializable::jsonSerialize(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice 

Deprecated: Return type of x::jsonSerialize() should either be compatible with JsonSerializable::jsonSerialize(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice 
x::__construct

PHP version change

This behavior changed in 8.1.

Error Messages

Extension

Analyzer

Keywords In Namespace

PHP didn’t accept its own keywords in the definition of a namespace. Nowadays, namespaces are parsed as a whole, and there are no keywords in there. Before, the namespaces were parsed bit by bit, and the presence of the keywords was an impediment in that process.

PHP code

<?php
namespace a\eval\b;
echo __NAMESPACE__;
?>

Before

syntax error, unexpected token "\", expecting "{"

After

a\eval\b

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

krsort() Places Integers Before Strings In Keys

krsort() used the PHP 7 way to compare values: then, strings would rank below integers, in particular below 0.

In PHP 8.2, strings are now ranking above integers, and are moved to the end of the sorted array. This is related to the change of rules in comparisons.

PHP code

<?php

$x = array('a' => 1, 
		   0 => 2, 
		   1 => 3, 
		   '0' => 4,
);
krsort($x);
print_r($x);

?>

Before

Array
(
    [1] => 3
    [a] => 1
    [0] => 4
)

After

Array
(
    [a] => 1
    [1] => 3
    [0] => 4
)

PHP version change

This behavior changed in 8.2.

Extension

ksort() Now Places Integers Before Strings

ksort() used the PHP 7 way to compare values: then, strings would rank below integers, in particular below 0.

In PHP 8.2, strings are now ranking above integers, and are moved to the end of the sorted array. This is related to the change of rules in comparisons.

PHP code

<?php

$x = array('a' => 1, 
           0 => 2, 
           1 => 3, 
           '0' => 4,
);
ksort($x);
print_r($x);
?>

Before

Array
(
    [a] => 1
    [0] => 4
    [1] => 3
)

After

Array
(
    [0] => 4
    [1] => 3
    [a] => 1
)

PHP version change

This behavior changed in 8.2.

Extension

ksort() now uses regular comparison

ksort() used a different sorting method to sort the keys than to sort the values. Since PHP 8.2, it uses the same method than sort() does on values. This means some values may have a different position.

This applies to krsort() too. It may apply to uksort(), depending on the code of the custom comparison function.

PHP code

<?php

$array = [ 0, '-f' => 1, 'f' => 2];

ksort($array);

print_r($array);

?>

Before

Array
(
    [0] => 0
    [-f] => 1
    [f] => 2
)

After

Array
(
    [-f] => 1
    [0] => 0
    [f] => 2
)

PHP version change

This behavior changed in 8.2.

Extension

libxml_disable_entity_loader() Is Deprecated

libxml_disable_entity_loader() has been deprecated since PHP 8.0, and actually, does not execute any code. The error message was upgraded to make it more explicit.

PHP code

<?php

var_dump(libxml_disable_entity_loader(true));

?>

Before

PHP Deprecated:  Function libxml_disable_entity_loader() is deprecated 

Deprecated: Function libxml_disable_entity_loader() is deprecated 
bool(false)

After

PHP Deprecated:  Function libxml_disable_entity_loader() is deprecated since 8.0, as external entity loading is disabled by default 

Deprecated: Function libxml_disable_entity_loader() is deprecated since 8.0, as external entity loading is disabled by default 
bool(false)

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

linkinfo() Rejects An Empty Path

linkinfo() used to accept an empty string as its path argument, emit a warning and return -1. In PHP 8.6, an empty path throws a ValueError.

PHP code

<?php

var_dump(linkinfo(''));

?>

Before

PHP Warning:  linkinfo(): No such file or directory

Warning: linkinfo(): No such file or directory
int(-1)

After

linkinfo(): Argument #1 ($path) must not be empty

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

Duplicate Enum Cases Are Not Linted Anymore

Two different cases in an enumeration cannot have duplicate values.

In PHP 8.1, it was a compilation error, and the code would not be executed.

Since PHP 8.2, it is only checked at execution time, when the enumeration is first used. This means that it may be a hidden bug, until that code is actually used.

PHP code

<?php

enum A : int{
    case A = 1;
    case B = 1;
}

function foo(?A $x = null) { 
    var_dump($x);
}

// A is not used, as it default to NULL
foo();

?>

Before

Fatal error: Duplicate value in enum A for cases A and B

After

NULL

PHP version change

This behavior changed in 8.2.

See Also

Error Messages

Extension

Covariance And Contravariance Are Fatal

Type mismatch between signatures of the same method in different classes of the same hierarchy used to be a warning. It is not a fatal error, althought it is only checked at execution time, when all the classes are loaded.

PHP code

<?php

class Foo {
    public function process(stdClass $item): array{}
}

class SuperFoo extends Foo{
    public function process(array $items): array{}
    //                      ^^^^^ mismatch
}

?>

Before

PHP Warning:  Declaration of SuperFoo::process(array $items): array should be compatible with Foo::process(stdClass $item): array 

Warning: Declaration of SuperFoo::process(array $items): array should be compatible with Foo::process(stdClass $item): array 

After

PHP Fatal error:  Declaration of SuperFoo::process(array $items): array must be compatible with Foo::process(stdClass $item): array 

Fatal error: Declaration of SuperFoo::process(array $items): array must be compatible with Foo::process(stdClass $item): array 

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

FUNCTION In Closure Changed

When using the magic constant __FUNCTION__ inside a closure, the content used to be the {closure} string only. Since PHP 8.4, it also includes the name of the original file, and the line number, so as to identify the origin source code.

PHP code

<?php

$foo = fn() => __FUNCTION__;

$closure = function() { return __FUNCTION__;};

echo $foo();

echo $closure();


?>

Before

{closure}{closure}

After

{closure:/codes/magicConstantInClosure.php:3}{closure:/codes/magicConstantInClosure.php:5}

PHP version change

This behavior changed in 8.4.

Extension

isset()-Materialized Magic Property Skips __get()

?? and empty() on an inaccessible property call __isset() first. If __isset() materializes the property, by writing directly into the object’s property table, for example $this->$name = …, PHP 8.6 returns that freshly-written value directly instead of calling __get() afterwards. Until PHP 8.6, __get() was still called even though the property now exists, and its return value was used instead of the value __isset() had just written. Plain isset() is unaffected by this change.

PHP code

<?php

#[AllowDynamicProperties]
class Magic {
    public function __isset($name) {
        echo "__isset($name) called\n";
        $this->$name = 'materialized-by-isset';
        return true;
    }
    public function __get($name) {
        echo "__get($name) called\n";
        return 'from __get';
    }
}

$m = new Magic();
var_dump($m->x ?? 'default');

?>

Before

__isset(x) called
__get(x) called
string(10) "from __get" 

After

__isset(x) called
string(21) "materialized-by-isset" 

PHP version change

This behavior changed in 8.6.

See Also

Extension

match Is Now A Keyword

match() was introduced as a new command. As a side effect, it is now a PHP keyword, and it is not possible to create classes, functions or constants with that name.

PHP code

<?php

function match() {
	echo __FUNCTION__;
}

match();

?>

Before

match

After

PHP Parse error:  syntax error, unexpected token "match", expecting "(" 

Parse error: syntax error, unexpected token "match", expecting "(" 

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

No Max() On Empty Array

max() does not accept an empty array as argument. In that case, it used to return NULL, but NULL is also a valid return value, and it is not possible to differentiate between the NULL of an empty array and the NULL that is really a maximum value.

PHP code

<?php

max([]);

?>

Before

PHP Warning:  max(): Array must contain at least one element 

Warning: max(): Array must contain at least one element 

After

PHP Fatal error:  Uncaught ValueError: max(): Argument #1 ($value) must contain at least one element 

Fatal error: Uncaught ValueError: max(): Argument #1 ($value) must contain at least one element 

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

max() Must Contain At Least One Element

max() returns the largest value in the argument. When that argument is an empty array, there is an ambiguity related to the returned value, as there is no such value. PHP would return null, thought it is possible for max() to return null.

To be consistent, PHP emits an error on an empty array : it is not possible to get the maximum value when there are none.

PHP code

<?php

try {
	$a = max([]);
} catch (\Error $e) {
	print $e->getMessage();
}

var_dump($a);

?>

Before

PHP Warning:  max(): Array must contain at least one element 

Warning: max(): Array must contain at least one element 
bool(false)

After

max(): Argument #1 ($value) must contain at least one elementPHP Warning:  Undefined variable $a 

Warning: Undefined variable $a 
NULL

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

max() On String And Integer

In PHP 8, the rules of comparison between integers and strings have changed. Hence, max() may return a different value on PHP 7 and PHP 8.

PHP code

<?php

var_dump( max(['', 0]));

?>

Before

string(0) 

After

int(0)

PHP version change

This behavior changed in 8.0.

Extension

mb_convert_encoding() Has Deprecated Formats

4 previous formats have been removed from mb_convert_encoding() options: uuencode, base64, qprint, html.

They are all handled by dedicated PHP functions, which should be used instead of this one.

PHP code

<?php

echo mb_convert_encoding('foo', 'uuencode');
echo mb_convert_encoding('foo', 'base64');
echo mb_convert_encoding('foo', 'qprint');
echo mb_convert_encoding('foo', 'html');

?>

Before

Zm9vfoofoo

After

PHP Deprecated:  mb_convert_encoding(): Handling Uuencode via mbstring is deprecated; use convert_uuencode/convert_uudecode instead 

Deprecated: mb_convert_encoding(): Handling Uuencode via mbstring is deprecated; use convert_uuencode/convert_uudecode instead 
begin 0644 filename
#9F]O
PHP Deprecated:  mb_convert_encoding(): Handling Base64 via mbstring is deprecated; use base64_encode/base64_decode instead 

Deprecated: mb_convert_encoding(): Handling Base64 via mbstring is deprecated; use base64_encode/base64_decode instead 
Zm9vPHP Deprecated:  mb_convert_encoding(): Handling QPrint via mbstring is deprecated; use quoted_printable_encode/quoted_printable_decode instead 

Deprecated: mb_convert_encoding(): Handling QPrint via mbstring is deprecated; use quoted_printable_encode/quoted_printable_decode instead 
fooPHP Deprecated:  mb_convert_encoding(): Handling HTML entities via mbstring is deprecated; use htmlspecialchars, htmlentities, or mb_encode_numericentity/mb_decode_numericentity instead 

Deprecated: mb_convert_encoding(): Handling HTML entities via mbstring is deprecated; use htmlspecialchars, htmlentities, or mb_encode_numericentity/mb_decode_numericentity instead 
foo

PHP version change

This behavior was deprecated in 8.2.

This behavior changed in 8.2.

Error Messages

Extension

Integer Regex With mb_ereg_replace()

mb_ereg_replace() used to accept an integer as a regex. It would turn that integer into its equivalent ASCII character and use it as a regex. This behavior has been removed.

A similar change of behavior happened with strpos().

PHP code

<?php

var_dump(mb_ereg_replace(98, 'Z', 'abc'));

?>

Before

PHP Deprecated:  mb_ereg_replace(): Non-string patterns will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior 

Deprecated: mb_ereg_replace(): Non-string patterns will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior 
string(3) "aZc" 

After

string(3) "abc" 

PHP version change

This behavior changed in 8.0.

Extension

mb_strrpos() Third Argument Is Not Encoding

The third argument of mb_strrpos() was the offset where to start the search in the string. It was often 0, although the 4th argument was the encoding. Since the encoding was more often used, and the offset forgotten, mb_strrpos() used to recognize the encoding when it is used in position 3, and use it. In PHP 8.0, it is not the case anymore.

PHP code

<?php

// Valid in PHP 7.x
echo mb_strrpos('abc', 'a', 'utf8');

// Valid in PHP 8.+
echo mb_strrpos('abc', 'a', 0, 'utf8');
echo mb_strrpos('abc', 'a', encoding: 'utf8');

?>

Before

PHP Deprecated:  mb_strrpos(): Passing the encoding as third parameter is deprecated. Use an explicit zero offset 

Deprecated: mb_strrpos(): Passing the encoding as third parameter is deprecated. Use an explicit zero offset 
0

After

PHP Fatal error:  Uncaught TypeError: mb_strrpos(): Argument #3 ($offset) must be of type int, string given 

Fatal error: Uncaught TypeError: mb_strrpos(): Argument #3 ($offset) must be of type int, string given 

PHP version change

This behavior changed in 8.0.

Extension

Passing An Object To mb_convert_variables() Is Deprecated

mb_convert_variables() accepts one or more variables by reference and converts their character encoding in place; historically, an object passed as one of these variables had its accessible public properties converted, just like an array. Until PHP 8.6, this worked silently. In PHP 8.6, passing an object to mb_convert_variables() emits a deprecation notice suggesting get_object_vars() first, even though the conversion still happens exactly as before.

PHP code

<?php

$o = new stdClass();
$o->a = "abc";
$o->b = "def";

$from = mb_convert_variables("UTF-8", "auto", $o);
var_dump($from);
var_dump($o);

?>

Before

string(5) "ASCII" 
object(stdClass)#1 (2) {
  ["a"]=>
  string(3) "abc" 
  ["b"]=>
  string(3) "def" 
}

After

PHP Deprecated:  mb_convert_variables(): Passing an object for argument #3 $vars to mb_convert_variables() is deprecated, call get_object_vars() first instead in /codes/mbConvertVariablesObjectDeprecated.php on line 7

Deprecated: mb_convert_variables(): Passing an object for argument #3 $vars to mb_convert_variables() is deprecated, call get_object_vars() first instead in /codes/mbConvertVariablesObjectDeprecated.php on line 7
string(5) "ASCII" 
object(stdClass)#1 (2) {
  ["a"]=>
  string(3) "abc" 
  ["b"]=>
  string(3) "def" 
}

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

The mbstring Regex (mbregex) Functions Are Deprecated

The mb_ereg*() family of functions (mb_ereg(), mb_eregi(), mb_ereg_match(), mb_ereg_replace(), mb_ereg_search() and its siblings, mb_split(), and related functions) wraps the bundled Oniguruma regex library, which is no longer maintained upstream. Until PHP 8.6, calling any of these functions worked silently. In PHP 8.6, calling any mbregex function emits a deprecation notice, even though the function still behaves exactly as before.

PHP code

<?php

var_dump(mb_ereg("^a", "abc"));

?>

Before

bool(true)

After

PHP Deprecated:  Function mb_ereg() is deprecated since 8.6, because the underlying library is no longer maintained in /codes/mbregexDeprecated.php on line 3

Deprecated: Function mb_ereg() is deprecated since 8.6, because the underlying library is no longer maintained in /codes/mbregexDeprecated.php on line 3
bool(true)

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

metaphone() Is Deprecated

metaphone() computes a phonetic key for an ASCII string. In PHP 8.6, calling it emits a deprecation notice recommending a userland phonetic matching library instead; the function still returns the same key as before.

PHP code

<?php

var_dump(metaphone("Thompson"));

?>

Before

string(5) "0MPSN" 

After

PHP Deprecated:  Function metaphone() is deprecated since 8.6, use a userland phonetic matching library instead in /codes/metaphoneDeprecated86.php on line 3

Deprecated: Function metaphone() is deprecated since 8.6, use a userland phonetic matching library instead in /codes/metaphoneDeprecated86.php on line 3
string(5) "0MPSN" 

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

No min() On Empty Array

min() does not accept an empty array as argument. In that case, it used to return NULL, but NULL is also a valid return value, and it is not possible to differentiate between the NULL of an empty array and the NULL that is really a minimum value.

PHP code

<?php

min([]);

?>

Before

PHP Warning:  min(): Array must contain at least one element 

Warning: min(): Array must contain at least one element

After

PHP Fatal error:  Uncaught ValueError: min(): Argument #1 ($value) must contain at least one element in codes/minOnEmpty.php:3

Fatal error: Uncaught ValueError: min(): Argument #1 ($value) must contain at least one element in codes/minOnEmpty.php:3

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

min() Doesn’t Accept Empty Arrays

min() doesn’t accept empty arrays anymore. It used to, and returned false, which is a type away from 0.

Nowadays, to distinguish between returned false or null and an empty array, an exception is raised. It is recommended to check the array for content before using min() or to catch the Error with a try catch.

Note that max() behave the same.

PHP code

<?php

try {
	$a = min([]);
} catch (\Error $e) {
	print $e->getMessage();
}

var_dump($a);

?>

Before

PHP Warning:  min(): Array must contain at least one element

Warning: min(): Array must contain at least one element
bool(false)

After

min(): Argument #1 ($value) must contain at least one elementPHP Warning:  Undefined variable $a

Warning: Undefined variable $a
NULL

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

mixed Is Now A Keyword

mixed was introduced in PHP 8.0 as a new type. As a side effect, it is now a PHP keyword, and it is not possible to create classes, functions or constants with that name.

PHP code

<?php

class mixed {
	function __construct() {
		echo __METHOD__;
	}
}

new mixed;

?>

Before

mixed::__construct

After

PHP Fatal error:  Cannot use 'mixed' as class name as it is reserved 

Fatal error: Cannot use 'mixed' as class name as it is reserved 

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

No New Line In Namespaces

It was possible to use new lines inside a namespace: they would be removed at execution time, to build the actual namespace.

Since PHP 8.0, it is not allowed anymore.

PHP code

<?php

// constant
    \A 
                           \B 
                           \C
                           ;

// type
    function foo() : \A 
                           \B 
                           \C
                           {}

?>

Before

PHP Parse error:  syntax error, unexpected ';', expecting '{' 

Parse error: syntax error, unexpected ';', expecting '{' 

After

PHP Parse error:  syntax error, unexpected fully qualified name \B

Parse error: syntax error, unexpected fully qualified name \B 

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Yield Must Use Integer Or String Keys

A generator is unpacked as an array, and as such, it doesn’t allow keys to be anything else but string or integer. The generator may still be used in a foreach() structure, and yield usable keys, but it can’t be unpacked or turned into a array without an error. In previous versions, the keys would be ignored, and re-indexed.

PHP code

<?php

function foo(...$args) {
    var_dump($args);
}
function gen() {
    yield 1.23 => 123;
}
foo(...gen());

?>

Before

array(3) {
  [0]=>
  int(123)
  [1]=>
  int(123)
  [2]=>
  int(123)
}

After

Fatal error: Uncaught Error: Keys must be of type int|string during argument unpacking

PHP version change

This behavior changed in 7.2.

Error Messages

Extension

mysqli::stmt_init() And mysqli_get_charset() Are Deprecated

mysqli::stmt_init() creates a statement object to prepare later, and mysqli_get_charset() returns information about the connection’s current character set. In PHP 8.6, calling either emits a deprecation notice, recommending mysqli::prepare() and mysqli_character_set_name() respectively; both functions keep their previous behavior otherwise.

PHP code

<?php

$mysqli = new mysqli();

try {
    $mysqli->stmt_init();
} catch (\Error $e) {
    echo "Error: ".$e->getMessage()."\n";
}

try {
    var_dump(mysqli_get_charset($mysqli));
} catch (\Error $e) {
    echo "Error: ".$e->getMessage()."\n";
}

?>

Before

Error: mysqli object is not fully initialized
Error: mysqli object is not fully initialized

After

PHP Deprecated:  Method mysqli::stmt_init() is deprecated since 8.6, use mysqli::prepare() instead in /codes/mysqliStmtInitGetCharsetDeprecated86.php on line 6

Deprecated: Method mysqli::stmt_init() is deprecated since 8.6, use mysqli::prepare() instead in /codes/mysqliStmtInitGetCharsetDeprecated86.php on line 6
Error: mysqli object is not fully initialized
PHP Deprecated:  Function mysqli_get_charset() is deprecated since 8.6, did you mean mysqli_character_set_name()? in /codes/mysqliStmtInitGetCharsetDeprecated86.php on line 12

Deprecated: Function mysqli_get_charset() is deprecated since 8.6, did you mean mysqli_character_set_name()? in /codes/mysqliStmtInitGetCharsetDeprecated86.php on line 12
Error: mysqli object is not fully initialized

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

Passing A $mode To mysqli_store_result() Is Deprecated

mysqli_store_result() used to accept an optional $mode flag telling mysqlnd whether to copy fetched data out of its internal buffer. As of PHP 8.1, mysqlnd’s internal buffering changed and this flag stopped having any effect: it is silently ignored no matter what is passed, without any notice. In PHP 8.4, passing the parameter at all, regardless of its value, additionally emits a deprecation notice; the (lack of) effect on the actual buffering behavior is unchanged, only the notice is new.

PHP code

<?php

mysqli_report(MYSQLI_REPORT_OFF);
$mysqli = mysqli_init();
@$mysqli->real_connect('127.0.0.1', 'nouser', 'nopass', 'nodb', 1);
try {
    var_dump(mysqli_store_result($mysqli, 0));
} catch (\Throwable $e) {
    echo get_class($e), ': ', $e->getMessage(), "\n";
}

Before

Error: mysqli object is not fully initialized

After

PHP Deprecated:  Passing the $mode parameter is deprecated since 8.4, as it has been ignored since 8.1 in /codes/mysqliStoreResultModeDeprecated.php on line 7

Deprecated: Passing the $mode parameter is deprecated since 8.4, as it has been ignored since 8.1 in /codes/mysqliStoreResultModeDeprecated.php on line 7
Error: mysqli object is not fully initialized

PHP version change

This behavior was deprecated in 8.4.

This behavior changed in .

See Also

Error Messages

Extension

Named Parameters And Variadic

It is possible to use the three dots ... operator and named parameters when calling a method. The unpacked array must have named arguments, and so does the arguments after it.

In PHP 8.0, it was not possible.

PHP code

<?php

function foo($a, ...$b) {
    echo $a.' '.implode(', ', $b);
}

foo(...[b => 1], a: 2);

?>

Before

PHP Fatal error:  Cannot combine named arguments and argument unpacking 

Fatal error: Cannot combine named arguments and argument unpacking 

After

2 1

PHP version change

This behavior changed in 8.1.

Error Messages

Extension

namespace Is Not Valid As A Class Constant Name

namespace is a PHP keyword, and it is allowed inside class for naming methods or properties. After PHP 8.6, it is not possible to use it for constant name.

The actual motivation is a future feature: reserving namespace would allow a future ::namespace pseudo-constant, analogous to ::class, for directory namespaces. For example, replacing stringy APIs like Order\Domain\Entities with \Order\Domain\Entities::namespace, which gives you IDE support, refactoring, and static analysis for free.

PHP code

<?php

class x { const namespace= 1;}

echo x::namespace;

?>

Before

1

After

PHP Deprecated:  Declaring class constant called 'namespace' is deprecated in /codes/namespaceClassConst.php on line 3

Deprecated: Declaring class constant called 'namespace' is deprecated in /codes/namespaceClassConst.php on line 3
1

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in 8.6.

See Also

Error Messages

Extension

Negative Index On Strings

Negative index reaches an offset in a string, starting from the last elements in that string, instead of starting from position 0.

This feature is also supported by substr(), and was introduced in PHP 7.1.

PHP code

<?php

$string = 'abc';

var_dump($string[-1]);

?>

Before

PHP Notice:  Uninitialized string offset: -1

Notice: Uninitialized string offset: -1
string(0) "" 

After

string(1) "c" 

PHP version change

This behavior changed in 7.1.

Error Messages

Extension

Analyzer

Negative Offset With Strings

Negative offsets on strings were introduced in PHP 7.1. They allow accessing individual characters, starting from the end of the string, rather than from the beginning.

PHP code

<?php

$string = 'abc';

echo $string[-1]; // c
echo $string[1]; // a

?>

Before

PHP Notice:  Uninitialized string offset: -1

Notice: Uninitialized string offset: -1
b

After

cb

PHP version change

This behavior changed in 7.1.

Extension

Nested Attributes

Attributes can handle nested new calls since PHP 8.1. They can use literals, constants and now, full objects as part of the attribute expression.

PHP code

<?php

#[JoinTable(joinColumns: [new JoinColumn])]
class x {
	function __construct() {
		print __METHOD__;
	}
}

new x;

?>

Before

PHP Fatal error:  Constant expression contains invalid operations

Fatal error: Constant expression contains invalid operations

After

x::__construct

PHP version change

This behavior changed in 8.1.

Error Messages

Extension

Never Arrow Function

The never type requires the closure to not return, but the arrow function always returns something. By using die(), that closure doesn’t return anymore, but PHP only recognized this since PHP 8.2. Before PHP 8.1, it was valid syntax, as never was recognized as a class name.

PHP code

<?php

fn($a) : never => die(); 

?>

Before

PHP Fatal error:  A never-returning function must not return

Fatal error: A never-returning function must not return

After


PHP version change

This behavior changed in 8.1.

Error Messages

Extension

never Is Now A Keyword

Never became a PHP reserved keyword in PHP 8.1. It is used as special type, and cannot be used anymore for function names, classnames, etc.

PHP code

<?php

class never {
	function __construct() {
		print __METHOD__;
	}
}

new never;

?>

Before

never::__construct

After

PHP Fatal error:  Cannot use 'never' as class name as it is reserved 

Fatal error: Cannot use 'never' as class name as it is reserved 

PHP version change

This behavior changed in 8.1.

Extension

Direct calls on new

Calling an object directly upon instantiation was not possible in PHP 8.3: it required parenthesis, like every other new call.

In PHP 8.4, it is now possible to call a method or access a property directly at instantiation time. It is also possible to call its __invoke method.

PHP code

<?php

class x {
    function __construct($i = 0) { echo __METHOD__.PHP_EOL;}
    
    function __invoke()          { echo __METHOD__.PHP_EOL;}
}

$x = new x;

$y = new $x()();
// identical to 
//$y = (new $x(0)) ()

var_dump($y);
// NULL 

?>

Before

PHP Parse error:  syntax error, unexpected token "(" 

Parse error: syntax error, unexpected token "(" 

After

x::__construct
x::__construct
x::__invoke
NULL

PHP version change

This behavior changed in 8.4.

Error Messages

Extension

New Cases In Switch

The result of comparisons between empty values, such as 0, '' (empty string), or [] (empty array), have changed. The impact is obvious with the == operator, and it is less obvious with switch, which relies on the same underlying code.

In particular, when there are several falsy cases in a switch, the selection of cases may be different between PHP versions, like in this illustration.

In PHP 7.4 and older, 0 == '', so the first case is selected. After PHP 8.0, 0 != '', and the second case is selected.

PHP code

<?php

foreach([0, '', null, []] as $a )
	switch($a) {
		case 0;
			print Zero\n;
			break;
			
		case '':
			print Empty string\n;
			break;
			
		case []:
			print []\n;
			break;
	}

?>

Before

Zero
Zero
Zero
[]

After

Zero
Empty string
Zero
[]

PHP version change

This behavior changed in 8.0.

Extension

Call Method On New

It was not possible to call a method directly after instantiating an object: the instantiation had to be done within a parenthesis.

In PHP 8.4, it is now possible to call directly a method after instantiation, as long as the new call includes the parenthesises.

PHP code

<?php

class x {} 

new x()->a();

// This is not possible: it's missing the parenthesis
//new x->a();

?>

Before

PHP Parse error:  syntax error, unexpected token "->" 

Parse error: syntax error, unexpected token "->" 

After


PHP version change

This behavior changed in 8.4.

Error Messages

Extension

__sleep() Method Enforces Return Type

__sleep is a magic method that lists the name of the variables to serialize. It should come as an array, and is enforced as such since PHP 8.0.

PHP code

<?php

class x {
	function __sleep() {
		return 3;
	}
}

serialize(new x);

?>

Before

PHP Notice:  serialize(): __sleep should return an array only containing the names of instance-variables to serialize

Notice: serialize(): __sleep should return an array only containing the names of instance-variables to serialize

After

PHP Warning:  serialize(): x::__sleep() should return an array only containing the names of instance-variables to serialize

Warning: serialize(): x::__sleep() should return an array only containing the names of instance-variables to serialize

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Non-canonical Cast

Non canonical cast operators (integer), (binary), (double), (boolean) are deprecated, since PHP 8.5.

PHP code

<?php

print (integer) 2;
print (double) 2;
print (boolean) 2;
print (binary) 2;

?>

Before

2212

After

PHP Deprecated:  Non-canonical cast (integer) is deprecated, use the (int) cast instead

Deprecated: Non-canonical cast (integer) is deprecated, use the (int) cast instead
PHP Deprecated:  Non-canonical cast (double) is deprecated, use the (float) cast instead

Deprecated: Non-canonical cast (double) is deprecated, use the (float) cast instead
PHP Deprecated:  Non-canonical cast (boolean) is deprecated, use the (bool) cast instead

Deprecated: Non-canonical cast (boolean) is deprecated, use the (bool) cast instead
PHP Deprecated:  Non-canonical cast (binary) is deprecated, use the (string) cast instead

Deprecated: Non-canonical cast (binary) is deprecated, use the (string) cast instead
2212

PHP version change

This behavior was deprecated in 8.5.

This behavior changed in 8.5.

Error Messages

Extension

Non Static Method Called Statically

It is not possible to call a non-static method statically, and now, it is also not possible to call non-statically a static method.

The static syntax is still valid with expression like parent::__construct. Be aware that a call such as self::foo also checks if the target method is static.

PHP code

<?php

class Foo {
    public function bar() {}
    
    static function foo() {
    	self::bar();
    }
}
//Non-static method Foo::bar() cannot be called statically (line 10)
Foo::bar();

//Non-static method Foo::bar() cannot be called statically (line 6)
Foo::foo();

?>

Before

PHP Deprecated:  Non-static method Foo::bar() should not be called statically 

Deprecated: Non-static method Foo::bar() should not be called statically 

After

PHP Fatal error:  Uncaught Error: Non-static method Foo::bar() cannot be called statically 

Fatal error: Uncaught Error: Non-static method Foo::bar() cannot be called statically 

PHP version change

This behavior was deprecated in 7.0.

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

No Reference To $GLOBALS Variable

It is not possible anymore to create a reference to the $GLOBALS variable. It prevents any unexpected updates to this array. It was possible until PHP 8.2.

It is still possible to make a reference to any of the element of that array, individually.

PHP code

<?php

$b = &$GLOBALS;

print_r($b);

?>

Before

Array
(
    [_GET] => Array
        (
        )

    [_POST] => Array
        (
        )

    [_COOKIE] => Array
        (
        )
// .... and more

?>

After

PHP Fatal error:  Cannot acquire reference to $GLOBALS

PHP version change

This behavior changed in 8.2.

Error Messages

Extension

Not In A Closure

Calling Closure native methods outside a closure or an arrow function leads to an error message.

This applies to functions or methods, that are later turned into a closure with the first class callable syntax: while that syntax creates a closure, the underlying code is not a closure, and cannot access the related features.

The warning message, used in previous PHP version, was not as explicit as the new one.

PHP code

<?php

function foo() {
    Closure::getCurrent();
}

foo(); // Error

foo(...)(); // Error: foo was put inside a closure, but it is still not a closure itself.

?>

Before

PHP Fatal error:  Uncaught Error: Call to undefined method Closure::getCurrent()

After

PHP Fatal error:  Uncaught Error: Current function is not a closure

Fatal error: Uncaught Error: Current function is not a closure

PHP version change

This behavior changed in 8.5.

Error Messages

Extension

Null Used As Array

Null is not an array, but it is possible to use the array syntax with it. The values are then always null, and since PHP 7.4, a warning is emitted.

PHP code

<?php

var_dump(null[0]);

var_dump(null['dsds']);

?>

Before

NULL

After

PHP Warning:  Trying to access array offset on null 

Warning: Trying to access array offset on null 
NULL

PHP version change

This behavior changed in 7.4.

Error Messages

Extension

Null As Array Offset

Array indices may be integers or strings. They may also be boolean or null, although both these types are converted in integers and string (respectively).

In PHP 8.5, a warning is emitted when a null value is used as an index.

PHP code

<?php

$array = ['a' => 2];
$array[null] = 3;

print $array['']; 
print $array[null]; 

?>

Before

33

After

PHP Deprecated:  Using null as an array offset is deprecated, use an empty string instead

Deprecated: Using null as an array offset is deprecated, use an empty string instead
3PHP Deprecated:  Using null as an array offset is deprecated, use an empty string instead

Deprecated: Using null as an array offset is deprecated, use an empty string instead
3

PHP version change

This behavior changed in 8.5.

Error Messages

Extension

Null With array_key_exists()

null is not accepted anymore as the first argument of the PHP native function array_key_exists(). Since PHP 8.5, null is not accepted anymore as a key in an array, so it is also not accepted with the function array_key_exists(), which checks if a key exists in an array.

PHP code

<?php

$array = [null => 1]; // silent error 
var_dump(array_key_exists(null, $array)); 

?>

Before

bool(true)

After

PHP Deprecated:  Using null as the key parameter for array_key_exists() is deprecated, use an empty string instead

Deprecated: Using null as the key parameter for array_key_exists() is deprecated, use an empty string instead
bool(true)

PHP version change

This behavior changed in 8.5.

Error Messages

Extension

number_format() Validates Its Decimals Argument Range

number_format()’s $decimals argument is internally clamped to a 32-bit signed integer range (-2147483648 to 2147483647). Until PHP 8.6, a value outside that range was silently clamped, for very negative values this collapsed to 0 decimals, and for very large positive values it could trigger a huge memory allocation while building the result string. In PHP 8.6, an out-of-range value throws a ValueError instead.

PHP code

<?php

try {
    var_dump(number_format(1234.5678, PHP_INT_MIN));
} catch (\ValueError $e) {
    echo $e->getMessage(), "\n";
}

?>

Before

string(1) 0

After

number_format(): Argument #2 ($decimals) must be between -2147483648 and 2147483647

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

PHP Warns When Finding Unconvertible Characters

PHP emits a deprecation when reaching a character that cannot be converted. For example, when converting from octal to decimal, a 8, a 9, or a letter cannot be converted to a number.

Until PHP 7.4, PHP would stop at that character, then return the converted part. Later, it also emits a warning.

PHP code

<?php

// 9 is not an octal number
echo octdec(342391);

?>

Before

14489

After

PHP Deprecated:  Invalid characters passed for attempted conversion, these have been ignored

Deprecated: Invalid characters passed for attempted conversion, these have been ignored
14489

PHP version change

This behavior changed in 7.4.

Error Messages

Extension

Old Constructors

In PHP 4, the constructor was the method of the same name as the class. They were called during instantiation of an object. In PHP 7, there were replaced with the __construct method, and were used in case of fallback.

Old constructors are also called PHP 4 constructor, as they were used during that time; they are also called eponymous constructors, as they use the same name as the class.

PHP code

<?php

class X {
	function x() {
		print __METHOD__;
	}

	function foo() {
		print __METHOD__;
	}
}

(new x())->foo();
?>

Before

PHP Deprecated:  Methods with the same name as their class will not be constructors in a future version of PHP; x has a deprecated constructor

Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; x has a deprecated constructor
x::xx::foo

After

x::foo

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

Old Style Constructor

Since PHP 5, the constructor method of a class was the eponymous method: the method with the same name as the class.

In PHP 7, this feature was deprecated in favor of using the __construct. During that phase, __construct had priority over the eponymous function, but the latter was still used in case of fallback, for backward compatibility.

In PHP 8, the eponymous method is now a normal method.

PHP code

<?php

class X {
    function X() {
        echo __METHOD__;
    }
}

var_dump(new X());

?>

Before

PHP Deprecated:  Methods with the same name as their class will not be constructors in a future version of PHP; X has a deprecated constructor

Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; X has a deprecated constructor
X::Xobject(X)#1 (0) {
}

After

object(X)#1 (0) {
}

PHP version change

This behavior was deprecated in 7.0.

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

Only First Byte

When assigning a string to a position inside another string, PHP reports a warning: indeed, only the first byte is used.

PHP code

<?php

$str = 'xy';  

// first letter is now a
$str[0] = 'a';

// second letter is now b, c is ignored
$str[1] = 'bc';

echo $str;

?>

Before

ab

After

PHP Warning:  Only the first byte will be assigned to the string offset 

Warning: Only the first byte will be assigned to the string offset 
ab

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

openlog() Rejects NUL Bytes In The Prefix

openlog() used to accept a syslog prefix containing a NUL byte and silently truncated it at the NUL byte. In PHP 8.6, a NUL byte in the argument throws a ValueError.

PHP code

<?php

try {
    var_dump(openlog("foo\0bar", LOG_PID, LOG_USER));
} catch (\ValueError $e) {
    echo $e->getMessage(), "\n";
}

?>

Before

bool(true)

After

openlog(): Argument #1 ($prefix) must not contain any null bytes

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

Optional Parameter Are After Compulsory Parameters

Optional parameters have a default value. When running the functioncall, PHP assigns the parameters by position. This way, the first parameter would get the value, even though it has the default value, and then, there will be a missing argument for the second one.

Since PHP 8.0, PHP reports that situation. It might be turned into an error in PHP 9.0

PHP code

<?php

function foo($a = 1, $b) {
    print $a $b\n;
}

foo(1, 2);

?>

Before

PHP Deprecated:  Required parameter $b follows optional parameter $a

Deprecated: Required parameter $b follows optional parameter $a
1 2

After

PHP Deprecated:  foo(): Optional parameter $a declared before required parameter $b is implicitly treated as a required parameter

Deprecated: foo(): Optional parameter $a declared before required parameter $b is implicitly treated as a required parameter
1 2

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

Providing A String That Is Not One Byte Long To ord() Is Deprecated

The ord() function returns the ordinal value of the first byte of a string. When the provided string is longer than one byte, only the first byte is used and the remaining bytes are silently discarded. This implicit behavior is a source of confusion, especially when working with multi-byte encodings such as UTF-8, where a single character may span several bytes. Since PHP 8.5, calling ord() with a string that is not exactly one byte long is deprecated: use the $str[0] syntax to extract the first byte explicitly before passing it to ord().

PHP code

<?php

$str = '我';

echo ord($str);

?>

Before

230

After

PHP Deprecated:  Providing a string that is not one byte long is deprecated. Use ord($str[0]) instead in /codes/ordNonSingleByteString.php on line 5

Deprecated: Providing a string that is not one byte long is deprecated. Use ord($str[0]) instead in /codes/ordNonSingleByteString.php on line 5
230

PHP version change

This behavior was deprecated in 8.5.

This behavior changed in 8.5.

Error Messages

Extension

Orphaned Parent

Calling the parent class of a class without parent is not possible. It used to be a deprecated error, where the code would keep on executing. In PHP 8.0, it stops the execution entirely.

PHP code

<?php

class X {
    function __construct() {
        parent::__construct();
    }
}

new X();

?>

Before

Deprecated: Cannot use "parent" when current class scope has no parent

After

PHP Fatal error:  Cannot use "parent" when current class scope has no parent

PHP version change

This behavior was deprecated in 7.4.

This behavior changed in 8.0.

Error Messages

Extension

Override Attribute Extended To Class Constants And Enum Cases

The #[\Override] attribute, introduced in PHP 8.3 for methods and extended to properties in PHP 8.5, is further extended in PHP 8.6 to class constants and enum cases (an enum case being internally a special kind of class constant). It tells the engine that the constant is expected to override a constant of the same name declared in a parent class or an implemented interface, so the engine can check that this is really the case.

If the constant marked #[\Override] has no matching parent constant – because the parent/interface declares no constant of that name, or because the class has no parent and implements no interface at all – PHP reports a compile-time error. Only public and protected constants of a parent class or implemented interface satisfy the attribute; private constants do not count.

PHP code

<?php

interface Shape {
    const SIDES = 0;
}

class Square implements Shape {
    #[\Override]
    const SIDES = 4; // Fine, overrides Shape::SIDES

    #[\Override]
    const COLOR = 'blue'; // Fatal error, no matching parent constant
}

?>

Before

PHP Fatal error:  Attribute "Override" cannot target class constant (allowed targets: method)

Fatal error: Attribute "Override" cannot target class constant (allowed targets: method)

After

PHP Fatal error:  Square::COLOR has #[\Override] attribute, but no matching parent constant exists

PHP version change

This behavior changed in 8.6.

Error Messages

Extension

Override Attribute Extended To Properties

The #[\Override] attribute, introduced in PHP 8.3, could originally only target methods. PHP 8.5 extends it so it can also be applied to properties, telling the engine that the property is expected to override a property of the same name declared in a parent class or an implemented interface, so the engine can check that this is really the case.

If the property marked #[\Override] has no matching parent property – because the parent has no property of that name, because the class has no parent at all, or because the property comes from a trait whose using class has no matching parent property – PHP reports a compile-time error.

PHP code

<?php

class X {
    public int $a = 1;
}

class Y extends X {
    #[\Override]
    public int $b = 2;
}

?>

Before

PHP Fatal error:  Attribute "Override" cannot target property (allowed targets: method)

After

PHP Fatal error:  Y::$b has #[\Override] attribute, but no matching parent property exists

PHP version change

This behavior changed in 8.5.

Error Messages

Extension

parent Cannot Be Used Anymore In Callable Arrays

PHP supports a callable syntax, based on array: it must be an array of two elements, where the index 0 is the object or the class, and the index 1 is the method name.

Until PHP 8.2, it was possible to use the keyword parent, to make the callable work with the parent class.

In the example, parent would be calling the static method replace, in A, or any other parent.

Since PHP 8.2, this is a deprecated feature, and it will be removed in PHP 9.

PHP code

<?php

class A {
    public static function replace($a) {
    	return 'a';
    }
}

class B extends A
{
    public static function work($it) {
		return preg_replace_callback('~\w+~', array('parent', 'parent::replace'), $it);
    }
}

echo b::work('abc');

?>

Before

PHP Fatal error:  Uncaught TypeError: preg_replace_callback(): Argument #2 ($callback) must be a valid callback, cannot access "parent" when current class scope has no parent 

Fatal error: Uncaught TypeError: preg_replace_callback(): Argument #2 ($callback) must be a valid callback, cannot access parent when current class scope has no parent 

After

PHP Deprecated:  Use of "parent" in callables is deprecated 

Deprecated: Use of parent in callables is deprecated 
PHP Fatal error:  Uncaught TypeError: preg_replace_callback(): Argument #2 ($callback) must be a valid callback, cannot access parent when current class scope has no parent 

Fatal error: Uncaught TypeError: preg_replace_callback(): Argument #2 ($callback) must be a valid callback, cannot access parent when current class scope has no parent 

PHP version change

This behavior was deprecated in 8.2.

This behavior changed in 9.0.

Error Messages

Extension

Analyzer

parse_str() Rejects NUL Bytes In The Query String

parse_str() used to accept a query string containing a NUL byte and silently parsed only the part before it. In PHP 8.6, a NUL byte in the $string argument throws a ValueError.

PHP code

<?php

try {
    parse_str("foo\0bar=1", $result);
    var_dump($result);
} catch (\ValueError $e) {
    echo $e->getMessage(), "\n";
}

?>

Before

array(1) {
  [foo]=>
  string(0) 
}

After

parse_str(): Argument #1 ($string) must not contain any null bytes

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

Passing Objects Is Deprecated

Several array functions, such as current, next, prev, reset used to accept both objects and arrays. Since PHP 8.0, they only work on arrays.

each is also part of this modernization, although it was entirely removed in PHP 8.0.

PHP code

<?php

$x = (object) ['a' => 1];

var_dump(current($x));

?>

Before

int(1)

After

PHP Deprecated:  current(): Calling current() on an object is deprecated

Deprecated: current(): Calling current() on an object is deprecated
int(1)

PHP version change

This behavior changed in 8.1.

Error Messages

Extension

pathinfo() Validates Its Flags Argument

pathinfo() accepts a second argument made of PATHINFO_* constants. Until PHP 8.6, any other value was silently accepted, and the whole set of parts was returned. In PHP 8.6, an invalid value throws a ValueError.

PHP code

<?php

var_dump(pathinfo('/foo/bar.txt', 999));

?>

Before

string(4) /foo

After

pathinfo(): Argument #2 ($flags) must be one of the PATHINFO_* constants

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

Calling pg_fetch_result() With 2 Arguments Is Deprecated

pg_fetch_result() accepted two call shapes: a 3-argument form giving the row and field explicitly, and a 2-argument form that omitted the row and implicitly read from an internal, auto-advancing row cursor instead. Mixing an explicit-row API with a stateful, cursor-based one in the same overloaded function made the behavior harder to predict. Until PHP 8.4, calling the 2-argument form was completely silent. In PHP 8.4, it emits a deprecation notice, in favor of always passing 3 arguments and using null for $row when the old auto-advancing behavior is wanted; the underlying validation of the other arguments (here, that $result is a real PgSql\Result) is unaffected.

PHP code

<?php

try {
    var_dump(pg_fetch_result('not-a-resource', 'field'));
} catch (\Throwable $e) {
    echo get_class($e), ': ', $e->getMessage(), "\n";
}

Before

TypeError: pg_fetch_result(): Argument #1 ($result) must be of type PgSql\Result, string given

After

PHP Deprecated:  Calling pg_fetch_result() with 2 arguments is deprecated, use the 3-parameter signature with a null $row parameter instead in /codes/pgFetchResultTwoArgsDeprecated.php on line 4

Deprecated: Calling pg_fetch_result() with 2 arguments is deprecated, use the 3-parameter signature with a null $row parameter instead in /codes/pgFetchResultTwoArgsDeprecated.php on line 4
TypeError: pg_fetch_result(): Argument #1 ($result) must be of type PgSql\Result, string given

PHP version change

This behavior was deprecated in 8.4.

This behavior changed in .

See Also

Error Messages

Extension

$php_errormsg has been removed

$php_errormsg used to hold the message of the last error that PHP emitted. This is a feature handled by the error_get_last() function.

$php_errormsg was only set if the tracks_error directive was activated (by default, it was not).

PHP code

<?php

ini_set('track_errors', 1);

echo $a;

echo $php_errormsg;

?>

Before

PHP Notice:  Undefined variable: a 

Notice: Undefined variable: a 
Undefined variable: a

After

PHP Warning:  Undefined variable $a 

Warning: Undefined variable $a 
PHP Warning:  Undefined variable $php_errormsg 

Warning: Undefined variable $php_errormsg 

PHP version change

This behavior was deprecated in 7.2.

This behavior changed in 8.0.

See Also

Error Messages

Extension

Plus And Concat Precedence

+ and -, and the dot operators used to have the same priority. Thus, they used to be processed one after the other, from left to right.

In PHP 8.0, the addition has now the highest precedence, and will happen before the concatenation.

PHP code

<?php

echo 35 + 7 . '.' . 0 + 5;

?>

Before

42.5

After

47

PHP version change

This behavior was deprecated in 7.4.

This behavior changed in 8.0.

Extension

Analyzer

preg_grep() Returns false On A PCRE Execution Error

preg_grep() filters an array using a regular expression. Until PHP 8.6, when the underlying PCRE engine failed on one of the array’s entries (for example malformed UTF-8 input combined with the /u modifier), that entry was silently skipped and a partial array was returned for the rest. In PHP 8.6, preg_grep() returns false as soon as a PCRE execution error occurs, matching the behavior of the other preg_* functions.

PHP code

<?php

$arr = ["\xC3\x28", 'valid'];
var_dump(preg_grep('/./u', $arr));

?>

Before

array(0) {
}

After

bool(false)

PHP version change

This behavior changed in 8.6.

See Also

Extension

printf() Warns About Unknown Formats

printf(), and its related functions, reports unknown format specifiers. The format specifiers are letters that format the data, passed in later arguments.

Until PHP 8.0, printf() would check if there were enough arguments for the format. Otherwise, unknown formats were ignored, and the related argument was omitted silently.

PHP code

<?php

print sprintf("%s %Z", 1, 3);
// after  PHP 8.0:  Unknown format specifier Z
// before PHP 8.0:  1

?>

Before

 

After

PHP Fatal error:  Uncaught ValueError: Unknown format specifier "Z"

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

proc_open() Rejects NUL Bytes In The Working Directory

proc_open() used to accept a $cwd argument containing a NUL byte and passed the truncated path straight to the operating system, which then failed to spawn the process. In PHP 8.6, a NUL byte in $cwd throws a ValueError before attempting to start the process.

PHP code

<?php

try {
    var_dump(proc_open('echo hi', [], $pipes, "foo\0bar"));
} catch (\ValueError $e) {
    echo $e->getMessage(), "\n";
}

?>

Before

PHP Warning:  proc_open(): posix_spawn() failed: No such file or directory in /codes/procOpenCwdNullByteValueError.php on line 4

Warning: proc_open(): posix_spawn() failed: No such file or directory in /codes/procOpenCwdNullByteValueError.php on line 4
bool(false)

After

proc_open(): Argument #4 ($cwd) must not contain any null bytes

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

putenv() Rejects NUL Bytes In The Assignment

putenv() used to accept an assignment string containing a NUL byte and silently truncated it at the NUL byte, setting an environment variable from the part before it. In PHP 8.6, a NUL byte in the argument throws a ValueError.

PHP code

<?php

try {
    var_dump(putenv("FOO\0BAR=1"));
} catch (\ValueError $e) {
    echo $e->getMessage(), "\n";
}

?>

Before

bool(true)

After

putenv(): Argument #1 ($assignment) must not contain any null bytes

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

range() Uses Single Byte Strings

When the first argument of range() is a single byte string, then the second argument must also be a single byte string, to keep the range consistent. Until PHP 8.3, the first string was converted to a integer too, most often 0, and then, the range was created.

PHP code

<?php

print_r(range('c', 3));

?>

Before

Array
(
    [0] => 0
    [1] => 1
    [2] => 2
    [3] => 3
)

After

PHP Warning:  range(): Argument #2 ($end) must be a single byte string if argument #1 ($start) is a single byte string, argument #1 ($start) converted to 0

Warning: range(): Argument #2 ($end) must be a single byte string if argument #1 ($start) is a single byte string, argument #1 ($start) converted to 0
Array
(
    [0] => 0
    [1] => 1
    [2] => 2
    [3] => 3
)

PHP version change

This behavior changed in 8.3.

See Also

Error Messages

Extension

range() Lists Everything Between Strings

range() used to cast the arguments to integers. In PHP 8.3, strings are used as is, and range() returns the list of chars between the ASCII codes of those strings.

PHP code

<?php

print_r(range('0', 'A')); 

?>

Before

Array
(
    [0] => 0
)

After

Array
(
    [0] => 0
    [1] => 1
    [2] => 2
    [3] => 3
    [4] => 4
    [5] => 5
    [6] => 6
    [7] => 7
    [8] => 8
    [9] => 9
    [10] => :
    [11] => ;
    [12] => <
    [13] => =
    [14] => >
    [15] => ?
    [16] => @
    [17] => A
)

PHP version change

This behavior changed in 8.3.

Extension

range() With Int And String

range() now emits a warning when one of the arguments is a string, and the other is an integer. It still behaves like before, and cast the string to an integer.

PHP code

<?php

print_r(range(1, 'z')); 

?>

Before

Array
(
    [0] => 1
    [1] => 0
)

After

PHP Warning:  range(): Argument #1 ($start) must be a single byte string if argument #2 ($end) is a single byte string, argument #2 ($end) converted to 0 

Warning: range(): Argument #1 ($start) must be a single byte string if argument #2 ($end) is a single byte string, argument #2 ($end) converted to 0 
Array
(
    [0] => 1
    [1] => 0
)

PHP version change

This behavior changed in 8.3.

Error Messages

Extension

Calling A Function Named readonly() Is Deprecated

readonly has been a semi-reserved word since PHP 8.1, when it became the modifier for readonly properties and classes. It could still be used as the name of a function, class, or constant, and PHP even special-cased a global function literally called readonly() so that WordPress, which ships one, kept working. Until PHP 8.6, declaring and calling such a function produced no warning at all. In PHP 8.6, calling a function named readonly() emits a deprecation notice on every call, in preparation for the word becoming fully reserved.

PHP code

<?php

function readonly() {
    return 'not a property modifier';
}

echo readonly(), "\n";

?>

Before

not a property modifier

After

PHP Deprecated:  Calling a function “readonly” is deprecated in /codes/readonlyFunctionCallDeprecated.php on line 3

Deprecated: Calling a function “readonly” is deprecated in /codes/readonlyFunctionCallDeprecated.php on line 3
not a property modifier

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

Readonly Properties Can Have Default Values

A readonly property could not declare a default value. Until PHP 8.6, doing so was a fatal compile-time error, Readonly property Class::$prop cannot have default value. In PHP 8.6, a readonly property may declare a default value, which is used whenever the constructor does not explicitly initialize it. Assigning to the property once it already holds its default value, even from inside the constructor, still throws the usual Cannot modify readonly property error.

PHP code

<?php

class Point {
    public readonly int $x = 0;
    public function __construct(?int $x = null) {
        if ($x !== null) {
            $this->x = $x;
        }
    }
}

$p = new Point();
var_dump($p->x);

try {
    $p2 = new Point(5);
    var_dump($p2->x);
} catch (\Error $e) {
    echo "Error: ".$e->getMessage()."\n";
}

?>

Before

PHP Fatal error:  Readonly property Point::$x cannot have default value in /codes/readonlyPropertyDefaultValue86.php on line 4

After

int(0)
Error: Cannot modify readonly property Point::$x

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

(real) Is Replaced By (float)

(real) is replaced by (float) in PHP 8. It used to be a synonym of (float), and there is only one left.

PHP code

<?php

print (real) 1;


?>

Before

PHP Deprecated:  The (real) cast is deprecated, use (float) instead -D

Deprecated: The (real) cast is deprecated, use (float) instead -D
1

After

PHP Parse error:  The (real) cast has been removed, use (float) instead -D

Parse error: The (real) cast has been removed, use (float) instead -D

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Recursive Comparison Of Arrays

Recursive arrays should not be compared one another, as the engine might ends in an infinite loop. This happens when the array contains a reference to itself in one of its own elements. PHP did not caught such situation, and keep looping in the structure, even though it is infinite.

In PHP 8.4, it is now a catchable Error, and eventually stops.

PHP code

<?php

$array = [1,2,3, &$array];
$array2 = [1,2,3, &$array2];

var_dump($array == $array2);

?>

Before

PHP Fatal error:  Nesting level too deep - recursive dependency?

Fatal error: Nesting level too deep - recursive dependency?

After

PHP Fatal error:  Uncaught Error: Nesting level too deep - recursive dependency? 

Fatal error: Uncaught Error: Nesting level too deep - recursive dependency? 

PHP version change

This behavior changed in 8.4.

Error Messages

Extension

Reflection Doesn’t Return Self

Reflection used to return the original self type, including its case. Since PHP 8.5, it is now returning the actual name of the class, instead of the relative type.

PHP code

<?php

// code from https://www.reddit.com/r/PHP/comments/1rd3j74/php_85_reflectionnamedtypegetname_change/
class Foo
{
    function poop (self $a): self
    {
    }
}

$refMethod = new ReflectionMethod('Foo', 'poop');
$refParam = $refMethod->getParameters()[0];

print_r(array(
    'paramType' => $refParam->getType()->getName(),
    'returnType' => $refMethod->getReturnType()->getName(),
));

?>

Before

Array
(
    [paramType] => self
    [returnType] => self
)

After

Array
(
    [paramType] => Foo
    [returnType] => Foo
)

PHP version change

This behavior changed in 8.5.

See Also

Extension

Reflection Doesn’t Return Static

Reflection used to return the original static type, including its case. Since PHP 8.5, it is now returning the actual name of the class, instead of the relative type.

PHP code

<?php

// code from https://www.reddit.com/r/PHP/comments/1rd3j74/php_85_reflectionnamedtypegetname_change/
class Foo
{
    function poop (static $a): static
    {
    }
}

$refMethod = new ReflectionMethod('Foo', 'poop');
$refParam = $refMethod->getParameters()[0];

print_r(array(
    'paramType' => $refParam->getType()->getName(),
    'returnType' => $refMethod->getReturnType()->getName(),
));

?>

Before

Array
(
    [paramType] => static
    [returnType] => static
)

After

Array
(
    [paramType] => Foo
    [returnType] => Foo
)

PHP version change

This behavior changed in 8.5.

See Also

Extension

ReflectionMethod::invoke() Deprecates Passing An Object For A Static Method

ReflectionMethod::invoke() and ReflectionMethod::invokeArgs() take an object as the first argument, used as $this for instance methods. Until PHP 8.6, passing an object for a static method was silently accepted and the object was simply ignored. In PHP 8.6, passing a non-null object for a static method emits a deprecation notice, even though the call still succeeds.

PHP code

<?php

class C {
    public static function staticMethod() {
        return "called";
    }
}

$rm = new ReflectionMethod(C::class, 'staticMethod');
var_dump($rm->invoke(new C()));

?>

Before

string(6) "called" 

After

PHP Deprecated:  Calling ReflectionMethod::invoke() for static method C::staticMethod() does not need an object parameter in /codes/reflectionInvokeStaticObject.php on line 10

Deprecated: Calling ReflectionMethod::invoke() for static method C::staticMethod() does not need an object parameter in /codes/reflectionInvokeStaticObject.php on line 10
string(6) "called" 

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

ReflectionMethod Constructor With A Single Argument Is Deprecated

ReflectionMethod::__construct() accepted two call shapes: two arguments, new ReflectionMethod($classOrObject, $methodName), or a single string combining both as "Class::method". PHP 8.3 added ReflectionMethod::createFromMethodName() as a dedicated factory method for that single-string form. Until PHP 8.4, calling the constructor with just the combined string was completely silent. In PHP 8.4, it emits a deprecation notice in favor of the new factory method; the object it constructs, and the rest of the Reflection API, are unaffected.

PHP code

<?php

$m = new ReflectionMethod('DateTime::format');
echo $m->getName(), "\n";

Before

format

After

PHP Deprecated:  Calling ReflectionMethod::__construct() with 1 argument is deprecated, use ReflectionMethod::createFromMethodName() instead in /codes/reflectionMethodConstructorOneArgDeprecated.php on line 3

Deprecated: Calling ReflectionMethod::__construct() with 1 argument is deprecated, use ReflectionMethod::createFromMethodName() instead in /codes/reflectionMethodConstructorOneArgDeprecated.php on line 3
format

PHP version change

This behavior was deprecated in 8.4.

This behavior changed in .

See Also

Error Messages

Extension

ReflectionProperty::setValue() Deprecates A Wrong Object Type

ReflectionProperty::setValue() (and ReflectionProperty::setRawValue()) accept an object as the first argument to set the property on. Until PHP 8.6, passing an object that is not an instance of the class the property was declared in was silently accepted, and the property was simply created on that unrelated object. In PHP 8.6, doing so emits a deprecation notice.

PHP code

<?php

class A {
    public int $prop = 1;
}

class B {
    public int $prop = 0;
}

$rp = new ReflectionProperty(A::class, 'prop');
$rp->setValue(new B(), 42);
print "done\n";

?>

Before

done

After

PHP Deprecated:  Calling ReflectionProperty::setValue() with a given object that is not an instance of the class this property was declared in is deprecated in /codes/reflectionSetValueWrongObject.php on line 12

Deprecated: Calling ReflectionProperty::setValue() with a given object that is not an instance of the class this property was declared in is deprecated in /codes/reflectionSetValueWrongObject.php on line 12
done

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

Relative Callable In Strings

PHP has a syntax to designate a method, with its class and method name as a string. That syntax used to support relative class names, such as self, parent and static. That allowed the definition of callable that would be relative to their point of execution, and not their point of definition. This is a gone feature in PHP 8.2.

PHP code

<?php

class x {
    function a() {
        print __METHOD__;
    }
    
    function b() {
        call_user_func('self::a');
    }
}

(new x)->b();

?>

Before

x::a

After

PHP Deprecated:  Use of "self" in callables is deprecated

Deprecated: Use of "self" in callables is deprecated
x::a

PHP version change

This behavior was deprecated in 8.2.

This behavior changed in 9.0.

See Also

Error Messages

Extension

Analyzer

Relaxed Naming With Class Constant

Relaxed naming is the possibility to use PHP keywords as method or class constant names (for properties, the $ has allowed it for a long time).

private, protected and public were not valid class constant names, until PHP 8.3. They were eligible to be method names, though.

PHP code

<?php

class x {
    public const string private = 'protected';
}

echo x::private;

?>

Before

PHP Parse error:  syntax error, unexpected token "private", expecting "=" 

Parse error: syntax error, unexpected token "private", expecting "=" 

After

protected

PHP version change

This behavior changed in 8.3.

Error Messages

Extension

Return Reference On Void

There are methods that return void; and methods that return a reference. Until PHP 8.1, they could be the same, although a Notice was emitted. This is now deprecated behavior in PHP 8.1, and shall disappear in PHP 9.

PHP code

<?php

function &foo() : void {
	echo __METHOD__;
	
	return;
}

foo();

?>

Before

fooPHP Notice:  Only variable references should be returned by reference 

Notice: Only variable references should be returned by reference 

After

PHP Deprecated:  Returning by reference from a void function is deprecated 

Deprecated: Returning by reference from a void function is deprecated 
fooPHP Notice:  Only variable references should be returned by reference 

Notice: Only variable references should be returned by reference 

PHP version change

This behavior was deprecated in 8.1.

This behavior changed in 9.0.

Error Messages

Extension

Analyzer

Returning From A finally Block Is Deprecated

A return statement inside a finally block always wins: it replaces whatever the try/catch block was about to return, and it silently swallows any exception that was propagating out of the try. Until PHP 8.6, this was fully silent, and a pending exception could disappear without a trace. In PHP 8.6, returning from a finally block emits a deprecation notice, while the runtime behavior itself (the return value winning, the exception being discarded) is unchanged.

PHP code

<?php

function getValue(): string {
    try {
        throw new Exception('boom');
    } finally {
        return 'fallback';
    }
}

echo getValue(), "\n";

?>

Before

fallback

After

PHP Deprecated:  Returning from a finally block is deprecated in /codes/returnInFinallyDeprecated.php on line 7

Deprecated: Returning from a finally block is deprecated in /codes/returnInFinallyDeprecated.php on line 7
fallback

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

PHP native return types are now enforced

PHP provides native interfaces: they include methods and their type. Until PHP 8.1, such types were not enforced, for backward compatibility reasons. Nowadays, these types are enforced, just like any other interface. It makes PHP native interfaces on the same footing as custom interfaces.

In case this migration is too ambitious, it is possible to use the #[\ReturnTypeWillChange] to suppress the warning.

PHP code

<?php

class x implements Iterator {

function __construct() {
	print __METHOD__;
}

public function current()  {}
public function key(): mixed {}
public function next(): void {}
public function rewind(): void {}
public function valid(): bool {}
} 

new x; 

?>

Before

x::__construct

After

PHP Deprecated:  Return type of x::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice 

Deprecated: Return type of x::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice 
x::__construct

PHP version change

This behavior changed in 8.1.

Error Messages

Extension

round() Mode Validation

The round() function has four modes, defined with four constants. When the third argument is not one of those four constants, PHP used to silently use PHP_ROUND_HALF_UP as default value. In PHP 8.4, a ValueError is thrown.

PHP code

<?php

print $a = round(1.2, 2, 333);

?>

Before

1

After

round(): Argument #3 ($mode) must be a valid rounding mode (PHP_ROUND_*)

PHP version change

This behavior changed in 8.4.

See Also

Error Messages

Extension

scandir() Validates Its Sorting Order Argument

scandir() accepts a second argument that selects the sort order of the returned entries: SCANDIR_SORT_ASCENDING, SCANDIR_SORT_DESCENDING, or SCANDIR_SORT_NONE. Until PHP 8.6, any other value was silently accepted and treated as descending order. In PHP 8.6, an invalid sorting order throws a ValueError.

PHP code

<?php

$dir = sys_get_temp_dir() . '/scandir_86_test';
@mkdir($dir);
touch($dir . '/a.txt');
touch($dir . '/b.txt');

try {
    var_dump(scandir($dir, 99));
} catch (\ValueError $e) {
    echo $e->getMessage(), "\n";
}

?>

Before

array(4) {
  [0]=>
  string(5) b.txt
  [1]=>
  string(5) a.txt
  [2]=>
  string(2) ..
  [3]=>
  string(1) .
}

After

scandir(): Argument #2 ($sorting_order) must be one of the SCANDIR_SORT_ASCENDING, SCANDIR_SORT_DESCENDING, or SCANDIR_SORT_NONE constants

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

self Cannot Be Used Anymore In Callable Arrays

PHP supports a callable syntax, based on array: it must be an array of two elements, where the index 0 is the object or the class, and the index 1 is the method name.

Until PHP 8.2, it was possible to use the keyword self, to make the callable dependent on the context of usage of the callable.

In the example, self would be calling the static method replace, in A.

Since PHP 8.2, this is a deprecated feature, and it will be removed in PHP 9.

PHP code

<?php

class B
{
    public static function work($it) {
		return preg_replace_callback('~\w+~', array('self', 'self::replace'), $it);
    }

    public static function replace($a) {
    	return 'a';
    }
}

echo b::work('abc');

?>

Before

a

After

PHP Deprecated:  Use of "self" in callables is deprecated 

Deprecated: Use of "self" in callables is deprecated 
PHP Deprecated:  Callables of the form ["B", "self::replace"] are deprecated 

Deprecated: Callables of the form ["B", "self::replace"] are deprecated 
a

PHP version change

This behavior was deprecated in 8.2.

This behavior changed in 9.0.

Error Messages

Extension

Analyzer

Incomplete SessionHandlerInterface Implementations Now Warn

SessionHandlerInterface does not itself declare create_sid() or validateId(), yet the session extension has long called them on a custom handler when present. Until PHP 8.6, a class implementing SessionHandlerInterface without either method was silently accepted. In PHP 8.6, registering such a class with session_set_save_handler() emits a warning for each missing method, stating that it will be required starting in PHP 9.0.

PHP code

<?php

class MyHandler implements \SessionHandlerInterface {
    public function open($path, $name): bool { return true; }
    public function close(): bool { return true; }
    public function read($id): string { return ''; }
    public function write($id, $data): bool { return true; }
    public function destroy($id): bool { return true; }
    public function gc($max_lifetime): int|false { return 0; }
}

var_dump(session_set_save_handler(new MyHandler()));

?>

Before

bool(true)

After

PHP Warning:  Class MyHandler implementing SessionHandlerInterface is missing the create_sid() method which will be required in PHP 9.0 in /codes/sessionHandlerIncompleteMethodsWarning86.php on line 3

Warning: Class MyHandler implementing SessionHandlerInterface is missing the create_sid() method which will be required in PHP 9.0 in /codes/sessionHandlerIncompleteMethodsWarning86.php on line 3
PHP Warning:  Class MyHandler implementing SessionHandlerInterface is missing the validateId() method which will be required in PHP 9.0 in /codes/sessionHandlerIncompleteMethodsWarning86.php on line 3

Warning: Class MyHandler implementing SessionHandlerInterface is missing the validateId() method which will be required in PHP 9.0 in /codes/sessionHandlerIncompleteMethodsWarning86.php on line 3
PHP Warning:  session_set_save_handler(): Session save handler cannot be changed after headers have already been sent (sent from /codes/sessionHandlerIncompleteMethodsWarning86.php on line 3) in /codes/sessionHandlerIncompleteMethodsWarning86.php on line 12

Warning: session_set_save_handler(): Session save handler cannot be changed after headers have already been sent (sent from /codes/sessionHandlerIncompleteMethodsWarning86.php on line 3) in /codes/sessionHandlerIncompleteMethodsWarning86.php on line 12
bool(false)

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

Session Functions Reject NUL Bytes And session_encode() Changes Its Empty-Session Return Value

PHP hardens several session functions against embedded NUL bytes and tightens session_encode()’s return value. Until PHP 8.6, session_module_name() silently truncated a $name argument containing a NUL byte at the first null character and emitted only a generic "module not found" warning; in PHP 8.6, it throws a ValueError instead. Separately, calling session_encode() on a session with no data in $_SESSION used to return false, the same value returned on an actual encoding failure; in PHP 8.6, it returns an empty string "" for an empty session, reserving false for genuine encoding failures. The same NUL-byte hardening theme also applies to the session.cookie_path, session.cookie_domain and session.cache_limiter INI settings, which now emit a warning when a NUL byte is embedded in their value.

PHP code

<?php

$moduleName = shell_exec(PHP_BINARY . ' -n -r ' . escapeshellarg('var_dump(session_module_name("foo" . chr(0) . "bar"));'));
echo $moduleName;

$dir = sys_get_temp_dir() . '/sessionNulByteAndEncodeChanges_' . getmypid();
mkdir($dir);
$code = 'session_save_path(' . var_export($dir, true) . '); session_start(); var_dump(session_encode()); session_destroy();';
$encoded = shell_exec(PHP_BINARY . ' -n -r ' . escapeshellarg($code));
echo $encoded;
array_map('unlink', glob($dir . '/*'));
rmdir($dir);

?>

Before


Warning: session_module_name(): Session handler module "foo" cannot be found in Command line code on line 1
bool(false)
bool(false)

After


Fatal error: Uncaught ValueError: session_module_name(): Argument #1 ($module) must not contain any null bytes in Command line code:1
Stack trace:
#0 Command line code(1): session_module_name('foo\x00bar')
#1 {main}
  thrown in Command line code on line 1
string(0) "" 

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

Session Cookies Default To Secure Settings

The built-in defaults of three session INI settings changed to provide secure behavior out of the box: session.use_strict_mode is now 1 (was 0), session.cookie_httponly is now 1 (was 0), and session.cookie_samesite is now Lax (was unset). Applications that relied on the old permissive defaults, such as accepting externally supplied session IDs, reading the session cookie from JavaScript, or sending it on cross-site POST requests, must now set these directives explicitly.

PHP code

<?php

$out = shell_exec(PHP_BINARY . ' -n -r ' . escapeshellarg('var_dump(ini_get("session.use_strict_mode"), ini_get("session.cookie_httponly"), ini_get("session.cookie_samesite"));'));
echo $out;

?>

Before

string(1) 0
string(1) 0
string(0) 

After

string(1) 1
string(1) 1
string(3) Lax

PHP version change

This behavior changed in 8.6.

See Also

Extension

__set_state() Method Must Be Static

The magic method __set_state() must be static when declared in a class, starting with PHP 8.0. It used to be an optional keyword.

PHP code

<?php

class X {
    function __set_state() {}
}

?>

Before


After

PHP Fatal error:  Method x::__set_state() must be static

Fatal error: Method x::__set_state() must be static

PHP version change

This behavior changed in 8.0.

See Also

Error Messages

Extension

set_exception_handler() Must Update Its Type To Throwable

All thrown issues were children of the Exception class. In PHP 7.0, all issues are children of Throwable. Exception is not only one of two classes implementing it, along with Error.

To keep compatibility, it is important to switch types.

PHP code

<?php

// PHP 5.6- typed with Exception
class foo { 
    static function bar(\Exception $e) {
        print $e->getMessage();
    } 
}

set_exception_handler([Foo::class, 'bar']);

// Produces an error
1 / 0;

?>

Before

PHP Warning:  Division by zero 

Warning: Division by zero 

After

PHP Fatal error:  Uncaught TypeError: foo::bar(): Argument #1 ($e) must be of type Exception, DivisionByZeroError given 

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

setlocale() Does Not Affect Echo Anymore

setlocale() used to impact several functions, including echo. With the French or German (or other) conventions, the decimal separator is a comma, and PHP makes the conversion at echo time.

This is not the case anymore in PHP 8.0: anytime the float is converted to a string, the locale formatting is not applied anymore.

It is recommended to make this conversion explicit by using printf(), number_format() or a custom formatter function.

PHP code

<?php

setlocale(LC_ALL, 'fr_FR.UTF-8');

echo 1003.14;

?>

Before

1003,14

After

1003.14

PHP version change

This behavior changed in 8.0.

Extension

setlocale() Rejects Extra Arguments When $locales Is An Array

setlocale() accepts either a list of individual locale name arguments, or a single array of candidate locale names as its second parameter. Until PHP 8.6, passing an array as $locales while also passing further variadic locale arguments was silently accepted, and the extra arguments were ignored. In PHP 8.6, passing any additional locale argument alongside an array $locales throws a TypeError.

PHP code

<?php

try {
    var_dump(setlocale(LC_ALL, ['en_US'], 'fr_FR'));
} catch (\TypeError $e) {
    echo "TypeError: ".$e->getMessage()."\n";
}

?>

Before

string(5) "en_US" 

After

TypeError: setlocale() expects exactly 2 arguments when argument #2 ($locales) is an array, 3 given

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

sleep() Validates Its Seconds Argument Range

sleep()’s $seconds argument is passed to the operating system as an unsigned integer. Until PHP 8.6, a value greater than UINT_MAX (4294967295) silently overflowed, which could make the function sleep for a much shorter time than requested, or not at all. In PHP 8.6, a value greater than UINT_MAX throws a ValueError instead.

PHP code

<?php

try {
    var_dump(sleep(4294967296));
} catch (\ValueError $e) {
    echo "ValueError: ".$e->getMessage()."\n";
}

?>

Before

int(0)

After

ValueError: sleep(): Argument #1 ($seconds) must be between 0 and 4294967295

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

sodium_crypto_pwhash_str() Throws ValueError For Out-Of-Range Limits

The password-hashing functions sodium_crypto_pwhash(), sodium_crypto_pwhash_str(), sodium_crypto_pwhash_scryptsalsa208sha256() and sodium_crypto_pwhash_scryptsalsa208sha256_str() validate their $opslimit and $memlimit arguments against libsodium’s documented minimums. Until PHP 8.6, an out-of-range value threw a SodiumException. In PHP 8.6, it throws a ValueError instead, which better reflects that the problem is an invalid argument rather than an internal libsodium failure. SodiumException is still thrown for genuine libsodium failures.

PHP code

<?php

try {
    $hash = sodium_crypto_pwhash_str('password', 1, 1);
    var_dump($hash);
} catch (\ValueError $e) {
    echo "ValueError: ".$e->getMessage()."\n";
} catch (\SodiumException $e) {
    echo "SodiumException: ".$e->getMessage()."\n";
}

?>

Before

SodiumException: sodium_crypto_pwhash_str(): Argument #3 ($memlimit) must be greater than or equal to 8192

After

ValueError: sodium_crypto_pwhash_str(): Argument #3 ($memlimit) must be greater than or equal to 8192

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

sort() Places Integers Before Strings

sort() used the PHP 7 way to compare values: then, strings would rank below integers, in particular below 0.

In PHP 8, strings are now ranking above integers, and are moved to the end of the sorted array. This is related to the change of rules in comparisons.

PHP code

<?php

$x = array('a',
           0,
           1,
           '0',
);
sort($x);
print_r($x);
?>

Before

Array
(
    [0] => a
    [1] => 0
    [2] => 0
    [3] => 1
)

After

Array
(
    [0] => 0
    [1] => 0
    [2] => 1
    [3] => a
)

PHP version change

This behavior changed in 8.0.

Extension

Sorting Closure Must Return Integers

Comparison closures used in custom sorting need to return an integer, while they used to yield true or false. This applies to all custom sorting functions, including usort(), uasort(), and uksort().

There is no performance penalty nor gain with the usage of that returntype.

PHP code

<?php

$array = [1, 2, 3];

// Replace
usort($array, fn($a, $b) : bool => $a > $b);
// With
usort($array, fn($a, $b) : int => $a <=> $b);

print_r($array);
?>

Before

Array
(
    [0] => 1
    [1] => 2
    [2] => 3
)

After

PHP Deprecated:  usort(): Returning bool from comparison function is deprecated, return an integer less than, equal to, or greater than zero

Deprecated: usort(): Returning bool from comparison function is deprecated, return an integer less than, equal to, or greater than zero
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
)

PHP version change

This behavior was deprecated in 8.0.

This behavior changed in 9.0.

Error Messages

Extension

Analyzer

Spaces In Namespaces

It used to be valid syntax to have a new line or a space in a namespace name. This is not the case in PHP 8.0 anymore.

PHP code

<?php

namespace Vendor
\Package;

echo 1;

?>

Before

1

After

PHP Parse error:  syntax error, unexpected fully qualified name "\Package", expecting "{" 

Parse error: syntax error, unexpected fully qualified name "\Package", expecting "{" 

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

spaceship Operator Results

With the change of comparison between integers and strings, the spaceship was also impacted. Some spaceship comparisons did change, and are not returning the same results than before.

PHP code

<?php

var_dump( 0 <=> 'foo');
var_dump( 0 <=> '');

?>

Before

int(0)
int(0)

After

int(-1)
int(1)

PHP version change

This behavior changed in 8.0.

Extension

spl_classes() Is Deprecated

spl_classes() returns an array naming the classes and interfaces provided by the SPL extension. In PHP 8.6, calling it emits a deprecation notice recommending ReflectionExtension::getClassNames() instead; the returned array is unchanged.

PHP code

<?php

$classes = spl_classes();
var_dump(count($classes));

?>

Before

int(55)

After

PHP Deprecated:  Function spl_classes() is deprecated since 8.6, use ReflectionExtension::getClassNames() instead in /codes/splClassesDeprecated86.php on line 3

Deprecated: Function spl_classes() is deprecated since 8.6, use ReflectionExtension::getClassNames() instead in /codes/splClassesDeprecated86.php on line 3
int(55)

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

SplFileObject::fgetcsv() And fputcsv() Are Deprecated

SplFileObject::fgetcsv() and SplFileObject::fputcsv() read and write one CSV record on the wrapped file. In PHP 8.6, calling either method emits a deprecation notice recommending the plain fgetcsv()/fputcsv() functions on the underlying stream instead; both methods keep their previous behavior otherwise.

PHP code

<?php

file_put_contents(sys_get_temp_dir().'/splFileObjectCsvMethodsDeprecated86.csv', "a,b,c\n");

$file = new SplFileObject(sys_get_temp_dir().'/splFileObjectCsvMethodsDeprecated86.csv');
var_dump($file->fgetcsv());

$out = new SplFileObject(sys_get_temp_dir().'/splFileObjectCsvMethodsDeprecated86.out.csv', 'w');
var_dump($out->fputcsv(['x', 'y']));

unlink(sys_get_temp_dir().'/splFileObjectCsvMethodsDeprecated86.csv');
unlink(sys_get_temp_dir().'/splFileObjectCsvMethodsDeprecated86.out.csv');

?>

Before

PHP Deprecated:  SplFileObject::fgetcsv(): the $escape parameter must be provided, as its default value will change, either explicitly or via SplFileObject::setCsvControl() in /codes/splFileObjectCsvMethodsDeprecated86.php on line 6

Deprecated: SplFileObject::fgetcsv(): the $escape parameter must be provided, as its default value will change, either explicitly or via SplFileObject::setCsvControl() in /codes/splFileObjectCsvMethodsDeprecated86.php on line 6
array(3) {
  [0]=>
  string(1) "a" 
  [1]=>
  string(1) "b" 
  [2]=>
  string(1) "c" 
}
PHP Deprecated:  SplFileObject::fputcsv(): the $escape parameter must be provided, as its default value will change, either explicitly or via SplFileObject::setCsvControl() in /codes/splFileObjectCsvMethodsDeprecated86.php on line 9

Deprecated: SplFileObject::fputcsv(): the $escape parameter must be provided, as its default value will change, either explicitly or via SplFileObject::setCsvControl() in /codes/splFileObjectCsvMethodsDeprecated86.php on line 9
int(4)

After

PHP Deprecated:  Method SplFileObject::fgetcsv() is deprecated since 8.6 in /codes/splFileObjectCsvMethodsDeprecated86.php on line 6

Deprecated: Method SplFileObject::fgetcsv() is deprecated since 8.6 in /codes/splFileObjectCsvMethodsDeprecated86.php on line 6
PHP Deprecated:  SplFileObject::fgetcsv(): the $escape parameter must be provided, as its default value will change, either explicitly or via SplFileObject::setCsvControl() in /codes/splFileObjectCsvMethodsDeprecated86.php on line 6

Deprecated: SplFileObject::fgetcsv(): the $escape parameter must be provided, as its default value will change, either explicitly or via SplFileObject::setCsvControl() in /codes/splFileObjectCsvMethodsDeprecated86.php on line 6
array(3) {
  [0]=>
  string(1) "a" 
  [1]=>
  string(1) "b" 
  [2]=>
  string(1) "c" 
}
PHP Deprecated:  Method SplFileObject::fputcsv() is deprecated since 8.6 in /codes/splFileObjectCsvMethodsDeprecated86.php on line 9

Deprecated: Method SplFileObject::fputcsv() is deprecated since 8.6 in /codes/splFileObjectCsvMethodsDeprecated86.php on line 9
PHP Deprecated:  SplFileObject::fputcsv(): the $escape parameter must be provided, as its default value will change, either explicitly or via SplFileObject::setCsvControl() in /codes/splFileObjectCsvMethodsDeprecated86.php on line 9

Deprecated: SplFileObject::fputcsv(): the $escape parameter must be provided, as its default value will change, either explicitly or via SplFileObject::setCsvControl() in /codes/splFileObjectCsvMethodsDeprecated86.php on line 9
int(4)

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

SplFileObject::fgets() No Longer Caches The Current Line

SplFileObject::fgets() used to cache the line it read so that a following current() call returned that same cached line instead of reading again. It also let next() be called past end-of-file an unlimited number of times, incrementing key() without bound. In PHP 8.6, fgets() no longer caches the line for current(), so current() now reads and returns the next line from the stream; next() past EOF is now a no-op, so key() stops advancing once the end of the file is reached.

PHP code

<?php

$path = tempnam(sys_get_temp_dir(), 'spl');
file_put_contents($path, "line1\nline2\nline3\n");

$f = new SplFileObject($path);
var_dump(trim($f->fgets()));
var_dump(trim($f->current()));

for ($i = 0; $i < 5; $i++) {
    $f->next();
}
var_dump($f->key());

unlink($path);

?>

Before

string(5) line1
string(5) line1
int(5)

After

string(5) line1
string(5) line2
int(3)

PHP version change

This behavior changed in 8.6.

See Also

Extension

SplFileObject::next() Always Advances The Stream

SplFileObject::next() used to only advance to the next line when a prior current() call had already cached a line internally; without that cache, next() was a no-op and the following current() call re-read the same line. In PHP 8.6, next() unconditionally advances the underlying stream, so a subsequent current() call always returns the line after the one that was current before next() was called.

PHP code

<?php

$path = tempnam(sys_get_temp_dir(), 'spl');
file_put_contents($path, "line1\nline2\nline3\n");

$f = new SplFileObject($path);
$f->next();
var_dump(trim($f->current()));

unlink($path);

?>

Before

string(5) "line1" 

After

string(5) "line2" 

PHP version change

This behavior changed in 8.6.

See Also

Extension

SplFixedArray Is Now An IteratorAggregate

SplFixedArrayused to be anIterator, and is now an IteratorAggregate`.

They don’t have strictly identical behaviors. They’re both iterable but they go about it two completely different ways: Iterator means the object modifies itself during iteration, and `IteratorAggregater`` means the object remains unchanged because it uses a proxy object to handle iteration.

Note that is it not possible to extends both at the same time: they are incompatible.

PHP code

<?php
$array = new SplFixedArray(5);

var_dump($array instanceof Iterator);
var_dump($array instanceof IteratorAggregate);

?>

Before

bool(true)
bool(false)

After

bool(false)
bool(true)

PHP version change

This behavior changed in 8.0.

See Also

Extension

spl_object_hash() Is Deprecated

spl_object_hash() returns a string uniquely identifying an object for the lifetime of that object. In PHP 8.6, calling spl_object_hash() emits a deprecation notice, in favor of spl_object_id(), which returns an integer identifier instead of a string and is cheaper to compute. The function still returns a value in PHP 8.6.

PHP code

<?php

$obj = new stdClass();
var_dump(spl_object_hash($obj));

?>

Before

string(32) "0000000038bff86f0000000058eaf75e" 

After

PHP Deprecated:  Function spl_object_hash() is deprecated since 8.6, consider using spl_object_id() instead in /codes/splObjectHashDeprecated.php on line 4

Deprecated: Function spl_object_hash() is deprecated since 8.6, consider using spl_object_id() instead in /codes/splObjectHashDeprecated.php on line 4
string(32) "00000000000000010000000000000000" 

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

SplObjectStorage::getHash() May No Longer Mutate Storage

A custom getHash() implementation in an SplObjectStorage subclass used to be free to mutate any SplObjectStorage instance, including a different one than the one currently being hashed, while computing the hash. In PHP 8.6, attempting such a mutation from inside getHash() throws an Error, because SplObjectStorage internals are not reentrant during hashing.

PHP code

<?php

class WatchfulStorage extends SplObjectStorage {
    public SplObjectStorage $sideStorage;
    public function getHash(object $object): string {
        $this->sideStorage->offsetSet(new stdClass());
        return spl_object_hash($object);
    }
}

$watch = new WatchfulStorage();
$watch->sideStorage = new SplObjectStorage();

try {
    $watch->offsetSet(new stdClass());
    echo "offsetSet succeeded, sideStorage count=".count($watch->sideStorage)."\n";
} catch (\Error $e) {
    echo "Error: ".$e->getMessage()."\n";
}

?>

Before

offsetSet succeeded, sideStorage count=1

After

Error: Modification of SplObjectStorage during getHash() is prohibited

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

Cannot Bind $this To Static Closure

A static closure does not import any variables from the defining context. In particular, it doesn’t import the pseudo-variable $this. This also applies when trying to reconfigure a closure with its bindTo() method.

PHP code

<?php

class A {}

$fn = static function () {  };

$d = $fn->bindTo(new A, 'A');

?>

Before

PHP Warning:  Cannot bind an instance to a static closure

Warning: Cannot bind an instance to a static closure

After

PHP Warning:  Cannot bind an instance to a static closure, this will be an error in PHP 9

Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9

PHP version change

This behavior changed in 8.5.

Error Messages

Extension

Stateless Static Closures Are Cached And Reused

A static closure that captures nothing, no use() variables, no $this, is a stateless closure. Until PHP 8.6, each evaluation of such a closure literal created a brand new Closure object, so two closures built from the exact same code were never identical. In PHP 8.6, the engine creates the object once and reuses it on every subsequent evaluation, so two stateless static closures built from the same code are now the same object.

PHP code

<?php

function make() {
    return static function () {
        return 1;
    };
}

var_dump(make() === make());

?>

Before

bool(false)

After

bool(true)

PHP version change

This behavior changed in 8.6.

See Also

Extension

static Cannot Be Used Anymore In Callable Arrays

PHP supports a callable syntax, based on array: it must be an array of two elements, where the index 0 is the object or the class, and the index 1 is the method name.

Until PHP 8.2, it was possible to use the keyword static, to make the callable dependent on the context of usage of the callable.

In the example, static would be calling the static method replace, in A, or in any other class where it is used.

Since PHP 8.2, this is a deprecated feature, and it will be removed in PHP 9.

PHP code

<?php
class A
{
    public static function work($it) {
		return preg_replace_callback('~\w+~', array('static', 'static::replace'), $it);
    }
    
    public static function replace($a) {
    	return 'a';
    }
}

echo a::work('abc');

?>

Before

a

After

PHP Deprecated:  Use of "static" in callables is deprecated

Deprecated: Use of "static" in callables is deprecated
PHP Deprecated:  Callables of the form ["A", "static::replace"] are deprecated

Deprecated: Callables of the form ["A", "static::replace"] are deprecated
a

PHP version change

This behavior was deprecated in 8.2.

This behavior changed in 9.0.

Error Messages

Extension

Analyzer

Static Variables Are Linked To Their Method

Static variables are linked to their method: any call to that method should access the same property.

Until PHP 8.1, the static variables used to be linked to the class: this meant that changing the call to the class lead to different values of the static variable. The new behavior is the expcted one.

PHP code

<?php
class A {
    public static function counter() {
        static $counter = 0;
        $counter++;
        return $counter;
    }
}
class B extends A {}
var_dump(A::counter()); // int(1)
var_dump(A::counter()); // int(2)
var_dump(B::counter()); // int(3), previously int(1)
var_dump(B::counter()); // int(4), previously int(2)
?>

Before

int(1)
int(2)
int(1)
int(2)

After

int(1)
int(2)
int(3)
int(4)

PHP version change

This behavior changed in 8.1.

Extension

Analyzer

Storage Of Static Properties Trait

Static properties defined in a trait used to be merged with any existing static property in a parent class. Since PHP 8.3, the static property is directly related to the importing class, and is made distinct from any pre-existing static class.

PHP code

<?php

trait T {
    static $T = 1;
}

class X {
    static $T = 1;

    function goo() {
        echo self::$T;
    }

}

class Y extends X {
    use t;
    
    function foo() {
        self::$T = 2;
        echo self::$T;
        self::goo();
    }
    
}

(new y)->foo();

?>

Before

2

After

1

PHP version change

This behavior changed in 8.3.

Extension

Static Variable Accepts Functioncalls As Default

Static variables are actually variables: as such, they can be inited with the result of a functioncall.

Until PHP 8.3, their default values were using static constant expression, built around constants and operators.

Since PHP 8.3, it is possible to also set their first value as a function or method call.

Properties and parameters are not allowed to use these expressions: they must be valid at compile time.

PHP code

<?php

function foo() {
	static $x = goo(1);
	
	return ++$x;
}

function goo() {
	return 3;
}

echo foo();
echo foo();

?>

Before

PHP Fatal error:  Constant expression contains invalid operations

Fatal error: Constant expression contains invalid operations

After

45

PHP version change

This behavior changed in 8.3.

Error Messages

Extension

Static Properties With References

Static properties are shared between inheriting classes. However, due to an implementation artifact, it was possible to separate the static properties by assigning a reference. This loophole has been fixed in PHP 7.3.

PHP code

<?php

    class Test {
        public static $x = 0;
    }
    class Test2 extends Test { }
    
    Test2::$x = &$x;
    $x = 1;
    
    var_dump(Test::$x, Test2::$x);
    // Previously: int(0), int(1)
    // Now: int(1), int(1)

?>

Before

int(0)
int(1)

After

int(1)
int(1)

PHP version change

This behavior changed in 7.3.

Extension

str_pos() Requires Only Strings

str_pos() accepted integers as second argument, and would convert them into their equivalent ASCII char. This was a hidden feature of PHP.

Since PHP 8.0, the integer is converted to string as is, and used for the search.

PHP code

<?php

var_dump(strpos('abc ', 32));

?>

Before

PHP Deprecated:  strpos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior

Deprecated: strpos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior
int(3)

After

bool(false)

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

str_replace() Checks For Arguments

str_replace() can replace a string with another string; an array of strings with another array of strings, with a one to one relationship; or an array of strings with a single string, where all of the searched strings are replaced with the same target string. Yet, it is not possible to replace one string by an array of strings, as they are not of the same type, and PHP would need to choose one of the target strings.

This is an example of conditional typing : the type of one of the arguments depends on the type of the other argument.

PHP code

<?php

print str_replace( array('b', 'c'), 'a', 'abc');
?>

Before

Notice: Array to string conversion in /in/GhW96 on line 3
Arraybc

After

Uncaught TypeError: str_replace(): Argument #2 ($replace) must be of type string when argument #1

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

str_replace() On Arrays Of Objects

str_replace() accepts an array of strings as third argument: it applies all the replacements to all the strings in that arguments.

Until PHP 8.0, it was possible to pass an array of arrays, and the inner arrays would be omitted in the replacement. In PHP 8.0, the objects are cast to a string: stringeable objects are always converted, while non-stringeable objects yields a Fatal error.

This is also applicable to str_ireplace().

PHP code

<?php

class x {
	function __toString() {
		return 'def';
	}
}

var_dump(str_replace('a', 'b', [new x]));

var_dump(str_replace('a', 'b', [new stdclass]));

?>

Before

array(1) {
  [0]=>
  object(stdClass)#1 (0) {
  }
}

After

array(1) {
  [0]=>
  string(3) def
}
PHP Fatal error:  Uncaught Error: Object of class stdClass could not be converted to string

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

str_replace() Enforces Strings In Array Argument

str_replace() accepts an array of strings as third argument: it applies all the replacements to all the strings in that arguments.

Until PHP 8.0, it was possible to pass an array of arrays, and the inner arrays would be omitted in the replacement. In PHP 8.0, the inner arrays are cast to a string, aka Array and then, the replacements occurs.

This is also applicable to str_ireplace().

PHP code

<?php

var_dump(str_replace('a', 'b', [[]]));

class x {
	function __toString() {
		return 'def';
	}
}

var_dump(str_replace('a', 'b', [new x]));

?>

Before

array(1) {
  [0]=>
  array(0) {
  }
}
array(1) {
  [0]=>
  object(x)#1 (0) {
  }
}

After

PHP Warning:  Array to string conversion

Warning: Array to string conversion
array(1) {
  [0]=>
  string(5) Arrby
}
array(1) {
  [0]=>
  string(3) def
}

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

strcoll() And SORT_LOCALE_STRING Are Deprecated

strcoll() compares strings according to the current locale, and the SORT_LOCALE_STRING flag makes sort() and related functions do the same. Until PHP 8.6, both relied silently on the global, thread-unsafe locale set by setlocale(). In PHP 8.6, using either strcoll() or SORT_LOCALE_STRING emits a deprecation notice recommending the intl extension’s Collator class instead.

PHP code

<?php

var_dump(strcoll('a', 'b'));

$arr = ['banana', 'Apple', 'cherry'];
sort($arr, SORT_LOCALE_STRING);
var_dump($arr);

?>

Before

int(-1)
array(3) {
  [0]=>
  string(5) "Apple" 
  [1]=>
  string(6) "banana" 
  [2]=>
  string(6) "cherry" 
}

After

PHP Deprecated:  Function strcoll() is deprecated since 8.6, use Collator::compare() instead

Deprecated: Function strcoll() is deprecated since 8.6, use Collator::compare() instead
int(-1)
PHP Deprecated:  Constant SORT_LOCALE_STRING is deprecated since 8.6, use one of the Collator::*sort*() methods instead

Deprecated: Constant SORT_LOCALE_STRING is deprecated since 8.6, use one of the Collator::*sort*() methods instead
array(3) {
  [0]=>
  string(5) "Apple" 
  [1]=>
  string(6) "banana" 
  [2]=>
  string(6) "cherry" 
}

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

Calling stream_context_set_option() With 2 Arguments Is Deprecated

stream_context_set_option() accepted two call shapes: a 4-argument form setting a single wrapper/option/value triple, and a 2-argument form that instead took a whole nested ["wrapper"]["option"] = value array in place of the last three arguments. PHP 8.3 added a dedicated stream_context_set_options() function for that array form. Until PHP 8.4, calling the 2-argument array form was completely silent. In PHP 8.4, it emits a deprecation notice in favor of the new function; the context is still updated exactly as before, and the call still returns true.

PHP code

<?php

$context = stream_context_create();
var_dump(stream_context_set_option($context, ['http' => ['method' => 'POST']]));

Before

bool(true)

After

PHP Deprecated:  Calling stream_context_set_option() with 2 arguments is deprecated, use stream_context_set_options() instead in /codes/streamContextSetOptionTwoArgsDeprecated.php on line 4

Deprecated: Calling stream_context_set_option() with 2 arguments is deprecated, use stream_context_set_options() instead in /codes/streamContextSetOptionTwoArgsDeprecated.php on line 4
bool(true)

PHP version change

This behavior was deprecated in 8.4.

This behavior changed in .

See Also

Error Messages

Extension

strftime() And gmstrftime() Are Deprecated

strftime() and gmstrftime() format time and date according to locale settings. These functions are deprecated in PHP 8.1, and should be replaced with date() and gmdate(), respectively, or with gmdate() or with IntlDateFormatter::format(): both of them, with the right format.

PHP code

<?php

echo strftime(1);
echo gmstrftime(2);

?>

Before

12

After

PHP Deprecated:  Function strftime() is deprecated since 8.1, use IntlDateFormatter::format() instead

Deprecated: Function strftime() is deprecated since 8.1, use IntlDateFormatter::format() instead
1PHP Deprecated:  Function gmstrftime() is deprecated since 8.1, use IntlDateFormatter::format() instead

Deprecated: Function gmstrftime() is deprecated since 8.1, use IntlDateFormatter::format() instead
2

PHP version change

This behavior changed in 8.4.

See Also

Error Messages

Extension

String Increments

String increments are the ++ operator applied to a string. The last character is updated to the next one in ASCII order, with a wrap up after z. This feature was deprecated in PHP 8.3, and the str_increment() and str_decrement() functions are introduced to replace it.

PHP code

<?php

$a = 'abc';
// $a = 'ab!';
// in PHP 8.4, the last char must be non-alpha numeric to emit the warning
$a++;

echo $a;

?>

Before

abd

After

PHP Deprecated:  Increment on non-alphanumeric string is deprecated

abds

PHP version change

This behavior changed in 5.6.

Error Messages

Extension

String To Integer Comparison

The comparison between a string and an integer has changed. In particular, PHP 7 used to convert both operands to integer before comparison, leading to 0 and any string being equal.

In PHP 8.0 and more recent, this doesn’t happen and strings are now different from integers.

Also, strings used to be smaller than 0, but they are now bigger.

PHP code

<?php

var_dump(0 == 'a');

?>

Before

bool(true)

After

bool(false)

PHP version change

This behavior changed in 8.0.

See Also

Extension

Strings Are Bigger Than Integers

When comparing strings and integers with inequalities (<, =<, >, >=), strings used to be smaller than numbers and they are bigger than numbers in PHP 8.0. Unless, they can be converted to integer safely.

PHP code

<?php

var_dump('a' > -1);
var_dump('a' > 0);
var_dump('a' > 1);

var_dump('a' < -1);
var_dump('a' < 0);
var_dump('a' < 1);

?>

Before

bool(true)
bool(false)
bool(false)

bool(false)
bool(false)
bool(true)

After

bool(true)
bool(true)
bool(true)

bool(false)
bool(false)
bool(false)

PHP version change

This behavior changed in 8.0.

See Also

Extension

strpos() With Out-Of-Range Offset Is A Fatal Error

Requesting a large offset, beyond the size of the searched string, leads to a Fatal error in PHP 8.0 and more recent.

Until then, it was a warning.

This error message is shared by several PHP native and extension functions, namely mbstring and iconv: strpos(), strrpos(), stripos(), strripos(), mb_strpos(), mb_strrpos(), mb_stripos(), mb_strripos(), iconv_strpos and iconv_strrpos.

PHP code

<?php

strpos('abc', 'b', 6);

?>

Before

PHP Warning:  strpos(): Offset not contained in string -

Warning: strpos(): Offset not contained in string -

After

PHP Fatal error:  Uncaught ValueError: strpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) -

Fatal error: Uncaught ValueError: strpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) -

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

strpos() Emits TypeError

strpos() and stripos() emit a TypeError when the offset is of the wrong type. In PHP 7.4, it emitted a warning.

PHP code

<?php
strpos('a', 'abc', null);
?>

Before

PHP Warning:  strpos() expects parameter 3 to be int, string given

After

PHP Fatal error:  Uncaught TypeError: strpos(): Argument #3 ($offset) must be of type int, string given

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

strpos() Emits ValueError

strpos() and stripos() emits a ValueError when the offset is out of range. In PHP 7.4, it emitted a warning.

PHP code

<?php
  strpos('a', 'abc', 17);
?>

Before

PHP Warning:  strpos(): Offset not contained in string

Warning: strpos(): Offset not contained in string
bool(false)

After

PHP Fatal error:  Uncaught ValueError: strpos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) 

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

strpos() Does Not Accept False

PHP used to type cast false to 0 then to a string, when it is used as second argument to strpos().

PHP code

<?php

var_dump(strpos('abc', false));
var_dump(strpos('a'.chr(0), false));
?>

Before

PHP Deprecated:  strpos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior

Deprecated: strpos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior
bool(false)
int(1);

After

int(0)
int(0)

PHP version change

This behavior was deprecated in 7.4.

This behavior changed in 8.0.

See Also

Error Messages

Extension

strpos() With Integer Argument

strpos() used to accept integer arguments as second argument, $needle. Then, PHP would turn the integer into the equivalent ASCII character, and look for that character.

Since PHP 8.0, it is not the case anymore. If the code requires such behavior, add a call to chr() or mb_chr() to convert the integer to an character, before searching for it.

PHP code

<?php

var_dump(@strpos('abc', 98));

?>

Before

int(1)

After

false

PHP version change

This behavior changed in 8.0.

Extension

Analyzer

strpos() Does Not Accept Null As Second Parameter

strpos() and stripos() used to accept NULL as second argument. This was deprecated with a warning, and then removed in PHP 8.

PHP code

<?php

var_dump(strpos('1', null));

?>

Before

strpos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior

After

strpos(): Passing null to parameter #2 ($needle) of type string is deprecated

PHP version change

This behavior was deprecated in 7.3.

This behavior changed in 8.0.

Error Messages

Extension

strpos() With Null Haystack

PHP accepted null as first parameter $string of strpos(). Then, it cast the null to empty string, and returned immediately false, as nothing was found in such string.

Since PHP 8.2, this is a deprecated behavior, with a warning message. It will be removed in PHP 9.

PHP code

<?php

var_dump(strpos(null, '1'));

?>

Before

false

After

strpos(): Passing null to parameter #1 ($haystack) of type string is deprecated

PHP version change

This behavior was deprecated in 8.2.

This behavior changed in 9.0.

Error Messages

Extension

strsplit() With Empty String

strsplit() splits a string into smaller strings of the same size. Until PHP 8.2, it used to return an array with an empty string when splitting an empty string. Since then, it returns an empty array.

This has impact on the code after, in processing or testing the result of the split.

PHP code

<?php
var_dump(str_split('', 3));
?>

Before

Array
(
    [0] => 
)

After

Array
(
)

PHP version change

This behavior changed in 8.2.

Extension

str_split() Throws ValueError With Negative Lengths

str_split() used to emit a warning and return false, when provided with $length, the second argument, as an integer less then 1. In PHP 8.0, it now throws a ValueError.

PHP code

<?php
  str_split('abc', 0);
?>

Before

Warning: str_split(): The length of each segment must be greater than zero

After

Fatal error: Uncaught ValueError: str_split(): Argument #2 ($length) must be greater than 0

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

substr() Returns Empty String On Out Of Bond Offset

substr() used to return false when the parameters used to extract the string were out of bound, or well out of the string sizes. With PHP 8.0, this is not reported as an error anymore, and fails silently.

One collateral impact is that code that checks on the returned value to be false is now dead code.

PHP code

<?php

var_dump(substr('FooBar', 42, 3)); // "" in PHP >=8.0, false in PHP < 8.0
var_dump(mb_substr('FooBar', 42, 3)); // "" in PHP >=8.0, false in PHP < 8.0);
var_dump(iconv_substr('FooBar', 42, 3)); // "" in PHP >=8.0, false in PHP < 8.0);
var_dump(grapheme_substr('FooBar', 42, 3)); // "" in PHP >=8.0, false in PHP < 8.0);
?>

Before

bool(false)
string(0) "" 
bool(false)
bool(false)

After

string(0) "" 
string(0) "" 
string(0) "" 
string(0) "" 

PHP version change

This behavior changed in 8.0.

See Also

Extension

switch() Changed Comparison Style

The switch command uses a relaxed comparison style. Hence, the associated cases changed in PHP 8.0, whenever they use the special values such a 0, empty string ‘’ or null.

PHP code

<?php

$a = 0;
switch ($a) {
    case 'a': 
        print 'a'.PHP_EOL;
        break;

    case 0: 
        print 'Null'.PHP_EOL;
        break;
        
    default:
        print 'Default'.PHP_EOL;
}

?>

Before

a

After

Null

PHP version change

This behavior changed in 8.0.

Extension

Analyzer

Length Of tempnam() Has Been Raised

The temporary name, provided by tempname() used to be 6 characters, added to the provided prefix. It is now 19 characters, so 13 more characters.

There might be impact on database, if this is stored.

PHP code

<?php

print strlen(tempnam(/tmp, FOO));
// PHP 8.4+ : /tmp/FOO3u8m0hgq3afe2eSwgTld
// PHP 8.3- : /tmp/FOO3u8m0h

?>

Before

22

After

35

PHP version change

This behavior changed in 8.4.

Extension

Tentative Static Returntype With Datetime

The createFromImmutable() method from DateTime and DateTimeImmutable always return an object of the same class. In PHP 8.2 and later, the return type is now static, it will tentatively return a children class, when the method is called from that child class.

PHP code

<?php

class A extends DateTime{}

$date = new DateTimeImmutable("2014-06-20 11:45 Europe/London");

$mutable = A::createFromImmutable( $date );

var_dump($mutable);
?>

Before


After

object(A)#2 (3) {
  ["date"]=>
  string(26) "2014-06-20 11:45:00.000000" 
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(13) "Europe/London" 
}

PHP version change

This behavior changed in 8.2.

Extension

Ternary Associativity

The ternary operator used to have a left associativity : it would process first the then and else clauses, before executing itself.

Since PHP 8.0, the ternary operator produces a fatal error when the nested ternaries are ambiguous.

The update forces the code to use parenthesis, and set the priorities between the operators explicitly.

This doesn’t apply to the then clause, which is always unambiguous.

PHP code

<?php

$a = 2;
print $a == 1 ? 'one'
     : $a == 2 ? 'two'
     : $a == 3 ? 'three'
     : 'other';

?>

Before

three

After

Fatal error: Unparenthesized `a ? b : c ? d : e` is not supported. Use either `(a ? b : c) ? d : e` or `a ? b : (c ? d : e)` 

PHP version change

This behavior was deprecated in 7.4.

This behavior changed in 8.0.

See Also

Error Messages

Extension

Analyzer

$this Must Be The Local Object

$this used to be a variable like any other, except for being filled with the local object in a method.

Since PHP 7.1, it is now only used for this purpose. This means that $this cannot be used outside a class, an enumeration or a trait, and for any other purpose.

PHP code

<?php

var_dump($this);

?>

Before

PHP Notice:  Undefined variable: this

Notice: Undefined variable: this
NULL

After

PHP Fatal error:  Uncaught Error: Using $this when not in object context

Fatal error: Uncaught Error: Using $this when not in object context

PHP version change

This behavior changed in 7.1.

Error Messages

Extension

Analyzer

throw Is An Expression

throw was a standalone expression: it needed to be alone, between semicolons (or equivalents).

Since PHP 8.0, throw may be included in another expression. This is useful with or, or the coalesce operator, to execute the expression when a value is missing or failing.

PHP code

<?php

foo() or throw new \Exception();

$x = $_GET['x'] ?? throw new \Exception('Missing value for x');

?>

Before

PHP Parse error:  syntax error, unexpected 'throw' (T_THROW)

Parse error: syntax error, unexpected 'throw' (T_THROW)

After


PHP version change

This behavior changed in 8.0.

See Also

Error Messages

Extension

Analyzer

__toString Can Throw Exceptions

The magic method __toString() could not throw exception, in case of problem occurring during processing.

Since PHP 7.4, it is possible.

PHP code

<?php

class X {
    function __toString() {
        throw new \Exception('error'.__METHOD__);
    }
}

(string) new X;

?>

Before

PHP Fatal error:  Method X::__toString() must not throw an exception, caught Exception: errorX::__toString

Fatal error: Method X::__toString() must not throw an exception, caught Exception: errorX::__toString

After

PHP Fatal error:  Uncaught Exception: errorX::__toString

Fatal error: Uncaught Exception: errorX::__toString

PHP version change

This behavior changed in 7.4.

Error Messages

Extension

Analyzer

Trailing Comma In Arguments

Trailing comma in arguments is the last argument left empty. This last argument is not transmitted, so the last comma has no effect. This feature is useful when arguments are kept on a different line : the last argument has now also a comma, and adding one extra argument will yield a one line diff, compared to the previous version. Without it, the diff would be two lines, and include the preceding line.

PHP code

<?php

function foo($a,
             $b,
             $c,
              ) { echo __METHOD__; }

echo foo(1);

?>

Before

PHP Parse error:  syntax error, unexpected ')', expecting variable (T_VARIABLE)

After

foo

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

Trailing Comma In Calls

Trailing commas in parameters is the last parameter left empty. This last parameter is not transmitted, so the last comma has no effect. This feature is useful when parameters are kept on a different line : the last argument has now also a comma, and adding one extra argument will yield a one line diff, compared to the previous version. Without it, the diff would be two lines, and include the preceding line.

PHP code

<?php

function foo($a,
             $b,
             $c) { echo __METHOD__; }

echo foo(1,
         2,
         3,
         );

?>

Before

PHP Parse error:  syntax error, unexpected ')', expecting variable (T_VARIABLE)

After

foo

PHP version change

This behavior changed in 7.3.

Error Messages

Extension

Analyzer

Constants In Traits

Constants are allowed in traits in PHP 8.3 and more recent. Until then, they were not supported.

PHP code

<?php

trait T {
    const A = 1;
}

class X {
    use T;
}

echo X::A;
?>

Before

PHP Fatal error:  Traits cannot have constants

After

1

PHP version change

This behavior changed in 8.2.

Error Messages

Extension

Analyzer

trim() Strips Form Feed By Default

trim(), ltrim() and rtrim() remove a fixed set of characters by default when no second argument is provided. Until PHP 8.6, that set was space, tab, newline, carriage return, NUL byte and vertical tab. In PHP 8.6, the form feed character (\f, \x0C) was added to that default set.

PHP code

<?php

var_dump(trim("\fHello\f"));

?>

Before

string(7) "Hello" 

After

string(5) "Hello" 

PHP version change

This behavior changed in 8.6.

See Also

Extension

Typed Class Constant

Support for typed class constants was added in PHP 8.3

PHP code

<?php

class x {
    public int A = 1;
}

echo X::A;

?>

Before

Parse error: syntax error, unexpected identifier "A", expecting variable

After

1

PHP version change

This behavior changed in 8.3.

See Also

Error Messages

Extension

Analyzer

Removing $this from a closure is deprecated

When a closure is created in a non-static method, it imports automatically the current object. Nowadays, it is not possible to remove that object from the closure, as it would not run anymore.

PHP code

<?php

class x {
	private $p = 1;
	
	function foo() {
		return function () { return $this->p; };
	}
}

$x = new x;
$closure = $x->foo();
print $closure->bindTo(null);

?>

Before

PHP Deprecated:  Unbinding $this of closure is deprecated

Deprecated: Unbinding $this of closure is deprecated
PHP Fatal error:  Uncaught Error: Object of class Closure could not be converted to string

Fatal error: Uncaught Error: Object of class Closure could not be converted to string

After

PHP Warning:  Cannot unbind $this of closure using $this

Warning: Cannot unbind $this of closure using $this

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Undefined Constants

Undefined global constants used to fallback to their equivalent string. It would keep the execution going, too. In PHP 8.0, such mistake is a Fatal Error

PHP code

<?php

echo D;

?>

Before

D

After

Uncaught Error: Undefined constant "D"

PHP version change

This behavior was deprecated in 7.0.

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

Unpack Arrays In Arrays

The ellipsis operator can now be used in arrays, with an effect similar to array_merge(). In particular, the string keys are now supported.

PHP code

<?php

$array = [...['a' => 'foo'], ...['b' => 'bar']];

print_r($array);

?>

Before

PHP Fatal error:  Cannot unpack array with string keys

Fatal error: Cannot unpack array with string keys

After

Array
(
    [a] => foo
    [b] => bar
)

PHP version change

This behavior changed in 8.1.

Error Messages

Extension

Analyzer

Unpack Array With String Keys

The ellipsis operator was introduced to unpack arrays. In PHP 7.4, it only supported integer keys, and not string keys. This was introduced in PHP 8.0.

PHP code

<?php

$array = ['a' => 1];

foo(...$array);

function foo($a) {
	echo $a;
}

?>

Before

PHP Fatal error:  Uncaught Error: Cannot unpack array with string keys

Fatal error: Uncaught Error: Cannot unpack array with string keys

After

1

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

unpack() Strips Endianness Modifiers From The Result Key

unpack() format codes accept an endianness modifier, such as < for little-endian or > for big-endian, directly after the type code. Until PHP 8.6, a modifier placed right before the slash-separated key, such as s<value, was not recognized as a modifier there and was kept as part of the resulting array key, so the key became <value instead of value. In PHP 8.6, the modifier is recognized and stripped in that position too, so the key is the clean value.

PHP code

<?php

var_dump(unpack("s<value", pack("s", 5)));

?>

Before

array(1) {
  ["<value"]=>
  int(5)
}

After

array(1) {
  ["value"]=>
  int(5)
}

PHP version change

This behavior changed in 8.6.

See Also

Extension

unserialize() Checks The End Of The String

The format used by unserialize() is a closed format: it might be smaller than the string that contains it. Until PHP 8.3, unserialize() stops as soon as it is satisfied, leaving the possible remainder of the string hanging. In PHP 8.3, a warning is raised.

PHP code

<?php

print_r(unserialize('O:1:"a":1:{s:8:"property";s:3:"yes";}  '));

?>

Before

__PHP_Incomplete_Class Object
(
    [__PHP_Incomplete_Class_Name] => a
    [property] => yes
)

After

PHP Warning:  unserialize(): Extra data starting at offset 37 of 39 bytes

Warning: unserialize(): Extra data starting at offset 37 of 39 bytes
__PHP_Incomplete_Class Object
(
    [__PHP_Incomplete_Class_Name] => a
    [property] => yes
)

PHP version change

This behavior changed in 8.3.

See Also

Error Messages

Extension

unserialize() max_depth Option

unserialize() has now an option to limit the depth of nesting in the decoded structure. When that limit is reached, serialize() emits a warning, and stops processing the string. This is a security option, that prevents deep nested structures to be created and consume a lot of memory and processing power.

PHP code

<?php

// 4 levels of nesting
$a = [[[[]]]];

$b = serialize($a);

print_r(unserialize($b, ['max_depth' => 2]));

?>

Before

Array
(
    [0] => Array
        (
            [0] => Array
                (
                    [0] => Array
                        (
                        )

                )

        )

)

After

PHP Warning:  unserialize(): Maximum depth of 2 exceeded. The depth limit can be changed using the max_depth unserialize() option or the unserialize_max_depth ini setting

Warning: unserialize(): Maximum depth of 2 exceeded. The depth limit can be changed using the max_depth unserialize() option or the unserialize_max_depth ini setting
PHP Warning:  unserialize(): Error at offset 23 of 36 bytes

Warning: unserialize(): Error at offset 23 of 36 bytes

PHP version change

This behavior changed in 7.4.

See Also

Error Messages

Extension

unserialize() Error Report

unserialize() parses a string into a PHP data structure: array, int, object, etc. When the parser encounters an error, it emits a specific message, and returns null. This error used to be a notice and it now a warning.

PHP code

<?php

unserialize("an invalid string");

?>

Before

PHP Notice:  unserialize(): Error at offset 0 of 17 bytes

Notice: unserialize(): Error at offset 0 of 17 bytes

After

PHP Warning:  unserialize(): Error at offset 0 of 17 bytes

Warning: unserialize(): Error at offset 0 of 17 bytes

PHP version change

This behavior changed in 8.3.

Error Messages

Extension

Unserialize with the upper case S is deprecated

When using the unserialize() function, the string should not use S (upper case S) to format a string. It should only use s (lower case S).

Other formats, such as i, b or N are already case sensitive.

PHP code

<?php

var_dump(unserialize('S:1:e;'));

?>

Before

string(1) e

After

PHP Deprecated:  unserialize(): Unserializing the 'S' format is deprecated

Deprecated: unserialize(): Unserializing the 'S' format is deprecated
string(1) e

PHP version change

This behavior changed in 8.4.

Error Messages

Extension

(unset) Was Removed

The (unset) operator has been removed. Use the unset() function for that feature.

PHP code

<?php

$a = 1;
(unset) $a;

var_dump($a);

?>

Before

PHP Deprecated:  The (unset) cast is deprecated

Deprecated: The (unset) cast is deprecated
int(1)

After

PHP Fatal error:  The (unset) cast is no longer supported

Fatal error: The (unset) cast is no longer supported

PHP version change

This behavior was deprecated in 7.4.

This behavior changed in 8.0.

Error Messages

Extension

Analyzer

usleep() Validates Its Microseconds Argument Range

usleep()’s $microseconds argument is passed to the operating system as an unsigned integer. Until PHP 8.6, a value greater than UINT_MAX (4294967295) silently overflowed, which could make the function sleep for a much shorter time than requested. In PHP 8.6, a value greater than UINT_MAX throws a ValueError instead.

PHP code

<?php

try {
    var_dump(usleep(4294967296));
} catch (\ValueError $e) {
    echo "ValueError: ".$e->getMessage()."\n";
}

?>

Before

NULL

After

ValueError: usleep(): Argument #1 ($microseconds) must be between 0 and 4294967295

PHP version change

This behavior changed in 8.6.

See Also

Error Messages

Extension

var_export() Format

PHP used to export an object with a fully qualified name, except for the first backslash. Since PHP 8.2, the name is a fully qualified one, and may be used in any namespace, without adaptation.

PHP code

<?php

class X {}

var_export(new X);

?>

Before

\X::__set_state(array(
))

After

\\X::__set_state(array(
))

PHP version change

This behavior changed in 8.2.

See Also

Extension

var_export() With Stdclass

PHP used to export stdClass objects like other classes, with a call to the magic method __set_state(). Since PHP 7.2, it does the export with the cast of an array to (object). This is more readable, and acknowledge the absence of such method for stdClass.

PHP code

<?php

   var_export(new stdClass);

?>

Before

stdClass::__set_state(array())

After

(object) array()

PHP version change

This behavior changed in 7.2.

Extension

version_compare() Stricter Operators

version_compare() compares version strings, using an operator, passed as third parameter. Until PHP 8.3, unknown operators are ignored, and the function uses the default value.

Valid values of comparisons are : <, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne.

Nowadays, it is generating a fatal error.

PHP code

<?php

print version_compare('1.0', '2.3', '!');

?>

Before

1

After

PHP Fatal error:  Uncaught ValueError: version_compare(): Argument #3 ($operator) must be a valid comparison operator

PHP version change

This behavior changed in 8.1.

Error Messages

Extension

Analyzer

vsprint() Requires An Array

vsprint() used to skip argument type validation, and wrongly report missing arguments, when the last argument was not a array. Since PHP 8.0, the error message is clear.

PHP code

<?php

print vsprintf('%04d-%02d-%02d', 1);
vprintf('%04d-%02d-%02d', 1);

?>

Before

vsprintf(): Too few arguments

After

vsprintf(): Argument #2 ($values) must be of type array, int given

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

vsprintf() Returns Empty String On Error

vsprintf() always returns a string, or raises an exception. Until PHP 8.0, it used to return false in case of error. Errors include having insufficient arguments in the second argument’s array.

PHP code

<?php

var_dump(vsprintf("%04d-%02d-%02d", []));

?>

Before

Warning: vsprintf(): Too few arguments in /in/1pYdW on line 3
bool(false)

After

Fatal error: Uncaught ValueError: The arguments array must contain 3 items, 0 given

PHP version change

This behavior changed in 8.0.

Error Messages

Extension

WeakMap Allows Reference Assignment To A Missing Key

Taking a reference to a WeakMap offset with =& used to require the key to already be present in the map, otherwise PHP threw an Error saying the object was not contained in the WeakMap. In PHP 8.6, a reference assignment on a missing key first creates the entry, exactly like $array[$key] =& $ref does for a regular array, and then binds the reference to it.

PHP code

<?php

$map = new WeakMap();
$obj = new stdClass();

$ref =& $map[$obj];
$ref = 'value via reference';

var_dump($map[$obj]);

?>

Before

PHP Fatal error:  Uncaught Error: Object stdClass#2 not contained in WeakMap in /codes/weakMapReferenceAssign86.php:6
Stack trace:
#0 {main}
  thrown in /codes/weakMapReferenceAssign86.php on line 6

Fatal error: Uncaught Error: Object stdClass#2 not contained in WeakMap in /codes/weakMapReferenceAssign86.php:6
Stack trace:
#0 {main}
  thrown in /codes/weakMapReferenceAssign86.php on line 6

After

string(19) "value via reference" 

PHP version change

This behavior changed in 8.6.

See Also

Extension

each() Is No More

The each function is the base for the while loop that traverse arrays. The modern version of this loop is foreach, which does not rely on each, and improves the loop in speed and reliability. Hence, each was deprecated in PHP 7.4, and removed in 8.0.

PHP code

<?php

while(list($k, $v) = each($array)) {
    print $k . ' => '. $v.PHP_EOL;
}

?>

Before

PHP Deprecated:  The each() function is deprecated. This message will be suppressed on further calls

Deprecated: The each() function is deprecated. This message will be suppressed on further calls
PHP Warning:  Variable passed to each() is not an array or object

Warning: Variable passed to each() is not an array or object

After

PHP Fatal error:  Uncaught Error: Call to undefined function each()

Fatal error: Uncaught Error: Call to undefined function each()

PHP version change

This behavior was deprecated in 7.4.

This behavior changed in 8.0.

Error Messages

Extension

Comment Inside yield from

It was possible to insert a comment between the yield and the from.

In PHP 8.3 more recent, this would not compile, unless there was a defined constant called from.

PHP code

<?php
 
function foo() {
    yield /*a*/  from [3];
} 

foreach(foo() as $i) {
    print $i;
}
?>

Before

PHP Fatal error:  Uncaught Error: Undefined constant "from" 

Fatal error: Uncaught Error: Undefined constant "from" 

After

3

PHP version change

This behavior changed in 8.3.

Error Messages

Extension

Cannot Raise Zero To Negative Powers

Raising 0 to a negative power used to generate a INF value, aka infinity. The standard behavior is to generate a DivisionByZeroError, as this is not mathematically allowed. This behavior is deprecated in PHP 8.4, and will be removed in PHP 8.4. During the transition, a function called fpow() is provided, with the new behavior.

PHP code

<?php

var_dump(0 ** -1); //Deprecated: Zero raised to a negative power is deprecated

?>

Before

float(INF)

After

PHP Deprecated:  Power of base 0 and negative exponent is deprecated

Deprecated: Power of base 0 and negative exponent is deprecated
float(INF)

PHP version change

This behavior was deprecated in 8.4.

This behavior changed in 9.0.

See Also

Error Messages

Extension

Analyzer

Passing An Object As Filter Options To zlib/bzip2 Functions Is Deprecated

deflate_init(), inflate_init(), and the zlib.deflate/bzip2.compress stream filters accept an options value that PHP reads as an array. Until PHP 8.6, an object was silently accepted and read the same way as an array. In PHP 8.6, passing an object emits a deprecation notice recommending get_object_vars() first; the object’s properties are still read the same way as before.

PHP code

<?php

class Options {
    public $level = 6;
}

$context = deflate_init(ZLIB_ENCODING_RAW, new Options());
var_dump($context !== false);

?>

Before

bool(true)

After

PHP Deprecated:  deflate_init(): Passing an object for argument #2 $option to deflate_init() is deprecated, call get_object_vars() first instead in /codes/zlibBzip2ObjectOptionsDeprecated86.php on line 7

Deprecated: deflate_init(): Passing an object for argument #2 $option to deflate_init() is deprecated, call get_object_vars() first instead in /codes/zlibBzip2ObjectOptionsDeprecated86.php on line 7
bool(true)

PHP version change

This behavior was deprecated in 8.6.

This behavior changed in .

See Also

Error Messages

Extension

© 2023-2026, Damien Seguy - Exakat