PHP 8.4 Deprecations List: The Ones That Hit Real Code

The PHP 8.4 deprecations list, sorted by how likely each is to be in your code, with the grep to find it and the fix to apply before PHP 9.

PHP 8.4 deprecation warnings in a code editor highlighting nullable parameters, CSV escape values, E_USER_ERROR, E_STRICT, and legacy mysqli calls.

The PHP 8.4 deprecations list is long, but for most application code only five entries matter: implicitly nullable parameter types, the default $escape value in the CSV functions, trigger_error() with E_USER_ERROR, the E_STRICT constant, and a handful of old mysqli calls. Everything else lives in extensions most projects never touch (LDAP, DBA, SOAP, PDO_PGSQL). All of them are E_DEPRECATED notices, so nothing stops working today. This guide sorts the full list by how likely it is to be in your codebase and gives the search and the fix for each.

Everything below targets PHP 8.4 and is checked against the official migration guide. Where a fix also runs on older versions, that is noted.

Why a Deprecation Is Not an Error (Until It Is)

A deprecation in PHP 8.4 raises an E_DEPRECATED notice and then carries on. Your code produces the same result it did on 8.3. The notice is a warning that the behavior is scheduled for removal, usually in the next major version.

Three situations turn that harmless notice into a real problem:

  1. display_errors is on in production. The notice prints into your HTML, or worse, into a JSON response, and the client that parses it breaks. If you have ever seen Unexpected token in JSON at position 0, a deprecation notice leaking into an AJAX response is one of the classic causes.
  2. Your error handler converts notices into exceptions. Some frameworks and test runners do this in debug or strict modes, so a deprecation becomes a fatal in CI.
  3. The log fills up. One deprecated call inside a loop can write thousands of identical lines per request.

The fix for the first one is the same regardless of which deprecations you have: log them, never display them. On WordPress, that means setting up WP_DEBUG_LOG without displaying errors to visitors before you switch the PHP version, not after.

The PHP 8.4 Deprecations List, Sorted by Likelihood

The full list comes from the “Deprecated Features” section of the PHP 8.4 migration guide on php.net, which we read on 2026-09-25. The table below regroups it by how often each item shows up in ordinary application, WordPress and Laravel style code. The likelihood column is our editorial judgment, not a measured figure.

DeprecationLikely in your code?Replacement
Implicitly nullable parameter (Type $x = null)Very likely?Type $x = null or Type|null $x = null
Default $escape in fputcsv(), fgetcsv(), str_getcsv(), SplFileObject CSV methodsLikely if you export CSVPass escape explicitly
trigger_error() with E_USER_ERRORLikely in older codeThrow an exception, or call exit()
E_STRICT constantLikely in old bootstrap filesRemove it from error_reporting() masks
mysqli_ping(), mysqli_kill(), mysqli_refresh(), mysqli_store_result() mode argumentPossible in legacy codeReconnect logic, SQL KILL, SQL FLUSH
Session INI changes (session.use_only_cookies, session.use_trans_sid, session.sid_length, and others), the SID constantPossible in custom session codeStop changing these settings
session_set_save_handler() with more than two argumentsPossible in custom session codePass a handler object
lcg_value()OccasionalRandom\Randomizer::getFloat()
0 ** -1 and pow(0, -1)Rare, usually a bugfpow() if you want IEEE 754 behavior
xml_set_object(), string method names in xml_set_*RareCallable arrays like [$this, 'method']
stream_context_set_option() with two argumentsRarestream_context_set_options()
DatePeriod ISO string constructorRareDatePeriod::createFromISO8601String()
ReflectionMethod with one argumentRare outside frameworksReflectionMethod::createFromMethodName()
unserialize() uppercase S tagVery rareNone needed
A class named exactly _Very rareRename it
Extension-specific: cURL, DBA, DOM, Hash, Intl, LDAP, PDO_PGSQL, PGSQL, SOAP, SPL __wakeupOnly if you use themSee the migration guide

