diff --git a/benchmark/scripts/bootstrap.php b/benchmark/scripts/bootstrap.php index a3c8ebf..b366236 100644 --- a/benchmark/scripts/bootstrap.php +++ b/benchmark/scripts/bootstrap.php @@ -50,19 +50,23 @@ class Benchmark { if($double) { $aspect->fetch($tpl, $data); } - $start = microtime(true); + $_SERVER["t"] = $start = microtime(true); $aspect->fetch($tpl, $data); printf(self::$t, __FUNCTION__, $message, round(microtime(true)-$start, 4), round(memory_get_peak_usage()/1024/1024, 2)); } public static function run($engine, $template, $data, $double, $message) { - passthru(sprintf("php %s/run.php --engine '%s' --template '%s' --data '%s' --message '%s' %s", __DIR__, $engine, $template, $data, $message, $double ? '--double' : '')); + passthru(sprintf("php -dmemory_limit=512M %s/run.php --engine '%s' --template '%s' --data '%s' --message '%s' %s", __DIR__, $engine, $template, $data, $message, $double ? '--double' : '')); } public static function runs($engine, $template, $data) { self::run($engine, $template, $data, false, '!compiled and !loaded'); - self::run($engine, $template, $data, false, 'compiled and !loaded'); - self::run($engine, $template, $data, true, 'compiled and loaded'); + self::run($engine, $template, $data, false, ' compiled and !loaded'); + self::run($engine, $template, $data, true, ' compiled and loaded'); echo "\n"; } +} + +function t() { + if(isset($_SERVER["t"])) var_dump(round(microtime(1) - $_SERVER["t"], 4)); } \ No newline at end of file diff --git a/src/Aspect.php b/src/Aspect.php index 16c6fa0..1f5ddce 100644 --- a/src/Aspect.php +++ b/src/Aspect.php @@ -526,7 +526,6 @@ class Aspect { * @return Aspect\Template */ public function getTemplate($template) { - if(isset($this->_storage[ $template ])) { /** @var Aspect\Template $tpl */ $tpl = $this->_storage[ $template ]; @@ -562,6 +561,7 @@ class Aspect { if(!is_file($this->_compile_dir."/".$file_name)) { return $this->compile($tpl); } else { + $aspect = $this; /** @var Aspect\Render $tpl */ return include($this->_compile_dir."/".$file_name); } diff --git a/src/Aspect/Compiler.php b/src/Aspect/Compiler.php index 0469f4c..4fe2085 100644 --- a/src/Aspect/Compiler.php +++ b/src/Aspect/Compiler.php @@ -388,28 +388,18 @@ class Compiler { public static function extendBody(&$body, $tpl) { if(isset($tpl->_extends)) { // is child if(is_object($tpl->_extends)) { // static extends - $t = $tpl; - while(isset($t->_extends)) { + $t = $tpl->_extends; + /* @var Template $t */ + do { + $t->_blocks = &$tpl->_blocks; $t->compile(); - $t->_blocks += (array)$t->_extends->_blocks; - $t = $t->_extends; - - } - - if(empty($t->_blocks)) { - $body = $t->getBody(); - } else { - $b = $t->getBody(); - foreach($t->_blocks as $name => $pos) { - - } - } + $tpl->addDepend($t); + } while(isset($t->_extends) && $t = $t->_extends); + $body = $t->_body; } else { // dynamic extends $body .= 'blocks = &$tpl->blocks; $parent->display((array)$tpl); unset($tpl->blocks, $parent->blocks); ?>'; - //return '$tpl->blocks['.$scope["name"].'] = ob_get_clean();'; } } - /*$body = 'blocks)) {$tpl->blocks = array();} ob_start(); ?>'.$body.'blocks = &$tpl->blocks; $parent->display((array)$tpl); unset($tpl->blocks, $parent->blocks); ?>';*/ } @@ -425,25 +415,30 @@ class Compiler { * @throws ImproperUseException */ public static function tagBlockOpen(Tokenizer $tokens, Scope $scope) { - $p = $scope->tpl->parseParams($tokens); - if (isset($p[0])) { - $scope["name"] = $p[0]; + $p = $scope->tpl->parseFirstArg($tokens, $name); + if ($name) { + $scope["static"] = true; + $scope["name"] = $name; + $scope["cname"] = '"'.addslashes($name).'"'; } else { - throw new ImproperUseException("{block} must be named"); + $scope["static"] = false; + $scope["name"] = $scope["cname"] = $p; } if(isset($scope->tpl->_extends)) { // is child - if(is_object($scope->tpl->_extends)) { // static extends + if(is_object($scope->tpl->_extends) && $scope["static"]) { // static extends $code = ""; } else { // dynamic extends - $code = 'if(empty($tpl->blocks['.$scope["name"].'])) { ob_start();'; + $code = 'if(empty($tpl->blocks['.$scope["cname"].'])) { $tpl->blocks['.$scope["cname"].'] = function($tpl) {'; } } else { // is parent - if(isset($scope->tpl->_blocks[ $scope["name"] ])) { // skip own block and insert child's block after - $scope["body"] = $scope->tpl->_body; - $scope->tpl->_body = ""; + if(isset($scope->tpl->_blocks)) { // has blocks from child + if(isset($scope->tpl->_blocks[ $scope["name"] ])) { // skip own block and insert child's block after + $scope["body"] = $scope->tpl->_body; + $scope->tpl->_body = ""; + } // else just put block content as is return ''; } else { - $code = 'if(isset($tpl->blocks['.$scope["name"].'])) { echo $tpl->blocks['.$scope["name"].']; } else {'; + $code = 'if(isset($tpl->blocks['.$scope["cname"].'])) { echo $tpl->blocks['.$scope["cname"].']->__invoke($tpl); } else {'; } } $scope["offset"] = strlen($scope->tpl->getBody()) + strlen($code); @@ -457,35 +452,26 @@ class Compiler { * @return string */ public static function tagBlockClose($tokens, Scope $scope) { - $scope->tpl->_blocks[ $scope["name"] ] = substr($scope->tpl->getBody(), $scope["offset"]); + if(isset($scope->tpl->_extends)) { // is child - if(is_object($scope->tpl->_extends)) { // static extends + if(is_object($scope->tpl->_extends) && $scope["static"]) { // static extends + if(!isset($scope->tpl->_blocks[ $scope["name"] ])) { + $scope->tpl->_blocks[ $scope["name"] ] = substr($scope->tpl->getBody(), $scope["offset"]); + } return ""; } else { // dynamic extends - return '$tpl->blocks['.$scope["name"].'] = ob_get_clean(); }'; + return '} }'; } } else { // is parent - if(isset($scope["body"])) { - $scope->tpl->_body = $scope["body"].$scope->tpl->_blocks[ $scope["name"] ]; + if(isset($scope->tpl->_blocks)) { + if(isset($scope["body"])) { + $scope->tpl->_body = $scope["body"].$scope->tpl->_blocks[ $scope["name"] ]; + } return ""; } else { return '}'; } } - /* $scope->tpl->_blocks[ $scope["name"] ] = substr($scope->tpl->getBody(), $scope["offset"]); - return '}';*/ - /*if(isset($scope->tpl->_extends) && is_object($scope->tpl->_extends)) { - - //var_dump("fetched block ".$scope->tpl->_blocks[ $scope["name"] ]); - } else { - return '}'; - }*/ - /*if(isset($scope->tpl->_extends)) { - $var = '$i'.$scope->tpl->i++; - return $var.' = ob_get_clean(); if('.$var.') $tpl->blocks['.$scope["name"].'] = '.$var.';'; - } else { - return 'if(empty($tpl->blocks['.$scope["name"].'])) { ob_end_flush(); } else { print($tpl->blocks['.$scope["name"].']); ob_end_clean(); }'; - }*/ } /** diff --git a/src/Aspect/Misc.php b/src/Aspect/Misc.php index 686daa0..4dfff2e 100644 --- a/src/Aspect/Misc.php +++ b/src/Aspect/Misc.php @@ -28,6 +28,11 @@ class Misc { return $mask; } + /** + * Clean directory from files + * + * @param string $path + */ public static function clean($path) { if(is_file($path)) { unlink($path); @@ -46,6 +51,11 @@ class Misc { } } + /** + * Recursive remove directory + * + * @param string $path + */ public static function rm($path) { self::clean($path); if(is_dir($path)) { diff --git a/src/Aspect/Render.php b/src/Aspect/Render.php index 5e11694..4eebf2e 100644 --- a/src/Aspect/Render.php +++ b/src/Aspect/Render.php @@ -66,6 +66,10 @@ class Render extends \ArrayObject { return $this->_aspect; } + public function getDepends() { + return $this->_depends; + } + public function getScm() { return $this->_scm; } @@ -93,7 +97,7 @@ class Render extends \ArrayObject { return $this->_name; } - public function getCompileTime() { + public function getTime() { return $this->_time; } @@ -108,7 +112,7 @@ class Render extends \ArrayObject { return false; } foreach($this->_depends as $tpl => $time) { - if($this->_aspect->getTemplate($tpl)->getCompileTime() !== $time) { + if($this->_aspect->getTemplate($tpl)->getTime() !== $time) { return false; } } diff --git a/src/Aspect/Template.php b/src/Aspect/Template.php index 990726a..baaf156 100644 --- a/src/Aspect/Template.php +++ b/src/Aspect/Template.php @@ -137,16 +137,29 @@ class Template extends Render { // $frag = ltrim($frag); //} - $this->_body .= str_replace("', $frag); + $this->_body .= str_replace("', $frag); - $tag = $this->_tag($tag, $this->_trim); // dispatching tags + $this->_body .= $this->_tag($tag, $this->_trim); // dispatching tags + // duplicate new line http://docs.php.net/manual/en/language.basic-syntax.instruction-separation.php + /*if(isset($this->_src[ $end+1 ])) { + $c = $this->_src[ $end+1 ]; + if($c === "\n") { + $this->_body .= "\n"; + } elseif($c === "\r") { + if(isset($this->_src[ $end+2 ]) && $this->_src[ $end+2 ] == "\n") { + $this->_body .= "\r\n"; + } else { + $this->_body .= "\r"; + } + } + }*/ //if($this->_trim) { // if current tag has trim flag // $frag = rtrim($frag); //} - $this->_body .= $tag; + //$this->_body .= $tag; } - $this->_body .= str_replace("', substr($this->_src, $this->_pos)); + $this->_body .= str_replace("', substr($this->_src, $this->_pos)); if($this->_stack) { $_names = array(); $_line = 0; @@ -185,7 +198,7 @@ class Template extends Render { public function getTemplateCode() { return "_name."' compiled at ".date('Y-m-d H:i:s')." */\n". - "return new Aspect\\Render(\$this, ".$this->_getClosureSource().", ".var_export(array( + "return new Aspect\\Render(\$aspect, ".$this->_getClosureSource().", ".var_export(array( //"options" => $this->_options, "provider" => $this->_scm, "name" => $this->_name, @@ -227,8 +240,7 @@ class Template extends Render { * @param Render $tpl */ public function addDepend(Render $tpl) { - - $this->_depends[$tpl->getName()] = $tpl->getCompileTime(); + $this->_depends[$tpl->getScm()][$tpl->getName()] = $tpl->getTime(); } /** diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..3895cff --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,57 @@ +aspect = Aspect::factory(ASPECT_RESOURCES.'/template', ASPECT_RESOURCES.'/compile'); + } + + /** + * Compile and execute template + * + * @param string $code source of template + * @param array $vars variables of template + * @param string $result expected result. + * @param bool $dump dump source and result code (for debug) + */ + public function exec($code, $vars, $result, $dump = false) { + $tpl = $this->aspect->compileCode($code, "runtime.tpl"); + if($dump) { + echo "\n========= DUMP BEGIN ===========\n".$code."\n--- to ---\n".$tpl->getBody()."\n========= DUMP END =============\n"; + } + $this->assertSame(Modifier::strip($result), Modifier::strip($tpl->fetch($vars), true), "Test $code"); + } + + /** + * Try to compile the invalid template + * @param string $code source of the template + * @param string $exception exception class + * @param string $message exception message + * @param int $options Aspect's options + */ + public function execError($code, $exception, $message, $options = 0) { + $opt = $this->aspect->getOptions(); + $this->aspect->setOptions($options); + try { + $this->aspect->compileCode($code, "inline.tpl"); + } catch(\Exception $e) { + $this->assertSame($exception, get_class($e), "Exception $code"); + $this->assertStringStartsWith($message, $e->getMessage()); + $this->aspect->setOptions($opt); + return; + } + self::$aspect->setOptions($opt); + $this->fail("Code $code must be invalid"); + } +} diff --git a/tests/autoload.php b/tests/autoload.php index 3647243..fda7597 100644 --- a/tests/autoload.php +++ b/tests/autoload.php @@ -6,4 +6,12 @@ require_once __DIR__."/../vendor/autoload.php"; define('ASPECT_RESOURCES', __DIR__."/resources"); -require_once ASPECT_RESOURCES."/actions.php"; \ No newline at end of file +require_once ASPECT_RESOURCES."/actions.php"; +require_once __DIR__."/TestCase.php"; + +function drop() { + call_user_func_array("var_dump", func_get_args()); + $e = new Exception(); + echo "-------\nDump trace: \n".$e->getTraceAsString()."\n"; + exit(); +} \ No newline at end of file diff --git a/tests/cases/Aspect/Template/ExtendsTest.php b/tests/cases/Aspect/Template/ExtendsTest.php index 3e877f7..5c06aa3 100644 --- a/tests/cases/Aspect/Template/ExtendsTest.php +++ b/tests/cases/Aspect/Template/ExtendsTest.php @@ -1,22 +1,8 @@ compileCode($code, "inline.tpl"); - if($dump) { - echo "\n===========================\n".$code.": ".$tpl->getBody(); - } - $this->assertSame(Modifier::strip($result), Modifier::strip($tpl->fetch($vars), true), "Test $code"); - } - - public function execError($code, $exception, $message, $options) { - self::$aspect->setOptions($options); - try { - self::$aspect->compileCode($code, "inline.tpl"); - } catch(\Exception $e) { - $this->assertSame($exception, get_class($e), "Exception $code"); - $this->assertStringStartsWith($message, $e->getMessage()); - self::$aspect->setOptions(0); - return; - } - self::$aspect->setOptions(0); - $this->fail("Code $code must be invalid"); - } - /** * @group extends */ - public function testParent() { - //echo(self::$aspect->getTemplate("parent.tpl")->getBody()); exit; + public function testParentLevel() { + //echo($this->aspect->getTemplate("parent.tpl")->_body); exit; + $this->assertSame($this->aspect->fetch("parent.tpl", array("a" => "a char")), "Parent template\nBlock1: Block2: Block3: default"); } /** * @group extends */ - public function ___testChildLevel1() { - echo(self::$aspect->getTemplate("child1.tpl")->getBody()); exit; + public function testChildLevel1() { + //echo($this->aspect->fetch("child1.tpl", array("a" => "a char"))); exit; } /** * @group extends */ - public function __testExtends() { - echo(self::$aspect->fetch("child1.tpl", array("a" => "value", "z" => ""))."\n"); exit; + public function _testChildLevel3() { + echo($this->aspect->getTemplate("child3.tpl")->getBody()); exit; } } diff --git a/tests/cases/Aspect/TemplateTest.php b/tests/cases/Aspect/TemplateTest.php index 620a6fc..3a97f65 100644 --- a/tests/cases/Aspect/TemplateTest.php +++ b/tests/cases/Aspect/TemplateTest.php @@ -4,20 +4,11 @@ use Aspect\Template, Aspect, Aspect\Render; -class TemplateTest extends \PHPUnit_Framework_TestCase { - /** - * @var Aspect - */ - public static $aspect; +class TemplateTest extends TestCase { public function setUp() { - if(!file_exists(ASPECT_RESOURCES.'/compile')) { - mkdir(ASPECT_RESOURCES.'/compile', 0777, true); - } else { - exec("rm -f ".ASPECT_RESOURCES.'/compile/*'); - } - self::$aspect = Aspect::factory(ASPECT_RESOURCES.'/template', ASPECT_RESOURCES.'/compile'); - self::$aspect->addTemplate(new Render(self::$aspect, function ($tpl) { + parent::setUp(); + $this->aspect->addTemplate(new Render($this->aspect, function ($tpl) { echo "Welcome, ".$tpl["username"]." (".$tpl["email"].")"; }, array( "name" => "welcome.tpl" @@ -569,28 +560,6 @@ class TemplateTest extends \PHPUnit_Framework_TestCase { ); } - public function exec($code, $vars, $result, $dump = false) { - $tpl = self::$aspect->compileCode($code, "inline.tpl"); - if($dump) { - echo "\n===========================\n".$code.": ".$tpl->getBody(); - } - $this->assertSame(Modifier::strip($result), Modifier::strip($tpl->fetch($vars), true), "Test $code"); - } - - public function execError($code, $exception, $message, $options) { - self::$aspect->setOptions($options); - try { - self::$aspect->compileCode($code, "inline.tpl"); - } catch(\Exception $e) { - $this->assertSame($exception, get_class($e), "Exception $code"); - $this->assertStringStartsWith($message, $e->getMessage()); - self::$aspect->setOptions(0); - return; - } - self::$aspect->setOptions(0); - $this->fail("Code $code must be invalid"); - } - /** * @dataProvider providerVars */ diff --git a/tests/resources/template/child2.tpl b/tests/resources/template/child2.tpl index 0095c8f..fab7d20 100644 --- a/tests/resources/template/child2.tpl +++ b/tests/resources/template/child2.tpl @@ -1,4 +1,4 @@ -{extends "parent.tpl"} +{extends "child1.tpl"} {block blk2} blk2.{$a} {/block} \ No newline at end of file diff --git a/tests/resources/template/child3.tpl b/tests/resources/template/child3.tpl index fb75924..e3a7770 100644 --- a/tests/resources/template/child3.tpl +++ b/tests/resources/template/child3.tpl @@ -1,7 +1,7 @@ -{extends "parent.tpl"} +{extends "child2.tpl"} {block blk1} -blk1.{$a} (rewrited) +blk1.{$a} (overwritten) {/block} {block blk3}