Always Use Type Declarations For Function Parameters and Return Values in PHP
What if I told you PHP has a built-in feature that will reduce the number of bugs in our code base? What if I told you it’s super simple to introduce into your daily coding routine? Would you be interested in using it?
In this article, we discuss why we should always use type declarations for function parameters and return values.
The Problem
Let’s say we’ve just written the following function to generate a formatted string indicating the length of another string.
When we call this with a string we get the expected output.
length is 6
But what if we accidentally send it a boolean? What happens then?
length is 1
Length is 1? That’s not what we would want to happen and we’ve just found a potential bug in our software.
As a brief sidebar, you might be wondering why this happened. Because PHP is loosely typed when we pass a boolean to a function that’s expecting a string it automatically type juggles it into a string. In the background, true
is stored as an integer 1
so PHP converts it to a string "1"
and then finds the length of the single digit.
Our example above is simple so it’s not a huge problem and most likely people aren’t going to notice it but what if we were doing a calculation with the length of that string?
This is not something we want to have happened but what is the solution?
Type Declarations for Parameters
Now what we want to have happened is for PHP to say “Hey, I’m looking for a string and not a boolean” but instead, it allows us to pass it data we didn’t account for in our function.
Thankfully PHP gives us a way to not shoot ourselves in the foot. This is done by adding declare(strict_types=1);
at the top of our PHP file and then specifying the type of the variable we’re looking for in our function call.
Now when we run this code we’ll get an error:
Fatal error: Uncaught TypeError: Argument 1 passed to initThermonuclearWar() must be of the type string, bool given, called in Standard input code on line 16 and defined in Standard input code:4
Stack trace:
#0 Standard input code(16): initThermonuclearWar(false)
#1 {main}
thrown in Standard input code on line 4
We’ve started requiring declare(strict_types=1);
and types on our parameters on every PR we review just because of the potential to catch bugs like this before it causes problems. We also wouldn’t have allowed such a sloppy check. :-)
What If We Want To Support Both?
Let’s say there’s a case where we might want to support both a string
and a bool
type in our function? How could we support that?
Before PHP 8.0 our only option was to specify the parameter as a mixed
type. A mixed
type parameter will take any type but to work with the variable we have to add checks to determine what type we’re working with.
Of course, the downside to this is that we’ve run into the same problem as before because we can now pass a new type to our function and run into the same problem.
To solve this problem PHP 8.0 added Union Types which allow us to specify a range of possible types. This is done by listing each of the types that could be safely accepted as a parameter using a pipe symbol (|) to separate them. For example, if we want to accept a string or a bool we would use string|bool
.
Now we can change our function to support Union Types.
When we run this we’ll get an error.
Type Declarations for Return Values
Another feature that will help us reduce bugs is to always declare our return type. Again because PHP is a loosely typed language we can get a value from a function and then operate on it in ways that are “unexpected”. For example, we might have a function that returns a string and then we check its string length to see if it’s what we expect.
In the future, another developer comes along and adds some additional checks. They don’t realize what the return value of the function is and modify the function to return a false
(maybe through a quick copy and paste in multiple locations).
When we run our code again and it returns false
our results may be unexpected.
Valid launch codes. Initialize missiles
Again not what we want.
To fix this we can declare the return type when we create the function.
Then when we run our code again we’ll get an error instead of unexpected behavior.
Fatal error: Uncaught TypeError: Return value of getLaunchCodes() must be of the type
string, bool returned in Standard input code:6
Conclusion
In this article, we discussed how to use type declarations for parameters and return values to reduce bugs and make our code easier to read. By declaring these we can more easily have bugs brought to our attention and act on them.
Scott Keck-Warren
Scott is the Director of Technology at WeCare Connect where he strives to provide solutions for his customers needs. He's the father of two and can be found most weekends working on projects around the house with his loving partner.
Top Posts
- Working With Soft Deletes in Laravel (By Example)
- Fixing CMake was unable to find a build program corresponding to "Unix Makefiles"
- Upgrading to Laravel 8.x
- Get The Count of the Number of Users in an AD Group
- Multiple Vagrant VMs in One Vagrantfile
- Fixing the "this is larger than GitHub's recommended maximum file size of 50.00 MB" error
- Changing the Directory Vagrant Stores the VMs In
- Accepting Android SDK Licenses From The OSX Command Line
- Fixing the 'Target class [config] does not exist' Error
- Using Rectangle to Manage MacOS Windows