/** * @dataProvider sourcesProvider */ public function testCompiles($source, $expectedOutput, $locals) { // // parse // $parseContext = new ParserContext(); $parseContext->setString($source)->setBaseDirectory(__DIR__); $template = $parseContext->parse(); $this->assertInstanceOf('Skrz\\Templating\\Engine\\AST\\TemplateNode', $template); // // compile // $compilerContext = new CompilerContext(); $compilerContext->setNamespace('Skrz\\Templating\\Engine')->setClassName("Template_" . sha1(uniqid('', true)))->setTemplate($template)->setParserContext($parseContext)->addModifier("strip_tags", function (Compiler $compiler, ModifierNode $modifier, $text) { return new ModifierCompilerResult("strip_tags({$text})"); })->addFunction("inferno", function (Compiler $compiler, FunctionNode $function) { /** @var StatementAndExpressionVO $hell */ $hell = $compiler->walk($function->getArgument("hell")); $ret = ""; $ret .= "echo '666 ';"; $ret .= $hell->getStatement(); $ret .= "echo " . $hell->getExpression() . ";"; $ret .= "echo ' 666';"; return new FunctionCompilerResult($ret); }); $compiled = $compilerContext->compile(); // // debug // // echo "\n\n"; // echo "$source\n"; // echo "--------\n"; // echo $compiled; // echo "\n"; // // run // $this->assertNull(eval("?>{$compiled}")); $fullyQualifiedClassName = $compilerContext->getFullyQualifiedClassName(); $templateInstance = new $fullyQualifiedClassName(); ob_start(); $templateInstance($locals); $actualOutput = ob_get_contents(); ob_end_clean(); $this->assertEquals($expectedOutput, $actualOutput); }
public function fetch($templateFile) { if (strncmp($templateFile, "string:", strlen("string:")) === 0) { $this->parserContext->setString(substr($templateFile, strlen("string:"))); $this->compilerContext->setClassName("Template_" . md5($this->parserContext->getString())); } else { if (!is_null($this->parserContext->getBaseDirectory()) && strncmp($templateFile, $this->parserContext->getBaseDirectory(), strlen($this->parserContext->getBaseDirectory())) === 0) { $templateFile = substr($templateFile, strlen($this->parserContext->getBaseDirectory()) + 1); } // must be set before CompilerContext::getFullyQualifiedClassName() is called // FIXME: uncouple this $this->parserContext->setFile($templateFile); } $className = $this->compilerContext->getFullyQualifiedClassName(); if (!class_exists($className)) { $saveFileName = $this->classDirectory . "/" . str_replace("\\", "/", trim($className, "\\") . ".php"); if (file_exists($saveFileName)) { require_once $saveFileName; } else { $oldUmask = umask(0); $compiledTemplate = $this->compilerContext->setTemplate($this->parserContext->parse())->compile(); if (!is_dir(dirname($saveFileName))) { @mkdir(dirname($saveFileName), $this->dirPerms, true); } file_put_contents($saveFileName, $compiledTemplate); @chmod($saveFileName, $this->dirPerms); umask($oldUmask); require_once $saveFileName; } } $templateInstance = new $className(); ob_start(); $templateInstance($this->locals); $output = ob_get_clean(); foreach ($this->outputFilters as $filter) { $output = $filter($output); } return $output; }
/** * @return string * @throws CompilerException */ public function compile() { $namespace = null; $className = trim($this->context->getFullyQualifiedClassName(), "\\"); $pos = strrpos($className, "\\"); if ($pos !== false) { $namespace = substr($className, 0, $pos); $className = substr($className, $pos + 1); } // prelude $ret = ""; $ret .= "<?php\n"; if ($namespace) { $ret .= "namespace {$namespace};\n"; $ret .= "\n"; } foreach ($this->context->getUses() as $alias => $fqn) { $ret .= "use {$fqn} as {$alias};\n"; } $ret .= "\n"; $ret .= "class {$className} implements \\Skrz\\Templating\\Engine\\TemplateInterface {\n"; $ret .= "\n"; $ret .= "public function __invoke(array \$____) {\n"; $ret .= "\t\$this->render(\$____);\n"; $ret .= "}\n"; $ret .= "\n"; $ret .= "public function fetch(array \$____) {\n"; $ret .= "\tob_start();\n"; $ret .= "\t\$this->render(\$____);\n"; $ret .= "\treturn ob_get_clean();\n"; $ret .= "}\n"; $ret .= "\n"; $ret .= "public function fetchFunction(\$functionName, array \$____) {\n"; $ret .= "\tob_start();\n"; $ret .= "\t\$this->{'render' . \$functionName}(\$____);\n"; $ret .= "\treturn ob_get_clean();\n"; $ret .= "}\n"; $ret .= "\n"; $ret .= "public function renderFunction(\$functionName, array \$____) {\n"; $ret .= "\t\$this->{'render' . \$functionName}(\$____);\n"; $ret .= "}\n"; $ret .= "\n"; // find function declarations $functionDeclarationFinder = new FunctionDeclarationsWalker(); $functionDeclarationFinder->walk($this->context->getTemplate()); // function declarations have to be registered first and compiled into render functions, // otherwise recursive calls would not be possible $ret .= "private \$functions = array("; foreach ($functionDeclarationFinder->getDeclarations() as $declaration) { $ret .= var_export($declaration->getName(), true) . "=>true,"; $this->context->addFunction($declaration->getName(), function (Compiler $compiler, FunctionNode $node) use($declaration) { $statement = ""; $args = "array("; foreach ($node->getArguments() as $name => $expression) { $statementAndExpression = $compiler->walkExpression($expression); $statement .= $statementAndExpression->getStatement(); $args .= var_export($name, true) . '=>' . $statementAndExpression->getExpression() . ","; } $args .= ")"; return new FunctionCompilerResult($statement . "\$this->" . $this->getRenderName($declaration->getName()) . "(" . $args . " + \$____);"); }); } $ret .= ");\n\n"; $ret .= "public function hasFunction(\$functionName) {\n"; $ret .= "\treturn isset(\$this->functions[\$functionName]);\n"; $ret .= "}\n\n"; foreach ($functionDeclarationFinder->getDeclarations() as $declaration) { $ret .= $this->compileRender($declaration->getName(), $declaration->getBody(), $declaration->getDefaultArguments()); } // compile main render $ret .= $this->compileRender("", array($this->context->getTemplate())); // end $ret .= "\n"; $ret .= "}\n"; return $ret; }