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
- Accessing Trait Resources Directly Is Not Allowed
- Cannot Call Traits Methods Directly
- Cannot Raise Zero To Negative Powers
- E_STRICT Is Deprecated
- Enforcing Native PHP ReturnType
- Implicit Array Key Conversion
- Implicit Nullable
- Increment On Boolean Is Deprecated
- No Dynamic Properties By Default
- Relative Callable In Strings
- Return Reference On Void
- Sorting Closure Must Return Integers
- get_class() Needs An Argument
- parent Cannot Be Used Anymore In Callable Arrays
- self Cannot Be Used Anymore In Callable Arrays
- static Cannot Be Used Anymore In Callable Arrays
- strpos() With Null Haystack
8.6
- Base Conversion Functions Warn When Precision Is Lost
- CURLOPT_READFUNCTION Callback Validates Its Return Value
- DOM Readonly Properties Use Asymmetric Visibility
- Enums May Now Define __debugInfo()
- GMP Shift Operators Validate A GMP Right Operand
- Making A Constructor A Generator Is Deprecated
- Making A Destructor A Generator Is Deprecated
- Multiple File Functions Reject NUL Bytes In Filenames
- Override Attribute Extended To Class Constants And Enum Cases
- Readonly Properties Can Have Default Values
- Session Cookies Default To Secure Settings
- Session Functions Reject NUL Bytes And session_encode() Changes Its Empty-Session Return Value
- Several GMP Functions Validate Their Numeric Arguments
- SplFileObject::fgets() No Longer Caches The Current Line
- SplFileObject::next() Always Advances The Stream
- SplObjectStorage::getHash() May No Longer Mutate Storage
- Stateless Static Closures Are Cached And Reused
- WeakMap Allows Reference Assignment To A Missing Key
- array_change_key_case() Validates Its Case Argument
- array_filter() Validates Its Mode Argument
- array_intersect() Converts Values While Scanning Inputs
- dl() Rejects NUL Bytes In The Extension Filename
- getenv() Rejects NUL Bytes In The Variable Name
- gmp_powm() Reports More Detail On Modulo-By-Zero
- hexdec() Warns When The Result Loses Precision
- ini_get_all() Includes The Built-in Default Value
- isset()-Materialized Magic Property Skips __get()
- linkinfo() Rejects An Empty Path
- namespace Is Not Valid As A Class Constant Name
- number_format() Validates Its Decimals Argument Range
- openlog() Rejects NUL Bytes In The Prefix
- parse_str() Rejects NUL Bytes In The Query String
- pathinfo() Validates Its Flags Argument
- preg_grep() Returns false On A PCRE Execution Error
- proc_open() Rejects NUL Bytes In The Working Directory
- putenv() Rejects NUL Bytes In The Assignment
- scandir() Validates Its Sorting Order Argument
- setlocale() Rejects Extra Arguments When $locales Is An Array
- sleep() Validates Its Seconds Argument Range
- sodium_crypto_pwhash_str() Throws ValueError For Out-Of-Range Limits
- trim() Strips Form Feed By Default
- unpack() Strips Endianness Modifiers From The Result Key
- usleep() Validates Its Microseconds Argument Range
8.5-
8.5
- $http_response_header Is Deprecated
- Array Has No Absolute Name
- Cannot Bind $this To Static Closure
- Cast In Const
- Destructuring Non Arrays
- E_USER_ERROR Is Deprecated
- Final Promoted Properties
- First-Class Callables Inside Constant Expressions Must Resolve To A Literal Method Name
- Increment Non-alphanumeric
- Magic Method __debugInfo() cannot return null
- No Case With A Semi-colon
- Non-canonical Cast
- Not In A Closure
- Null As Array Offset
- Null With array_key_exists()
- Override Attribute Extended To Properties
- PHP 8.5 split the missing-abstract-method message by declaration kind
- Providing A String That Is Not One Byte Long To ord() Is Deprecated
- Reflection Doesn’t Return Self
- Reflection Doesn’t Return Static
- array And callable Cannot Be Absolute
- get_defined_functions() Doesn’t Exclude Diabled Functions Anymore
8.4
- Alias Replace Class
- Array Syntax With Curly Braces Are No More
- Call Method On New
- Constant %s is deprecated
- Die And Exit As Functions
- Direct calls on new
- Init Readonly Properties In Child Class
- Length Of tempnam() Has Been Raised
- Recursive Comparison Of Arrays
- Underscore Named Class
- Unserialize with the upper case S is deprecated
- FUNCTION In Closure Changed
- fputcsv() Needs Escape Parameter
- http_build_query() supports enumerations
- round() Mode Validation
- strftime() And gmstrftime() Are Deprecated
8.3
- Backed Enum Values Needed To Compile
- Calling FFI::cast() statically is deprecated
- Calling FFI::new() statically is deprecated
- Calling FFI::type() statically is deprecated
- Can Clone Readonly Properties
- Comment Inside yield from
- Constants In Trait
- Duplicate Static Definition
- Dynamic Class Constant
- Final Method In Trait
- Interface Constant Visibility Checks
- Interface Imported Constant Visibility Is Checked
- Negative Automatic Index From Empty Array
- Relaxed Naming With Class Constant
- Static Variable Accepts Functioncalls As Default
- Storage Of Static Properties Trait
- Typed Class Constant
- array_product() New Checks
- array_sum() Checks Operands Thoroughly
- class_alias() Works On Internal Classes
- range() Lists Everything Between Strings
- range() Uses Single Byte Strings
- range() With Int And String
- unserialize() Checks The End Of The String
- unserialize() Error Report
8.2
- ${expression} is deprecated
- Constants In Traits
- Duplicate Enum Cases Are Not Linted Anymore
- No Reference To $GLOBALS Variable
- Tentative Static Returntype With Datetime
- datetime With Multiple Signs
- iterator_count() Also Count Arrays
- krsort() Places Integers Before Strings In Keys
- ksort() Now Places Integers Before Strings
- ksort() now uses regular comparison
- mb_convert_encoding() Has Deprecated Formats
- strsplit() With Empty String
- var_export() Format
8.1
- $GLOBALS Assignement
- Accessing Directly Properties In Trait
- Appending To $GLOBALS
- Cannot Explode() Null
- Clone A Constant
- Constants With Objects
- Copy Of $GLOBALS
- Default Values With htmlspecialchars()
- Enforcing Return Type With Spl Classes
- FilessytemIterator Skips Dot Files
- Final Class Constants
- Finfo Moved Away From Resource
- Indirect Modification In Clone
- Named Parameters And Variadic
- Nested Attributes
- Never Arrow Function
- No More dir() With Null
- PHP native return types are now enforced
- Passing Objects Is Deprecated
- Static Variables Are Linked To Their Method
- Unpack Arrays In Arrays
- compact() Throws Notice On Missing Variable
- jsonSerialize Must Have Return Type
- never Is Now A Keyword
- version_compare() Stricter Operators
8.0
- $php_errormsg has been removed
- (real) Is Replaced By (float)
- (unset) Was Removed
- ::class On Object
- Anonymous Class Name Includes Extends
- Array Usage With String Initialisation
- Automatic Index In Non Empty Array
- Bitshift And Concat Precedence
- Calling Non-Static Method Statically
- Calling Static Methods On Strings
- Catch Without Variable
- Covariance And Contravariance Are Fatal
- Creating Object On Null
- Dot And Bitshift Priority
- Dot And Minus Changed Precedence
- Dot And Plus Changed Precedence
- Inegality Comparisons
- Integer Non-silent Conversion
- Integer Regex With mb_ereg_replace()
- Interpolated String Dereferencing
- Keywords In Namespace
- Method export() in Reflection Is removed
- New Cases In Switch
- No Abstract Private Method In Traits
- No Max() On Empty Array
- No New Line In Namespaces
- No min() On Empty Array
- Non Static Method Called Statically
- Old Constructors
- Old Style Constructor
- Only First Byte
- Optional Parameter Are After Compulsory Parameters
- Orphaned Parent
- PHP Constants Are Not Case Insensitive
- Plus And Concat Precedence
- Removing $this from a closure is deprecated
- Spaces In Namespaces
- SplFixedArray Is Now An IteratorAggregate
- String To Integer Comparison
- Strings Are Bigger Than Integers
- Ternary Associativity
- Trailing Comma In Arguments
- Undefined Constants
- Unpack Array With String Keys
- Using __autoload() is deprecated
- __set_state() Method Must Be Static
- __sleep() Method Enforces Return Type
- array_key_exists() doesn’t work on objects
- assert Is Reserved Function
- assert() Throws Exception
- cUrl Moved Away From Resource
- ceil() Strict Mode
- convert_uuencode() Works On Empty Strings
- count() Must Count Countable
- curl_close() No Longer Flushes The Cookie Jar
- each() Has Been Removed
- each() Is No More
- explode() Forbids Empty Strings
- file_get_contents() Needs A Real Path
- get_called_class() Cannot Be Called Outside A Class
- implode() Arguments Order
- in_array() Doesn’t Confuse 0 And Empty String
- in_array() String Int Comparisons
- libxml_disable_entity_loader() Is Deprecated
- match Is Now A Keyword
- max() Must Contain At Least One Element
- max() On String And Integer
- mb_strrpos() Third Argument Is Not Encoding
- min() Doesn’t Accept Empty Arrays
- mixed Is Now A Keyword
- printf() Warns About Unknown Formats
- set_exception_handler() Must Update Its Type To Throwable
- setlocale() Does Not Affect Echo Anymore
- sort() Places Integers Before Strings
- spaceship Operator Results
- str_pos() Requires Only Strings
- str_replace() Checks For Arguments
- str_replace() Enforces Strings In Array Argument
- str_replace() On Arrays Of Objects
- str_split() Throws ValueError With Negative Lengths
- strpos() Does Not Accept False
- strpos() Does Not Accept Null As Second Parameter
- strpos() Emits TypeError
- strpos() Emits ValueError
- strpos() With Integer Argument
- strpos() With Out-Of-Range Offset Is A Fatal Error
- substr() Returns Empty String On Out Of Bond Offset
- switch() Changed Comparison Style
- throw Is An Expression
- vsprint() Requires An Array
- vsprintf() Returns Empty String On Error
7.4
- Auto-initialization From Boolean
- Base Conversion Reports Invalid Characters
- Boolean Used As Array
- Float Used As Array
- Integer Used As Array
- Null Used As Array
- PHP Warns When Finding Unconvertible Characters
- Parameter Contravariance
- Returntype Covariance
- __toString Can Throw Exceptions
- array_merge() And Variadic
- get_object_vars() Does Not Work On ArrayObject
- unserialize()
max_depthOption
7.3
- Heredoc Syntax In An Array
- Static Properties With References
- Trailing Comma In Calls
- instanceof Expect Objects
7.2
7.1
7.0
5.6
- Back-tick Operator Is Deprecated
- Calling A Function Named readonly() Is Deprecated
- Calling pg_fetch_result() With 2 Arguments Is Deprecated
- Calling stream_context_set_option() With 2 Arguments Is Deprecated
- Declaring A Class Constant Named namespace Is Deprecated
- GMP Shift And Power Operators Deprecate Float Operands
- Incomplete SessionHandlerInterface Implementations Now Warn
- Long php://filter Chains Are Deprecated
- Passing A $mode To mysqli_store_result() Is Deprecated
- Passing An Object As Filter Options To zlib/bzip2 Functions Is Deprecated
- Passing An Object To array_walk() Is Deprecated
- Passing An Object To mb_convert_variables() Is Deprecated
- ReflectionMethod Constructor With A Single Argument Is Deprecated
- ReflectionMethod::invoke() Deprecates Passing An Object For A Static Method
- ReflectionProperty::setValue() Deprecates A Wrong Object Type
- Returning A Value From A Constructor Is Deprecated
- Returning From A finally Block Is Deprecated
- Several ArrayIterator Methods Are Deprecated
- SplFileObject::fgetcsv() And fputcsv() Are Deprecated
- The mbstring Regex (mbregex) Functions Are Deprecated
- __debugInfo() Nullable Return Type Is Deprecated
- http_build_query() With An Object Is Deprecated
- is_double(), is_long(), is_integer() And doubleval() Are Deprecated
- metaphone() Is Deprecated
- mysqli::stmt_init() And mysqli_get_charset() Are Deprecated
- spl_classes() Is Deprecated
- spl_object_hash() Is Deprecated
- strcoll() And SORT_LOCALE_STRING Are Deprecated
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.
- Negative Automatic Index From Empty Array
- Automatic Index In Non Empty Array
- array_intersect() Converts Values While Scanning Inputs
- Bitshift And Concat Precedence
- Catch Without Variable
- convert_uuencode() Works On Empty Strings
- curl_close() No Longer Flushes The Cookie Jar
- cUrl Moved Away From Resource
- datetime With Multiple Signs
- Dot And Bitshift Priority
- FilessytemIterator Skips Dot Files
- Finfo Moved Away From Resource
- func_get_arg() Changed Behavior
- get_object_vars() Does Not Work On ArrayObject
- Copy Of $GLOBALS
- hexdec() Warns When The Result Loses Precision
- Default Values With htmlspecialchars()
- http_build_query() supports enumerations
- in_array() String Int Comparisons
- in_array() Doesn’t Confuse 0 And Empty String
- Increment Non-alphanumeric
- Increment On Boolean Is Deprecated
- Inegality Comparisons
- ini_get_all() Includes The Built-in Default Value
- krsort() Places Integers Before Strings In Keys
- ksort() Now Places Integers Before Strings
- isset()-Materialized Magic Property Skips __get()
- max() On String And Integer
- Negative Index On Strings
- New Cases In Switch
- Plus And Concat Precedence
- preg_grep() Returns false On A PCRE Execution Error
- range() Lists Everything Between Strings
- Reflection Doesn’t Return Self
- Reflection Doesn’t Return Static
- Session Functions Reject NUL Bytes And session_encode() Changes Its Empty-Session Return Value
- Session Cookies Default To Secure Settings
- setlocale() Does Not Affect Echo Anymore
- spaceship Operator Results
- SplFileObject::fgets() No Longer Caches The Current Line
- SplFileObject::next() Always Advances The Stream
- SplFixedArray Is Now An IteratorAggregate
- Static Variables Are Linked To Their Method
- Static Properties With References
- String To Integer Comparison
- Strings Are Bigger Than Integers
- strpos() With Integer Argument
- substr() Returns Empty String On Out Of Bond Offset
- switch() Changed Comparison Style
- Length Of tempnam() Has Been Raised
- Tentative Static Returntype With Datetime
- trim() Strips Form Feed By Default
- Typed Class Constant
- var_export() Format
- WeakMap Allows Reference Assignment To A Missing Key
PHP Extensions
- SPL
- Several ArrayIterator Methods Are Deprecated
- Enforcing Return Type With Spl Classes
- FilessytemIterator Skips Dot Files
- iterator_count() Also Count Arrays
- SplFileObject::fgets() No Longer Caches The Current Line
- SplFileObject::next() Always Advances The Stream
- SplFixedArray Is Now An IteratorAggregate
- spl_object_hash() Is Deprecated
- SplObjectStorage::getHash() May No Longer Mutate Storage
- standard
- Core
- curl
- date
- dom
- Reflection
- Method export() in Reflection Is removed
- Reflection Doesn’t Return Self
- Reflection Doesn’t Return Static
- ReflectionMethod::invoke() Deprecates Passing An Object For A Static Method
- ReflectionMethod Constructor With A Single Argument Is Deprecated
- ReflectionProperty::setValue() Deprecates A Wrong Object Type
- FFI
- fileinfo
- finfo
- gmp
- libxml
- mbstring
- mysqli
- pgsql
- pcre
- session
- sodium
- spl
- iconv
- intl
- zlib
- bz2
PHP Error Messages
- Cannot use “array” as a type name as it is reserved
- Cannot use “%s” as a type name as it is reserved
- %s %s contains %d abstract method%s and must therefore be declared abstract or implement the remaining method%s (
- %s %s must implement %d abstract method%s (
- %s method %s::%s() must not be abstract
- Abstract function t::foo() cannot be declared private
- Calling static trait method t::foo is deprecated, it should only be called on a class using the trait
- Cannot use%s %s as %s because the name is already in use
- Uncaught TypeError: array_key_exists(): Argument #2 ($array) must be of type array, stdClass given
- array_merge() expects at least 1 parameter, 0 given
- Cannot access offset of type string on string
- array_change_key_case(): Argument #2 ($case) must be either CASE_LOWER or CASE_UPPER
- array_filter(): Argument #3 ($mode) must be one of ARRAY_FILTER_USE_VALUE, ARRAY_FILTER_USE_KEY, or ARRAY_FILTER_USE_BOTH
- Method ArrayIterator::getFlags() is deprecated since 8.6
- Method ArrayIterator::setFlags() is deprecated since 8.6
- Method ArrayIterator::asort() is deprecated since 8.6
- array_product(): Multiplication is not supported on type array
- array_sum(): Addition is not supported on type array
- array_walk(): Passing an object for argument #1 $array to array_walk() is deprecated, call get_object_vars() first instead
- Defining a custom assert() function is deprecated, as the function has special semantics
- Static property may not have asymmetric visibility
- Cannot auto-initialize an array inside property %s::$%s of type %s
- __autoload() is no longer supported, use spl_autoload_register() instead
- Enum case value must be compile-time evaluatable
- The backtick (`) operator is deprecated, use shell_exec() instead
- Input number is larger than PHP_INT_MAX, precision has been lost in conversion
- Trying to access array offset on %s
- Non-static method %s::%s() should not be called statically
- Accessing static trait property %s::%s is deprecated, it should only be accessed on a class using the trait
- Case statements followed by a semicolon (;) are deprecated, use a colon (:) instead
- define(): Argument #3 ($case_insensitive) is ignored since declaration of case-insensitive constants is no longer supported
- define(): delaration of case insensitive constants is deprecated
- Constant expression contains invalid operations
- syntax error, unexpected ‘)’, expecting ‘|’ or variable (T_VARIABLE)
- ceil(): Argument #1 ($num) must be of type int|float, GMP given
- must be a user-defined class name, internal class name given
- Cannot use ::class with dynamic class name
- Using “_” as %s is deprecated since 8.4
- __clone method called on non-object
- Cannot modify readonly property %s::$%s
- Undefined variable
- Cannot use dynamic method name in constant expression
- Illegal method name
- Access level to x::IPri must be public (as in interface i)
- Traits cannot have constants
- Declaring class constant called ‘namespace’ is deprecated
- 0
- Returning a value from a constructor is deprecated
- Returning a value from a destructor is deprecated
- Declaration of %s::%s() should be compatible with %s::%s()
- count(): Argument #1 ($value) must be of type Countable|array, int given
- Creating default object from empty value
- Function curl_close() is deprecated since 8.5, as it has no effect since PHP 8.0
- The CURLOPT_READFUNCTION callback must return a string or CURL_READFUNC_ABORT or CURL_READFUNC_PAUSE
- Array and string offset access syntax with curly braces is deprecated
- syntax error, unexpected token “{”
- Returning null from %s::__debugInfo() is deprecated, return an empty array instead
- Constant SUNFUNCS_RET_TIMESTAMP is deprecated
- Cannot use %s as array
- Call to undefined function exit()
- Passing null to parameter #1 ($directory) of type string is deprecated
- dl(): Argument #1 ($extension_filename) must not contain any null bytes
- Using ${expr} (variable variables) in strings is deprecated, use {${expr}} instead
- The behavior of unparenthesized expressions containing both ‘.’ and ‘+’/‘-’ will change in PHP 8: ‘+’/‘-’ will take a higher precedence
- Duplicate declaration of static variable $%s
- syntax error, unexpected token “;”, expecting “(”
- Creation of dynamic property User::$name is deprecated
- Constant %s is deprecated
- Passing E_USER_ERROR to trigger_error() is deprecated since 8.4, throw an exception or call exit with a string message instead
- The each() function is deprecated. This message will be suppressed on further calls
- 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
- Return type of %s::%s() should either be compatible with %s::%s(): mixed
- Empty delimiter
- %s(): Passing null to parameter #%
- file_get_contents(): Filename cannot be empty
- file_exists(): Argument #1 ($filename) must not contain any null bytes
- 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
- Cannot use ‘final’ as constant modifier
- Cannot use ‘final’ as method modifier
- Cannot use the final modifier on a parameter
- the $escape parameter must be provided as its default value will change
- Generator return type must be a supertype of Generator
- get_called_class() called from outside a class
- Calling get_class() without arguments is deprecated
- getenv(): Argument #1 ($name) must not contain any null bytes
- syntax error, unexpected token “->”, expecting “,” or “;”
- Cannot append to $GLOBALS
- Cannot acquire reference to $GLOBALS
- Implicit conversion from float 2.5 to int loses precision
- gmp_fact(): Argument #1 ($num) must be between 0 and 18446744073709551615
- gmp_powm(): Argument #3 ($modulus) Modulo by zero
- Shift must be between 0 and 18446744073709551615
- syntax error, unexpected end of file
- The predefined locally scoped $http_response_header variable is deprecated, call http_get_last_response_headers() instead
- http_build_query(): Passing an object for argument #1 $data to http_build_query() is deprecated, call get_object_vars() first instead
- Implicit conversion from float 15.5 to int loses precision
- Default value for property of type int may not be null. Use the nullable type ?int to allow null default value
- implode(): Argument #2 ($array) must be of type ?array, string given
- Increment on non-numeric string is deprecated, use str_increment() instead
- Increment on type bool has no effect, this will change in the next major version of PHP
- Cannot indirectly modify readonly property X::$p
- Csyntax error, unexpected identifier “readonly”, expecting “function” or “const”
- Cannot %s readonly property %s::$%s from %s%s
- instanceof expects an object instance, constant given
- Cannot access %s constant %s::%s
- Access level to %s::%s must be %s (as in %s)
- [syntax error, unexpected ’’, expecting ‘;’ or ‘,’
- syntax error, unexpected ‘::’ (T_PAAMAYIM_NEKUDOTAYIM)
- syntax-error,-unexpected-‘::’-(t_paamayim_nekudotayim),-expecting-‘;’-or-‘,’
- A non-numeric value encountered
- Invalid characters passed for attempted conversion, these have been ignored
- Function is_double() is deprecated since 8.6, use is_float() instead
- Function is_long() is deprecated since 8.6, use is_int() instead
- Function is_integer() is deprecated since 8.6, use is_int() instead
- Function doubleval() is deprecated since 8.6, use floatval() instead
- Cannot use isset() on the result of an expression (you can use “null !== expression” instead)
- Uncaught TypeError: iterator_count(): Argument #1 ($iterator) must be of type Traversable, array given
- syntax error, unexpected token “–”, expecting “{”
- Function libxml_disable_entity_loader() is deprecated since 8.0, as external entity loading is disabled by default
- linkinfo(): Argument #1 ($path) must not be empty
- Duplicate value in enum A for cases A and B
- Declaration of SuperFoo::process(array $items): array should be compatible with Foo::process(stdClass $item): array
- syntax error, unexpected token “match”, expecting “(”
- Array must contain at least one element
- Must contain at least one element
- Handling Base64 via mbstring is deprecated; use base64_encode/base64_decode instead
- Handling HTML entities via mbstring is deprecated; use htmlspecialchars, htmlentities, or mb_encode_numericentity/mb_decode_numericentity instead
- Handling QPrint via mbstring is deprecated; use quoted_printable_encode/quoted_printable_decode instead
- Handling Uuencode via mbstring is deprecated; use convert_uuencode/convert_uudecode instead
- mb_convert_variables(): Passing an object for argument #3 $vars to mb_convert_variables() is deprecated, call get_object_vars() first instead
- Function mb_ereg() is deprecated since 8.6, because the underlying library is no longer maintained
- Function metaphone() is deprecated since 8.6, use a userland phonetic matching library instead
- Cannot use ‘mixed’ as class name as it is reserved
- syntax error, unexpected ‘;’, expecting ‘{’
- syntax error, unexpected fully qualified name “\B”
- Keys must be of type int|string during array unpacking
- Method mysqli::stmt_init() is deprecated since 8.6, use mysqli::prepare() instead
- Function mysqli_get_charset() is deprecated since 8.6, did you mean mysqli_character_set_name()?
- Passing the $mode parameter is deprecated since 8.4, as it has been ignored since 8.1
- Cannot combine named arguments and argument unpacking
- Uninitialized string offset: -1
- A never-returning function must not return
- syntax error, unexpected token “(”
- syntax error, unexpected token “->”
- serialize(): __sleep should return an array only containing the names of instance-variables to serialize
- Non-canonical cast (binary) is deprecated, use the (string) cast instead
- Non-canonical cast (binary) is deprecated, use the (bool) cast instead
- Non-canonical cast (double) is deprecated, use the (float) cast instead
- Non-canonical cast (integer) is deprecated, use the (int) cast instead
- Non-static method Foo::bar() cannot be called statically
- Call to undefined method Closure::getCurrent()
- Current function is not a closure
- Using null as an array offset is deprecated, use an empty string instead
- Using null as the key parameter for array_key_exists() is deprecated, use an empty string instead
- number_format(): Argument #2 ($decimals) must be between -2147483648 and 2147483647
- Methods with the same name as their class will not be constructors in a future version of 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
- Only the first byte will be assigned to the string offset
- openlog(): Argument #1 ($prefix) must not contain any null bytes
- Required parameter $%s follows optional parameter $%s
- Cannot use “parent” when current class scope has no parent
- %s::%s has #[\Override] attribute
- %s::$%s has #[\Override] attribute
- Use of “parent” in callables is deprecated
- parse_str(): Argument #1 ($string) must not contain any null bytes
- Calling %s() on an object is deprecated
- pathinfo(): Argument #2 ($flags) must be one of the PATHINFO_* constants
- Calling pg_fetch_result() with 2 arguments is deprecated, use the 3-parameter signature with a null $row parameter instead
- Unknown format specifier “Z”
- proc_open(): Argument #4 ($cwd) must not contain any null bytes
- putenv(): Argument #1 ($assignment) must not contain any null bytes
- 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
- 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
- Calling a function “readonly” is deprecated
- Cannot modify readonly property Point::$x
- The (real) cast is deprecated, use (float) instead
- Nesting level too deep - recursive dependency?
- Calling ReflectionMethod::invoke() for static method %s::%s() does not need an object parameter
- Calling ReflectionMethod::__construct() with 1 argument is deprecated, use ReflectionMethod::createFromMethodName() instead
- Calling ReflectionProperty::setValue() with a given object that is not an instance of the class this property was declared in is deprecated
- Use of “self” in callables is deprecated
- syntax error, unexpected token “private”, expecting “=”
- Returning by reference from a void function is deprecated
- Returning from a finally block is deprecated
- Return type of x::current() should either be compatible with Iterator::current(): mixed
- must be a valid rounding mode (RoundingMode::*)
- scandir(): Argument #2 ($sorting_order) must be one of the SCANDIR_SORT_ASCENDING, SCANDIR_SORT_DESCENDING, or SCANDIR_SORT_NONE constants
- Class MyHandler implementing SessionHandlerInterface is missing the create_sid() method which will be required in PHP 9.0
- Class MyHandler implementing SessionHandlerInterface is missing the validateId() method which will be required in PHP 9.0
- session_module_name(): Argument #1 ($module) must not contain any null bytes
- Method x::__set_state() must be static
- foo::bar(): Argument #1 ($e) must be of type Exception, DivisionByZeroError given
- setlocale() expects exactly 2 arguments when argument #2 ($locales) is an array, 3 given
- ValueError: sleep(): Argument #1 ($seconds) must be between 0 and 4294967295
- sodium_crypto_pwhash_str(): Argument #3 ($memlimit) must be greater than or equal to 8192
- usort(): Returning bool from comparison function is deprecated, return an integer less than, equal to, or greater than zero
- syntax error, unexpected fully qualified name “\Package”, expecting “{”
- Function spl_classes() is deprecated since 8.6, use ReflectionExtension::getClassNames() instead
- Method SplFileObject::fgetcsv() is deprecated since 8.6
- Method SplFileObject::fputcsv() is deprecated since 8.6
- Function spl_object_hash() is deprecated since 8.6, consider using spl_object_id() instead
- Modification of SplObjectStorage during getHash() is prohibited
- Cannot bind an instance to a static closure
- Use of “static” in callables is deprecated
- strpos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior
- str_replace(): Argument #2 ($replace) must be of type string when argument #1
- Object of class stdClass could not be converted to string
- Array to string conversion
- Function strcoll() is deprecated since 8.6, use Collator::compare() instead
- Constant SORT_LOCALE_STRING is deprecated since 8.6, use one of the Collator::sort() methods instead
- Calling stream_context_set_option() with 2 arguments is deprecated, use stream_context_set_options() instead
- Function %s() is deprecated%S
- Increment on non-alphanumeric string is deprecated
- %s(): Argument #%d ($%s) must be contained in argument #%d ($%s)
- Offset not contained in string.
- Argument #3 ($offset) must be of type int, string given
- Offset not contained in string
- Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior
- Passing null to parameter #2 ($needle) of type string is deprecated
- Passing null to parameter #1 ($haystack) of type string is deprecated
- Argument #2 ($length) must be greater than 0
- Unparenthesized `a ? b : c ? d : e` is not supported.
- Using $this when not in object context
- syntax error, unexepected ‘throw’ (T_THROW)
- Method %s::%s() must not throw an exception, caught %s
- syntax error, unexpected ‘)’, expecting variable (T_VARIABLE)
- syntax error, unexpected identifier “%s”, expecting variable
- Object of class %s could not be converted to string
- Uncaught Error: Undefined constant “%s”
- Cannot unpack array with string keys
- unserialize(): Extra data starting at offset 37 of 39 bytes
- Maximum depth of %d exceeded. The depth limit can be changed using the max_depth unserialize() option
- Error at offset 0 of 17 bytes
- Unserializing the ‘S’ format is deprecated
- The (unset) cast is deprecated
- ValueError: usleep(): Argument #1 ($microseconds) must be between 0 and 4294967295
- version_compare(): Argument #3 ($operator) must be a valid comparison operator
- Too few arguments
- Argument #%d ($%s) must be of type %s, %s given
- The arguments array must contain %d items, %d given
- Call to undefined function each()
- Undefined constant “%s”
- Power of base 0 and negative exponent is deprecated
- deflate_init(): Passing an object for argument #2 $option to deflate_init() is deprecated, call get_object_vars() first instead
- inflate_init(): Passing an object for argument #2 $option to inflate_init() is deprecated, call get_object_vars() first instead
- stream_filter_append(): Passing an object for filter parameters for zlib.deflate is deprecated, call get_object_vars() first instead
- stream_filter_append(): Passing an object for filter parameters for bzip2.compress is deprecated, call get_object_vars() first instead
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
- %s %s contains %d abstract method%s and must therefore be declared abstract or implement the remaining method%s (
- %s %s must implement %d abstract method%s (
- %s method %s::%s() must not be abstract
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
- Method ArrayIterator::getFlags() is deprecated since 8.6
- Method ArrayIterator::setFlags() is deprecated since 8.6
- Method ArrayIterator::asort() is deprecated since 8.6
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
- define(): Argument #3 ($case_insensitive) is ignored since declaration of case-insensitive constants is no longer supported
- define(): delaration of case insensitive constants is deprecated
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
- Returning a value from a constructor is deprecated
- Returning a value from a destructor is deprecated
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
- The CURLOPT_READFUNCTION callback must return a string or CURL_READFUNC_ABORT or CURL_READFUNC_PAUSE
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
- Array and string offset access syntax with curly braces is deprecated
- syntax error, unexpected token “{”
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
- PHP RFC: Kill proprietary CSV escaping mechanism
- PHP 8.4: CSV: The $escape parameter must be provided
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
'
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
- Cannot indirectly modify readonly property X::$p
- Csyntax error, unexpected identifier “readonly”, expecting “function” or “const”
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
- [syntax error, unexpected ’’, expecting ‘;’ or ‘,’
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
- syntax error, unexpected ‘::’ (T_PAAMAYIM_NEKUDOTAYIM)
- syntax-error,-unexpected-‘::’-(t_paamayim_nekudotayim),-expecting-‘;’-or-‘,’
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
- Function is_double() is deprecated since 8.6, use is_float() instead
- Function is_long() is deprecated since 8.6, use is_int() instead
- Function is_integer() is deprecated since 8.6, use is_int() instead
- Function doubleval() is deprecated since 8.6, use floatval() instead
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
- Handling Base64 via mbstring is deprecated; use base64_encode/base64_decode instead
- Handling HTML entities via mbstring is deprecated; use htmlspecialchars, htmlentities, or mb_encode_numericentity/mb_decode_numericentity instead
- Handling QPrint via mbstring is deprecated; use quoted_printable_encode/quoted_printable_decode instead
- Handling Uuencode via mbstring is deprecated; use convert_uuencode/convert_uudecode instead
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
- Method mysqli::stmt_init() is deprecated since 8.6, use mysqli::prepare() instead
- Function mysqli_get_charset() is deprecated since 8.6, did you mean mysqli_character_set_name()?
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
- Non-canonical cast (binary) is deprecated, use the (string) cast instead
- Non-canonical cast (binary) is deprecated, use the (bool) cast instead
- Non-canonical cast (double) is deprecated, use the (float) cast instead
- Non-canonical cast (integer) is deprecated, use the (int) cast instead
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
- Class MyHandler implementing SessionHandlerInterface is missing the create_sid() method which will be required in PHP 9.0
- Class MyHandler implementing SessionHandlerInterface is missing the validateId() method which will be required in PHP 9.0
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
- Method SplFileObject::fgetcsv() is deprecated since 8.6
- Method SplFileObject::fputcsv() is deprecated since 8.6
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
- Standard PHP Library (SPL)
- Classes extending IteratorAggregate can not implement RecursiveIterator
- Introduction to Iterators and Generators in PHP
- IteratorAggregate
- Iterator
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
- Function strcoll() is deprecated since 8.6, use Collator::compare() instead
- Constant SORT_LOCALE_STRING is deprecated since 8.6, use one of the Collator::sort() methods instead
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
- Classes/StaticContainsThis
- Classes/ThisIsNotForStatic
- Classes/UsingThisOutsideAClass
- Classes/ThisIsForClasses
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
- var_export() combined with enum produces code unsuitable for inclusion in namespaces
- Add leading backslash to enum and class names in var_export
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
- deflate_init(): Passing an object for argument #2 $option to deflate_init() is deprecated, call get_object_vars() first instead
- inflate_init(): Passing an object for argument #2 $option to inflate_init() is deprecated, call get_object_vars() first instead
- stream_filter_append(): Passing an object for filter parameters for zlib.deflate is deprecated, call get_object_vars() first instead
- stream_filter_append(): Passing an object for filter parameters for bzip2.compress is deprecated, call get_object_vars() first instead