Browse Source

Add block functions to the template, so they can be evaluated lazily.

Fixes #264
Damyon Wiese 10 years ago
parent
commit
1e63ba5fd9
2 changed files with 55 additions and 10 deletions
  1. 38 10
      src/Mustache/Compiler.php
  2. 17 0
      test/Mustache/Test/Functional/InheritanceTest.php

+ 38 - 10
src/Mustache/Compiler.php

@@ -19,6 +19,7 @@ class Mustache_Compiler
     private $pragmas;
     private $defaultPragmas = array();
     private $sections;
+    private $blocks;
     private $source;
     private $indentNextLine;
     private $customEscape;
@@ -43,6 +44,7 @@ class Mustache_Compiler
     {
         $this->pragmas         = $this->defaultPragmas;
         $this->sections        = array();
+        $this->blocks          = array();
         $this->source          = $source;
         $this->indentNextLine  = true;
         $this->customEscape    = $customEscape;
@@ -195,6 +197,7 @@ class Mustache_Compiler
                 return $buffer;
             }
         %s
+        %s
         }';
 
     const KLASS_NO_LAMBDAS = '<?php
@@ -225,18 +228,18 @@ class Mustache_Compiler
     {
         $code     = $this->walk($tree);
         $sections = implode("\n", $this->sections);
-        $klass    = empty($this->sections) ? self::KLASS_NO_LAMBDAS : self::KLASS;
+        $blocks   = implode("\n", $this->blocks);
+        $klass    = empty($this->sections) && empty($this->blocks) ? self::KLASS_NO_LAMBDAS : self::KLASS;
 
         $callable = $this->strictCallables ? $this->prepare(self::STRICT_CALLABLE) : '';
 
-        return sprintf($this->prepare($klass, 0, false, true), $name, $callable, $code, $sections);
+        return sprintf($this->prepare($klass, 0, false, true), $name, $callable, $code, $sections, $blocks);
     }
 
     const BLOCK_VAR = '
         $blockFunction = $context->findInBlock(%s);
         if (is_callable($blockFunction)) {
-            $boundFunction = $blockFunction->bindTo($this, $this);
-            $boundFunction($context, $buffer);
+            $buffer .= call_user_func($blockFunction, $context);
         } else {
             %s
         }
@@ -259,15 +262,12 @@ class Mustache_Compiler
     {
         $id = var_export($id, true);
 
-        return sprintf($this->prepare(self::BLOCK_VAR, $level), $id, $this->walk($nodes));
+        return sprintf($this->prepare(self::BLOCK_VAR, $level), $id, $this->walk($nodes, $level));
     }
 
     const BLOCK_ARG = '
         // %s block_arg
-        $blockFunction = function(& $context, & $buffer, $indent=\'\') {
-            %s
-        };
-        $newContext[%s] = $blockFunction;
+        $newContext[%s] = array($this, \'block%s\');
     ';
 
     /**
@@ -285,10 +285,38 @@ class Mustache_Compiler
      */
     private function blockArg($nodes, $id, $start, $end, $otag, $ctag, $level)
     {
+        $key = $this->block($nodes);
+        $keystr = var_export($key, true);
         $id = var_export($id, true);
+
+        return sprintf($this->prepare(self::BLOCK_ARG, 1), $keystr, $id, $key);
+    }
+
+    const BLOCK_FUNCTION = 'public function block%s($context) {
+            $indent = $buffer = \'\';
+
+            %s
+
+            return $buffer;
+        }';
+
+    /**
+     * Generate Mustache Template inheritance block function PHP source.
+     *
+     * @param array  $nodes Array of child tokens
+     *
+     * @return string key of new block function
+     */
+    private function block($nodes)
+    {
         $code = $this->walk($nodes, 1);
 
-        return sprintf($this->prepare(self::BLOCK_ARG, 1), $id, $code, $id);
+        $key = ucfirst(md5($code));
+
+        if (!isset($this->blocks[$key])) {
+            $this->blocks[$key] = sprintf($this->prepare(self::BLOCK_FUNCTION, 1), $key, $code);
+        }
+        return $key;
     }
 
     const SECTION_CALL = '

+ 17 - 0
test/Mustache/Test/Functional/InheritanceTest.php

@@ -467,6 +467,23 @@ class Mustache_Test_Functional_InheritanceTest extends PHPUnit_Framework_TestCas
         $this->assertEquals('<1><2><3>', $tpl->render($data));
     }
 
+    public function testInheritanceWithLazyEvaluationAndSections()
+    {
+        $partials = array(
+            'parent' => '{{#items}}{{$value}}\n\nignored {{.}} {{#more}} there is more {{/more}}\n\n{{/value}}{{/items}}',
+        );
+
+        $this->mustache->setPartials($partials);
+
+        $tpl = $this->mustache->loadTemplate(
+            '{{<parent}}\n\n\n{{$value}}<{{ . }}>{{#more}} there is less {{/more}}{{/value}}\n\n{{/parent}}'
+        );
+
+        $data = array('items' => array(1, 2, 3), 'more' => 'stuff');
+
+        $this->assertEquals('<1> there is less <2> there is less <3> there is less ', $tpl->render($data));
+    }
+
     /**
      * @dataProvider getIllegalInheritanceExamples
      * @expectedException Mustache_Exception_SyntaxException