Rethinking PHP Comparisons: A Case for Strict Equality in PHP<8

Rethinking PHP Comparisons: A Case for Strict Equality in PHP<8

Opinions expressed are solely my own and do not express the views or opinions of my employer

You've probably heard the following advice with regard to PHP comparisons:

"Use == if you care only about equal values, and use === if you care about matching types."
"An exact comparison (===) is necessary only to ensure both the values and data types match."

But let's pause for a moment. Is this sound advice? Or are we perpetuating a misconception that could lead to subtle bugs and security vulnerabilities?

Let's challenge these notions and explore why using === by default might be a better approach.

The Fundamental Issue

At the most fundamental level, computers don't have a metaphysical notion of abstract values. There's no Platonic ideal of the number 3 floating in your CPU. Everything is ultimately represented as patterns of ones and zeros in memory.

Intriguing side note: While traditional computers operate this way, modern AI language models might be inching closer to understanding metaphysical concepts. But that's a discussion for another post!

In programming, there's no such thing as a "value" separate from its type, as the quotes would imply. An integer 3 and a string "3" might look the same to us, but to a computer, they're fundamentally different beasts with distinct memory representations and behaviors.

The Root of the Problem

The issue traces back to PHP's evolution. The strict equality operator (===) didn't appear until PHP 4. Early PHP developers had no choice but to use ==. When === arrived, it was framed as the option for type matching. In truth, PHP was flawed from the start by omitting an identity operator.

So what's the deal with ==? It's the loose comparison operator, performing type juggling before comparison. It forces both operands into the same type and then compares them.

Let's revisit the original advice. Understanding that == performs type juggling reveals where the interpretation goes awry. The semantics are subtle: not caring about type and actively allowing type juggling are distinct concepts.

A more precise statement would be: "Using == signals a conscious choice to accept the consequences of type juggling in this comparison, should the variables differ in type."

This nuance matters. It shifts our perspective from passive acceptance of type differences to active engagement with PHP's type system. It prompts us to consider: do we really want type juggling here, or are we inviting unexpected behavior?

PHP's Dynamic Nature

PHP, being a dynamic language, further complicates the situation. Variables don't have fixed types – only the values they hold do. It's a bit like Schrödinger's variable:

function unpredictableReturn() {
    $options = [5, "5", 5.0, null, false, []];
    return $options[array_rand($options)];
}

$result = unpredictableReturn();
// What type of value does $result hold? As Willy Wonka said:
// "There's no earthly way of knowing which direction we are going"        

This flexibility is part of PHP's charm, but it's also a potential pitfall. Can we ever safely assume a variable's type? Not really. That function you're calling might return an integer today and a string tomorrow.

Real-World Pitfalls

Let's look at some real-world examples that showcase how == can be a lurking landmine:

The telephone trap:

$a = 1;
$b = "1-800-555-0123";
if ($a == $b) {
    echo "$a is equal to '$b'\n";
} else {
    echo "$a is not equal to '$b'\n";
}
// Output: 1 is equal to '1-800-555-0123'        

Surprise! They're "equal". Now imagine this in a system handling phone numbers. Yikes!

The Password Peril:

if (0 == "Hunter2") {
    echo "Access granted\n";
} else {
    echo "Access denied\n";
}
// Output: Access granted        

Double yikes! This is why we can't have nice things in PHP.

These aren't just contrived examples. They represent real issues that have caused real headaches (and security breaches) for developers.

Community Frustrations

This has been a point of contention in the PHP community for years. In 2006, a bug report was filed about PHP's type juggling behavior. The discussion continued for years, highlighting the ongoing struggle developers face with this "feature."

As recently as 2020, developers were still getting caught out:

"This 'feature' just burned an hour of my time as I wracked my brain trying figure out why a continue; statement in my foreach loop was being triggered. I had a value of (int) 0 in one of the array items, and it passed an if-test that compared it to a particular string. Super frustrating."

Some developers have even stronger opinions. One frustrated programmer in 2013 wrote:

"Making zero equal any string is just wrong. It completely goes against how PHP operates in every other instance, and no doubt causes developers no end of distress and frustration. It's a feature like how getting beaten up in a dark alley is a feature of that alley."

While this borders on the hyperbolic, it underscores the very real frustration this behavior can cause.

The Case for ===

So, why lean towards ===? Here are a few thoughts:

