SnowDriven Research · Reference

PHP 8.2 to 8.4: what breaks in WordPress plugins, themes and PHP apps

PHP’s migration guides for 8.3 and 8.4 list every backward-incompatible change, deprecation and removed extension. We read them all for a WordPress plugin or theme and for a custom PHP app: 2 are likely to need attention, 24 are possible and 40 are rare. Each links to php.net, checked on 26 October 2026.

For
WordPress developers and PHP teams
Guides read
PHP 8.3 and 8.4, php.net
On this page
66 changes
Likely
2
Checked
26 Oct 2026

The short answer

The 2 changes we rate likely for a typical plugin, theme or app, and the extensions PHP 8.4 no longer ships.

Last updated

  1. Implicitly nullable parameter

    PHP 8.4 · Deprecation. A typed parameter with a default of null, implicitly widened to accept null, is deprecated. Declare the type nullable explicitly.

  2. fputcsv(), fgetcsv(), str_getcsv() need an explicit escape argument

    PHP 8.4 · Deprecation. Using the default value of the escape parameter for fputcsv(), fgetcsv() and str_getcsv() is now deprecated; it must be passed explicitly.

Removed from PHP 8.4

php.net says these extensions “have been moved to PECL and are no longer part of the PHP distribution”. Code that uses one stops working on PHP 8.4 unless the host installs it from PECL:

Before you change anything. This page is general information, not advice for your site or app. Each entry summarizes php.net’s own page, checked on 26 October 2026, and the PHP project can change its documentation, so read the linked page before you act on an entry. Whether a change is likely, possible or rare for you is our judgement for a typical plugin, theme or app, not the PHP project’s, and so is any fix marked as our suggestion. Back up, test every change on a copy that isn’t in production, keep a way back, and follow your own change process. You follow these steps at your own risk (terms). SnowDriven isn’t affiliated with the PHP project, WordPress or WooCommerce (trademarks).

Before you upgrade

The upgrade steps (back up, staging, update, switch, test) are in our PHP 8.2 guide. This page is the detail behind “what changes”.

  1. WordPress itself is ready

    WordPress’s compatibility table marks PHP 8.4 as supported for WordPress 6.7 and later. WooCommerce 10.8 and later lists “PHP version 8.3 or greater (tested up to PHP 8.4)”. So for a site or store, the question is the plugins and the theme (WordPress, WooCommerce, checked 26 October 2026).

  2. Deprecations warn; the rest can break

    A deprecation logs an E_DEPRECATED notice and the code keeps working on PHP 8.4. Backward-incompatible changes and removed extensions are the ones that can stop a page, a checkout or a job.

  3. Search for each “How to tell”

    Each entry names what to look for: a function, a constant, a setting or a warning. Our guide lists the tools that find these for you, static analysis and automated fixes.

  4. Watch the logs on a copy

    Switch a staging copy to PHP 8.4 with deprecation notices logged, not shown, then run the forms, logins and orders that matter. The log tells you which entries apply.

Security support for PHP 8.2 ends on 31 December 2026. What that means and the upgrade path: PHP 8.2 end of life.

Likely (2)

Changes most plugins, themes or apps moving from PHP 8.2 will meet. Check each one.

Possible (24)

Changes that hit some plugins, themes or apps, depending on what the code uses. Search for what’s under “How to tell”.

