Strict vs. Loose Comparisons in PHP

If you’ve come up against this in the past, this is going to sound like a no-brainer, but for programmers who have only used PHP and don’t know the difference between loose comparisons and the strict variety, this tip might save a lot of headache.

What’s the Problem?

In what was an effort to make PHP more accessible to programmers, variables in PHP can’t be declared with a specific type. While this does make it easier to get started, it also leaves room for some confusing situations.

Unexpected Output in Mathematical Operations

The lack of a specific type means that PHP operates on a “best-guess” principle (they call it type juggling), where PHP converts the value of a variable to the most appropriate type for the action being carried out.

This means that while you’ll usually get the expected results, there are some cases where you can end up with some seemingly bizarre output.

For example, check out this code:

echo 'fortieth' + 7; // Outputs 7
echo '40th' + 7; // Outputs 47

Seems weird, right? What’s happening is that if a string starts with a number, PHP will pull it out and use it as the value for mathematical operations. Otherwise, it becomes 0.

Unexpected Output from Loose Comparisons

A loose comparison is one performed using two equals signs (==). It follows suit with the “best-guess” approach, which can lead to some unexpected results.

As proof, here are three different data types:

echo gettype(0); // Outputs "integer"
echo gettype(FALSE); // Outputs "boolean"
echo gettype('test'); // Outputs "string"

Now check out what happens when we compare these types of data:

var_dump(0==FALSE); // bool(true)
var_dump(0=='test'); // bool(true)
var_dump(FALSE=='test'); // bool(false)

This is confusing on several levels.

It does make sense that 0 and FALSE would be considered equal. So we’re good so far, but the number 0 and the word “test”, in pretty much every situation I can think of, should probably come back FALSE. This one’s pretty odd to have come back TRUE, but it goes back to the fact that PHP will try and convert “test” to an integer, so it becomes 0 for the comparison.

Next, we’ve got FALSE and “test” coming back FALSE. Well, that seems inconsistent. If “test” becomes 0, and 0==FALSE, why doesn’t this one come out TRUE as well?

(It’s happening because FALSE=='test' would require “test” to undergo two type changes: string to integer 0, then integer to boolean. I think. Not the point. It’s just not intuitive.)

In most cases, loose comparisons aren’t a problem. But as we’ve seen, there are a few cases where loose comparisons can cause really confusing bugs.

Extra Credit: Read the PHP manual entry on type comparisons.

I Don’t See How This Affects Me

The above examples are pretty unlikely to appear in production code, so it might be easy to write this off as a “Geek on a Soapbox” type post. That’s not the case, though.

Let’s look at an example that you might actually see in a real project. In this snippet, the comments for an entry are counted and returned from the database as an integer. If the query fails, boolean FALSE is returned instead.

To make this example easy to reproduce, we’re going to replace the database query with an explicitly set variable. Let’s start by returning a count of 5.

<?php

echo "<pre>";

$comment_count = get_comment_count();

if( $comment_count==FALSE )
{
    echo "The entry count database query failed.";
}
else
{
    echo "The entry count is $comment_count.";
}

echo "</pre>";

function get_comment_count(  )
{
    /*
     * This is a DB query; it returns an integer (number of comments) or
     * boolean FALSE on query failure. To keep this example concise, we're going
     * to fake the return value
     */
    $comment_count = 5;

    // Output the type of the comment count
    echo 'Database return value: ';
    var_dump($comment_count);

    // If our "DB query" fails, return FALSE to indicate failure
    if( $comment_count==FALSE )
    {
        return FALSE;
    }

    return $comment_count;
}

?>

The output is what we’d expect:

Database return value: int(5)
The entry count is 5.

And if we change line 25 to read $comment_count = FALSE; (which means the query failed), we also see the expected output:

Database return value: bool(false)
The entry count database query failed.

However, what happens when an entry is new and doesn’t have any comments yet? Check out the results when you change line 25 to $comment_count = 0; in the test code:

Database return value: int(0)
The entry count database query failed.

Uh oh. A valid integer, 0, was returned, but our script misinterpreted it as a failed database query!

Keeping It Clear and Logical — Enter Strict Comparisons

Here’s where we can take one easy step to ensure that our code avoids as many weird bugs as possible: switch to strict comparisons.

A strict comparison is done with three equals signs (===), and it requires the two pieces of data being compared to have not only the same value, but the same type as well.

Personally, I think you should use strict comparisons whenever possible.

If we apply strict comparisons to our comment count example, we can see that the bug goes away:

<?php

echo "<pre>";

$comment_count = get_comment_count();

if( $comment_count===FALSE )
{
    echo "The entry count database query failed.";
}
else
{
    echo "The entry count is $comment_count.";
}

echo "</pre>";

function get_comment_count(  )
{
    /*
     * This is a DB query; it returns an integer (number of comments) or
     * boolean FALSE on query failure. To keep this example concise, we're going
     * to fake the return value
     */
    $comment_count = 0;

    // Output the type of the comment count
    echo 'Database return value: ';
    var_dump($comment_count);

    // If our "DB query" fails, return FALSE to indicate failure
    if( $comment_count===FALSE )
    {
        return FALSE;
    }

    return $comment_count;
}

?>

The output now comes out properly.

Database return value: int(0)
The entry count is 0.

A Small Adjustment for a Lot Less Headache

It doesn’t take much to use strict comparisons in your code. And I can promise you that you’ll barely notice the adjustment.

You will, however, avoid one of those ghost-in-the-machine type bugs that keeps you banging your head against the keyboard for an hour or two. (Trust me. I know from experience.)

NOTE: For a table detailing how comparisons work in both loose and strict comparisons, see the PHP manual.

On an Unrelated Note

For anyone who missed it, I had a couple articles run on Nettuts+ recently. If you haven’t already, check them out and read through them:

And, as always, I want to hear your thoughts, love, hate, and haiku in the comments!