Tech14 min read

PHP Breaking Changes from 5.3 to 8.6: Functions, Arguments, and Namespaces

IkesanContents

When migrating legacy PHP code to newer versions, the most time-consuming issues stem not from fatal syntax errors, but from silent behavioral shifts where return values or comparison results change without raising notices or warnings. For example, 0 == "foo" evaluated to true in PHP 7.x, but evaluates to false starting in PHP 8.0. The script passes linting cleanly and throws no exceptions, yet executes with altered logic.

PHP 8.6 Release Candidate 4 (RC4) was released on October 22, 2026, ahead of its scheduled general availability on November 19, 2026. Below is a breakdown of backward-incompatible changes introduced from PHP 5.3 through 8.6, focusing on built-in function behaviors, argument handling, and namespace resolution, alongside the foundational object handling introduced in PHP 5.

Deprecations vs. Direct Behavioral Changes

Breaking changes in PHP fall into two distinct categories.

Deprecations issue an E_DEPRECATED notice while execution proceeds as usual. These features are generally removed or turned into errors in the following major version, such as features deprecated across PHP 8.x being targeted for removal in PHP 9.0. In environments where notice logging is suppressed, the application runs uninterrupted until reaching the release that drops the code entirely.

Direct behavioral changes, by contrast, take effect immediately upon upgrading. While changes that raise uncaught exceptions halt scripts and show up in server logs, modifications to comparison rules or data structure traversal alter calculations silently without triggering any error.

PHP 4 to 5 Object Assignment Switched to Handles

In PHP 4, $b = $a cloned the entire object. In PHP 5 and later, variables store an object identifier. Assignment causes both variables to point to the same instance, so updating a property on $b modifies $a. Code ported from PHP 4 that relied on implicit copies requires explicit calls to clone.

Namespaces and Legacy Directives in PHP 5.3 to 5.6

PHP 5.3 introduced namespaces, while PHP 5.4 removed several long-standing configuration directives and legacy APIs.

VersionChangeImpact / Failure Mode
5.3Deprecated ereg() family functions (ereg(), eregi(), split())Deprecated. Removed in PHP 7.0
5.3Introduced namespaces (\ separator chosen over :: during development)No impact on code without namespaces
5.4Removed register_globals, magic_quotes_gpc, and safe_modeDirectives removed entirely; code depending on automatic input escaping fails
5.4Changed default charset in htmlspecialchars() and htmlentities() from ISO-8859-1 to UTF-8Immediate. Passing Shift_JIS or EUC-JP strings without explicit encoding may return empty strings
5.6htmlspecialchars() and htmlentities() now default to php.ini’s default_charsetImmediate. Results depend on server configuration

Removed Functions and Evaluation Order in PHP 7.0

PHP 7.0 removed features deprecated throughout PHP 5.x and normalized variable evaluation order.

ChangeImpact / Failure Mode
Removed mysql_* functions (ext/mysql)Immediate. Functions no longer exist
Removed ereg family functions and preg_replace() /e modifierImmediate
Uniform variable evaluation order (left-to-right). $$foo['bar'] evaluated as ${$foo['bar']} in 5.x, but as ($$foo)['bar'] in 7.0+Immediate. Does not error, but accesses a different variable
list() assigns elements in left-to-right orderImmediate. Affects code assigning to the same variable multiple times in one list()
Non-by-reference foreach loops no longer advance array internal pointersImmediate. Calls to current() inside loops yield different results
Hexadecimal numeric strings like "0x1A" are no longer considered numericImmediate. is_numeric("0x1A") returns false

Progressive Warnings and Deprecations in PHP 7.1 to 7.4

The second half of the PHP 7 cycle introduced warnings and deprecations for operations that previously failed silently, establishing a migration bridge toward PHP 8.0.