Backward-incompatible changes · 9

  • PHP 8.3DateBackward-incompatible changeDate extension throws its own errors and exceptionsThe DateTime extension now uses DateError and DateException hierarchies instead of warnings and generic exceptions, so code must check for these errors and exceptions.
    How to tell
    DateTime-related calls whose failures were handled via warnings or generic exceptions; the classes DateError and DateException.
    What to do
    Catch DateError and DateException where date input can be invalid; php.net notes that the change means checking for them.
    Why it’s possible (our view)
    Date parsing of user or stored input is common; code relying on warnings may now see thrown errors.

    php.net’s entry for this change

  • PHP 8.3StandardBackward-incompatible changerange() now throws errors and warnings for bad inputsrange() throws TypeError/ValueError for objects, arrays, resources, zero or wrong-sign steps, INF/NAN; warns on empty or multibyte strings; range('9', 'A') now returns characters.
    How to tell
    range() calls with object/array/resource bounds, step 0 or negative step on increasing ranges, INF/NAN, empty-string or mixed number/letter bounds.
    What to do (our suggestion)
    Pass valid integer, float or single-character bounds and a positive step for increasing ranges.
    Why it’s possible (our view)
    range() is common, but only odd or unvalidated inputs trigger the new errors and warnings.

    php.net’s entry for this change

  • PHP 8.4PHP CoreBackward-incompatible changeRemoval of E_STRICT error levelThe E_STRICT error level was removed because the engine no longer used it, and the E_STRICT constant is deprecated.
    How to tell
    E_STRICT in error_reporting() masks, the error_reporting ini value, set_error_handler() callbacks or wp-config.php snippets; an E_DEPRECATED notice where the constant is used.
    What to do (our suggestion)
    Remove E_STRICT from error masks and handlers; the level no longer exists.
    Why it’s possible (our view)
    Older plugins, themes, wp-config snippets and legacy apps often mask or test for E_STRICT.

    php.net’s entry for this change

  • PHP 8.4GdBackward-incompatible changeWordPress possible · apps rareGD image functions throw ValueError on invalid argumentsimagejpeg(), imagewebp(), imagepng() and imageavif() throw ValueError for invalid quality; imageavif(), imagescale() and imagefilter() (IMG_FILTER_SCATTER) also validate further parameters.
    How to tell
    imagejpeg(), imagewebp(), imagepng(), imageavif() quality arguments; imagescale() width, height and mode; imagefilter() with IMG_FILTER_SCATTER; uncaught ValueError.
    What to do (our suggestion)
    Validate quality and size arguments before calling, or catch ValueError.
    Why it’s possible (our view)
    WordPress: Image optimization and thumbnail plugins pass configurable quality values to GD. Custom apps: Apps doing image processing usually go through libraries with fixed, valid settings.

    php.net’s entry for this change

  • PHP 8.4MySQLndBackward-incompatible changeWordPress rare · apps possibleMySQL wait-timeout error code changed from 2006 to 4031For MySQL server 8.0.24 and above, the error code reported for server wait timeouts changed from 2006 to 4031.
    How to tell
    Code that compares MySQL error numbers with 2006, for example reconnect or retry logic.
    What to do
    Handle error code 4031 wherever 2006 is checked.
    Why it’s possible (our view)
    WordPress: Few plugins inspect raw MySQL error codes. Custom apps: Long-running workers and queue consumers often reconnect on specific error codes.

    php.net’s entry for this change

  • PHP 8.4OpcacheBackward-incompatible changeWordPress rare · apps possibleJITJIT defaults changed to opcache.jit=disable and opcache.jit_buffer_size=64M. Enabling JIT through opcache.jit_buffer_size alone no longer works; JIT initialization failure is now fatal at startup.
    How to tell
    opcache.jit and opcache.jit_buffer_size in php.ini or FPM pool configuration.
    What to do
    To enable JIT, set opcache.jit accordingly (php.net’s advice).
    Why it’s possible (our view)
    WordPress: A host setting; most WordPress sites do not tune JIT. Custom apps: Teams that enabled JIT via opcache.jit_buffer_size only will silently lose it.

    php.net’s entry for this change

  • PHP 8.4PCREBackward-incompatible changeBundled PCRE2 updated to 10.44Bundled pcre2lib is now 10.44: {,3} is recognized as a quantifier instead of text, and some character classes changed meaning in UCP mode.
    How to tell
    preg_* patterns containing a literal {,n}; patterns using character classes in UCP mode (our note: PHP’s /u modifier turns UCP on).
    What to do
    Escape the brace where a literal is meant; review the PCRE2 changelog that php.net links.
    Why it’s possible (our view)
    Regex-heavy content, shortcode and validation code is common, though the affected patterns are uncommon.

    php.net’s entry for this change

  • PHP 8.4PDO_MYSQLBackward-incompatible changeWordPress rare · apps possiblePDO MySQL attributes now booleanPDO::ATTR_AUTOCOMMIT, PDO::ATTR_EMULATE_PREPARES and PDO::MYSQL_ATTR_DIRECT_QUERY now act as boolean attributes; setAttribute() expects and getAttribute() returns a bool.
    How to tell
    Those attributes in PDO::setAttribute(), PDO::getAttribute() or constructor options, especially integer values or comparisons such as === 1.
    What to do
    Set and compare these attributes with true/false rather than integers.
    Why it’s possible (our view)
    WordPress: Most WordPress code talks to MySQL through wpdb, not PDO. Custom apps: Framework and plain-PHP apps commonly set PDO::ATTR_EMULATE_PREPARES.

    php.net’s entry for this change

  • PHP 8.4XMLBackward-incompatible changeWordPress possible · apps rarexml_set_*_handler now checks callables; xml_set_object() firstxml_set_*_handler functions now check for callable|string|null; method-name strings are checked against the xml_set_object() object, which must be set first. Empty-string handlers are deprecated.
    How to tell
    xml_set_*_handler calls (for example xml_set_element_handler()) that pass method-name strings; xml_set_object().
    What to do
    Pass a callable that refers to the method directly, as php.net recommends, instead of a method-name string with xml_set_object().
    Why it’s possible (our view)
    WordPress: Older feed, import and XML-RPC parsing code in plugins uses xml_set_object() with method names. Custom apps: Modern apps rarely use the expat xml_* functions directly.

    php.net’s entry for this change

Removed extensions · 1

  • PHP 8.4Removed ExtensionsRemoved extensionIMAP extension moved to PECLIMAP has been moved to PECL and is no longer part of the PHP distribution; new PECL releases are published ad hoc according to user demand.
    How to tell
    Calls to imap_open() and other imap_* functions; extension_loaded() checks; “undefined function” errors when the extension is absent.
    What to do
    Install IMAP from PECL if you still need it, since php.net says it moved there.
    Why it’s possible (our view)
    WordPress: Email-to-ticket, helpdesk and mailbox-polling plugins use IMAP. Custom apps: Apps that read mailboxes may call imap_* directly.

    php.net’s entry for this change

