PHP 7 type hinting: what you shouldn't do

an elephant never forgets

When PHP 7 came up with strong types, I saw the light. I had the hope not to see anymore bugs and inconsistencies due to weak typing in PHP.

I remember reading some code and having no idea what could be the type of the variables I had in front of me. Can I use the return of this method as an int? A boolean? Will it create silent bugs and unexpected behaviors?

Strict types are big help as well as return type hint. You know what is the data you’re manipulating. You don’t have to guess anymore.

However, PHP 7 wasn’t the end of my typing struggle. You can still add a lot of ambiguity even if apparently PHP 7 tried to fix the problem. You still need to follow a couple of rules to keep your code consistently typed.

I used PHP 7.1.1-dev in the cli for every examples in this article.

PHP type declaration

PHP 7 introduced two possible typing: scalar types and return types. I won’t explain here the difference and how to use them. The PHP RFC will do it better than me:

PHP 7 has strong types… or weird one?

Let be straightforward: typing in PHP 7 can have unexpected results.

Skeptical? Here some examples:

<?php

function mySuperFunction(): int
{
    return "hello world";
}

mySuperFunction();

No problem with this code. The type declaration indicate that the method should return an int. Instead, it returns a string. It is not astonishing that PHP throw an error:

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

Let see another example:

<?php

function mySuperFunction():int
{
    return "hello world1";
}

mySuperFunction();

The same results as above: a type error. Great! What the problem with return type then? Did I lie to you?

<?php

function mySuperFunction():int
{
    return "1hello world";
}

mySuperFunction();

This should throw an exception, isn’t it? The return is still a string, we clearly type the return as an int.

Wrong. This function returns 1.

<?php

function mySuperFunction():int
{
    return 1.1456415;
}

mySuperFunction();

The return type is clearly not an int but a float. However, this code doesn’t throw any exception.

It returns 1.

Still not convinced?

<?php

function mySuperFunction():bool
{
    return "1hello world";
}

mySuperFunction();

PHP consider “1hello world” as a boolean and it returns… 1.

<?php

function mySuperFunction():bool
{
     return "abcde";
}

mySuperFunction();

In the weird world of PHP, "abcde" is a boolean. Yes, this method returns true!

As you can see, with those typing rules you can still mess up things pretty badly. Simply because the code states clearly something which may not be true.

PHP strict types

strong hulk Types should be as strong as Hulk himself!

PHP like weak typing.

I don’t.

The fact that we can convert silently a string to a boolean to an int at runtime allow you… to mess your code! It adds ambiguity to something which should be simple and straightforward.

Let’s be clear here. We are developers. Therefore we should control the data types in our code.

If we don’t, we let the door open to bugs, weird behaviors and misunderstanding between developers. The code will change. Bugs will appear, your boss will fire you, your wife will let you down and you will finish in hell, sad, alone, burning in the flames.

But wait! Everything is still possible. PHP has the solution!

Beware the strict type mode:

<?php

declare(strict_types=1);

function mySuperFunction(): bool{
        return 'abcde';
}

echo mySuperFunction();

Run this code and you will get Fatal error: Uncaught TypeError: Return value of mySuperFunction() must be of the type boolean, string returned!

I was happy to see this error when it popup on my screen. I’m not happy about error usually, but this time I was relieved. Of course a string is not a boolean!

My advice: put this strict type declaration everywhere in your code. Everywhere! Create a snippet in your IDE for it. Each time you create a PHP file, you should put this strict type declaration on top.

Unfortunately you can’t set this strict mode globally. You need to implement it in every single PHP files. The reason is simple: it allows whatever bundle and other external resources to work in your application, even if they don’t implement this strict typing mode.

I know some of you won’t agree. I know some of you are ready to burn consistency and clarity for the sake of a supposedly ‘flexibility’.

I know the arguments: ‘yes but it is easier because my boolean need to be displayed and therefore should be a string at one point’.

I would answer to them: fix your architecture and/or your implementation. If you need this weak typing, something is wrong in your code. If you really need it, please fix the real problem, don’t go around by using a boolean as a string as an int and whatnot.

You are a developer. You are not a hacker. You solve problems. You don’t go around them.