VersionChangeImpact / Failure Mode
7.1Calling user functions with too few arguments throws ArgumentCountErrorImmediate. Halts execution
7.1Corrected mt_rand() algorithm implementationImmediate. The same random seed generates a different sequence, breaking reproducible test datasets
7.2Passing non-countable variables to count() emits a warningWarning. Becomes TypeError in PHP 8.0
7.2Deprecated each() and create_function()Deprecated. Removed in PHP 8.0
7.4Deprecated unparenthesized nested ternary operators and curly-brace array syntax ($str{0})Deprecated. Throws errors in PHP 8.0
7.4Deprecated passing separator and array in reverse order to implode()Deprecated. Removed in PHP 8.0
7.4Array access on non-arrays (null, int, etc.) emits a noticeNotice. Emits Trying to access array offset on value of type null

Comparison Engine Overhaul and Strict Errors in PHP 8.0

PHP 8.0 restructured type casting rules and tightened error reporting, altering several silent evaluation paths.

ChangeImpact / Failure Mode
Non-strict comparison == between numbers and non-numeric strings converts the number to string. 0 == "foo" was true in 7.x, but false in 8.0+Immediate, no error. Silently alters in_array(), switch, and array_search() outcomes
Internal functions throw TypeError on mismatched types and ValueError on out-of-range valuesImmediate. Code previously emitting warnings and returning null or false now throws uncaught exceptions
Resources converted to objects (cURL, GD, OpenSSL, XML parser, sockets)Immediate. is_resource() checks consistently fail. Check return values against false instead
Introduced named argumentsImmediate. Parameter names in functions and methods are now part of the public API
String concatenation . precedence changed relative to +, -, and bitwise operators. "Sum: " . $a + $b evaluated as ("Sum: " . $a) + $b in 7.x; in 8.0+, it evaluates $a + $b firstImmediate, no error
@ error suppression operator no longer silences fatal errorsImmediate. Previously masked fatal errors now halt execution
Methods named identically to their containing class are no longer treated as constructors (PHP 4 style)Immediate. Initialization logic fails to run
Sorting functions are now stableImmediate, no error. Relative order of equivalent elements is preserved
Non-string needles in strpos() and string functions are searched as literal stringsImmediate, no error. strpos($s, 65) searches for "65" instead of "A"

Null Restrictions and Default Settings in PHP 8.1 to 8.2

PHP 8.1 deprecated passing null to non-nullable parameters across built-in string and array functions, creating extensive notice output across older codebases.

VersionChangeImpact / Failure Mode
8.1Deprecated passing null to non-nullable internal function parameters (strlen(null), str_replace(), etc.)Deprecated. Slated to become TypeError in PHP 9.0
8.1mysqli default error reporting mode changed to throw exceptionsImmediate. Code checking $result === false now halts with uncaught mysqli_sql_exception
8.1PDO MySQL and SQLite return integers and floats as native PHP types under emulated preparesImmediate, no error. Strict checks like $row['id'] === "1" return false
8.1htmlspecialchars() default flags changed from ENT_COMPAT to ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401Immediate. Single quotes are now encoded as '
8.1Overriding internal class methods without return type declarations emits deprecation noticesDeprecated. Suppressed via #[\ReturnTypeWillChange] attribute
8.1Deprecated strftime(), gmstrftime(), and FILTER_SANITIZE_STRINGDeprecated
8.2Deprecated dynamic properties (assigning to undeclared class properties)Deprecated. Emits notices on legacy frameworks and classes
8.2Deprecated ${var} string interpolation and utf8_encode() / utf8_decode()Deprecated
8.2Case conversion functions (strtolower(), strtoupper()) no longer depend on system localeImmediate, no error. Locale-dependent character mappings stop converting
8.2Deprecated context-dependent callables such as "self::method"Deprecated
8.2str_split("") returns an empty array []Immediate, no error

Type Enforcement and Rounding Changes in PHP 8.3 to 8.4

PHP 8.3 and 8.4 tightened type consistency and adjusted floating-point rounding precision.