Deprecations · 9

  • PHP 8.3PHP CoreDeprecationSaner Increment/Decrement operators++ on empty, non-numeric or non-alphanumeric strings, and -- on empty or non-numeric strings, are deprecated. Incrementing non-numeric strings is soft-deprecated.
    How to tell
    E_DEPRECATED from ++ or -- applied to string variables that are empty or non-numeric (for example a counter initialized to '').
    What to do
    php.net says the new str_increment() function should be used instead of incrementing non-numeric strings. Initialize numeric counters as ints.
    Why it’s possible (our view)
    Loosely typed counters initialized as '' are a known pattern in older code.

    php.net’s entry for this change

  • PHP 8.3PHP CoreDeprecationget_class()/get_parent_class() call without argumentsCalling get_class() and get_parent_class() without arguments is now deprecated.
    How to tell
    get_class() or get_parent_class() with empty parentheses, typically inside class methods; E_DEPRECATED.
    What to do (our suggestion)
    Pass the object explicitly, for example get_class($this) or get_parent_class($this).
    Why it’s possible (our view)
    WordPress: Argument-less get_class() appears in older plugin and framework code. Custom apps: Modern app code rarely uses it, but older dependencies may.

    php.net’s entry for this change

  • PHP 8.4PHP CoreDeprecationUsing trigger_error() with E_USER_ERRORCalling trigger_error() with E_USER_ERROR as the error level is now deprecated.
    How to tell
    trigger_error(..., E_USER_ERROR).
    What to do
    Throw an exception or call exit(), whichever is more appropriate (php.net’s advice).
    Why it’s possible (our view)
    Some plugins and legacy apps use E_USER_ERROR to abort on fatal conditions.

    php.net’s entry for this change

  • PHP 8.4cURLDeprecationCURLOPT_BINARYTRANSFER constant deprecatedThe CURLOPT_BINARYTRANSFER constant is now deprecated.
    How to tell
    curl_setopt() or curl_setopt_array() using CURLOPT_BINARYTRANSFER.
    What to do (our suggestion)
    Remove the CURLOPT_BINARYTRANSFER option.
    Why it’s possible (our view)
    Older HTTP download and API-client code still sets this option.

    php.net’s entry for this change

  • PHP 8.4MySQLiDeprecationWordPress possible · apps raremysqli ping, kill, refresh and store_result mode deprecatedDeprecated: mysqli_ping()/ping(), mysqli_kill()/kill(), mysqli_refresh()/refresh() with MYSQLI_REFRESH_* constants, and passing mode to mysqli_store_result() (MYSQLI_STORE_RESULT_COPY_DATA).
    How to tell
    mysqli_ping(), mysqli::ping(), mysqli_kill(), mysqli::kill(), mysqli_refresh(), mysqli::refresh(), MYSQLI_REFRESH_*, mysqli_store_result() with a mode argument.
    What to do
    Use an SQL KILL command instead of kill(), and SQL FLUSH instead of refresh(). php.net names no ping() replacement.
    Why it’s possible (our view)
    WordPress: Plugins and database drop-ins that use mysqli directly sometimes call mysqli_ping() to check connections. Custom apps: Framework apps mostly use PDO.

    php.net’s entry for this change

  • PHP 8.4RandomDeprecationlcg_value() deprecatedlcg_value() is now deprecated, as the function is broken in multiple ways.
    How to tell
    lcg_value().
    What to do
    Use Random\Randomizer::getFloat() instead.
    Why it’s possible (our view)
    Still used in some plugins and older code for random floats.

    php.net’s entry for this change

  • PHP 8.4SessionDeprecationWordPress rare · apps possibleSession INI settings, SID constant and save-handler signature deprecatedDeprecated: session_set_save_handler() with more than two arguments; changing session.sid_length, sid_bits_per_character, use_only_cookies, use_trans_sid, trans_sid_tags, trans_sid_hosts or referer_check; the SID constant.
    How to tell
    session_set_save_handler() with more than two arguments; those session.* settings in php.ini, .user.ini or ini_set(); the SID constant.
    What to do
    Use the two-argument session_set_save_handler(); accept 32-character hexadecimal session IDs and stop changing these INI settings.
    Why it’s possible (our view)
    WordPress: WordPress itself does not rely on PHP sessions; only some plugins do. Custom apps: Apps and hardened host php.ini files sometimes set session.sid_length or session.use_only_cookies.

    php.net’s entry for this change

  • PHP 8.4SPLDeprecationSplFileObject CSV methods need an explicit escape argumentRelying on the default escape parameter of SplFileObject::setCsvControl(), fputcsv() and fgetcsv() is deprecated, unless setCsvControl() has set a new default.
    How to tell
    SplFileObject::setCsvControl(), SplFileObject::fputcsv() and SplFileObject::fgetcsv() calls without an escape argument.
    What to do
    Pass the escape argument explicitly, positionally or as a named argument.
    Why it’s possible (our view)
    CSV handling through SplFileObject is fairly common in import and export code.

    php.net’s entry for this change

  • PHP 8.4XMLDeprecationWordPress possible · apps rarexml_set_object() and non-callable handler strings deprecatedThe xml_set_object() function is deprecated, as is passing non-callable strings to the xml_set_* functions.
    How to tell
    xml_set_object(); xml_set_* handler calls given method-name strings.
    What to do
    Pass a callable that refers to the method directly (php.net’s recommendation on the incompatible-changes page).
    Why it’s possible (our view)
    WordPress: Older feed, import and XML-RPC parsing code in plugins uses this pattern. Custom apps: Modern apps rarely use the expat xml_* functions directly.

    php.net’s entry for this change

