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:
display_errorsis 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.- 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.
- 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.
| Deprecation | Likely 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 methods | Likely if you export CSV | Pass escape explicitly |
trigger_error() with E_USER_ERROR | Likely in older code | Throw an exception, or call exit() |
E_STRICT constant | Likely in old bootstrap files | Remove it from error_reporting() masks |
mysqli_ping(), mysqli_kill(), mysqli_refresh(), mysqli_store_result() mode argument | Possible in legacy code | Reconnect logic, SQL KILL, SQL FLUSH |
Session INI changes (session.use_only_cookies, session.use_trans_sid, session.sid_length, and others), the SID constant | Possible in custom session code | Stop changing these settings |
session_set_save_handler() with more than two arguments | Possible in custom session code | Pass a handler object |
lcg_value() | Occasional | Random\Randomizer::getFloat() |
0 ** -1 and pow(0, -1) | Rare, usually a bug | fpow() if you want IEEE 754 behavior |
xml_set_object(), string method names in xml_set_* | Rare | Callable arrays like [$this, 'method'] |
stream_context_set_option() with two arguments | Rare | stream_context_set_options() |
DatePeriod ISO string constructor | Rare | DatePeriod::createFromISO8601String() |
ReflectionMethod with one argument | Rare outside frameworks | ReflectionMethod::createFromMethodName() |
unserialize() uppercase S tag | Very rare | None needed |
A class named exactly _ | Very rare | Rename it |
Extension-specific: cURL, DBA, DOM, Hash, Intl, LDAP, PDO_PGSQL, PGSQL, SOAP, SPL __wakeup | Only if you use them | See 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:
- Turn off display, turn on logging in the environment you are testing.
- Run your test suite and a crawl of the main pages on 8.4, then collect unique deprecation messages from the log.
- Group by file path. Anything under
vendor/orwp-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. - Fix your own code in the order of the table above. Implicitly nullable types and CSV escape are mechanical.
E_USER_ERRORneeds 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.

