January 2, 2009

Classes
Tags blog

PHP Type Conversions for Comparison

<p>There has been some discussion recently among our dev team regarding PHP type conversion. I’ll give some of the prob

There has been some discussion recently among our dev team regarding PHP type conversion. I’ll give some of the problems we’ve run into and then try to shed some light on the inner workings of PHP when it does comparisons.

The first example may seem familiar to most seasoned developers, but when chained together it brings up an interesting point about PHP: The = = operator isn’t transitive.

echo (null    = = 0        ? "YES" : "NO") . "\
";  //YES
echo ("null" = = 0        ? "YES" : "NO") . "\
";  //YES
echo ("null" = = null    ? "YES" : "NO") . "\
";  //NO

As you can see, null = = 0 = = "null", but null != "null"

You may be familiar with the following kind of error. The erroneous code is usually similar to:

if ( $a = "Hello" && $b != "World" )

Seeded with $b = "World", the function assigned FALSE to $a. This is because there was a single = instead of = = in $a = "Hello", so PHP was interpreting the whole thing as an assignment operator. Since $b was not equal to "World" $b != "World" was returning TRUE, and TRUE was && with "Hello", so "Hello" was converted to FALSE, then FALSE && TRUE was assigned to $a.

PHP has a certain order of precedence for data types. It is defined loosely in the manual’s comparison operators page, but I will try to spell it out more explicitly here. There are 8 basic types of data in PHP. In order of operator precedence, they are:

  • Boolean
  • Object
  • Array
  • Floating Point Number
  • Integer
  • String
  • Resource
  • NULL

That is to say, if you compare any two data types on the list, the variable with the data type lower on the list will be converted to the upper variable’s data type, and then the comparison is applied. However, when applying the first example to this hard and fast rule, we find it lacking. In reality, there are certain comparisons that are so far off PHP converts BOTH data types to a third data type. The first example actually works out like:

  • null = = 0. both were converting to FALSE, so the comparison was succeeding
  • "null" = = 0. "null" was converting to 0, so the comparison was succeeding
  • "null" = = null. "null" was converting to TRUE, NULL was converting to false.

It’s much more easily represented as a table:

  Boolean Object Array Floating Point Number Integer String Resource NULL
Boolean   Boolean
Objects always resolve to true
Boolean
Empty arrays are false, all others are true
Boolean
0 resolves to false, all others are true
Boolean
0 resolves to false, all others are true
Boolean
"" resoves to false, all others are true
Boolean
Resources always resolve to true
Boolean
NULL is always false
Object Boolean
Objects always resolve to true
  No conversion made
Objects are always greater-than
No conversion made
Objects are always greater-than
No conversion made
Objects are always greater-than
No conversion made
Objects are always greater-than
No conversion made
Objects are always greater-than
Boolean
Objects always resolve to true
Array Boolean
Empty arrays are false, all others are true
No conversion made
Objects are always greater-than
  No conversion made
Arrays are always greater-than
No conversion made
Arrays are always greater-than
No conversion made
Arrays are always greater-than
No conversion made
Arrays are always greater-than
Boolean
Empty arrays are false, all others are true
Floating Point Boolean
0 resolves to false, all others are true
No conversion made
Objects are always greater-than
No conversion made
Arrays are always greater-than
  Floating Point Floating Point Floating Point Boolean
0 resolves to false, all others are true
Integer Boolean
0 resolves to false, all others are true
No conversion made
Objects are always greater-than
No conversion made
Arrays are always greater-than
Floating Point   Floating Point Integer Boolean
0 resolves to false, all others are true
String Boolean
0 resolves to false, all others are true
No conversion made
Objects are always greater-than
No conversion made
Arrays are always greater-than
Floating Point Floating Point   Floating Point String
NULL is converted to ""
Resource Boolean
Resources always resolve to true
No conversion made
Objects are always greater-than
No conversion made
Arrays are always greater-than
Floating Point Integer Floating Point   Boolean
Resources always resolve to true
NULL Boolean
NULL resolves to false
Boolean
Objects always resolve to true
Never = = null
Boolean
Empty arrays are false, all others are true
Boolean
0 resolves to false, all others are true
Boolean
0 resolves to false, all others are true
String
NULL is converted to ""
Boolean
Resources always resolve to true
Never = = null
 

In the table where you see the phrase "No Conversion Made" that means that those two data types will never = = each other. However, in most of those situations data types are given specific return values for quantitative comparisons, such as greater-than and less-than. Note the specific case of NULL, where almost every instance of comparing to NULL results in both types being converted to Boolean.

Armed with this information, we are now capable of determining the outcome of almost any comparison in PHP. We know, for instance, that array() is greater than "Hello", but "Hello" is less than 2. We know that stdClass() is greater than array(), but both of them are equal to TRUE. There are plenty of places where PHP contradicts normal logic, because of the sometimes convoluted process involved in comparing different data types.

The fact that PHP sometimes internally converts two operands to a third, unrelated data type can be quite confusing. I hope, however, that the chart in this article will help you work out exactly what it’s doing.

Of course, as one of our lead developers is quick to point out, this whole discussion would be moot if everyone used = = =.