PHP 8.4: Implicitly nullable parameter declarations deprecated

Version8.4
TypeDeprecation

Prominent deprecation in PHP 8.4
This is a prominent deprecation in PHP 8.4 and legacy PHP applications will likely cause deprecation notices due to this change in PHP 8.4. The suggested replacement is straight-forward, safe, and works in PHP 7.1 and later.


PHP supports declaring types for function parameters, return values, class properties, class constants, and Enums. Being a dynamic typed language, and a language with decades of history, PHP has received several improvements and features over the years.

With the introduction of features such as scalar types (PHP 7.0), nullable types (7.1), typed properties (7.4), union types (8.0), intersection types (8.1), DNF types (8.2), and typed class properties (8.3), PHP has always been on the trajectory to make the type declarations expressive.


Since PHP 5.1, it was possible to declare a type with a default value of null, and even if there was a type declared, it made the type implicitly and effectively allow null as well:

function test(string $test = null) {}

test('PHP'); // Allowed
test(null); // Allowed

PHP 7.1 added support for nullable types, which introduced the ?TYPE syntax to declare the types as nullable:

- function test(string $test = null) {}
+ function test(?string $test = null) {}

  test('PHP'); // Allowed
  test(null); // Allowed

With the introduction of Union Types in PHP 8.0, it was possible to declare types as one type (such as string) or another type (not limited to null, but also int, etc.).

Nullable types can also be declared as a union with null. e.g.:

function test_with_default(string|null $test = null) {}

Although PHP received several type improvements such as the deprecation of required parameters after optional parameters in PHP 8.0, support for implicit nullable parameter types was not deprecated in earlier PHP versions given the large number of potential backward compatibility issues in existing PHP applications and projects.

Nullable types are only allowed in function/method types. Typed properties (added in PHP 7.4) do not allow implicit nullable type declarations. Return types do not support default values. Enums (added in PHP 8.1) only support string and int as types. Further, property-promoted constructor parameters do not support implicit nullable syntax.


PHP 8.4 deprecates implicitly nullable types. PHP applications are recommended to explicitly declare the type as nullable. All type declarations that have a default value of null, but without declaring null in the type declaration emit a deprecation notice:

function test(array $value = null) {}
Implicitly marking parameter $value as nullable is deprecated, the explicit nullable type must be used instead

The deprecation is emitted when PHP encounters a declaration with an implicit nullable type. Not when such functions are called.

Recommended Changes

Change the implicit nullable type declaration to a nullable type declaration:

- function test(string $test = null) {}
+ function test(?string $test = null) {}

  test('PHP'); // Allowed
  test(null); // Allowed

Alternately, PHP applications running on PHP 8.0 and later can use a Union Type to make this more visible:

- function test(string $test = null) {}
+ function test(string|null $test = null) {}

  test('PHP'); // Allowed
  test(null); // Allowed

Both type declarations are effectively equivalent, even at the Reflection API. The second example is more verbose, and only works on PHP 8.0 and later. Choose a variant based on the PHP version requirement, code style, and with consideration to the code clarity.


On class methods, changing implicit nullable parameters to explicit parameters is not a backward-compatibility break. Parent classes and child classes can replace the implicitly nullable parameter declarations with explicit declarations independently.

Consider the following snippet:

class ParentClass {
    public function tester(string $value = null) {}
}

class SubClass extends ParentClass {
    public function tester(?string $value = null) {}
}

The ParentClass::tester method triggers the deprecation notice. The SubClass::tester correctly declares the nullability explicitly, and does not trigger the deprecation notice. It is possible to update the parent class without method compatibility issues:

 class ParentClass {
-   public function tester(string $value = null) {}
+   public function tester(?string $value = null) {}
 }

 class SubClass extends ParentClass {
     public function tester(?string $value = null) {}
 }

Dropping parameter type

Applications and PHP libraries that must run on all PHP versions from PHP 7.0 or PHP 5 all the way to PHP 8.4 cannot use the nullable types because nullable types are only supported on PHP 7.1 and later.

Although highly discouraged, removing the type declaration in the parameter, and then checking the type from inside the function is a last-resort way to avoid the deprecation notice while keeping the code compatible with PHP 7.0 and later.

Removal of type safety
When the parameter is untyped (i.e. not provided), the parameter can accept any type (mixed type). Make sure to check the type from inside of the function to counter the lack of type safety.

For example, a function parameter that previously had a string $value = null declaration can technically drop it if the value is checked from within the function:

- function test_discouraged(string $value = null) {
+ /**
+  * @param string|null $value
+  */
+ function test_discouraged($value = null) {
+     if (is_array($value) || is_object($value)) {
+         throw new TypeError(__FUNCTION__ . ': Argument #1 ($value) must be of type string, '. gettype($value) .' given');
+     }
+ 
+     if ($value !== null) {
+         $value = (string) $value;
+     }
+ 
     // Rest of the function here.
 }

The type checking will be different from each type, and if the types can be coerced/juggled into another type. On files with strict_types enabled, this type juggling check is not necessary.

Although dropping the parameter type allows the function to avoid the deprecation notice, this is at a significant loss of type safety. This change can also be observed from the Reflection API, which means any code that relied on the type of the parameter will observe this change too. Most notable use cases include Dependency Injection containers and object hydrators that inspect the type of parameters to decide the correct type of the values being hydrated.

Adding PHPDoc comments can help alleviate the lack of IDE support.



Related Changes

Backward Compatibility Impact

This deprecation promotes more visible and expressive type declarations. The suggested replacement is compatible with PHP 7.1 and later.

Automated Fixes

The following tools can mark and automatically fix implicit nullable type declarations.


RFC Discussion Implementation