Implicitly Nullable Parameters: The One Everybody Has

This is the deprecation you will see first, and in the largest numbers. Before 8.4, giving a typed parameter a null default silently made the type nullable:

<?php
// PHP 8.4: Deprecated
function find_user(User $current = null) { /* ... */ }

// Fix, works on PHP 7.1 and later
function find_user(?User $current = null) { /* ... */ }

// Or, PHP 8.0 and later
function find_user(User|null $current = null) { /* ... */ }

The message reads: “Implicitly marking parameter $current as nullable is deprecated, the explicit nullable type must be used instead.”

Two details make this one different from every other item on the list. First, according to the implicitly nullable types RFC, the notice is emitted at compile time. The function does not have to be called; the file only has to be loaded. That is why a plugin can flood your log with it on every request even though the function in question never runs. Second, the RFC states that support is removed in PHP 9, so the fix is required eventually, not optional.

Because it fires at compile time, the PHP linter surfaces it without running your application. Run this with the PHP 8.4 binary:

find src -name '*.php' -print0 \
  | xargs -0 -n1 php -d error_reporting=E_ALL -l 2>&1 \
  | grep -i 'implicitly marking'

There is a second form worth knowing. If a parameter with a null default is followed by a required parameter, the default was never usable anyway. The migration guide says to drop the default and make the type explicitly nullable:

<?php
// Before
function save(string $key, array $meta = null, int $ttl) {}

// After
function save(string $key, ?array $meta, int $ttl) {}

We cover the vendor code side of this, where the notice comes from a dependency you do not own, in the PHP 8.4 implicitly nullable parameter fix.

The CSV Escape Parameter

If your code exports or imports CSV, this is the second most likely hit. Calling fputcsv(), fgetcsv() or str_getcsv() without passing the escape argument is now deprecated, because PHP’s default escape character (a backslash) implements a nonstandard mechanism the core team wants to retire.

<?php
// PHP 8.4: Deprecated, relies on the default escape
fputcsv($handle, $row);

// Keeps today's exact behavior
fputcsv($handle, $row, escape: '\\');

// Standard CSV (RFC 4180 style), may change output
fputcsv($handle, $row, escape: '');

Named arguments need PHP 8.0 or later. Which value to pass is a real decision, not a formality. Passing '\\' keeps byte-for-byte identical output. Passing '' disables the proprietary escaping, which is what most spreadsheet tools expect, but a field that contains a backslash next to a double quote may be written differently. If another system parses your CSV, test with a row that contains \" before you switch.

The same rule applies to SplFileObject::fputcsv(), fgetcsv() and setCsvControl(), unless you already called setCsvControl() to set a new default.

trigger_error() With E_USER_ERROR

Older code uses trigger_error('Something broke', E_USER_ERROR) as a way to stop execution with a message. In 8.4 that call is deprecated. The migration guide’s advice is to replace it with an exception or with exit(), whichever fits:

<?php
// Before
if (!$config) {
    trigger_error('Missing config file', E_USER_ERROR);
}

// After
if (!$config) {
    throw new RuntimeException('Missing config file');
}

Pick the exception in almost every case. It can be caught, logged with a stack trace, and handled by your framework. exit() is only right in a CLI script where stopping is the whole point. E_USER_WARNING, E_USER_NOTICE and E_USER_DEPRECATED are not affected.

Search for it:

grep -rn "E_USER_ERROR" --include='*.php' src/

The E_STRICT Constant

The E_STRICT error level itself is gone, so the constant is now deprecated. You rarely use it directly. You find it inside an old error mask:

<?php
// Before
error_reporting(E_ALL & ~E_STRICT & ~E_DEPRECATED);

// After
error_reporting(E_ALL & ~E_DEPRECATED);

Check bootstrap files, wp-config.php, old CMS installers and any .ini file. Removing E_STRICT from the mask changes nothing about which errors are reported, since the level no longer exists.