Other changes · 5

  • PHP 8.3StandardOther changeunserialize() notices promoted to warningsThe E_NOTICEs emitted by unserialize() have been promoted to E_WARNING.
    How to tell
    unserialize() on malformed or truncated data; E_WARNING in logs where E_NOTICE appeared before.
    What to do (our suggestion)
    Validate or repair serialized data, and handle unserialize() returning false.
    Why it’s possible (our view)
    WordPress: Plugins unserialize stored options, meta and transients; corrupted data now logs warnings. Custom apps: Apps unserializing cached or stored data may see louder logs.

    php.net’s entry for this change

  • PHP 8.3StandardOther changeunserialize() warns on trailing unconsumed bytesunserialize() now emits a new E_WARNING if the input contains unconsumed bytes.
    How to tell
    New E_WARNING from unserialize() for inputs with extra data after the serialized value.
    What to do (our suggestion)
    Ensure serialized strings are stored and passed without trailing data.
    Why it’s possible (our view)
    Serialized data altered by search-replace or concatenation can carry trailing bytes.

    php.net’s entry for this change

  • PHP 8.3StandardOther changearray_sum()/array_product() warn on unconvertible valuesarray_sum() and array_product() now warn when values cannot be converted to int/float; arrays and objects were previously ignored. Numeric-castable objects (for example GMP) are now cast.
    How to tell
    array_sum() or array_product() on arrays that may contain nested arrays, objects or non-numeric values; new warnings.
    What to do (our suggestion)
    Filter or cast array values to numbers before summing.
    Why it’s possible (our view)
    Summing loosely typed data (form input, meta values) is common.

    php.net’s entry for this change

  • PHP 8.4StandardOther changepassword_hash() bcrypt default cost raised from 10 to 12The default “cost” value for the PASSWORD_BCRYPT algorithm in password_hash() increased from 10 to 12.
    How to tell
    password_hash() with PASSWORD_BCRYPT or PASSWORD_DEFAULT (bcrypt, per the password_hash() manual page) and no explicit cost; tests or code that expect a cost of 10 in stored bcrypt hashes.
    What to do (our suggestion)
    Pass an explicit “cost” option if you need a specific value.
    Why it’s possible (our view)
    Hashing becomes slower, and rehash-on-login logic may start rehashing stored passwords (our inference).

    php.net’s entry for this change

  • PHP 8.4StandardOther changeRounding with round()round() was rewritten and “pre-rounding” removed, so some inputs round differently (for example 0.49999999999999994 now gives 0.0); mode accepts RoundingMode; precision extended one digit.
    How to tell
    round() calls, especially in price, tax and currency calculations.
    What to do (our suggestion)
    Re-test code whose results depend on round() edge cases.
    Why it’s possible (our view)
    E-commerce and finance code rounds constantly, though most results are unchanged.

    php.net’s entry for this change

Rare (40)

Changes that need a specific function, setting or pattern. Read the ones for features you use.

