Problems arising from PHP type casting in ==

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

  1. $one = array (
  2.   ‘abc’,
  3.   ‘abc1′,
  4.   111,
  5.   ‘111b’,
  6.   2,
  7.   ‘2xyz’,
  8.   ‘123a’,
  9.   123
  10. );
  11. $two = $one;
  12. for ($i = 0; $i < count($one); $i++) {
  13.   $xkey = array_search($one[$i], $two);
  14.   if(strcmp(strval($one[$i]), strval($two[$xkey])) != 0) {
  15.     // This should NEVER be reached, but it is, often!
  16.     $eq = ‘FALSE’;
  17.   } else {
  18.     $eq = ‘true’;
  19.   }
  20. }
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

  1. $one = array (
  2.   ‘abc’,
  3.   ‘abc1′,
  4.   ‘111′,
  5.   ‘111b’,
  6.   ‘2′,
  7.   ‘2xyz’,
  8.   ‘123a’,
  9.   ‘123′
  10. );
  11. $two = $one;
  12. for ($i = 0; $i < count($one); $i++) {
  13.   $xkey = array_search($one[$i], $two);
  14.   if(strcmp(strval($one[$i]), strval($two[$xkey])) != 0) {
  15.     // This should NEVER be reached, and with all strings it isn’t.
  16.     $eq = ‘FALSE’;
  17.   } else {
  18.     $eq = ‘true’;
  19.   }
  20. }
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()

  1. $array = array(‘111′=>‘111′, ‘11b’=>‘11b’, ‘222b’=>‘222b’,‘2×22′=>‘2×22′);
  2. $keys = array_keys($array);
  3. $searches = array(‘111b’,222,11,‘222b’,2);
  4. foreach ($searches as $search) {
  5.   $ia = (in_array($search, $array))?‘true’:‘false’;
  6.   $ake = (array_key_exists($search, $array))?‘true’:‘false’;
  7.   if ($search === ‘222b’) {
  8.     // This is the only place where either should return true
  9.     $iaf = ($ia == ‘true’)?" class=\"$true\"":" class=\"$false\"";
  10.     $akef = ($ake == ‘true’)?" class=\"$true\"":" class=\"$false\"";
  11.     $notes = "** Both should be true **";
  12.   } else {
  13.     $iaf = ($ia == ‘false’)?" class=\"$true\"":" class=\"$false\"";
  14.     $akef = ($ake == ‘false’)?" class=\"$true\"":" class=\"$false\"";
  15.     $notes = "Both should be false";
  16.   }
  17. }

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()

  1. $array = array(111,‘222b’);
  2. foreach($array as $val)
  3. {
  4.   $row = ($row == ‘row’)?‘offset_row’:‘row’;
  5.   $false = ($false == ‘false’)?‘offset_false’:‘false’;
  6.   $true = ($true == ‘true’)?‘offset_true’:‘true’;
  7.   switch($val)
  8.   {
  9.     case ‘111b’: // this displays
  10.       $match = ‘111b’;
  11.       $f = " class=\"$false\"";
  12.       $notes = "Incorrect: should have fallen through to next case";
  13.       break;
  14.     case 111: // never makes it here even tho this is correct
  15.       $match = 111;
  16.       $f = " class=\"$true\"";
  17.       $notes = "** Correct **";
  18.       break;
  19.     case 222: // this displays
  20.       $match = 222;
  21.       $f = " class=\"$false\"";
  22.       $notes = "Incorrect: should have fallen through to next case";
  23.       break;
  24.     case ‘222b’: // never makes it here even tho this is correct
  25.       $match = ‘222b’;
  26.       $f = " class=\"$true\"";
  27.       $notes = "** Correct **";
  28.       break;
  29.     default:
  30.       $match = ‘no match’;
  31.       $f = " class=\"$false\"";
  32.       $notes = "Incorrect: should have matched";
  33.       break;
  34.   }
  35. }
Search Item Match Notes
111 111b Incorrect: should have fallen through to next case
222b 222 Incorrect: should have fallen through to next case

2 Responses so far »

  1. 1

    Sjan Evardsson said,

    March 10, 2008 @ 12:43 pm

    Just a quick comment before get tons of them - yes, I am aware of the availability of the third argument to array_search and in_array - the ’strict’ switch. That works fine in most situations, but not always - such as searching for a value in an array created by array_keys when the value you are searching with is a string, but may also be a valid integer. In the array it will be stored as an int and will fail on the search. (That is the issue I ran into at work.)

    The other place that strict does not work is in switch statements, where you don’t have that option.

  2. 2

    Aaron Saray said,

    November 7, 2008 @ 3:36 pm

    What I don’t understand about your last comment is … if you are searching with a string, and want an exact match, you should use the strict item. If it could be a string or an integer key - as you mentioned, then you wouldn’t use that option, right?

Comment RSS · TrackBack URI

Say your words