VersionChangeImpact / Failure Mode
8.3Deprecated calling get_class() and get_parent_class() without argumentsDeprecated
8.3Revised range() behavior to throw exceptions on invalid step values or boundariesImmediate
8.4Deprecated implicit nullable parameter types (function f(Foo $x = null))Deprecated. Requires explicit nullable syntax ?Foo $x = null
8.4Deprecated E_STRICT constant and passing E_USER_ERROR to trigger_error()Deprecated
8.4Removed preliminary decimal rounding inside round(). The previous workaround for floats like 0.285 (0.28499999999999998) caused misrounding on specific inputsImmediate, no error. Rounding outcomes change near fractional boundaries
8.4Passing an invalid rounding mode to round() throws ValueErrorImmediate. Halts execution
8.4Increased default password_hash() bcrypt cost from 10 to 12Immediate. Hashing computation time increases; existing hashes remain valid
8.4Unbundled IMAP, OCI8, PDO_OCI, and pspell extensions from core to PECLImmediate. Extensions must be installed separately

Cast Aliases and Null Array Keys in PHP 8.5

PHP 8.5 was released in November 2025, adding further deprecations to legacy aliases and null keys.

ChangeImpact / Failure Mode
Deprecated legacy cast aliases: (integer), (boolean), (double), and (binary)Deprecated. Use (int), (bool), (float), and (string)
Deprecated backtick shell execution syntax and semicolon-delimited case 'x'; statementsDeprecated
Deprecated using null as an array key and passing null to array_key_exists()Deprecated
Deprecated incrementing non-numeric strings with ++ ($s = "a"; $s++;)Deprecated. Use str_increment()
Deprecated passing multi-byte strings to ord() and out-of-range integers (outside 0–255) to chr()Deprecated
Deprecated no-op resource destructor functions: curl_close(), imagedestroy(), finfo_close(), and xml_parser_free()Deprecated
Deprecated $http_response_header auto-variable populated by HTTP stream wrappersDeprecated. Use http_get_last_response_headers()
Casting float values exceeding integer range to int emits a warningWarning
Casting NAN to other types emits a warningWarning
Destructuring non-array values emits a warningWarning
Changed raw integer values for PDO fetch mode constants (PDO::FETCH_GROUP, PDO::FETCH_UNIQUE, etc.)Immediate. Code using raw integer constants directly breaks
Removed disable_classes directiveImmediate
__sleep() and __wakeup() usage marked as soft-deprecatedSoft-deprecated without runtime notices

Evolution of Namespace Resolution Rules

PHP namespaces have undergone subtle refinements since their introduction. During early PHP 5.3 development, :: was initially used as the separator, but RFC consensus in October 2008 standardized on \.

When an unqualified function or constant is referenced inside a namespace, PHP falls back to global scope if no namespace-local definition exists. Classes, however, do not have fallback resolution. Calling strlen() inside a namespace invokes the global internal function, whereas referencing Exception without a leading backslash throws an error unless imported or defined locally. This asymmetry has remained consistent since 5.3.

In PHP 8.0, namespaced names like Foo\Bar are treated as single tokens during lexical scanning. This allowed newer reserved keywords like fn and match to appear as namespace components, but disallowed whitespace around separators (such as Foo \ Bar).

Starting with PHP 8.2, new standard library classes are placed under dedicated namespaces (such as Random\Randomizer in 8.2, Dom\HTMLDocument and BcMath\Number in 8.4, and Uri\Rfc3986\Uri in 8.5), which can collide with userland namespaces of the same name.

mb_ereg Deprecation and Secure Session Defaults in PHP 8.6

The PHP 8.6 deprecation RFC approved 31 out of 35 proposals, rejecting proposals to deprecate the list() construct, the _() gettext alias, in/out/inout identifiers, and the dechunk stream filter.

Deprecations in PHP 8.6

