Make `a.b is defined` not throw an exception if a is not defined (in strict mode)
authornikic <+@ni-po.com>
Thu, 9 Jun 2011 18:50:48 +0000 (20:50 +0200)
committernikic <+@ni-po.com>
Thu, 9 Jun 2011 18:50:48 +0000 (20:50 +0200)
lib/Twig/Node/Expression/Filter.php
lib/Twig/Node/Expression/GetAttr.php
lib/Twig/Node/Expression/Name.php
lib/Twig/Node/Expression/Test.php

index 1632778..101e6df 100644 (file)
@@ -23,28 +23,28 @@ class Twig_Node_Expression_Filter extends Twig_Node_Expression
             throw new Twig_Error_Syntax(sprintf('The filter "%s" does not exist', $name), $this->getLine());
         }
 
+        $node = $this->getNode('node');
+
         // The default filter is intercepted when the filtered value
         // is a name (like obj) or an attribute (like obj.attr)
         // In such a case, it's compiled to {{ obj is defined ? obj|default('bar') : 'bar' }}
-        if ('default' === $name && ($this->getNode('node') instanceof Twig_Node_Expression_Name || $this->getNode('node') instanceof Twig_Node_Expression_GetAttr)) {
-            $compiler->raw('((');
-            if ($this->getNode('node') instanceof Twig_Node_Expression_Name) {
-                $testMap = $compiler->getEnvironment()->getTests();
-                $compiler
-                    ->raw($testMap['defined']->compile().'(')
-                    ->repr($this->getNode('node')->getAttribute('name'))
-                    ->raw(', $context)')
-                ;
-            } elseif ($this->getNode('node') instanceof Twig_Node_Expression_GetAttr) {
-                $this->getNode('node')->setAttribute('is_defined_test', true);
-                $compiler->subcompile($this->getNode('node'));
-                $this->getNode('node')->removeAttribute('is_defined_test');
-            }
+        if ('default' === $name && ($node instanceof Twig_Node_Expression_Name || $node instanceof Twig_Node_Expression_GetAttr)) {
+            $compiler
+                ->raw('((')
+                ->subcompile(new Twig_Node_Expression_Test($node, 'defined', new Twig_Node(), $this->getLine()))
+                ->raw(') ? (')
+            ;
 
-            $compiler->raw(') ? (');
             $this->compileFilter($compiler, $filter);
+
             $compiler->raw(') : (');
-            $compiler->subcompile($this->getNode('arguments')->getNode(0));
+
+            if ($this->getNode('arguments')->hasNode(0)) {
+                $compiler->subcompile($this->getNode('arguments')->getNode(0));
+            } else {
+                $compiler->string('');
+            }
+
             $compiler->raw('))');
         } else {
             $this->compileFilter($compiler, $filter);
@@ -57,10 +57,9 @@ class Twig_Node_Expression_Filter extends Twig_Node_Expression
             ->raw($filter->compile().'(')
             ->raw($filter->needsEnvironment() ? '$this->env, ' : '')
             ->raw($filter->needsContext() ? '$context, ' : '')
+            ->subcompile($this->getNode('node'))
         ;
 
-        $this->getNode('node')->compile($compiler);
-
         foreach ($this->getNode('arguments') as $node) {
             $compiler
                 ->raw(', ')
index 0ac04f6..2208c9e 100644 (file)
@@ -18,9 +18,20 @@ class Twig_Node_Expression_GetAttr extends Twig_Node_Expression
 
     public function compile(Twig_Compiler $compiler)
     {
+        $compiler->raw('$this->getAttribute(');
+
+        if ($this->hasAttribute('is_defined_test')) {
+            $compiler->subcompile(new Twig_Node_Expression_Filter(
+                $this->getNode('node'),
+                new Twig_Node_Expression_Constant('default', $this->getLine()),
+                new Twig_Node(),
+                $this->getLine()
+            ));
+        } else {
+            $compiler->subcompile($this->getNode('node'));
+        }
+
         $compiler
-            ->raw('$this->getAttribute(')
-            ->subcompile($this->getNode('node'))
             ->raw(', ')
             ->subcompile($this->getNode('attribute'))
             ->raw(', array(')
index a7194b7..f7d1875 100644 (file)
@@ -18,16 +18,26 @@ class Twig_Node_Expression_Name extends Twig_Node_Expression
 
     public function compile(Twig_Compiler $compiler)
     {
-        if ('_self' === $this->getAttribute('name')) {
-            $compiler->raw('$this');
-        } elseif ('_context' === $this->getAttribute('name')) {
-            $compiler->raw('$context');
-        } elseif ('_charset' === $this->getAttribute('name')) {
-            $compiler->raw('$this->env->getCharset()');
+        static $specialVars = array(
+            '_self'    => '$this',
+            '_context' => '$context',
+            '_charset' => '$this->env->getCharset()',
+        );
+
+        $name = $this->getAttribute('name');
+
+        if ($this->hasAttribute('is_defined_test')) {
+            if (isset($specialVars[$name])) {
+                $compiler->repr(true);
+            } else {
+                $compiler->raw('array_key_exists(')->repr($name)->raw(', $context)');
+            }
+        } elseif (isset($specialVars[$name])) {
+            $compiler->raw($specialVars[$name]);
         } elseif ($compiler->getEnvironment()->isStrictVariables()) {
-            $compiler->raw(sprintf('$this->getContext($context, \'%s\')', $this->getAttribute('name')));
+            $compiler->raw(sprintf('$this->getContext($context, \'%s\')', $name));
         } else {
-            $compiler->raw(sprintf('(isset($context[\'%s\']) ? $context[\'%s\'] : null)', $this->getAttribute('name'), $this->getAttribute('name')));
+            $compiler->raw(sprintf('(isset($context[\'%s\']) ? $context[\'%s\'] : null)', $name, $name));
         }
     }
 }
index 204da25..ef35df6 100644 (file)
@@ -22,19 +22,15 @@ class Twig_Node_Expression_Test extends Twig_Node_Expression
             throw new Twig_Error_Syntax(sprintf('The test "%s" does not exist', $this->getAttribute('name')), $this->getLine());
         }
 
+        $name = $this->getAttribute('name');
+        $node = $this->getNode('node');
+
         // defined is a special case
-        if ('defined' === $this->getAttribute('name')) {
-            if ($this->getNode('node') instanceof Twig_Node_Expression_Name) {
-                $compiler
-                    ->raw($testMap['defined']->compile().'(')
-                    ->repr($this->getNode('node')->getAttribute('name'))
-                    ->raw(', $context)')
-                ;
-            } elseif ($this->getNode('node') instanceof Twig_Node_Expression_GetAttr) {
-                $this->getNode('node')->setAttribute('is_defined_test', true);
-                $compiler
-                    ->subcompile($this->getNode('node'))
-                ;
+        if ('defined' === $name) {
+            if ($node instanceof Twig_Node_Expression_Name || $node instanceof Twig_Node_Expression_GetAttr) {
+                $node->setAttribute('is_defined_test', true);
+                $compiler->subcompile($node);
+                $node->removeAttribute('is_defined_test');
             } else {
                 throw new Twig_Error_Syntax('The "defined" test only works with simple variables', $this->getLine());
             }
@@ -42,16 +38,16 @@ class Twig_Node_Expression_Test extends Twig_Node_Expression
         }
 
         $compiler
-            ->raw($testMap[$this->getAttribute('name')]->compile().'(')
-            ->subcompile($this->getNode('node'))
+            ->raw($testMap[$name]->compile().'(')
+            ->subcompile($node)
         ;
 
         if (null !== $this->getNode('arguments')) {
             $compiler->raw(', ');
 
             $max = count($this->getNode('arguments')) - 1;
-            foreach ($this->getNode('arguments') as $i => $node) {
-                $compiler->subcompile($node);
+            foreach ($this->getNode('arguments') as $i => $arg) {
+                $compiler->subcompile($arg);
 
                 if ($i != $max) {
                     $compiler->raw(', ');