Backward-incompatible changes · 22

  • PHP 8.3PHP CoreBackward-incompatible changePrograms that were very close to overflowing the call stackPrograms very close to overflowing the call stack may now throw an Error once stack use exceeds zend.max_allowed_stack_size minus zend.reserved_stack_size (fiber.stack_size for fibers).
    How to tell
    An Error thrown during deep or runaway recursion; INI settings zend.max_allowed_stack_size, zend.reserved_stack_size, fiber.stack_size.
    What to do (our suggestion)
    Reduce recursion depth; the INI section of Other Changes documents zend.max_allowed_stack_size and zend.reserved_stack_size.
    Why it’s rare (our view)
    Only code that already recursed almost to a crash is affected.

    php.net’s entry for this change

  • PHP 8.3PHP CoreBackward-incompatible changeExecuting proc_get_status() multiple timesRepeated proc_get_status() calls now return the right value on POSIX; proc_close() after proc_get_status() now returns the real exit code instead of -1.
    How to tell
    proc_get_status() called more than once, or proc_close() after proc_get_status(), especially code expecting -1.
    What to do
    If the previous behavior is required, check the cached key in the array returned by proc_get_status().
    Why it’s rare (our view)
    WordPress: Process-spawning code is uncommon in themes and plugins. Custom apps: Only apps that manage child processes and depended on the old buggy value.

    php.net’s entry for this change

  • PHP 8.3PHP CoreBackward-incompatible changeUses of traits with static propertiesA trait’s static properties now redeclare those inherited from the parent class, giving the using class its own separate static property storage.
    How to tell
    Classes that use a trait declaring a static property that the parent class also has, where code expects parent and child to share the value.
    What to do (our suggestion)
    Review code that relied on a parent and a trait-using child sharing one static property.
    Why it’s rare (our view)
    Needs a specific trait-plus-inheritance pattern that relies on shared static state.

    php.net’s entry for this change

  • PHP 8.3PHP CoreBackward-incompatible changeAssigning a negative index to an empty arrayAfter assigning a negative index $n to an empty array, the next appended index is now $n + 1 instead of 0.
    How to tell
    Code that assigns a negative key to an empty array (for example $a[-5] = x) and then appends with $a[] = y.
    What to do (our suggestion)
    Use explicit keys where code depended on the next appended index being 0.
    Why it’s rare (our view)
    Negative keys on an empty array followed by appends is an unusual pattern.

    php.net’s entry for this change

  • PHP 8.3PHP CoreBackward-incompatible changeClass constant visibility variance checkClass constant visibility variance is now correctly checked when constants are inherited from interfaces.
    How to tell
    Classes that redeclare a constant inherited from an interface with a different visibility; surfaces as a compile-time error.
    What to do (our suggestion)
    Make the redeclared constant’s visibility compatible with the interface’s declaration.
    Why it’s rare (our view)
    Redeclaring interface constants with changed visibility is uncommon.

    php.net’s entry for this change

  • PHP 8.3DOMBackward-incompatible changeafter()/before()/replaceWith() on parentless node is a no-opDOMChildNode::after(), before() and replaceWith() on a node with no parent are now a no-op instead of throwing a hierarchy exception.
    How to tell
    Calls to DOMChildNode::after(), ::before(), ::replaceWith() inside try/catch blocks expecting a DOMException.
    What to do (our suggestion)
    Do not rely on an exception here; check the node’s parent explicitly if needed.
    Why it’s rare (our view)
    HTML-manipulating plugins use DOM, but relying on this exception is unusual.

    php.net’s entry for this change

  • PHP 8.3DOMBackward-incompatible changeDOMParentNode/DOMChildNode methods work without a documentDOMParentNode and DOMChildNode methods used without a document now work instead of throwing a DOM_HIERARCHY_REQUEST_ERR DOMException.
    How to tell
    Code catching DOMException with code DOM_HIERARCHY_REQUEST_ERR around DOMParentNode/DOMChildNode methods.
    What to do (our suggestion)
    Remove logic that depended on the DOM_HIERARCHY_REQUEST_ERR exception being thrown.
    Why it’s rare (our view)
    Depending on this exception is unusual.

    php.net’s entry for this change

  • PHP 8.3DOMBackward-incompatible changecreateAttributeNS() without prefix no longer creates default namespaceDOMDocument::createAttributeNS() without a prefix wrongly created a default namespace, putting the element in the namespace; this bug is fixed.
    How to tell
    Calls to DOMDocument::createAttributeNS() with a qualified name that has no prefix.
    What to do (our suggestion)
    Re-check XML output from code calling createAttributeNS() without a prefix.
    Why it’s rare (our view)
    WordPress: Namespaced attribute creation is mostly in XML/SVG/feed generation code. Custom apps: Possible only in apps generating namespaced XML.

    php.net’s entry for this change

  • PHP 8.3DOMBackward-incompatible changecreateAttributeNS() picks a new prefix on conflictDOMDocument::createAttributeNS() no longer throws DOM_NAMESPACE_ERRNAMESPACE_ERR when a prefix is used for a different URI; it chooses a different prefix.
    How to tell
    Calls to DOMDocument::createAttributeNS() wrapped in try/catch for DOMException, and XML output with reused prefixes.
    What to do (our suggestion)
    Check generated XML prefixes and remove handling for the old exception.
    Why it’s rare (our view)
    Requires namespaced XML with conflicting prefixes.

    php.net’s entry for this change

  • PHP 8.3DOMBackward-incompatible changeNew DOM methods/properties may clash with subclass declarationsNew methods and properties were added to some DOM classes; userland subclasses declaring the same names must be compatible or a compile error is thrown.
    How to tell
    Classes extending DOM classes (for example DOMDocument, DOMElement, DOMNode) that declare methods or properties; compile error about incompatible declarations.
    What to do
    Make the subclass declarations compatible; php.net points to the new-features and new-functions DOM lists for the new names.
    Why it’s rare (our view)
    WordPress: A few HTML-processing plugins extend DOM classes; a name clash is needed to break. Custom apps: Possible in apps or libraries that extend DOM classes.

    php.net’s entry for this change

  • PHP 8.3OpcacheBackward-incompatible changeopcache.consistency_checks INI directive removedThe opcache.consistency_checks INI directive was removed; it was broken and had been disabled since PHP 8.1.18 and 8.2.5.
    How to tell
    opcache.consistency_checks in php.ini, conf.d files, .user.ini or pool config.
    What to do (our suggestion)
    Remove the opcache.consistency_checks line from configuration.
    Why it’s rare (our view)
    WordPress: A server-config item that plugin code does not set. Custom apps: Host or Docker configs only; rarely set.

    php.net’s entry for this change

  • PHP 8.3StandardBackward-incompatible changenumber_format() handles negative decimalsnumber_format() now rounds num to abs($decimals) digits before the decimal point for negative decimals; previously negative decimals were ignored.
    How to tell
    number_format() calls whose decimals argument can be negative (for example computed values).
    What to do (our suggestion)
    Ensure decimals is not negative unless rounding before the decimal point is intended.
    Why it’s rare (our view)
    Negative decimals are usually a bug in the caller.

    php.net’s entry for this change

  • PHP 8.3StandardBackward-incompatible changefile() rejects all invalid flags, including FILE_APPENDThe file() flags error check now catches all invalid flags; notably FILE_APPEND, previously silently accepted, is now caught.
    How to tell
    file() calls passing FILE_APPEND or other flags that file() does not support.
    What to do (our suggestion)
    Remove FILE_APPEND or other unsupported flags from file() calls.
    Why it’s rare (our view)
    Passing FILE_APPEND to file() is a mistake that exists in some copy-pasted code.

    php.net’s entry for this change

  • PHP 8.4PHP CoreBackward-incompatible changeexit() behavioral changeexit() and die() now behave more like functions: callable, affected by strict_types, using normal type coercion. Invalid argument types now consistently throw TypeError.
    How to tell
    exit(...) and die(...) calls, especially in files with declare(strict_types=1); an uncaught TypeError from exit()/die().
    What to do (our suggestion)
    Check what is passed to exit()/die() and correct any value of an invalid type.
    Why it’s rare (our view)
    Most exit/die calls pass nothing, a string or an int; invalid types are uncommon.

    php.net’s entry for this change

  • PHP 8.4Extension Class Constants which are now TypedBackward-incompatible changeExtension Class Constants which are now TypedClass constants of the Date, Intl, PDO, Reflection, SPL, Sqlite and XMLReader extensions now declare a type.
    How to tell
    Constants of classes from those extensions, chiefly where a user class extends one and redeclares a constant (our inference; the page does not say).
    What to do (our suggestion)
    Check any subclass that redeclares one of these constants with a value of a compatible type.
    Why it’s rare (our view)
    Redeclaring extension class constants in subclasses is unusual.

    php.net’s entry for this change

  • PHP 8.4SOAPBackward-incompatible changeSoapClient $httpurl and $sdl are now objectsSoapClient::$httpurl is now a Soap\Url object and SoapClient::$sdl a Soap\Sdl object, rather than resources, so is_resource() checks on them fail.
    How to tell
    is_resource($client->httpurl) or is_resource($client->sdl).
    What to do
    Replace with null checks, as php.net shows: $client->httpurl !== null and $client->sdl !== null.
    Why it’s rare (our view)
    Few codebases inspect these internal SoapClient properties.

    php.net’s entry for this change

  • PHP 8.4SessionBackward-incompatible changeInvalid session GC ini values now emit a warningSetting a non-positive value for session.gc_divisor or a negative value for session.gc_probability now emits a warning.
    How to tell
    session.gc_divisor and session.gc_probability in php.ini, .user.ini, .htaccess, pool config or ini_set().
    What to do
    Set session.gc_divisor to a positive value and session.gc_probability to zero or more.
    Why it’s rare (our view)
    Such invalid values are unusual in host or app configuration.

    php.net’s entry for this change

  • PHP 8.4StandardBackward-incompatible changeround(), str_getcsv(), php_uname(), unserialize() now validate argumentsround() throws ValueError for invalid modes (formerly treated as PHP_ROUND_HALF_UP); str_getcsv(), php_uname() and the allowed_classes option of unserialize() now throw on invalid arguments.
    How to tell
    round() mode argument; str_getcsv() separator, enclosure and escape not one byte; php_uname() mode; unserialize() allowed_classes not an array of class names or bool.
    What to do
    Pass a valid round() mode, one-byte CSV separator/enclosure/escape, a valid php_uname() mode, and allowed_classes as class-name array or bool.
    Why it’s rare (our view)
    These invalid arguments were already bugs and are uncommon in practice.

    php.net’s entry for this change

  • PHP 8.4DOMBackward-incompatible changeDOMXPath cloning throws; DOMImplementation::getFeature() removedSome DOM allocation failures now consistently throw DOMException (DOM_INVALID_STATE_ERR); cloning a DOMXPath now throws Error; DOMImplementation::getFeature() has been removed.
    How to tell
    clone on DOMXPath objects; DOMImplementation::getFeature(); checks for false or DOM_PHP_ERR after DOM node creation.
    What to do (our suggestion)
    Remove getFeature() calls and DOMXPath cloning (the clone was unusable anyway).
    Why it’s rare (our view)
    php.net itself says the allocation case is extremely unlikely; the other two are uncommon.

    php.net’s entry for this change

  • PHP 8.4GMPBackward-incompatible changeGMP class is now finalThe GMP class is now final and cannot be extended anymore.
    How to tell
    class ... extends GMP.
    What to do (our suggestion)
    Stop extending GMP; wrap a GMP instance instead.
    Why it’s rare (our view)
    Extending GMP is uncommon in plugins and apps.

    php.net’s entry for this change

  • PHP 8.4MySQLiBackward-incompatible changeUnused MySQLi constants removedMYSQLI_SET_CHARSET_DIR, MYSQLI_STMT_ATTR_PREFETCH_ROWS, MYSQLI_CURSOR_TYPE_FOR_UPDATE, MYSQLI_CURSOR_TYPE_SCROLLABLE and MYSQLI_TYPE_INTERVAL were removed as unused, unimplemented or aliases.
    How to tell
    Those constant names; an “undefined constant” Error.
    What to do
    Remove the references. php.net notes MYSQLI_TYPE_INTERVAL was an alias for MYSQLI_TYPE_ENUM.
    Why it’s rare (our view)
    php.net describes these constants as unused, undocumented or never implemented.

    php.net’s entry for this change

  • PHP 8.4SimpleXMLBackward-incompatible changeSimpleXMLElement methods no longer reset the iteratorasXML(), getName() and string casts no longer implicitly reset a SimpleXMLElement’s iterator, which could cause infinite loops; it is reset only when explicitly rewound.
    How to tell
    Loops over SimpleXMLElement that call asXML(), getName() or cast to string inside the loop.
    What to do (our suggestion)
    Code that relied on the implicit reset must rewind the iterator explicitly.
    Why it’s rare (our view)
    Code relying on the old reset was usually already buggy (php.net’s example loops forever).

    php.net’s entry for this change