For internationalized applications, the primary change is the deprecation of the mbregex extension. Sixteen functions—including mb_ereg(), mb_ereg_replace(), mb_split(), and mb_regex_encoding()—are deprecated and targeted for removal in PHP 9.0, following the end of upstream Oniguruma maintenance in April 2025. Applications migrating to core PCRE functions (preg_* with the u modifier) must account for differences in character class and named group syntax.

CategoryDeprecated FeatureReplacement / Impact
mbregexmb_ereg(), mb_ereg_replace(), and 14 related functionsDeprecated (removal in 9.0). Use preg_* with u modifier or PECL mb_onig
Function Aliasesis_double(), is_integer(), is_long(), doubleval()Use is_float(), is_int(), and floatval()
Functionsspl_object_hash()Use spl_object_id()
Locale Sortingstrcoll(), SORT_LOCALE_STRING sort flagUse Collator from the intl extension
Functionsmetaphone(), spl_classes(), mysqli_get_charset()Use userland implementations or ReflectionExtension
ArgumentsThird argument $case_insensitive of define()Omit. Has been ignored since PHP 8.0
ArgumentsPassing class name strings to is_a() or is_subclass_of() with $allow_string = falsePass an object instance or set $allow_string to true
ArgumentsPassing objects to array-only parameters in array_walk(), http_build_query(), mb_convert_variables(), or compression functionsEmits TypeError on unsupported functions; convert using get_object_vars() first
Syntaxreturn statements inside finally blocksValue overrides caller return; return outside the block
SyntaxReturning values or creating generators in __construct() and __destruct()Return values are ignored; use empty return;
IdentifiersUsing let, is, or _ as class or constant names; readonly as function name; namespace as class constant nameReserved for future syntax. Causes compile errors if duplicated
SPLCSV methods on SplFileObject, ArrayIterator::asort() and related methodsUse standalone fgetcsv() and array sorting functions
mysqlimysqli_stmt_init(), parameterless mysqli_stmt constructorUse mysqli::prepare()
SessionSession handlers lacking create_sid() and validateId() methodsEmits deprecation notices when registered

Immediate Behavioral Changes in PHP 8.6

ChangeImpact / Failure Mode
Changed session configuration defaults to session.use_strict_mode=1, session.cookie_httponly=1, and session.cookie_samesite=LaxImmediate. JavaScript cannot read session IDs from document.cookie. Cross-site POST requests omit session cookies, causing external SSO handoffs (such as SAML SP-initiated flows) to lose session state unless explicitly configured in php.ini
trim(), ltrim(), and rtrim() strip form feed (\f) characters by defaultImmediate, no error
Eighteen filesystem functions (file_exists(), is_file(), filesize(), etc.) throw ValueError when paths contain NUL bytesImmediate. Previously returned false
setlocale() accepts arrays only as the first argumentImmediate. Passing an array as subsequent arguments throws TypeError
bindec(), hexdec(), octdec(), and base_convert() emit notices on unconvertible large numbersNotice. Invalid character inputs have emitted deprecation notices since 7.4, and an RFC proposing exceptions is underway for PHP 8.7
PDO MySQL and mysqlnd reset connection state with COM_RESET_CONNECTION when reusing persistent connectionsImmediate. Persistent connections fail on MySQL < 5.7.3 and MariaDB < 10.2.4. Code assuming connection variables persist across requests breaks

Why PHP 6 Was Skipped

Although PHP jumped directly from PHP 5 to PHP 7, PHP 6 was never officially released.

Development of PHP 6 began in 2005 with the goal of integrating native Unicode support into the engine core. Storing internal strings as UTF-16 significantly increased memory consumption and degraded performance, and updating the C extension ecosystem proved difficult. The initiative was abandoned in 2010. Non-Unicode features developed during the effort, including namespaces and late static binding, were backported into PHP 5.3 and 5.4.

Because PHP 6 had been widely discussed at conferences and documented in published books, the project held a vote in 2014 to decide on the next version name. To prevent confusion with the abandoned release, the community chose PHP 7 over PHP 6.

References