php

Reducing Errors With Type Hinting in PHP

honeybadger_staff

Honeybadger Staff

Posted on March 14, 2023

Reducing Errors With Type Hinting in PHP

This article was originally written by Adebayo Adams on the Honeybadger Developer Blog.*

Errors, also known as bugs, are developers' biggest nightmare. Bugs are so ubiquitous that everything you do after building a website or app involves finding and fixing them. What if there was a way to reduce the number of errors you must fix after pushing a project to production?

In this article, I will explain everything you need to know about type-hinting in PHP, which will help you achieve this goal. We’ll begin by exploring what type-hinting in PHP is, and then I’ll show you how to start using it in your applications. However, before we begin, let's examine what you need to know to get the most out of this article.

Prerequisites

To get the most out of this tutorial, you should have at least the following:

  • Basic knowledge of PHP
  • Familiarity with Object-Oriented PHP

With the prerequisites out of the way, we’ll look at what type-hinting is in the next section.

Type-Hinting

Type-hinting means explicitly stating the expected type of declarations in your code. This allows you to enforce specific types in your code. PHP allows you to type-hint function parameters, return values, and class properties to write more robust code.

Note: A declaration is any function, class, variable, or value declared in your code.

Although it’s greatly beneficial, type-hinting in PHP is a consistently under-learned concept because most tutorials and tutors treat fixing bugs and errors as something you do later when building applications. We’ll look at the different type declarations in PHP in the next section.

Different Types in PHP

As of PHP 8.0, PHP has thirteen different types you can specify for declarations in your code. Let's take a look at each of them below:

  • string - Value must be a string.
  • int - Value must be an integer.
  • float - Value must be a floating-point number.
  • bool - Value must be Boolean (i.e., either true or false).
  • array - Value must be an array.
  • iterable - Value must be an array or object that can be used with the foreach loop.
  • callable - Value must be a callable function.
  • parent - Value must be an instance of the parent to the defining class. This can only be used on class and instance methods.
  • self - Value must be either an instance of the class that defines the method or a child of the class. This can only be used on class and instance methods.
  • interface name - Value must be an object that implements the given interface.
  • class name - Value must be an instance of the given class name.
  • mixed - Value can be any type.
  • void - Value must be nothing. It can only be used in function returns.

Now that you've learned all the different types supported in PHP and what value each of them allows, let's look at how to type-hint function parameters in the next section.

Type-Hinting Function Parameters

Specifying the types of arguments a function accepts might be the most important part of a program where type hinting is most beneficial. Type hinting has been available for function parameters since PHP version 5.

You can type-hint function parameters in your code like so:

function add(int $a, int $b) {
    return $a + $b;
}
Enter fullscreen mode Exit fullscreen mode

The code above declares an add function that takes two numbers and returns the sum of both numbers. However, there's something different about this function: the int keyword. This keyword changes only one thing about how the function will work; the function will only accept the int type and throw an error if given any other type.

However, you could give the function two string numbers, like so:

echo add("2","2"); // returns 4
Enter fullscreen mode Exit fullscreen mode

The code above will return 4, which is the correct result, which means the int keyword is not working. This is because PHP attempts to convert wrong scalar values into the correct type, which it did successfully in this case. However, if you gave a string value of echo add("2","three");, you will get an error:

normal type error

The image above shows an error that states "Argument #2 ($b) must be of type int, string given", which means it accepts the first string "2" that was given because it successfully converted the string to an integer.

Strict Types

Type hinting the function parameters does not enforce the types automatically; you need to declare strict types at the very top of your file:

declare(strict_types=1);
Enter fullscreen mode Exit fullscreen mode

Note: The strict_types declaration must be the very first statement in the script.

The code above tells the script to enforce strict types. Now the int keyword will work as expected, so your code should now return an error:

int type error

The image above shows a type error that says,
Fatal error: Uncaught TypeError: add(): Argument #1 ($a) must be of type int, string given. This tells you the problem and the fix in one message, which is Argument #1 ($a) must be of type int.

Furthermore, depending on which integrated development environment (IDE) you use, your editor detects the strict_types and warns you inside the editor before running your code. For example, I use the Intelephense extension inside Visual Studio Code, which warns me about wrong parameters before I run the code, like so:

Intelephense type error

The image shows my editor warning me about the type error before running the code. Now that you have learned how to type-hint function parameters, let's look at how to type-hint function returns.

Type-Hinting Function Returns

The PHP 7 update shipped with type-hinting support for function returns to prevent unexpected return values. You can type-hint return values by adding the intended type after the parameter list prefixed with a colon (:), like so:

function add(int $a, int $b): int {
    return $a + $b;
}
Enter fullscreen mode Exit fullscreen mode

The code above adds type-hinting to the add function from the previous section. The code should still work as intended, but you could change the return value to a string:

function add(int $a, int $b): int {
    return "Hello";
}
Enter fullscreen mode Exit fullscreen mode

The function above will return a TypeError, as shown below:

Return value error

Note: Only types that are allowed for function parameters are for return types.

Void Returns

Sometimes, you might not want to return anything from a function; if you would like to enforce this, you can use the void type:

function like(): void{
    $post->likes + 1;
    return;
}
Enter fullscreen mode Exit fullscreen mode

The code above declares a like function that adds 1 to the likes on a post and returns nothing.

Static Returns

Alternatively, you might want to return the instance of the object that defines a function from the same function. You can use the static type for this purpose:

class Person
{
    public function returnPerson(): static
    {
        return new Person();
    }
}

Enter fullscreen mode Exit fullscreen mode

The code above defines a class, Person, with a function, returnPerson, that returns a Person object. Next, we’ll look at how to type-hint optional declarations in the next section.

Nullable Types

Sometimes, you want a function to take a particular type, but the parameter is not required for the function to run properly. In this case, you can make the parameter nullable:

function greeting(?string $username)
{
    echo $username === null ? 'Hello User!' : "Hello $username";
}
Enter fullscreen mode Exit fullscreen mode

The function above defines a greeting function that returns "Hello John!" when given the name John and "Hello User!" if the function is given null.

Note: You must give null if the $username is not available because you'll get an error if the function is called:
greeting()

Nullable Return Types

You can also make a return type nullable by prefixing the return type with a ?:

function greeting(?string $username) : ?string
{
    if ($username) {
        return "Hello, $username!";
    }
    return null;
}
Enter fullscreen mode Exit fullscreen mode

The function above defines a greeting function that returns either a string value or null and will throw an error if any other type is returned.

Now that you know how to type-hint optional declarations, let's look at how to allow multiple types for one declaration.

Union Types

The PHP version 8 update introduced union types, which allow you to specify more than one type for a single declaration. For example, a function that formats prices should be able to take an int or a float:

function formatPrice(float | int $price): string
{
    return '$' . number_format($price, 2);
}
Enter fullscreen mode Exit fullscreen mode

The function above declares a formatPrice function that accepts either int or float. By using the | to separate the different types you want the function to accept, the function can now work with either int or float types. Therefore, you can run the function like so:

echo formatPrice(5.99);
echo "<br/>";
echo formatPrice(5);
Enter fullscreen mode Exit fullscreen mode

The code above will run without errors. However, if you give the function a string or any other type that is not int or float, you will get an error:

union type error

The image above shows Fatal error: Uncaught TypeError: formatPrice(): Argument #1 ($price) must be of type int|float, string given. Next, lets look at how to type-hint class properties and variables.

Type-Hinting Class Properties

In the second section of this article, you learned that PHP allows you to type-hint function parameters, return values, and class properties. In this section, I will show you how to type-hint class properties.

Since PHP 7.4, PHP has allowed developers to specify the values that class properties can hold. You can type-hint class properties like so:

class Person
{
    public string $name;
    public int $age;
    public float $height;
    public bool $is_married;

    public function __construct($name, $age, $height, $is_married)
    {
        $this->name = $name;
        $this->age = $age;
        $this->height = $height;
        $this->is_married = $is_married;
    }
}
Enter fullscreen mode Exit fullscreen mode

The code above declares a Person class with $name, $age, $height, and $is_married type-hinted properties. PHP will only allow specified types to be assigned to the properties.

Note: You can assign all the available types to the property except the callable and void types, but you can only type-hint variables and properties inside a class.

Now that you have learned how to type-hint class properties, we’ll look at how to use the callable type in the next section.

Using the callable Type