Removed extensions · 3

  • PHP 8.4Removed ExtensionsRemoved extensionOCI8 extension moved to PECLOCI8 has been moved to PECL and is no longer part of the PHP distribution; new PECL releases are published ad hoc according to user demand.
    How to tell
    Calls to oci_connect() and other oci_* functions; extension_loaded() checks; “undefined function” errors when the extension is absent.
    What to do
    Install OCI8 from PECL if you still need it, since php.net says it moved there.
    Why it’s rare (our view)
    WordPress: WordPress runs on MySQL/MariaDB. Custom apps: Only apps connecting to Oracle Database use it.

    php.net’s entry for this change

  • PHP 8.4Removed ExtensionsRemoved extensionPDO_OCI extension moved to PECLPDO_OCI has been moved to PECL and is no longer part of the PHP distribution; new PECL releases are published ad hoc according to user demand.
    How to tell
    Calls to the PDO Oracle driver (PDO DSNs for Oracle); extension_loaded() checks; “undefined function” errors when the extension is absent.
    What to do
    Install PDO_OCI from PECL if you still need it, since php.net says it moved there.
    Why it’s rare (our view)
    WordPress: WordPress runs on MySQL/MariaDB. Custom apps: Only apps connecting to Oracle Database through PDO use it.

    php.net’s entry for this change

  • PHP 8.4Removed ExtensionsRemoved extensionPSpell extension moved to PECLPSpell has been moved to PECL and is no longer part of the PHP distribution; new PECL releases are published ad hoc according to user demand.
    How to tell
    Calls to pspell_new() and other pspell_* functions; extension_loaded() checks; “undefined function” errors when the extension is absent.
    What to do
    Install PSpell from PECL if you still need it, since php.net says it moved there.
    Why it’s rare (our view)
    WordPress: Spell-checking in WordPress is normally done in the browser. Custom apps: Server-side spell-checking with pspell is uncommon.

    php.net’s entry for this change

