While 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
$one = array (
'abc',
'abc1',
111,
'111b',
2,
'2xyz',
'123a',
123
);
$two = $one;
for ($i = 0; $i < count($one); $i++) {
$xkey = array_search($one[$i], $two);
if(strcmp(strval($one[$i]), strval($two[$xkey])) != 0) {
// 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
$one = array (
'abc',
'abc1',
'111',
'111b',
'2',
'2xyz',
'123a',
'123'
);
$two = $one;
for ($i = 0; $i < count($one); $i++) {
$xkey = array_search($one[$i], $two);
if(strcmp(strval($one[$i]), strval($two[$xkey])) != 0) {
// 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()
$array = array('111'=>'111', '11b'=>'11b', '222b'=>'222b','2x22'=>'2x22');
$keys = array_keys($array);
$searches = array('111b',222,11,'222b',2);
foreach ($searches as $search) {
$ia = (in_array($search, $array))?'true':'false';
$ake = (array_key_exists($search, $array))?'true':'false';
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()
$array = array(111,'222b');
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 |