Press "Enter" to skip to content

A Bug Related to PHP Type Casting

To avoid confusion between numeric indexes and numeric-string indexes (see note 1), PHP introduced the zend_symtable_* family of functions and applied them to arrays.
As a result, a numeric-string index is also treated as a numeric index. But there are always some cases the PHP maintainers didn't think of...
For instance, type casting:

Given the many kind suggestions that json_decode's second parameter can give you an array directly:
Let me clarify — the code below is deliberate. The point is not json_decode itself, but constructing a "problematic" array.

Under PHP 5.2.* (json version 1.2.1):

$data = array(
    123 => 'laruence',
);
$value = json_encode($data);
$obj   = json_decode($value);
$arr   = (array)$obj;
var_dump($arr);

Also, shenxian provided an even simpler way to construct it:

$obj=new stdClass;
$obj->{'123'} = "laruence";
$arr = (array)$obj;
var_dump($arr);

At this point the problem appears. The output above is:

array(1) {
  ["123"]=>
  string(8) "laruence"
}

Now you're stuck, because the array key is a string, while through the normal access path PHP automatically converts numeric strings to numbers. So:

print_r($arr[123]);
//PHP Notice:  Undefined offset:  123 in ***
print_r($arr["123"]);
//PHP Notice:  Undefined offset:  123 in ***
var_dump(array_key_exists("123", $arr));
//bool(false)

I've filed the bug. However, PHP itself doesn't guarantee consistency in type casting, so in the end the maintainers felt it hardly mattered whether it's a bug or not — just keep it in mind in everyday use: http://bugs.php.net/bug.php?id=51915
Note 1
The numeric strings discussed here differ slightly from the numeric strings in my earlier article PHP String Comparison. In the zend_symtable_* family of functions, only strings matching /^-?[^0][0-9]*$/ are treated as numeric strings. The relevant core logic is:

#define HANDLE_NUMERIC(key, length, func) {
    register char *tmp=key;
    if (*tmp=='-') {
        tmp++;
    }
    if ((*tmp>='0' && *tmp<='9')) do {
        char *end=key+length-1;
        long idx;
        if (*tmp++=='0' && length>2) {
            break;
        }
        while (tmp<end) {
            if (!(*tmp>='0' && *tmp<='9')) {
                break;
            }
            tmp++;
        }
        if (tmp==end && *tmp=='0') {
            if (*key=='-') {
                idx = strtol(key, NULL, 10);
                if (idx!=LONG_MIN) {
                    return func;
                }
            } else {
                idx = strtol(key, NULL, 10);
                if (idx!=LONG_MAX) {
                    return func;
                }
            }
        }
    } while (0);
}

PS: I only have versions 5.2.8 and 5.2.11 here. If any readers have other PHP versions, please help test whether this problem also exists in your version. Thanks.
Also: thanks to yuanhao for raising this issue; the original problem was related to Memcached.

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.