PHP 8.4 Implicitly Nullable Parameter Deprecated: The Fix

Fix PHP 8.4’s implicitly nullable parameter deprecation by declaring nullable types explicitly with ?Type or Type|null, with practical examples.

PHP 8.4 implicitly nullable parameter deprecation with before and after code examples

Deprecated: Implicitly marking parameter $foo as nullable is deprecated, the explicit nullable type must be used instead means you have a typed parameter that defaults to null without the type itself declaring that it accepts null. Add a ? in front of the type, or write it as a union with null, and the notice goes away:

function greet(string $name = null) {}   // triggers the deprecation on PHP 8.4+
function greet(?string $name = null) {}  // fixed, and valid PHP back to 7.1

This is version-specific. The deprecation was introduced in PHP 8.4 by the RFC “Deprecate implicitly nullable parameter types”. On PHP 8.3 and earlier, the exact same code runs with no warning at all. Nothing about the underlying behavior changed between versions; PHP 8.4 simply started telling you about an implicit rule that always existed.

What was implicit, and what changed

Since long before PHP 8, if a parameter has a scalar or class type and its default value is null, PHP has quietly treated the parameter’s type as nullable even though the type declaration itself never said so. function greet(string $name = null) has always accepted greet(null) without complaint, on every PHP version, because the = null default silently widened the type. This was never announced anywhere in the signature; a developer reading string $name would reasonably assume null is not a legal argument, and would be wrong.

PHP 8.4 does not remove that behavior. Passing null to such a parameter still works exactly as before. What changed is that declaring a parameter this way, with an implicit rather than explicit nullable type, now emits an E_DEPRECATED notice at the point the function or method is defined, not when it is called. Per php.watch’s documentation of the change, the notice fires as soon as PHP parses the declaration, so it shows up the moment the file is loaded, whether or not the function is ever actually invoked with null in that request.

This is a deprecation notice, not a fatal error

Nothing stops running. E_DEPRECATED is PHP’s lowest-severity notice level for code that still works today but is scheduled to be tightened in a future version. On a production server with display_errors off (as it should be), the site’s visitors see nothing. What you will see is your error log filling up with one entry per affected declaration, and on WordPress specifically, an admin notice or a debug log entry if WP_DEBUG and WP_DEBUG_LOG are on. Do not confuse this with a fatal error that needs an emergency fix. Reading a stack trace is the skill for a genuine fatal error stopping a request; this notice is a different, much lower-stakes signal, and it is a maintenance item, not an outage.

The fix, and why it is safe on every PHP version you are likely running

// Before: implicit, deprecated on 8.4+
function setDiscount(int $percent = null) {}

// After: explicit, silences the notice
function setDiscount(?int $percent = null) {}

The ?Type nullable syntax has been valid PHP since PHP 7.1, per the PHP manual’s type declarations page, so this fix is not gated behind any minimum version your codebase is likely to still support. If a project’s style already uses PHP 8’s union types elsewhere, the equivalent form is int|null $percent = null, which behaves identically to ?int. Both are correct; pick whichever matches the rest of the file.

Class properties and return types follow the identical rule and the identical fix, if they were declared with an implicit null default rather than an explicit nullable type:

private ?Logger $logger = null;               // already explicit, no change needed
public function find(int $id): ?User { ... }   // return types were never implicit here; this pattern applies specifically to parameters

The RFC’s deprecation is scoped to parameter declarations specifically, because that is where the implicit-widening behavior existed; explicitly nullable properties and return types written with ?Type were already the correct, non-deprecated form before PHP 8.4. This is also exactly the same undeclared-null problem that shows up at runtime as a call to a member function on null: a value silently allowed to be null, without the signature saying so, is what both issues have in common, one caught at parse time as a notice, the other only discovered later as a fatal error.

Finding every occurrence in a real codebase

A grep for the shape catches most cases, though it needs a human pass afterward since not every match is a true positive:

grep -rn '(\(int\|string\|bool\|float\|array\|\w\+\) \$\w\+ = null)' --include="*.php" .

For a more reliable pass, static analysis tools built for exactly this kind of scan are the better route on anything beyond a small file: PHPStan and Psalm both flag implicitly nullable parameters at their default rule levels, and Rector ships an automated refactoring rule that rewrites the whole codebase’s occurrences to the explicit ?Type form without a human retyping each one by hand. On a legacy WordPress plugin or theme with hundreds of function definitions, an automated rewrite pass is faster and less error-prone than a manual grep-and-fix sweep, and it avoids the kind of copy-paste mistake that produces a “cannot redeclare function” error if a rewrite accidentally duplicates a declaration instead of editing it in place.

What this means if you are still on PHP 8.3 or earlier

Nothing, yet. The deprecation genuinely does not exist before PHP 8.4; code with implicitly nullable parameters runs silently, with no notice of any kind, on 8.3 and every earlier version. If your hosting or your project is not yet on 8.4, you will not see this message, and there is no urgency to fix it purely for that reason. It is still worth fixing ahead of an eventual upgrade rather than during it, since a deprecation sweep done calmly on the current version is easier to verify than the same sweep done under the time pressure of a live version migration. The RFC itself does not schedule a removal date for the underlying nullable-default behavior; as of PHP 8.4 this remains a notice-level deprecation, not a countdown to a fatal error in a named future version, and nothing here should be read as predicting one.

FAQ

Will my code stop working if I ignore this?
Not on PHP 8.4 itself. The deprecated form still functions exactly as before; only the notice is new. Whether it becomes a fatal error in some later major version is not stated in the current RFC, so treat it as a real but non-urgent maintenance item rather than an emergency.

Does this affect nullable return types or nullable properties?
No. The deprecation targets parameter declarations with an implicit null default specifically. A property or return type already written as ?Type was correct before PHP 8.4 and remains correct after it; nothing changes for those.

Is int|null $x = null different from ?int $x = null?
No, they are equivalent forms introduced at different times: ?Type has existed since PHP 7.1, and the union-type syntax Type|null became available in PHP 8.0. Either silences the PHP 8.4 deprecation notice.

Can a linter or IDE catch this automatically going forward?
Yes. PHPStan and Psalm flag it by default at typical strictness levels, and most modern IDEs with PHP language support surface the deprecation inline once the project’s configured PHP version is set to 8.4 or later in the IDE’s own settings.

Why introduce a deprecation for something that has always worked?
Per the RFC’s own stated motivation, an implicit nullable type is a mismatch between what the signature says and what the language actually enforces, and PHP’s broader direction since PHP 8.0 has been tightening exactly this kind of undeclared, silent type-widening so a parameter’s declared type is a reliable contract rather than one with a hidden exception.

Written by

Shah Alom

Leave a Reply

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