What is PHP's declare(strict_types=1); and why you should use it
You may have had people tell you that you should use declare(strict_types=1);
in your PHP files, but
what exactly does it do, and why should (or shouldn’t) you use it?
History
A long, long time ago PHP didn’t have scalar types, or even return types. In PHP 7.0 both of these got introduced. In 5.6 and before we had to use annotations, and couldn’t add it in the language. (If you still see code like this it’s probably from that era.)
/**
* @param string $input
* @return string
**/
function foo($input) {
var_dump($input);
return $input;
}
Nowadays we can use scalar types, return types, property types. We even have unions and intersection types now. Which means we can write the same code from before like so.
function foo(string $input): string {
var_dump($input);
return $input;
}
Using declare(strict_types=1)
But what do you think happens if we then call this method with an integer, or a boolean like so?
Will this give us a TypeError
, will it cast the type or will it stay the original type?
foo(3);
foo(2.5);
foo(true);
To determine what happens we need to look at the top of the file that is doing the calls. If we see declare(strict_types=1)
,
then a TypeError
will be thrown when we run this code. If you see nothing related to strict_types
or declare(strict_types=0);
Then the type will be cast, and we would get the following output:
string(1) "3"
string(3) "2.5"
string(1) "1"
Why you should use declare strict types
Unintended type casts can result in unexpected behavior. If we have a function that takes a boolean, but we pass it a
string, any truthy string will become true
. So if you get user input from a JSON POST for example, and you pass the
incorrectly typed data, it will always be interpreted as true, and may cause hard to uncover bugs.
However, if you use declare(strict_types=1);
everywhere in your code base, you instead have a TypeError
being thrown,
causing execution of your code to stop, and (hopefully) your monitoring going off. So while it may break your website for a
bit, it will help you figure out the issue much faster and reduce weird behaviour.
When something breaks you want it to break loudly. It’s generally better to have an exception or error being thrown when unexpected issues arise, rather than ignore it and continue your program, even if everything is going wrong. Because before you know it, you have wrong data in the database, or even emails being sent to customers when it wasn’t intended.
For example, we had an older part of the code base that handled the login flow.
The login method looked something like this, and had no types as it was originally written for PHP 5.X.
Now the types here are actually wrong, for password a string
(namely the password itself), was
provided if there was one, and false was passed if it was a login without a password (a log in with google button for example).
/**
*
* @param string $method
* @param string $userName
* @param bool $password
*/
function login($method, $userName, $password = false)
At some point we had to do some refactors in this part of the codebase, and decided to add types.
And suddenly, no one could log in anymore with a password. Any password they passed was just cast to true
,
and then later when that was checked with password_verify
, the 2 passwords didn’t match.
function login(string $method, string $userName, bool $password = false)
If we had declare(strict_types=1);
at the top of this file, we would’ve had a TypeError
.
This does mean customers would’ve seen an Oops something went wrong!
type of screen.
It also would’ve meant our alerting would’ve gone off, and the change would’ve been rolled back, while
we looked into the issue. With a stack trace it would’ve been easy to spot where this went wrong, and
we could have updated the type like so. But because we didn’t have that, we had to spend hours debugging
why customers reported not being able to log in.
function login(string $method, string $userName, bool|string $password = false)
Caveats of strict_types in PHP
As I mentioned earlier, you have to look at where the code is calling from.
Let’s say we have 2 files, functions.php
and index.php
, where one has declare(strict_types=1)
, and the other doesn’t.
In this case, no TypeError
is thrown, as the code that is executing the takesString
, isn’t using strict_types
.
<?php
//functions.php
declare(strict_types=1);
function takesString(string $input) {}
<?php
//index.php
require_once __DIR__ . '/functions.php'
takesString(3); // no error
But if we now turn this around and make index.php
strictly typed, then we get a TypeError
, this is because the
code that is executing takesString
is now in strict_type
mode.
<?php
//functions.php
function takesString(string $input) {}
<?php
//index.php
declare(strict_types=1);
require_once __DIR__ . '/functions.php'
takesString(3); // throws a TypeError
How to use declare(strict_types=1) in PHP
If you decide to use strict_types
in PHP, then I would advise to use it everywhere in your code base.
When you are using tools like php-cs-fixer
or php code sniffer
, then you can add checks to make it required and add
it to the whole code base at once. That may not always be possible for a legacy project. In that case you could use a tool like
PHPStan to make it required, and add all files where you currently can’t add strict types to the baseline.
See this rule
on how to do that.
If you want to get notified of the next blog post, join the newsletter.