php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #63217 Constant numeric strings become integers when used as ArrayAccess offset
Submitted: 2012-10-04 10:14 UTC Modified: 2017-07-03 02:18 UTC
Votes:12
Avg. Score:4.8 ± 0.6
Reproduced:12 of 12 (100.0%)
Same Version:5 (41.7%)
Same OS:3 (25.0%)
From: kmsheng at pixnet dot tw Assigned:
Status: Closed Package: Arrays related
PHP Version: 5.4Git-2012-10-04 (Git) OS: freebsd
Private report: No CVE-ID: None
 [2012-10-04 10:14 UTC] kmsheng at pixnet dot tw
Description:
------------
Execute the test script provided below in php 5.3.10 and php 5.4.0-3 may get different results which are string(1) "0" and int(0).

I don't know if this is a bug or a patch.

Test script:
---------------
<?php
class Test implements ArrayAccess {

    public function offsetExists($off)
    {
    }

    public function offsetUnset($off)
    {
    }

    public function offsetSet($off, $el)
    {
    }

    public function offsetGet($off)
    {
        var_dump($off);
    }
}

$test = new Test();

$test['0'];

Expected result:
----------------
string(1) "0"

Actual result:
--------------
int(0)

Patches

Add a Patch

Pull Requests

Pull requests:

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2012-10-05 04:54 UTC] laruence@php.net
-Type: Bug +Type: Documentation Problem
 [2012-10-05 04:54 UTC] laruence@php.net
numeric string key will cast to number, I think this is a improvement, to be 
consistent with the internal dim fetch.

but yeah, we need a document of such change, change to doc problem.
 [2013-04-07 17:44 UTC] david at grudl dot com
This is really buggy.

$test['0']; // prints int(0)

but

$key = '0'; 
$test[$key]; // prints string(1) "0"
 [2015-11-24 05:22 UTC] rudolf dot theunissen at gmail dot com
It makes sense for array keys to be coerced to intergers, ie. 1, 1.0, and "1" will attempt to access the same index. However, this does not make sense for ArrayAccess. The argument for consistency is poor, because float and object keys are handled differently already (floats stay floats and objects don't raise warnings).  One example use case is SplObjectStorage, which makes use of object keys via ArrayAccess.

With the introduction of strict types and scalar typehints, it makes sense for the implementation to be responsible for handling various key types.

ArrayAccess should only provide array syntax, not array behaviour.

Test script:
https://3v4l.org/0BMYh
---------------
<?php

class Test implements ArrayAccess
{
    public function offsetGet($offset){
        return gettype($offset);
    }
    
    public function offsetSet($offset, $value){}
    public function offsetUnset($offset){}
    public function offsetExists($offset){}
}

$a = array('a', 'b', 'c');
$o = new Test();
$s = '1';

echo $o[1],   "\n"; // 'integer'
echo $o['1'], "\n"; // 'integer' !?
echo $o[1.0], "\n"; // 'double'
echo $o[$s],  "\n"; // 'string'
echo $o[$o],  "\n"; // 'object'

echo $a[1],   "\n"; // 'integer'
echo $a['1'], "\n"; // 'integer'
echo $a[1.0], "\n"; // 'integer'
echo $a[$s],  "\n"; // 'integer'
echo $a[$o],  "\n"; // Warning: Illegal offset type

Output:
---------------
integer
integer
double
string
object
b
b
b
b

Warning: Illegal offset type in...
 [2015-11-24 15:10 UTC] levim@php.net
-Assigned To: +Assigned To: ajf
 [2015-11-24 15:10 UTC] levim@php.net
Changing this back to a bug status. Here is an example where I hope it is clear it is a bug (on 3v4l: https://3v4l.org/XGtVh):

<?php
class Dictionary implements ArrayAccess {
	function offsetExists($offset) {}
	function offsetGet($offset) {}
	function offsetUnset($offset) {}

	function offsetSet($offset, $value) {
		if (!is_string($offset)) {
			throw new InvalidArgumentException();
		}
	}
}

try {
    $Dictionary = new Dictionary();
    $Dictionary["12"] = 0xDEADBEEF;
    echo "No Exception for \"12\"\n";
} catch (InvalidArgumentException $e) {
    echo "Caught Exception for \"12\"\n";
}

try {
    $str = "12";
    $Dictionary[$str] = 0xDEADBEEF;
    echo "No Exception for \$variable = \"12\"\n";
} catch (InvalidArgumentException $e) {
    echo "Caught Exception for \$variable = \"12\"\n";
}

?>
 [2015-11-24 15:11 UTC] levim@php.net
-Type: Documentation Problem +Type: Bug
 [2015-11-24 15:56 UTC] ajf@php.net
To clarify why this happens, it wasn't due to wanting to make ArrayAccess like arrays. Rather, PHP has an optimisation for array indexing with a constant (a literal like "123", 123, true, false, etc, not the const/define() kind) string (e.g. $_POST["password"]) where it will check at compile-time if it is numeric and replace it then if so (so $foobar["123"] becomes $foobar[123], but $foobar["bar"] stays the same). This means we don't have to run the numeric string check every time that line of code is executed. Arrays in PHP consider $foo["1"] and $foo[1] to be the same, so otherwise we'd need to check when we run the code if the string is a number.

The problem is that $foobar in this case might actually be an object with ArrayAccess and not an array, and so what apparently would be a transparent optimisation ends up causing this bug.

The solution is to remove this optimisation. This won't necessarily cause a performance hit, because there's other ways we could avoid doing the numeric string check at runtime.
 [2015-11-24 17:23 UTC] ajf@php.net
-Summary: Behavior of implementing the ArrayAccess interface has been changed in php 5.4 +Summary: Constant numeric strings become integers when used as ArrayAccess offset
 [2015-11-24 17:23 UTC] ajf@php.net
I updated the title to better describe the bug.
 [2016-04-04 04:20 UTC] whatchildisthis at gmail dot com
Examples using expressions:

<?php
$test["1" . "0"]; // int(10) expect string(2) "10"
$test[1 + 0 . "0"]; // int(10) expect string(2) "10"
$test[<<<_
10
_
]; // int(10) expect string (2) "10"
$test[(string) "10"]; // only one that worked, string(2) "10"
?>
 [2017-05-26 15:31 UTC] ajf@php.net
-Status: Assigned +Status: Analyzed -Assigned To: ajf +Assigned To:
 [2017-05-26 15:31 UTC] ajf@php.net
Removing myself from being assigned to this. I could fix it, but I lost interest in doing so for the most part.
 [2017-06-18 05:46 UTC] a dot schilder at gmx dot de
I also ran into this problem and found another strange behavior within it (PHP 7.1.x):

When I use $object["0"], the offset is int(0).

When I use $object[(string)"0"], offset is sometimes string(1) "0", sometimes int(0), without a recognizable pattern.
 [2018-07-02 14:43 UTC] nikic@php.net
Automatic comment on behalf of rudolf.theunissen@gmail.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=30156d588c07e26d4e752ddb62344e96854d4773
Log: Fixed bug #63217
 [2018-07-02 14:43 UTC] nikic@php.net
-Status: Analyzed +Status: Closed
 
PHP Copyright © 2001-2018 The PHP Group
All rights reserved.
Last updated: Wed Dec 12 07:01:25 2018 UTC