Problems arising from PHP type casting in ==
March 8th, 2008 by Sjan EvardssonWhile trying to work through the issues I mentioned in the last post I started doing some serious digging and testing. Here is what I have found.
PHP seems to use == for determining equivalance when performing array_search, in_array and switch, while using either === or strcmp when doing array_key_exists.
The result of this is that array_search and in_array will return improper results when used on an array with mixed string and integer values. (Another thing I found, that may or may not be related, is that array keys will be cast from strings to integers when those strings are valid integer values.)
array_search() with mixed types
-
‘abc’,
-
‘abc1′,
-
111,
-
‘111b’,
-
2,
-
‘2xyz’,
-
‘123a’,
-
123
-
);
-
$two = $one;
-
// This should NEVER be reached, but it is, often!
-
$eq = ‘FALSE’;
-
} else {
-
$eq = ‘true’;
-
}
-
}
| Row | $one | $two | Correct? | Found | Notes |
|---|---|---|---|---|---|
| 0 | abc | abc | true | 0 | abc == abc : array_search($one[0], $two) where $one[0] = string(3) “abc” |
| 1 | abc1 | abc1 | true | 1 | abc1 == abc1 : array_search($one[1], $two) where $one[1] = string(4) “abc1″ |
| 2 | 111 | 111 | true | 2 | 111 == 111 : array_search($one[2], $two) where $one[2] = int(111) |
| 3 | 111b | 111b | FALSE | 2 | 111b == 111 : array_search($one[3], $two) where $one[3] = string(4) “111b” |
| 4 | 2 | 2 | true | 4 | 2 == 2 : array_search($one[4], $two) where $one[4] = int(2) |
| 5 | 2xyz | 2xyz | FALSE | 4 | 2xyz == 2 : array_search($one[5], $two) where $one[5] = string(4) “2xyz” |
| 6 | 123a | 123a | true | 6 | 123a == 123a : array_search($one[6], $two) where $one[6] = string(4) “123a” |
| 7 | 123 | 123 | FALSE | 6 | 123 == 123a : array_search($one[7], $two) where $one[7] = int(123) |
array_search() with all strings
-
‘abc’,
-
‘abc1′,
-
‘111′,
-
‘111b’,
-
‘2′,
-
‘2xyz’,
-
‘123a’,
-
‘123′
-
);
-
$two = $one;
-
// This should NEVER be reached, and with all strings it isn’t.
-
$eq = ‘FALSE’;
-
} else {
-
$eq = ‘true’;
-
}
-
}
| Row | $one | $two | Correct? | Found | Notes |
|---|---|---|---|---|---|
| 0 | abc | abc | true | 0 | abc == abc : array_search($one[0], $two) where $one[0] = string(3) “abc” |
| 1 | abc1 | abc1 | true | 1 | abc1 == abc1 : array_search($one[1], $two) where $one[1] = string(4) “abc1″ |
| 2 | 111 | 111 | true | 2 | 111 == 111 : array_search($one[2], $two) where $one[2] = string(3) “111″ |
| 3 | 111b | 111b | true | 3 | 111b == 111b : array_search($one[3], $two) where $one[3] = string(4) “111b” |
| 4 | 2 | 2 | true | 4 | 2 == 2 : array_search($one[4], $two) where $one[4] = string(1) “2″ |
| 5 | 2xyz | 2xyz | true | 5 | 2xyz == 2xyz : array_search($one[5], $two) where $one[5] = string(4) “2xyz” |
| 6 | 123a | 123a | true | 6 | 123a == 123a : array_search($one[6], $two) where $one[6] = string(4) “123a” |
| 7 | 123 | 123 | true | 7 | 123 == 123 : array_search($one[7], $two) where $one[7] = string(3) “123″ |
in_array() and array_key_exists()
-
foreach ($searches as $search) {
-
if ($search === ‘222b’) {
-
// This is the only place where either should return true
-
$iaf = ($ia == ‘true’)?" class=\"$true\"":" class=\"$false\"";
-
$akef = ($ake == ‘true’)?" class=\"$true\"":" class=\"$false\"";
-
$notes = "** Both should be true **";
-
} else {
-
$iaf = ($ia == ‘false’)?" class=\"$true\"":" class=\"$false\"";
-
$akef = ($ake == ‘false’)?" class=\"$true\"":" class=\"$false\"";
-
$notes = "Both should be false";
-
}
-
}
Notice how the array keys are cast to type int in both the original array and in array_keys.
| $array | $keys |
|---|---|
array(4) {
[111]=>
string(3) "111"
["11b"]=>
string(3) "11b"
["222b"]=>
string(4) "222b"
["2x22"]=>
string(4) "2x22"
}
|
array(4) {
[0]=>
int(111)
[1]=>
string(3) "11b"
[2]=>
string(4) "222b"
[3]=>
string(4) "2x22"
}
|
| Search Item | in_array | array_key_exists | Notes |
|---|---|---|---|
| 111b | false | false | Both should be false |
| 222 | true | false | Both should be false |
| 11 | true | false | Both should be false |
| 222b | true | true | ** Both should be true ** |
| 2 | true | false | Both should be false |
So, it appears that array_key_exists() uses either or === strcmp() while in_array() uses ==
NOTE: Calling array_key_exists() with a string value ‘111′ will return true for an item with a key of int 111. This is not the same behavior as ===, but is the same behavior as strcmp() which must be what is used internally for array_key_exists().
The difference between the 3 operations is clear:
| $a | $b | $a == $b | $a === $b | strcmp(strval($a),strval($b)) |
|---|---|---|---|---|
| int(123) | string(4) “123b” | bool(true) | bool(false) | int(-1) |
Another area where this becomes an issue is in switch statements. Take the following, for example:
switch()
-
foreach($array as $val)
-
{
-
$row = ($row == ‘row’)?‘offset_row’:‘row’;
-
$false = ($false == ‘false’)?‘offset_false’:‘false’;
-
$true = ($true == ‘true’)?‘offset_true’:‘true’;
-
switch($val)
-
{
-
case ‘111b’: // this displays
-
$match = ‘111b’;
-
$f = " class=\"$false\"";
-
$notes = "Incorrect: should have fallen through to next case";
-
break;
-
case 111: // never makes it here even tho this is correct
-
$match = 111;
-
$f = " class=\"$true\"";
-
$notes = "** Correct **";
-
break;
-
case 222: // this displays
-
$match = 222;
-
$f = " class=\"$false\"";
-
$notes = "Incorrect: should have fallen through to next case";
-
break;
-
case ‘222b’: // never makes it here even tho this is correct
-
$match = ‘222b’;
-
$f = " class=\"$true\"";
-
$notes = "** Correct **";
-
break;
-
default:
-
$match = ‘no match’;
-
$f = " class=\"$false\"";
-
$notes = "Incorrect: should have matched";
-
break;
-
}
-
}
| Search Item | Match | Notes |
|---|---|---|
| 111 | 111b | Incorrect: should have fallen through to next case |
| 222b | 222 | Incorrect: should have fallen through to next case |