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 |