mysqli Calls in Legacy Code

Code written against mysqli before PHP 8.2 sometimes calls mysqli_ping() to check whether a long-running connection is still alive. The migration guide notes it is deprecated because the reconnect feature it relied on was already removed in PHP 8.2. If you have it, it has probably not been doing what you thought for a while. The honest replacement is to catch the connection error and open a new connection.

mysqli_kill() should become a SQL KILL statement, and mysqli_refresh() a SQL FLUSH statement. Explicitly passing the mode argument to mysqli_store_result() is also deprecated.

Session Settings and the Rest of the List

If your application sets session.use_only_cookies, session.use_trans_sid, session.trans_sid_tags, session.trans_sid_hosts, session.referer_check, session.sid_length or session.sid_bits_per_character at runtime or in .htaccess, those changes are now deprecated. So is the SID constant. The direction of travel is clear: cookie-only sessions with the default ID format. Remove the overrides and, if you store sessions yourself, make sure the backend accepts the default ID length.

The remaining items are narrow. lcg_value() becomes (new Random\Randomizer())->getFloat(). Raising zero to a negative power is almost always a missing guard against division by zero, and fpow() exists only if you genuinely want IEEE 754 semantics. The cURL, DOM, Intl, LDAP, PostgreSQL, SOAP and SPL entries only matter if you use those extensions directly, and a framework you depend on is more likely than your own code to trip them.

Triage: What to Fix First

You do not need to clear the whole PHP 8.4 deprecations list before upgrading. You need to know which entries you have and make sure none of them reach a visitor. A workable order:

  1. Turn off display, turn on logging in the environment you are testing.
  2. Run your test suite and a crawl of the main pages on 8.4, then collect unique deprecation messages from the log.
  3. Group by file path. Anything under vendor/ or wp-content/plugins/ is an update or an upstream report, not your edit. Our guide to reading a WordPress debug log and naming the guilty code walks through doing this from a real log.
  4. Fix your own code in the order of the table above. Implicitly nullable types and CSV escape are mechanical. E_USER_ERROR needs a moment of thought about what should happen instead.

If you use an AI assistant for the mechanical part, check its output. Assistants trained mostly on pre-8.4 code will happily write Type $x = null in new code, and some will “fix” a CSV call by picking an escape value without asking whether the output format matters.

FAQ

Do PHP 8.4 deprecations break my site?

No. A deprecation raises an E_DEPRECATED notice and the code keeps running as before. It becomes a visible problem only if notices are displayed to visitors, if your error handler turns notices into exceptions, or if the log grows out of control.

Which PHP 8.4 deprecation is most common?

Implicitly nullable parameter types, written as Type $param = null without a ?. It appears across older libraries and plugins, and because it is emitted at compile time, it fires as soon as the file loads.

When will deprecated PHP 8.4 features be removed?

The implicitly nullable types RFC states removal in PHP 9. Other items follow PHP’s usual pattern of deprecating in a minor release and removing in the next major one, but check each RFC before assuming a date.

How do I find PHP 8.4 deprecations without running every page?

For implicitly nullable types, run php -l under PHP 8.4 across your files, because the notice is raised at compile time. For call-based items like E_USER_ERROR, E_STRICT and the mysqli functions, a text search is fast and reliable. The CSV escape notice needs either a search for the function names or a test run.

Should I pass an empty string or a backslash as the CSV escape?

Pass '\\' if you need identical output to what you produce today. Pass '' if you want standard CSV and can test that nothing downstream relied on the old escaping.

Written by

Shah Alom

Shah Alom is the founder and writer behind Ebuhu, where he covers PHP, WordPress development, plugin and theme development, debugging, practical programming techniques, and AI-assisted coding. Drawing on hands-on web development experience, he focuses on clear, practical guidance that helps developers understand how things work, avoid common mistakes, and write more reliable code.

Leave a Reply

Your email address will not be published. Required fields are marked *