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 |