1. Security first: Many vulnerabilities lurk in the shadows of unexpected type-coercions. Using === shines a light on these dark corners.

2. Clear intentions: When you use ===, you're telling future maintainers (including yourself) exactly what you expect.

3. Following PHP's evolution: PHP 8's stricter type handling suggests the language designers see the value in this approach.

4. Consistency is key: Adopting === as a default reduces cognitive load. No more guessing whether type-coercion is intended or accidental.

Don't take my word for it. Nikita Popov, a core PHP 8 developer, wrote in RFC "string_to_number_comparison":

"The current dogma in the PHP world is that non-strict comparisons should always be avoided, because their conversion semantics are rarely desirable and can easily lead to bugs or even security issues. The single largest source of bugs is likely the fact that 0 == "foobar" returns true."

Real-World Security Implications

The security risks of loose comparisons in PHP have led to real-world vulnerabilities in popular software:

- CVE-2022-47034: A type juggling vulnerability in PlaySMS v1.4.5 and earlier allowed attackers to bypass authentication entirely.

- CVE-2020-8088: UseBB forum software had a severe vulnerability where using != instead of !== for comparing password hashes allowed for login bypass in certain cases.

These vulnerabilities underscore the critical importance of using strict comparisons, especially in security-sensitive operations like authentication.

Intentional Use of Type-Coercion

While we've been advocating for strict comparisons, it's important to note that there are times when type-coercion can be useful, such as comparing integers to floats.

In the case of string comparison especially, you almost never want to have a string equal to a variable of another type. There are exceptional cases, such as this function that compares a hex byte code to a decimal number:

function check_byte_equals($hex_byte, $dec) {
    // Intentional use of == for type-coercion
    return '0x'.$hex_byte == $dec;
}
check_byte_equals('FF', 255); // evaluates to True        

Here, we're explicitly using == to coerce the hexadecimal string to an integer. But notice how this use is intentional and localized. The key is to be mindful and explicit when using type-coercion, rather than relying on it by default.

The Silver Lining

The good news is that PHP 8 has improved type-coercion behavior, addressing some of these issues. The bad news? It came at the cost of backwards compatibility.

Conclusion

As PHP developers, we shape the future of our projects with the technical decisions we make along the way. Embracing strict comparisons builds a foundation of clarity and predictability.

Next time you reach for a comparison operator, pause. Ask yourself: Is type-coercion necessary here? If not, === becomes your ally in crafting robust code.

In PHP, a touch of strictness yields cleaner, safer, and more maintainable software. It's a small change with significant impact.

For those still using PHP versions prior to 8, consider this a call to action. PHP 8 offers stricter type handling and improved comparisons, addressing many issues we've discussed. PHP 7 introduced type declarations which can also improve type safety. Upgrading brings not just these improvements, but enhanced performance, new features, and better error handling.

Don't let outdated quirks hold your projects back. Embrace PHP 8's advancements – or better yet, consider languages built on more rigorous design principles. Your future self will thank you.

References and further reading:

PHP Wiki. (n.d.). RFC: String to Number Comparison. Retrieved September 29, 2024, from https://wiki.php.net/rfc/string_to_number_comparison

PHP Manual. (n.d.). Upgrading to PHP 8.0. Retrieved September 29, 2024, from https://www.php.net/manual/en/migration80.incompatible.php

National Institute of Standards and Technology. (2020). CVE-2020-8088: UseBB Forum PHP Type Juggling Vulnerability. Retrieved September 29, 2024, from https://nvd.nist.gov/vuln/detail/CVE-2020-8088

National Institute of Standards and Technology. (2022). CVE-2022-47034: playsms Remote Code Execution Vulnerability. Retrieved September 29, 2024, from https://nvd.nist.gov/vuln/detail/CVE-2022-47034

Gynvael Coldwind. (January 2, 2013). Non-transitive equality cases and other inconsistencies. Retrieved September 29, 2024, from https://gynvael.coldwind.pl/?id=492

PHP Bug Tracker. (n.d.). Bug #39579: Incorrect handling of NULL in comparison with boolean. Retrieved September 29, 2024, from https://bugs.php.net/bug.php?id=39579

PHP Sadness. (n.d.). PHP Sadness: Comparison Operators. Retrieved September 29, 2024, from http://www.phpsadness.com/sad/52

To view or add a comment, sign in

More articles by Stefano Palmieri

Others also viewed

Explore content categories