PHP 8.1: Return types in PHP built-in class methods and deprecation notices

Version8.1
TypeDeprecation

In PHP 8.1, most of the PHP internal classes are updated with tentative return types. A tentative return types means that the return types are added merely to inform the classes that extend them, and they will be enforced since PHP 9.0.

With tentative return types, PHP 8.1 and later emits a deprecation notice if a class declaration has an incompatible return type. PHP 8.0 started to throw fatal errors on incompatible method signatures, but that was only for parameter types. This change in PHP 8.1 affects return types in addition to parameters.

Most notably, lack of a return type declaration is also considered a return type mismatch. Because return types are only supported since PHP 7.0, this effectively makes once the return types are enforced in PHP 9.0, any PHP 5-compatible applications that extend internal PHP classes will not work in PHP 9.0.

For example, below is the synopsis of the ArrayAccess interface::

interface ArrayAccess {
    public function offsetExists(mixed $offset): bool;
    public function offsetGet(mixed $offset): mixed;
    public function offsetSet(mixed $offset, mixed $value): void;
    public function offsetUnset(mixed $offset): void;
}

Prior to PHP 8.1, any class that extended the ArrayAccess interface were not required to add a return type (which makes them compatible with PHP 5, despite being End-Of-Life), and even a mismatching return type was allowed.

class Foo implements ArrayAccess {
    public function offsetExists(mixed $offset) {}
    // ...
}

From PHP 8.1 and later, this type of declaration results in a deprecation notice:

Deprecated: Return type of Test::offsetExists(mixed $offset) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in... on line ...

Lack of Return Type is Considered a Mismatch

Note that lack of an explicit return declaration is also considered a signature mismatch, and thus results in the deprecation notice. This applies for parent classes/interfaces that declare mixed return types.

class Foo implements ArrayAccess {
    public function offsetGet(mixed $offset) {}
    // ...
}
Deprecated: Return type of Foo::offsetGet(mixed $offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in ... on line ...

ArrayAccess::offsetGet method has a tentative return type of mixed. The Foo::offsetGet declaration has no explicit return type declared, and this results in a deprecation notice as well.

Return Types are Tentative

The return types added to internal classes are called tentative return types, because they do not cause fatal errors as is the case of user-land PHP classes.

The existing ReflectionMethod::hasReturnType() and ReflectionMethod::getReturnType() methods do not return these tentative return types. See the Reflection API changes on obtaining this tentative return type information programmatically.

Nullable Type Considerations

Nullable types were added in PHP 7.1, and the nullable type syntax causes syntax errors on PHP versions prior to PHP 7.1.

Applications that require compatibility with PHP 7.0 may not be able to declare return types due to lack of nullable type syntax. See the #[ReturnTypeWillChange] section on omitting the deprecation notice with the new #[ReturnTypeWillChange] attribute.

Union Types Considerations

Some internal classes have return types that are Union Types, added in PHP 8.0. Since Union Types are not supported in PHP versions prior to 8.0, and results in syntax errors, it may not be possible to declare return types on applications that require PHP 7 compatibility.

Adding #[ReturnTypeWillChange] attribute omits the deprecation notices, and provides cross-version compatibility PHP versions prior to PHP 8.0.

Reflection API Changes

The ReflectionMethod class receives two new methods to retrieve these tentative return types:

class ReflectionMethod {
    public function hasTentativeReturnType(): bool {}
    public function getTentativeReturnType(): ?ReflectionType {}
    // ...
}

ReflectionMethod::hasTentativeReturnType

ReflectionMethod::hasTentativeReturnType returns whether the queried method has a tentative return type declared.

$method = new ReflectionMethod(\ArrayAccess::class, 'offsetGet');
var_dump($method->hasTentativeReturnType()); // bool(true)

ReflectionMethod::getTentativeReturnType

ReflectionMethod::getTentativeReturnType returns a ReflectionType instance if a tentative return type is available, or null otherwise.

$method = new ReflectionMethod(\ArrayAccess::class, 'offsetGet');
echo $method->getTentativeReturnType(); // mixed

Avoiding the Deprecation Notice

To avoid the deprecation notice, update the existing child classes and interface implementations to be compatible with the parent method's return type. Any covariant return type is accepted.

class Foo implements ArrayAccess {
- public function offsetGet(mixed $offset) {}
+ public function offsetGet(mixed $offset): mixed {}
  // ...
}

Following the covariance rules, it is encouraged to narrow the scope of the return type to type specific to the use-case of the implementation.

class Foo implements ArrayAccess {
- public function offsetGet(mixed $offset) {}
+ public function offsetGet(mixed $offset): string {}
  // ...
}

#[\ReturnTypeWillChange] attribute

If it is not possible to add a return type (for example, to maintain PHP 5 compatibility, or to support PHP 7.0 due to lack of nullable types, or to account for lack of Union Types), this deprecation notice can be omitted by adding #[\ReturnTypeWillChange] attribute to the method declaration.

Attributes are new in PHP 8.0, but PHP 5 and PHP 7 simply consider them to comments, and do not cause syntax errors.

class Foo implements ArrayAccess {
- public function offsetGet(mixed $offset) {}
+ #[\ReturnTypeWillChange] 
+ public function offsetGet(mixed $offset): string {}
  // ...
}

Note that this is merely a temporary workaround; From PHP 9.0, all of such instances will result in a fatal error.

Backwards Compatibility Impact

It is likely that many PHP applications that extend/implement PHP internal classes/interfaces, but did not add return types, will encounter this deprecation notice. If the application can afford to drop PHP 5 and PHP 7.0 support, updating the child classes and implementations to add return types is the ideal fix.

If the application must support PHP 5 (which does not support return types), adding the #[\ReturnTypeWillChange] attribute avoids the deprecation notice in PHP 8.1 and upcoming PHP 8.x series. However, this workaround will not work in PHP 9.0 and later, making it absolutely essential to add matching return type declarations.

Related Changes


RFC Discussion Implementation