Deprecations · 9

  • PHP 8.3LDAPDeprecationldap_connect() with separate hostname and port deprecatedCalling ldap_connect() with separate hostname and port arguments is deprecated.
    How to tell
    ldap_connect($host, $port) with two arguments; E_DEPRECATED.
    What to do (our suggestion)
    See the ldap_connect() manual page for the supported call forms.
    Why it’s rare (our view)
    WordPress: Only LDAP/SSO login plugins call it. Custom apps: Possible in apps with LDAP or Active Directory login.

    php.net’s entry for this change

  • PHP 8.3MBStringDeprecationNegative width in mb_strimwidth() deprecatedPassing a negative width to mb_strimwidth() is now deprecated.
    How to tell
    mb_strimwidth() calls whose width argument is negative or computed and may go negative.
    What to do (our suggestion)
    Pass a non-negative width.
    Why it’s rare (our view)
    mb_strimwidth() is used for excerpts, but negative widths are unusual.

    php.net’s entry for this change

  • PHP 8.3ReflectionDeprecationReflectionProperty::setValue() with one parameter deprecatedCalling ReflectionProperty::setValue() with only one parameter is deprecated.
    How to tell
    ReflectionProperty::setValue($value) with a single argument, typically for static properties; often in tests, DI or ORM code.
    What to do
    To set static properties, pass null as the first parameter: setValue(null, $value).
    Why it’s rare (our view)
    WordPress: Reflection is rare in plugins. Custom apps: Test suites and older libraries may set static properties this way.

    php.net’s entry for this change

  • PHP 8.3StandardDeprecationassert_options() function deprecatedThe assert_options() function is now deprecated.
    How to tell
    Calls to assert_options(); E_DEPRECATED.
    What to do
    php.net names no direct replacement; for the related assert.* INI settings it says to use zend.assertions instead.
    Why it’s rare (our view)
    Assertion configuration is uncommon in production plugin and app code.

    php.net’s entry for this change

  • PHP 8.3StandardDeprecationASSERT_* constants deprecatedThe ASSERT_ACTIVE, ASSERT_BAIL, ASSERT_CALLBACK, ASSERT_EXCEPTION and ASSERT_WARNING constants have been deprecated.
    How to tell
    References to ASSERT_ACTIVE, ASSERT_BAIL, ASSERT_CALLBACK, ASSERT_EXCEPTION or ASSERT_WARNING.
    What to do (our suggestion)
    Use the zend.assertions INI setting, which php.net recommends in place of the related assert.* settings.
    Why it’s rare (our view)
    Usually appear alongside assert_options(), which is uncommon.

    php.net’s entry for this change

  • PHP 8.3StandardDeprecationassert.* INI settings deprecatedassert.active, assert.bail, assert.callback, assert.exception and assert.warning are deprecated; no notice is emitted when a setting equals its default.
    How to tell
    assert.* lines in php.ini or .user.ini, or ini_set('assert.…') calls with non-default values.
    What to do
    The zend.assertions INI setting should be used instead (per the Changes to INI File Handling section).
    Why it’s rare (our view)
    Only non-default values warn; few sites change them.

    php.net’s entry for this change

  • PHP 8.3SQLite3DeprecationSQLite3::enableExceptions(false) triggers deprecationExceptions are now preferred and warnings will be removed; calling SQLite3::enableExceptions(false) triggers a deprecation warning.
    How to tell
    SQLite3::enableExceptions(false) calls.
    What to do (our suggestion)
    Use exceptions: stop calling enableExceptions(false) and handle exceptions instead.
    Why it’s rare (our view)
    WordPress: Few plugins use the SQLite3 class directly. Custom apps: Only apps that use the SQLite3 class rather than PDO.

    php.net’s entry for this change

  • PHP 8.4ReflectionDeprecationReflectionMethod constructor with one argument deprecatedCalling ReflectionMethod::__construct() with one argument is deprecated.
    How to tell
    new ReflectionMethod(...) with a single argument.
    What to do
    Use ReflectionMethod::createFromMethodName() instead.
    Why it’s rare (our view)
    Mostly found in framework and library internals, which have their own release cycles.

    php.net’s entry for this change

  • PHP 8.4StandardDeprecationstream_context_set_option() with two arguments deprecatedCalling stream_context_set_option() with two arguments is deprecated.
    How to tell
    stream_context_set_option($context, $optionsArray).
    What to do
    Use stream_context_set_options() instead.
    Why it’s rare (our view)
    The two-argument array form is less common than the four-argument form.

    php.net’s entry for this change

