diff --git a/lib/Twig/Node/Expression/GetAttr.php b/lib/Twig/Node/Expression/GetAttr.php index cee6804fa..05a3fc283 100644 --- a/lib/Twig/Node/Expression/GetAttr.php +++ b/lib/Twig/Node/Expression/GetAttr.php @@ -23,6 +23,30 @@ class Twig_Node_Expression_GetAttr extends Twig_Node_Expression public function compile(Twig_Compiler $compiler) { + // optimize array calls + if ( + (!$compiler->getEnvironment()->isStrictVariables() || $this->getAttribute('ignore_strict_check')) + && !$this->getAttribute('is_defined_test') + && Twig_Template::ARRAY_CALL === $this->getAttribute('type') + ) { + $var = '$'.$compiler->getVarName(); + $compiler + ->raw('(('.$var.' = ') + ->subcompile($this->getNode('node')) + ->raw(') && is_array(') + ->raw($var) + ->raw(') || ') + ->raw($var) + ->raw(' instanceof ArrayAccess ? (') + ->raw($var) + ->raw('[') + ->subcompile($this->getNode('attribute')) + ->raw('] ?? null) : null)') + ; + + return; + } + $compiler->raw('twig_get_attribute($this->env, $this->source, '); if ($this->getAttribute('ignore_strict_check')) { diff --git a/test/Twig/Tests/Fixtures/expressions/array.test b/test/Twig/Tests/Fixtures/expressions/array.test index c69b1192f..de0487129 100644 --- a/test/Twig/Tests/Fixtures/expressions/array.test +++ b/test/Twig/Tests/Fixtures/expressions/array.test @@ -37,8 +37,11 @@ Twig supports array notation {% set ary = { (a): 'a', (b): 'b', 'c': 'c', (a ~ b): 'd' } %} {{ ary|keys|join(',') }} {{ ary|join(',') }} + +{# ArrayAccess #} +{{ array_access['a'] }} --DATA-- -return array('bar' => 'bar', 'foo' => array('bar' => 'bar')) +return array('bar' => 'bar', 'foo' => array('bar' => 'bar'), 'array_access' => new ArrayObject(array('a' => 'b'))) --EXPECT-- 1,2 foo,bar @@ -59,3 +62,5 @@ FOO,BAR, 1,foo,c,1foo a,b,c,d + +b diff --git a/test/Twig/Tests/Fixtures/expressions/array_non_strict.test b/test/Twig/Tests/Fixtures/expressions/array_non_strict.test new file mode 100644 index 000000000..8ceb8d9e3 --- /dev/null +++ b/test/Twig/Tests/Fixtures/expressions/array_non_strict.test @@ -0,0 +1,68 @@ +--TEST-- +Twig supports array notation +--TEMPLATE-- +{# empty array #} +{{ []|join(',') }} + +{{ [1, 2]|join(',') }} +{{ ['foo', "bar"]|join(',') }} +{{ {0: 1, 'foo': 'bar'}|join(',') }} +{{ {0: 1, 'foo': 'bar'}|keys|join(',') }} + +{{ {0: 1, foo: 'bar'}|join(',') }} +{{ {0: 1, foo: 'bar'}|keys|join(',') }} + +{# nested arrays #} +{% set a = [1, 2, [1, 2], {'foo': {'foo': 'bar'}}] %} +{{ a[2]|join(',') }} +{{ a[3]["foo"]|join(',') }} + +{# works even if [] is used inside the array #} +{{ [foo[bar]]|join(',') }} + +{# elements can be any expression #} +{{ ['foo'|upper, bar|upper, bar == foo]|join(',') }} + +{# arrays can have a trailing , like in PHP #} +{{ + [ + 1, + 2, + ]|join(',') +}} + +{# keys can be any expression #} +{% set a = 1 %} +{% set b = "foo" %} +{% set ary = { (a): 'a', (b): 'b', 'c': 'c', (a ~ b): 'd' } %} +{{ ary|keys|join(',') }} +{{ ary|join(',') }} + +{# ArrayAccess #} +{{ array_access['a'] }} +--DATA-- +return array('bar' => 'bar', 'foo' => array('bar' => 'bar'), 'array_access' => new ArrayObject(array('a' => 'b'))) +--CONFIG-- +return array('strict_variables' => false) +--EXPECT-- +1,2 +foo,bar +1,bar +0,foo + +1,bar +0,foo + +1,2 +bar + +bar + +FOO,BAR, + +1,2 + +1,foo,c,1foo +a,b,c,d + +b diff --git a/test/Twig/Tests/Node/Expression/GetAttrTest.php b/test/Twig/Tests/Node/Expression/GetAttrTest.php index 2764478c4..17e9b397f 100644 --- a/test/Twig/Tests/Node/Expression/GetAttrTest.php +++ b/test/Twig/Tests/Node/Expression/GetAttrTest.php @@ -37,7 +37,8 @@ class Twig_Tests_Node_Expression_GetAttrTest extends Twig_Test_NodeTestCase $tests[] = array($node, sprintf('%s%s, "bar", array())', $this->getAttributeGetter(), $this->getVariableGetter('foo', 1))); $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, Twig_Template::ARRAY_CALL, 1); - $tests[] = array($node, sprintf('%s%s, "bar", array(), "array")', $this->getAttributeGetter(), $this->getVariableGetter('foo', 1))); + $tests[] = array($node, '(($__internal_%s = // line 1'."\n". + '($context["foo"] ?? null)) && is_array($__internal_%s) || $__internal_%s instanceof ArrayAccess ? ($__internal_%s["bar"] ?? null) : null)', null, true); $args = new Twig_Node_Expression_Array(array(), 1); $args->addElement(new Twig_Node_Expression_Name('foo', 1));