Common causes
- A lookup returns null when nothing is found, but the return type is not nullable
- A built-in function returned false on failure (file_get_contents(), json_decode(), strpos()) and it was returned directly
- A code path with no return statement, which returns null implicitly
- Database values returned as strings while the function promises int or float under strict_types
- A library extended by your code changed its return type
How to fix it
- Read expected vs returned. The message states the declared type and the actual type. Open the function and find every return statement.
- Handle the failure path. When a lookup can fail, either declare ?User and let callers check for null, or throw a specific exception like UserNotFoundException.
- Check built-in return values. Test for false before returning results of functions such as file_get_contents() or json_decode(), or use JSON_THROW_ON_ERROR.
- Add the missing return. Make sure every branch, including the end of the function, returns a value of the declared type.
- Cast database values. PDO often returns numbers as strings; cast with (int) or (float) before returning, or set PDO::ATTR_EMULATE_PREPARES to false with mysqlnd to get native types.
- Fix return types on interface implementations. For the 'should be compatible' deprecation, add the matching return type (e.g. count(): int) or #[\ReturnTypeWillChange] for code that must support older PHP.
PHP 8
public function findByEmail(string $email): ?User
{
$row = $this->db->fetchOne('SELECT * FROM users WHERE email = ?', [$email]);
return $row ? User::fromRow($row) : null;
}
public function getTotal(): int
{
return (int) $this->db->fetchValue('SELECT SUM(qty) FROM items');
} How to stop it happening again
- Decide upfront whether a function returns null or throws on failure
- Run PHPStan or Psalm to catch mismatched returns
- Cast values at the data access layer