Introduction
In PHP every variable has one of the built-in types, such as null, bool, int, string, array, or object.
Because PHP is a dynamically typed language (also called weakly typed or loosely typed), you do not need to declare your variables' types; this is determined and checked at runtime based on the value the variable stores.
By contrast, strongly typed languages do require you to declare the type of each variable you use, and it's impossible to store a different type of data than the one the variable is declared for. These languages check for type errors at compile time - before the code actually gets executed.
PHP doesn't really have this first manual code compilation step. The code gets compiled Just-In-Time (JIT) right before being executed. As a result, many problems - from subtle bugs to fatal errors - are often only visible when your code is executed, and not before then.
Type juggling
Consider the following example:
→ $total will contain the value 1250. Even though we didn't specify that $salary and $bonus are integer numbers, PHP figured it out.
What happens if we let PHP conclude that $salary and $bonus are strings instead of integers?
→ $total still contains the integer 1250. Even though we provided two strings, PHP knows that you can't really add up two strings, so it has cast $salary and $bonus to integers and then added the two up.
This is what is called type juggling or type coercion: PHP will silently cast variables to other types depending on their value and the operation you're trying to perform to do the best it can to do what you are asking.
And, of course, sometimes PHP doesn't do what you expect and because this type juggling happens silently, it may take a while for you to realize what is happening and why it is happening.
Avoiding type errors
As software developers we want to avoid confronting our users with errors, and we sleep better at night if we know our code is robust and as error-free as possible.
One approach to increasing the quality, stability, and maintainability of your code is to use type hinting.
Type hinting
Type hinting is a PHP feature that lets you specify:
- the type of parameters a function or method expects to receive
- the type of variable it is expected to return - conveniently called the return type.
- the types of your class properties
By using type hinting, you are giving PHP more information about the type of data your variables are supposed to contain. PHP can then alert you about incorrect type usage instead of trying to run that code and risk buggy behavior.
Example: Type problem 1
Consider the following scenario:
Expected result: $avg = (12 + 8) / 2 = 10
Actual result: $avg = (12 + 0) / 2 = 6
Because of dynamic typing, inside the average() function, PHP treats $num2 as a boolean because it has the value FALSE.
To be able to add up integers and booleans, PHP will silently cast the booleans into integers (TRUE becomes 1, FALSE becomes 0).
So we expected $num2 to contain an integer value (8), but due to an unrelated bug, it contains a boolean that gets cast to 0. As a result, average() produces an incorrect result (6) instead of 10.
No error was raised because there was no problem with the PHP syntax. The problem was that a function expected to receive a certain type, while a different type was received, and coercing it into the type it expected resulted in unexpected behaviour.
Let's fix the above scenario.
Example: Type problem 2
Consider this slightly different variation of the same problem:
You correctly call average() and pass two integer values. However, inside that function something goes wrong with your calculation, and it returns NULL instead. Or FALSE.
When you attempt to print NULL or FALSE, they are cast to an empty string.
When you attempt to perform calculations with NULL or FALSE, they are cast to the integer 0.
Whatever you want to do with the return value of average(), the value could be correct or incorrect, depending on whether or not some kind of silent and unexpected type casting happened along the way.
Solution
In many cases type hinting can prevent these kinds of hard to trace errors.
Let's use type hints to make average() more robust.
Step 1: enable strict type checking
Type hints are completely ignored unless you enable strict type checking.
At the top of each .php file where you want to enable strict type checking, after the opening <?php tag, add the following line:
declare(strict_types=1);Step 2: add type hints
declare(strict_types=1);
function average(int $num1, int $num2): float {
return ($num1 + $num2) / 2;
}
$avg = average(12, 7);Changes made to average():
- the two parameters are now type-hinted to int (integer)
- the return type is set to float (floating point / number with decimal point)
With these changes in place, try to call average() with incorrectly typed parameters:
declare(strict_types=1);
function average (int $num1, int $num2): float {
return ($num1 + $num2) / 2;
}
$avg = average(12, FALSE);
This will result in the following error:
Fatal error: Uncaught TypeError: average(): Argument #2 ($num2) must be of type int, bool given [...]
If you leave the type hints in place but remove the strict types declaration, $avg will be set to 6 because $num2 (FALSE) was silently cast to 0 during the calculation (see problem 1).
Return types
Example: Incorrect return type
Let's say we introduce a bug in our code, and average(), which is supposed to return a float, attempts to return a boolean:
declare(strict_types=1);
function average (int $num1, int $num2): float {
return TRUE;
}
$avg = average(12, 7);This will result in the following error:
Fatal error: Uncaught TypeError: average(): Return value must be of type int, bool returned [...]The great thing about these errors is that they alert you when incorrect value types are passed to a function or returned from a function, instead of doing silent casting and possibly returning unexpected values.
Typed properties
Class properties and methods can be type-hinted too:
This works for constructor parameter promotion as well:
To try if your type hinting worked, try to instantiate a Car object with incorrect parameter types, such as passing the string "4" instead of the int 4:
$mycar = new Car("Toyota", "4"); Fatal error: Uncaught TypeError: Car::__construct(): Argument #2 ($doors) must be of type int, string given [...]PHPStan: static analysis
Static analysis tools can evaluate your code for problems without having to execute it.
PHPStan is an often-used static analysis tool to help detect type errors (and other problems).
See the materials on developer tools for more info on how to set up and use PHPStan and related tools.
Summary
- PHP is a weakly typed / loosely typed / dynamically typed language.
- Dynamically typed languages have no type declarations; each variable's type is determined at runtime based on its value. This can have unintended consequences (bugs) when PHP silently assumes a variable has a different type than you intended.
- PHP has an optional system for working with strict types, which improves reliability, quality, and maintainability of your code.
- Enable it with
declare(strict_types=1) at the top of every.phpfile where you want to use it. - It's a good practice to always enable strict types in all your .php files.
- If strict types are not enabled, type hints are silently ignored.