How often do you find yourself adding extra checks to ensure variable types are what you expect? Maybe you have encountered a bug such as:
- Notice: Array to string conversion
- Fatal error: Call to a member function getEmail() on null
Let's be real, debugging is not the most exciting part of your job, especially when the issue boils down to a simple type error. That's where strict types come in handy, making your life a bit easier.
In today's fast-paced world of web development, it is essential to write code that is both efficient and reliable. One way to achieve this is by using PHP strict types. By enforcing strong data types, you can improve the readability and maintainability of your code while reducing potential errors and bugs.
In this article, you will learn about the benefits and best practices for implementing strict types in PHP development. So if you're looking to take your web development skills to the next level, let's dive into the world of PHP strict types!
What are strict types in PHP
Release of PHP 7.4 introduced restrictive typed properties.
Strict types in PHP refer to a feature that allows developers to declare variables with specific data types. This helps improve code readability, maintainability, and catches vast amount of bugs before they reach production.
By using strict types, developers can ensure that their code is working as intended and makes it easier for other team members or future maintainers of the codebase to understand and modify the code.
Importance of using strict types in PHP development
Using strict types in PHP development is important for several reasons.
Firstly, it helps to improve the readability and maintainability of your code by making it clear what data type a variable represents. This is often necessary for complex projects where multiple team members modify the codebase.
Secondly, strict types reduce potential errors and bugs in your code.
By declaring variables as specific data types, you can ensure that they are used correctly throughout your codebase. This can prevent unexpected behavior or crashes caused by incorrect data type conversions or other common programming mistakes.
One of the biggest benefits of using strict types in PHP development is that it can help reduce the amount of defensive programming required.
Defensive programming involves writing code that checks for potential errors or unexpected behavior, such as verifying whether a variable is null or not. When you declare strict data types they serve you as a contract which in turn increases your confidence about the application.
This means that you no longer need to clog your codebase with checks like if ($foo !== null)
or is_numeric
.
Instead, you can validate the variable at the edges of the application (such as controllers) and be certain about its type everywhere else.
The disadvantages of defensive programming is that it add lots of checks and protections to stop problems which can make the code complicated, slow and harder to understand.
Definition and explanation of declare(strict_types=1);
Without strict mode PHP is weakly typed. There is no compiler which could check the code before shipping to production. Even in strict mode types are verified only at runtime.
You can rely on static analysis to mitigate this limitation.
The best description for Modern PHP could be: a gradually typed language. This is common for scripted languages that want to offer type safety.
Once declared, variable type can't be changed later.
The syntax for enforcing strict types in PHP is as follows:
<?php
declare(strict_types=1);
The declare(strict_types=1)
specifies that typed variable should have it throughout its life.
How to use strict types in function arguments and return types
<?php
declare(strict_types=1);
$a = "1";
function foo(?string $a): int
{
return (int)$a;
}
// union types; note how the shorthand '?' is replaced with 'null'
function bar(null|string|int $a): int
{
return (int)$a;
}
function fooBar(int $a): int {
return $a;
}
var_dump(foo($a));
var_dump(bar($a));
var_dump(fooBar($a)); // this fails
Result of executing this code:
int(1)
int(1)
Fatal error: Uncaught TypeError: fooBar(): Argument #1 ($a) must be of type int, string given, called in /home/user/scripts/code.php on line 23 and defined in /home/user/scripts/code.php:17
Stack trace:
#0 /home/user/scripts/code.php(23): fooBar('1')
#1 {main}
thrown in /home/user/scripts/code.php on line 17
If you have enabled linter, good IDEs will underline the offending expression:
The strict types won't let you pass string("1") to argument expecting int:
<?php
declare(strict_types=1);
function fooBar(int $a): int {
return $a;
}
var_dump(fooBar("1"));
var_dump(fooBar("text"));
Result:
Fatal error: Uncaught TypeError: fooBar(): Argument #1 ($a) must be of type int, string given, called in /home/user/scripts/code.php on line 9 and defined in /home/user/scripts/code.php:5
Stack trace:
#0 /home/user/scripts/code.php(9): fooBar('1')
#1 {main}
thrown in /home/user/scripts/code.php on line 5
Same code without strict types passes string to the method (int argument type is not enforced):
<?php
function fooBar(int $a): int {
return $a;
}
var_dump(fooBar("1"));
var_dump(fooBar("text"));
Results in error only with "text" string value that could not be interpreted as integer:
int(1)
Fatal error: Uncaught TypeError: fooBar(): Argument #1 ($a) must be of type int, string given, called in /home/user/scripts/code.php on line 8 and defined in /home/user/scripts/code.php:3
Stack trace:
#0 /home/user/scripts/code.php(8): fooBar('text')
#1 {main}
thrown in /home/user/scripts/code.php on line 3
How to use strict types in PHP functions
<?php
// actually this is not necessary for calling standard functions with strict argument
declare(strict_types=1);
$ids = ['1', '2', 3, 4];
// returns true
var_dump(in_array('3', $ids)); // strict is false by default
// returns false
var_dump(in_array('3', $ids, strict: true)); // named parameter is optional, but can improve readability for people who are not PHP-ninjas
// returns true
var_dump(in_array(3, $ids, strict: true));
$values = ['banana', 'terracotta', 'pie'];
// returns true in PHP before 8.0 and false in PHP >= 8.0
// in expression 0 == "any string" string used to be cast to int
var_dump(in_array(0, $values));
// returns false
var_dump(in_array(0, $values, strict: true));
// returns true
var_dump(in_array('banana', $values, strict: true));
How to use strict types in Value Objects
With Domain Driven Design you can utilize types to strengthen the codebase depending on the application context.
Side note: In Domain-Driven Design (DDD), we use this idea to build software as from LEGO bricks. We start by understanding the "domain," which is the specific area or topic the software is about (a shop, a school, a library). We learn all about it by talking to the people who know it best, just like how you might ask a friend for ideas on what to include in your LEGO city.
<?php
declare(strict_types=1);
class Email {
public function __construct(
public readonly ?string $value
) {
if (!$this->isValid($value)) {
throw new LogicException('invalid email format');
}
}
public function __toString(): string {
return $this->value;
}
private function isValid(?string $email): bool {
// secret email validation algorithm
}
}
// *** Another file ***
try {
$rawEmail = $request->getParam('email');
$email = new Email($email);
$emailSender->sendTo($email)
} catch (Throwable $e) {
// handle exception
}
Dangers of the lack of strict types
In weak typed mode, there are no runtime nor static analysis checks because code has no such information.
This means that variable or argument type can change during execution and match value assigned to it. While this flexibility can be useful in certain situations, it can also lead to potential bugs.
For example, if you accidentally assign a string value to an integer variable or vice versa, PHP will automatically convert the data type to match the new value. This can cause unexpected behavior or runtime crashes.
Moreover, some type juggling is not intuitive.
If variable is not declared with a specific data type it requires more effort to understand how it should be used. Lack of strict types might result in a difficult to understand and maintain codebase with many assertions.
Checking whether variable is not null or is initialized bloats code with nested decision trees.
Best Practices for Implementing Strict Types in PHP Code
Here are some tips for using strict types effectively in your PHP code:
- Declare all variables with specific data types whenever possible.
- Use the
declare(strict_types=1)
construct to enable strict types throughout your entire PHP file. - Use type hinting and comments to document the data types of variables throughout your codebase.
- Be consistent in using strict types throughout your codebase.
- Consider using a linter tool, such as PHPStorm's built-in linting feature or PHPStan, to receive visual feedback related to strict types in your codebase.
Common mistakes you should avoid when implementing strict types
Here are some common mistakes to avoid when implementing strict types in PHP:
- Not declaring all variables with specific data types.
- Using type hinting or comments inconsistently throughout your codebase.
- Using type hinting or comments in place of actual variable declarations.
Examples of how to use strict types in real-world scenarios
Here are a few examples of how to use strict types in real world scenarios:
When working with databases, using strict data types can prevent unexpected behavior or crashes caused by incorrect data type conversions. For example, if you declare a variable as an integer but try to assign it a string value from a database query. PHP will automatically convert the data type of the variable to match the new value.
There are cases when 0 and empty string "" should not be treated equally.
Strict types handled with form submissions can prevent injection attacks and other security vulnerabilities. It is better to crash the program than to corrupt database with fraudulent row.
When working with APIs or third-party libraries, strict types ensure compatibility and consistent behavior.
Complex calculations and mathematical operations must be expressed with precise types. For example, if you declare a variable as an integer but try to perform arithmetic operations on it with floating point values, PHP will automatically convert the data type of the variable to match the new value.
Imagine what happens what you lose fraction of amount on each transaction. What if you multiply the amount by quantity and increase rounding errors more?
Conclusion
Using strict types in PHP development is important for several reasons.
It helps improve the readability and maintainability of your codebase by making it clear what data type a variable represents. This is helpful when working with large or complex projects where multiple team members may need to modify the codebase.
Using strict types in PHP development can reduce potential errors and bugs in your code. Declaring variables as specific data types ensure that they are used correctly throughout your codebase. This prevents unexpected data type conversions.
However, it might not be suitable to work with legacy codebases or libraries that have not been migrated to modern PHP. In that case, strict typing should be implemented gradually.
Finally, strict types can reduce the amount of defensive programming.
Strong typed code defines a contract within your application.
Lower amount of decision trees (ifs
) means lower cognitive effort to understand the logic.
If you're looking to take your web development skills to the next level, I encourage you to start using strict types in your own PHP projects. It is an essential tool for improving the quality of your code and making it more robust and reliable. So why not give it a try today?
Learn more about declare in the PHP documentation.