From d501e0e5bdf2fa3e94c32fcac0b127bbeb8fe56e Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 30 Jan 2018 10:54:31 +0100 Subject: [PATCH 1/3] optimized compilation for arrays when using a['b'] notation under certain conditions --- lib/Twig/Node/Expression/GetAttr.php | 21 +++++++++++++++++++ .../Tests/Node/Expression/GetAttrTest.php | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/Twig/Node/Expression/GetAttr.php b/lib/Twig/Node/Expression/GetAttr.php index cee6804fa..68c27ea32 100644 --- a/lib/Twig/Node/Expression/GetAttr.php +++ b/lib/Twig/Node/Expression/GetAttr.php @@ -23,6 +23,27 @@ 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') + ) { + $compiler + ->raw('(is_array(') + ->subcompile($this->getNode('node')) + ->raw(') || ') + ->subcompile($this->getNode('node')) + ->raw(' instanceof ArrayAccess ? (') + ->subcompile($this->getNode('node')) + ->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/Node/Expression/GetAttrTest.php b/test/Twig/Tests/Node/Expression/GetAttrTest.php index 2764478c4..f7925c060 100644 --- a/test/Twig/Tests/Node/Expression/GetAttrTest.php +++ b/test/Twig/Tests/Node/Expression/GetAttrTest.php @@ -37,7 +37,7 @@ 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, sprintf('(is_array(%s) || ($context["foo"] ?? null) instanceof ArrayAccess ? (($context["foo"] ?? null)["bar"] ?? null) : null)', $this->getVariableGetter('foo', 1), $this->getVariableGetter('foo', 1))); $args = new Twig_Node_Expression_Array(array(), 1); $args->addElement(new Twig_Node_Expression_Name('foo', 1)); From f3b8f4a8d4c56fd987eb25aea90215f615166a20 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 30 Jan 2018 12:38:28 +0100 Subject: [PATCH 2/3] added some tests --- .../Tests/Fixtures/expressions/array.test | 7 +- .../expressions/array_non_strict.test | 68 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 test/Twig/Tests/Fixtures/expressions/array_non_strict.test 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 From 6f7ea45c0378f48fe1d31b51256658493454b21b Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 30 Jan 2018 14:00:01 +0100 Subject: [PATCH 3/3] optimized nested array calls --- lib/Twig/Node/Expression/GetAttr.php | 9 ++++++--- test/Twig/Tests/Node/Expression/GetAttrTest.php | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/Twig/Node/Expression/GetAttr.php b/lib/Twig/Node/Expression/GetAttr.php index 68c27ea32..05a3fc283 100644 --- a/lib/Twig/Node/Expression/GetAttr.php +++ b/lib/Twig/Node/Expression/GetAttr.php @@ -29,13 +29,16 @@ class Twig_Node_Expression_GetAttr extends Twig_Node_Expression && !$this->getAttribute('is_defined_test') && Twig_Template::ARRAY_CALL === $this->getAttribute('type') ) { + $var = '$'.$compiler->getVarName(); $compiler - ->raw('(is_array(') + ->raw('(('.$var.' = ') ->subcompile($this->getNode('node')) + ->raw(') && is_array(') + ->raw($var) ->raw(') || ') - ->subcompile($this->getNode('node')) + ->raw($var) ->raw(' instanceof ArrayAccess ? (') - ->subcompile($this->getNode('node')) + ->raw($var) ->raw('[') ->subcompile($this->getNode('attribute')) ->raw('] ?? null) : null)') diff --git a/test/Twig/Tests/Node/Expression/GetAttrTest.php b/test/Twig/Tests/Node/Expression/GetAttrTest.php index f7925c060..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('(is_array(%s) || ($context["foo"] ?? null) instanceof ArrayAccess ? (($context["foo"] ?? null)["bar"] ?? null) : null)', $this->getVariableGetter('foo', 1), $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));