Return Type Declarations

Before PHP 7, there was no way to prevent return values of different types. Because of this, far too many developers wrote code like this:

function f()
{
    // ...

    if ($something) {
        return 'result';
    } else {
        return -1;
    }
}

Functions and methods should only return values of a single type. This is not always easy:

function add($a, $b)
{
    return $a + $b;
}

var_dump(add(1, 2));
var_dump(add(1.0, 2.0));

Executing the shown above will print the output shown below:

int(3)
float(3)

Since the days of PHP 4, it has been considered a best practice to document not only parameter types but also return types using annotations in code comments:

/**
 * @param int $a
 * @param int $b
 *
 * @return int
 */
function add($a, $b)
{
    return $a + $b;
}

This best practice does not, however, lead to a runtime error as it is only interpreted by static analysis tools, documentation generators, and IDEs. It is not used by PHP itself.

To enforce that the add() function always returns an integer value with PHP 5, we would have to manually check the return value before actually returning it and raise an exception when the type of the value to be returned does not match the expected return type:

function add($a, $b)
{
    $result = $a + $b;

    if (!is_int($result)) {
        throw new RuntimeException(
            'Result cannot be represented as integer'
        );
    }

    return $result;
}

Nobody ever did that, of course.

PHP 7 introduced optional type declarations for function and method return values to address this. Just put a colon after the parenthesis of the parameter list followed by a type to use this new feature:

function add($a, $b): int
{
    return $a + $b;
}

Return type declarations are subject to the same two modes of interpretation for scalar types, coercive and strict, that we discussed earlier for parameter type declarations.

When interpreted strictly, a type error is raised at runtime when the return statement’s argument does not match the type that is declared for the return value of a function or method:

declare(strict_types=1);

function f(): int
{
    return '1';
}

f();

Executing the code above leads to the following type error:

PHP Fatal error: Uncaught TypeError:
Return value of f() must be of the type integer, string returned

In coercive mode, the value passed to the return statement will be type cast to the declared return type:

function f(): int
{
    return '1';
}

var_dump(f());

Executing the code above generates the output shown below:

int(1)

Functions and methods that do not declare a return type work in PHP 7 exactly as they would have in PHP 5. The declaration of a return type is entirely optional, unless you override a method from a parent class that has a declared return type.

The same holds true for implementing a method defined in an interface that already declared a return type. Let us look at an example:

interface I {
    public function m(): A;
}

Let us try to cheat by changing the return type:

class C implements I {
    public function m(): B
    {
        return new B;
    }
}

PHP will raise an error at compile-time:

PHP Fatal error:
Declaration of C::m(): B must be compatible with I::m(): A

The introduction of optional type declarations for function and method return values does not change the location of the & symbol when returning by reference.

The interceptor methods __construct(), __clone(), and __destruct() cannot declare a return type because they do not return anything:

class C
{
    public function __construct(): C
    {
    }
}

This will result in a fatal error:

Fatal error: Constructor C::__construct() cannot declare a return type

The void return type can be used to express that a function or method does not return anything:

function f(): void
{
    return 'value';
}

Executing the code shown above will lead to the error shown below:

Fatal error: A void function must not return a value in ...

As mentioned earlier, the interceptor methods __construct(), __clone(), and __destruct() cannot declare a return type. This is also the case for the void return type:

class C
{
    public function __construct(): void
    {
    }
}

PHP will not allow you to do this:

Fatal error: Constructor C::__construct() cannot declare a return type

A function or method that has a void return type declaration still has the default return value of null:

function f(): void
{
}

var_dump(f());

The result, unsurprisingly, is:

NULL

Please note that if you use a void return type declaration then the respective function or method must not even have a return null; statement in its body:

function f(): void
{
    return null;
}

Executing the code shown above will print the output shown below:

Fatal error: A void function must not return a value
(did you mean "return;" instead of "return null;"?)

The type of a return value can also be declared to be nullable:

function f(): ?stdClass
{
    return null;
}

function g(): ?stdClass
{
    return new stdClass;
}

function h(): ?stdClass
{
    return false;
}

Executing the code shown above will print the output shown below:

PHP Fatal error: Uncaught TypeError:
Return value of h() must be an instance of stdClass or null,
boolean returned in ...

The nullability of a return type declaration can be removed in a child class, but it cannot be added in a child class:

class ParentClass
{
    public function m(): int
    {
    }
}

class ChildClass extends ParentClass
{
    public function m(): ?int
    {
    }
}

Executing the code shown above will print the output shown below:

PHP Fatal error: Declaration of ChildClass::m():
?int must be compatible with ParentClass::m(): int in ...

This is in line with the Liskov Substitution Principle, because a child class cannot return a value that the parent class would not return. If this were possible, the calling code might suddenly have to deal with unexpected return values when the parent class had been substituted by such a child class.