The PHP 5.4 update came with the callable type, which allows you to force a function to accept another function as an argument. For example, if you want to type-hint a function that accepts another function as a parameter, you can do it like so:

function sortArray(callable $sort_function, array $array)
{
    $sort_function($array);
    return $array;
}
Enter fullscreen mode Exit fullscreen mode

The code above takes an array and a function, and then it uses the function to sort the array and returns the sorted array. To use the function above, you need to define the function that will be passed as an argument to the sortArray. In my case, I'll use a bubble sort function:

function bubbleSort(array $array): array
{
    $sorted = false;
    while (!$sorted) {
        $sorted = true;
        for ($i = 0; $i < count($array) - 1; $i++) {
            if ($array[$i] > $array[$i + 1]) {
                $temp = $array[$i];
                $array[$i] = $array[$i + 1];
                $array[$i + 1] = $temp;
                $sorted = false;
            }
        }
    }
    print_r($array);
    return $array;
}
Enter fullscreen mode Exit fullscreen mode

The code above takes an array of numbers, sorts it, and returns the sorted array. You can now call the sortArray function:

sortArray('bubbleSort', [1, 3, 2, 5, 4]);
Enter fullscreen mode Exit fullscreen mode

The function name should be wrapped in quotes without parentheses. If you give the sortArray any other type instead of a callable function, you'll get an error:

callable error

Fatal error: Uncaught TypeError: sortArray(): Argument #1 ($sort_function) must be of type callable, string given.

Note: Bubble sort is an algorithm that loops through a list, compares adjacent elements, and swaps them if they are in the wrong order. The loop is repeated until the list is sorted. Bubble sort is also sometimes referred to as sinking sort.

Now that you know the different types allowed in PHP and how to use them, let's look at the benefits of using type-hinting in your code.

Benefits of Type-Hinting

Type-hinting your code is one of the first steps to writing error-free code. In this section, we’ll look at some of the major benefits of type-hinting.

Eliminate Type Errors

Type errors are one of the main causes of bugs during development, and some errors even make it to production. Type-hinting allows you to crush such bugs in development because your code will force you to accept and return the expected types before testing your code.

Improve IDEs

As you saw in the Type-hinting Function Parameters section, type-hinting your code allows your IDEs and linters to indicate when you’ve used the wrong type, even before running your code. This might not be a big deal, but it can significantly improve the developer experience.

Readability

Readability might not seem like something you should consider when working on a side or personal project, but it's especially important when working in teams because you are probably going to write some complex code in your career. If every new developer on your team needed to talk with you before understanding your code, it would be stressful and unproductive.

Type-hinting helps with readability because, just by glancing through your code, other developers can understand the types you’ve used and easily avoid creating a bug in the program by using the wrong types.

Documentation

Type-hinting your code makes it significantly easier to document your code as a solo developer or when working in a team.

Type-hinting allows you to easily note the types a function accepts without testing the code with each type, which is the next most important step when writing documentation, after explaining what the function is used for.

It gets even better when everyone knows that type-hinting is being used in the code; this way, you can skip the part where you explain the types each function accepts.

Downsides of Type-hinting

Although the benefits of type-hinting are significantly greater than the drawbacks, I’ll discuss some of the drawbacks in this section.

More Code

Type-hinting helps you save a lot of debugging time, but it might result in writing more code. This, in turn, increases development time.

No Automatic Strict Typing

As you have learned in the previous sections, PHP requires you to declare strict types in every file you want to use type-hinting. However, when working on a large project, having to write declare(strict_types=1); every time you create a new file can be a little repetitive. This is not a big deal; it's just a line of code, but it can be better with strict automatic typing.

Unexpected Nullable Type Behavior

As you have learned in the previous sections, the null type doesn't work as expected.

For example, if a function accepts one argument, and the argument is nullable, I expect to be able to run the function like so: greeting();. However, this won't work and returns a type error instead.

Conclusion

Thanks for reading!
Hopefully, this article has taught you all you need to know about type-hinting, how to use it, its benefits, and the drawbacks of using it in PHP. I also hope this article will help you make a more informed decision about whether to add type-hinting to your PHP code in the future.

💖 💪 🙅 🚩
honeybadger_staff
Honeybadger Staff

Posted on March 14, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related