Twig: Problem with numeric keys in array

Created on 9 Jul 2012  Â·  18Comments  Â·  Source: twigphp/Twig

If I have an array like :

["006"] =>  int(100)
[700]   =>  int(150)
["008"] =>  int(200)
["a"]   =>  int(300)

I cannot retrieve the values from the keys that are numeric and starts with ZERO.

{{ test['006'] }}    ---> null !!!!
{{ test['700'] }}    ---> 150
{{ test['a'] }}      ---> 300

The problem is in the getAttribute() method in Twig_Template class when there's a INTEGER CAST

Class : Twig_Template

protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_TemplateInterface::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false)
   {
       $item = ctype_digit((string) $item) ? (int) $item : (string) $item;
...

This is a bug or not?
And if not why exists this CAST?

All 18 comments

To reproduce the error:

{% set data = {'01':'123', '09':'456'} %}
{% set key = '09' %}
{{ data[key] }}

and produce the error :
Twig_Error_Runtime: Key "9" for array with keys "01, 09" does not exist

When using a string with numbers greater than the max of an int, i get this message :

Twig_Error_Runtime # 0 - Key "2147483647" for array with keys "9900270000" does not exist in ...

Stangely i only have this behavior on windows. Commenting the line of the ctype_digit test of the getAttribute method of the Twig_Template class make it work.

I've prefixed my keys with '_' to solve my issue.

var_dump(filter_var("2147483648", FILTER_VALIDATE_INT));
false

var_dump(filter_var("2147483647", FILTER_VALIDATE_INT));
2147483647

I suppose using filter_var is better than using ctype_digit for detecting integer key.

var_dump(filter_var("09", FILTER_VALIDATE_INT));
false

So filter_var is a proper solution.

Replacing ctype_digit by filter_var in Template::getAttribute() would break this test: regression/simple_xml_element.test. Not sure what to do here.

filter_var returns the filtered value, so to replace ctype_digit, the return value should be compared with false

$item = filter_var((string) $item, FILTER_VALIDATE_INT) !== false ? (int) $item : (string) $item;

That's almost perfect but not quite as +4 is now considered as an integer; so it is implicitly converted to 4. This is probably an edge case, but that prevents us from using filter_var. The following would fail:

{% set data = {'+1':'123'} %}
{% set key = '+1' %}
{{ data[key] }}

I suppose there is no choice but using both functions :

$item = (filter_var((string) $item, FILTER_VALIDATE_INT) !== false) && (ctype_digit((string) $item)) ? (int) $item : (string) $item;

The problem I see is that this method is critical in terms of performance. So, complexifying the code is most of time a no-go. Of course, we need to benchmark first.

I know this is 2 function calls instead of one but i don't see any other possibility to solve this problem.
Perhaps there's a way to reduce the performance issue by making this test in ext/twig.

PHP already converts string key to number automatically, so why don't we leave it to PHP.

>> $a = [4 => 'x'];
array (
  4 => 'x',
)
>> $a[4];
'x'
>> $a['4'];
'x'
>> $a['04'];
Exception (code: 0) got thrown
exception 'Exception' with message 'phar:///root/bin/php-shell.phar/php-shell-cmd.php(121) : eval()'d code:1
Undefined index: 04' in phar:///root/bin/php-shell.phar/php-shell-cmd.php:54
Stack trace:
#0 phar:///root/bin/php-shell.phar/php-shell-cmd.php(121) : eval()'d code(1): __shell_default_error_handler(8, 'Undefined index...', 'phar:///root/bi...', 1, Array)
#1 phar:///root/bin/php-shell.phar/php-shell-cmd.php(121): eval()
#2 /root/bin/php-shell.phar(1): include('phar:///root/bi...')
#3 {main}
>> $a['+4'];
Exception (code: 0) got thrown
exception 'Exception' with message 'phar:///root/bin/php-shell.phar/php-shell-cmd.php(121) : eval()'d code:1
Undefined index: +4' in phar:///root/bin/php-shell.phar/php-shell-cmd.php:54
Stack trace:
#0 phar:///root/bin/php-shell.phar/php-shell-cmd.php(121) : eval()'d code(1): __shell_default_error_handler(8, 'Undefined index...', 'phar:///root/bi...', 1, Array)
#1 phar:///root/bin/php-shell.phar/php-shell-cmd.php(121): eval()
#2 /root/bin/php-shell.phar(1): include('phar:///root/bi...')
#3 {main}

I have tried to replace

$item = filter_var((string) $item, FILTER_VALIDATE_INT) !== false ? (int) $item : (string) $item;

with

$item = (string) $item;

in Twig_Template::getAttribute and then running the tests, and the result is no failing test case.

That's what we had before, but it definitely break one test: regression/simple_xml_element.test

For the SimpleXML case, I propose that we localize the integer conversion to the object property case

// object property
        if (Twig_TemplateInterface::METHOD_CALL !== $type) {
            if (isset($object->$item) || (false !== ($int_item = filter_var($item, FILTER_VALIDATE_INT)) && isset($object->$int_item) && (($item = $int_item) || true)) || array_key_exists($item, $object)) {
                if ($isDefinedTest) {
                    return true;
                }

                if ($this->env->hasExtension('sandbox')) {
                    $this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item);
                }

                return $object->$item;
            }
        }
PHPUnit 3.7.1 by Sebastian Bergmann.

.S.............................................................  63 / 829 (  7%)
............................................................... 126 / 829 ( 15%)
............................................................... 189 / 829 ( 22%)
............................................................... 252 / 829 ( 30%)
............................................................... 315 / 829 ( 37%)
............................................................... 378 / 829 ( 45%)
............................................................... 441 / 829 ( 53%)
............................................................... 504 / 829 ( 60%)
............................................................... 567 / 829 ( 68%)
............................................................... 630 / 829 ( 75%)
............................................................... 693 / 829 ( 83%)
............................................................... 756 / 829 ( 91%)
............................................................... 819 / 829 ( 98%)
..........

Time: 4 seconds, Memory: 13.00Mb

OK, but incomplete or skipped tests!
Tests: 829, Assertions: 2575, Skipped: 1.

I opened a PR in #878 so you can see my idea to fix it.

see #878

There is still no fix for that ?

it is a bug

Commenting on a closed issue that has been fixed in the past does not help. If you think there is still a bug, you should rather open a new issue with some steps describing the behaviour you experience an what you did expect instead.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dvladimirov77 picture dvladimirov77  Â·  5Comments

mpdude picture mpdude  Â·  3Comments

CriseX picture CriseX  Â·  4Comments

maidmaid picture maidmaid  Â·  5Comments

xxfaxy picture xxfaxy  Â·  6Comments