In 5 words: glory to the strict types! Now PHP 7 is finally a strongly typed language!

Nullable type in PHP 7.1

PHP nullable type: what a beast! The nullable type will smile at you but careful! He is a beast!

Here the PHP RFC for the new nullable type.

How can you use it wrong?

<?php 

declare(strict_types=1);

class User{
    //Value object
}

class UserRepository{

    private $database;

    public function __construct(Database $database){
        $this->database = $database;
    }

    public function getUserById(int $id):?User
    {
        //If user is not in the database, return null
        $user = $this->database->fetchUser($id);
        return $user;
    }
}

class EmailSender
{
    public function sendEmailToUser(int $userId)
    {
        $userRepository = new UserRepository(new Database());
        $user = $userRepository->getUserById($userId);

        //Can send email to... null!
        $this->sendEmail($user);
    }
}

$emailSender = new EmailSender();
$emailSender->sendEmailToUser(12345);

This code will crash if we try to fetch a User which doesn’t exist in the database. How can we send an email to null?

Obviously you can fix this with a condition as follow:

<?php 

...

class EmailSender
{
    public function sendEmailToUser($userId)
    {
        $userRepository = new UserRepository(new Database());
        $user = $userRepository->getUserById($userId);

        if ($user !== null) {
            $this->sendEmail($user);
        }
    }
}

However this approach has two problems:

  • Using The nullable operator will create this kind of null condition (if ($methodReturn !== null)) everywhere. It is useless and noisy.
  • The code above will fail silently if the user doesn’t exist. “Why this user didn’t receive his email?” will be your nightmare. You need to see that:
    1. The User doesn’t exists (probably a wrong user id passed to getUSerById)
    2. The User is null because of the nullable type!
    3. Somebody put this null condition and let the application… not doing anything

Here another solution:


...

class UserRepository{

    private $database;

    public function __construct($database){
        $this->database = $database;
    }

    public function getUserById($id):User
    {
        $user = $this->database->fetchUser($id);

        //If user is not in the database, an error will be thrown!
        return $user;
    }
}

...

There is no need for the nullable type in this specific case. Your code will throw an exception where it should. If an User doesn’t exist, the execution of the application should stop.

You just need to handle the error at that point. It is simple, clear, effective and don’t let space to confusion.

PHP nullable type and interface

The nullable type has other… surprises.

<?php 

declare(strict_types=1);

interface MySuperInterface
{
    public function superFunction():?int;
}

class SuperClass implements mySuperInterface
{
    public function superFunction():int
    {
        return 42;
    }
}

$superClass = new SuperClass();
echo $superClass->superFunction();

The implementation SuperClass of the interface MySuperInterface doesn’t fullfil his contract. The interface ask for a nullable return type, the implementation states that only int can be returned.

However, this piece of code work in PHP 7.1. No error will be thrown.

Wait… maybe we can anyway return null as stated in the interface?

Let’s try:

<?php

declare(strict_types=1);

interface MySuperInterface
{
    public function superFunction():?int;
}

class SuperClass implements mySuperInterface
{
    public function superFunction():int
    {
        return null;
    }
}

$superClass = new SuperClass();
echo $superClass->superFunction();

And the result is:

Fatal error: Uncaught TypeError: Return value of SuperClass::superFunction() must be of the type integer, null returned

Now I can understand that in some cases it could be useful. For example it’s not rare to have null values in a database.

I would advise you very strongly to use it with care. I rarely use this type in my code because I always find better solutions.

I would even prefer using a null object instead of null in most cases. Why? Simply because I hate checking if my variable is null all the time!

In a nutshell: you need to be careful

I like PHP. Especially since it has improved its typing. It’s not perfect, for sure, but it gets better and better.

Nevertheless you need to be careful when manipulating types in PHP. I won’t repeat it enough:

  • We need to use the strict mode.
  • We need to control the data which flow in our application.
  • If you need the weak typing, there is a problem in your implementation. Therefore: fix it!
  • We shouldn’t try to guess what variable has what type.
  • Avoid the nullable type as much as you can

We need to be consistent for the sake of every developers using our code. For me, it means being simply professional.

Obviously I would be happy to read your opinion on this in the comment below.