DEV Community

Peter Fox
Peter Fox

Posted on • Originally published at Medium on

Mastering PHP: Type Hinting techniques


Photo by 𝓴𝓘𝓡𝓚 𝕝𝔸𝕀 on Unsplash

Type hinting is considered by some to be the holy grail of features. One that all programming languages must have. PHP for the longest time didn’t have such a system but has now been adopted widely by most developers.

That said, PHP’s type system doesn’t go as far as other languages. Many implement what are known as Generics. This is the ability to enforce a type among structures and Collections. For instance, in Java we can specify that Arrays must only contain items of a certain type, for example, an Array of Strings.

Maybe someday we’ll have this functionality in PHP as well, but until then we can actually solve this with a few different techniques. For a lack of a better description, I refer to these as Soft Type hints and Runtime hints.

Type Hints

The first and most obvious type hints are the ones introduced in PHP 7 and are still being added to PHP. Type-hinted constants were only added in PHP 8.3.

Type hints are useful to help convey what needs to be passed to a method or function as a parameter or what that method will return. Type hints are going to affect the signatures of any classes they use them with as extending a class with type hints already established will mean they can’t be overridden.

An example of a class that makes full use of types would be:



<?php

class Foo
{
    public function bar(array $strings): \Closure
    {
       return function (string $string) use ($strings): bool {
           return in_array($string, $strings);
       };
    }
}


Enter fullscreen mode Exit fullscreen mode

There are, of course, limitations in our type hints because as previously mentioned, we can’t conform an array to be all of the same type and instead we must just use array . We also can’t constrain numbers to being only positive or within a certain range.

Another one can be Closures as there’s no way to describe anonymous functions within PHP’s native types. Instead, we must either use \Closure or callable . Often callable isn’t allowed to be used as a type as well.

Luckily, there’s still a way to describe these more complicated scenarios with type hints.

Soft Type Hints

Our next kinds of type hints are supplied via PHPDocs. While native types will throw exceptions during run time if a method is passed or returns the wrong type, PHPDoc type hints have no effect on the runtime of the application.

Instead, soft type hints help us purely when we’re using an IDE such as VS Code or PHPStorm, which will detect those types for us. The other use case is with static analysis tools like PHPStan and subsequently Rector.

The biggest advantage of using soft types is that it allows you to describe with more precision the type of any parameters, properties, etc. For instance, we can take the previous class and make it easier to understand the arrays or closures used.



<?php

class Foo
{
    /**
     * @param string[] $strings
     * @return \Closure(string): bool
     */
    public function bar(array $strings): \Closure
    {
       return function (string $string) use ($strings): bool {
           return in_array($string, $strings);
       };
    }
}


Enter fullscreen mode Exit fullscreen mode

The best way to make sure all your type usage is correct is to install PHPStan. From there you’ll likely need to use at least level 5. This can then be enforced through continuous integration steps that check the type hinting is correct.

There’s actually a list you can use if you want to use the correct soft type hint. Even better, there’s a PHPStan tool you can use to test if all the type hinting it correct per PHPStan if you’re unsure and want to run a quick test.

Runtime Hints

Our next way of supporting types is to use runtime hints. What this actually means is executing our own code to check the types from parameters. For instance, we can check if an array only contains a particular type of object. If it doesn’t, then we throw an InvalidArgumentException.



<?php

/**
 * @param string[] $foo
 */
function bar(array $foo) {
    foreach ($foo as $string) {
        if (! is_string($string)) {
            throw new \InvalidArgumentException('foo contains non-string value');
        }
    }

    // rest of the code
}


Enter fullscreen mode Exit fullscreen mode

By the way, this technique is sometimes referred to as defensive programming. Looking at the code example, this is pretty cumbersome. It’s a lot of code just to simply check if an array is correct. That’s why we often resort to a library instead, in this case webmozart/assert .



composer require webmozart/assert


Enter fullscreen mode Exit fullscreen mode

Now with this package installed we can shorten this down to a simple one-liner.



<?php

use Webmozart\Assert;

/**
 * @param string[] $foo
 */
function bar(array $foo) {
    Assert::allStrings($foo);
}


Enter fullscreen mode Exit fullscreen mode

One of the great things about this library is if you add the Assert extension to PHPStan, this will help your type coverage when the code is analysed.

Conclusion

In conclusion, mastering type hinting in PHP is an essential skill for developers looking to write clean, maintainable, and reliable code. While PHP’s type system still lacks some features found in other languages, such as generics, there are multiple strategies available to enforce stricter typing — whether through native type hints, PHPDoc annotations, or runtime checks. Leveraging tools like PHPStan and libraries like Assert can help you ensure type safety, even in complex scenarios. By integrating these techniques into your development workflow, you’ll be better equipped to handle PHP’s dynamic nature with confidence and precision.

I’m Peter Fox, a software developer in the UK who works with Laravel. Thank you for reading my article, I’ve got many more to read at https://articles.peterfox.me. I’m also now Sponsorable on GitHub. If you’d like to encourage me to write more articles like this please do consider dropping a small one-off donation.

Top comments (0)