Other changes · 6

  • PHP 8.3CoreOther change++/-- on bool values now emits warnings++ and -- on bool values now emit warnings: they have no effect today but will behave like $bool += 1 in future.
    How to tell
    Warnings from ++ or -- applied to variables holding true or false.
    What to do (our suggestion)
    Do not increment or decrement booleans; use an explicit int.
    Why it’s rare (our view)
    Usually a bug where a flag and counter share a variable.

    php.net’s entry for this change

  • PHP 8.3CoreOther change-- on null values now emits warnings-- on null values now emits a warning: it has no effect today but will behave like $null -= 1 in future.
    How to tell
    Warnings from -- applied to an uninitialized or null variable.
    What to do (our suggestion)
    Initialize the variable to an int before decrementing.
    Why it’s rare (our view)
    Decrementing an unset variable is uncommon.

    php.net’s entry for this change

  • PHP 8.3StandardOther changestrtok() warns when token omitted at startstrtok() raises an E_WARNING when token is not provided when starting tokenization.
    How to tell
    strtok($string) called with one argument as the first call of a tokenization; E_WARNING.
    What to do (our suggestion)
    Pass both the string and the token on the first strtok() call.
    Why it’s rare (our view)
    Misuse of strtok() is uncommon.

    php.net’s entry for this change

  • PHP 8.3MBStringOther changemb_detect_encoding() non-strict mode now matches documentationmb_detect_encoding() in non-strict mode now returns the closest matching encoding, as documented, instead of its previous elimination behavior; results may differ.
    How to tell
    mb_detect_encoding() calls with strict set to false (the default); code that checks for a false return.
    What to do (our suggestion)
    Re-test encoding detection on representative input, or use strict mode.
    Why it’s rare (our view)
    WordPress: Detected encodings change only for invalid or ambiguous input. Custom apps: Possible in apps that import CSV, email or legacy-encoded text.

    php.net’s entry for this change

  • PHP 8.3DOMOther changeDOMDocument load methods have tentative bool return typeDOMDocument::loadHTML(), loadHTMLFile() and loadXML() now have a tentative return type of bool, replacing the documented DOMDocument|bool.
    How to tell
    Classes extending DOMDocument that override loadHTML(), loadHTMLFile() or loadXML().
    What to do (our suggestion)
    Declare overriding methods with a compatible bool return type.
    Why it’s rare (our view)
    Only plugins with DOMDocument subclasses that override these methods.

    php.net’s entry for this change

  • PHP 8.3GdOther changeimagerotate() ignore_transparent parameter removedThe signature of imagerotate() changed: the ignore_transparent parameter was removed, as it was ignored since PHP 5.5.0.
    How to tell
    imagerotate() calls passing a fourth argument.
    What to do (our suggestion)
    Drop the ignore_transparent argument from imagerotate() calls; it had no effect since PHP 5.5.0.
    Why it’s rare (our view)
    Image plugins call imagerotate(), but rarely with the fourth argument.

    php.net’s entry for this change

What this page leaves out

The pages of each guide we read on 26 October 2026, and how many of their changes are here:

Sources

php.net’s two migration guides, every page read in full on the day shown, and what WordPress and WooCommerce say about PHP 8.4.

About this page

Updated
Verified
26 Oct 2026

Cite this page

SnowDriven. “PHP 8.2 to 8.4: what breaks in WordPress plugins, themes and PHP apps.” SnowDriven Research. https://snowdriven.com/research/php-8-4-breaking-changes/ (last updated 26 October 2026).

One click or tap selects the whole citation, ready to copy.

About these facts

Each entry summarizes the PHP project’s own page for that change, linked and checked on the day shown; the likely, possible and rare ratings, and the reasons for them, are ours. The PHP project can change its lists and pages without notice, and our summary can fall behind or contain mistakes, so read the linked page before you change a system. Information only, provided as is (terms). Reuse: free to quote, with credit to SnowDriven and a link (terms of use).

Start a project

Want this list checked against your site or app?

We can go through your plugins, theme or codebase with it: which entries apply, what each takes to fix, and the order to do it in. Tell us whether it’s a WordPress site, a store or a custom app; we reply within one business day.

Pittsburgh, PA · Since 2015 · First step: a 30-minute call