Add #48: allow call static method in templates and allow disable this option

This commit is contained in:
Ivan Shalganov 2014-01-28 19:24:47 +02:00
parent 7759df8453
commit 75bcc4e0ff
4 changed files with 135 additions and 22 deletions

View File

@ -1,13 +1,23 @@
<?php <?php
require_once __DIR__.'/../vendor/autoload.php'; namespace Ts {
class Math {
public static function multi($x, $y) {
return $x * $y;
}
}
}
$fenom = Fenom::factory(__DIR__.'/templates', __DIR__.'/compiled', 0); namespace {
require_once __DIR__.'/../vendor/autoload.php';
$fenom->display("../templates/../fenom.php", array( $fenom = Fenom::factory(__DIR__.'/templates', __DIR__.'/compiled', Fenom::FORCE_COMPILE);
"user" => array(
"name" => "Ivka", $fenom->display("greeting.tpl", array(
"user" => array(
"name" => "Ivka",
'type' => 'new'
),
'type' => 'new' 'type' => 'new'
), ));
'type' => 'new' }
));

View File

@ -23,7 +23,8 @@ use Fenom\Error\TokenizeException;
*/ */
class Template extends Render class Template extends Render
{ {
const VAR_NAME = '$var';
const TPL_NAME = '$tpl';
/** /**
* Disable array parser. * Disable array parser.
*/ */
@ -440,11 +441,8 @@ class Template extends Render
{ {
if (!$this->_code) { if (!$this->_code) {
// evaluate template's code // evaluate template's code
// $code = ("\$this->_code = " . $this->_getClosureSource() . ";\n\$this->_macros = " . $this->_getMacrosArray() . ';');
// file_put_contents('/tmp/last.tpl', $code);
eval("\$this->_code = " . $this->_getClosureSource() . ";\n\$this->_macros = " . $this->_getMacrosArray() . ';'); eval("\$this->_code = " . $this->_getClosureSource() . ";\n\$this->_macros = " . $this->_getMacrosArray() . ';');
if (!$this->_code) { if (!$this->_code) {
// exit;
throw new CompileException("Fatal error while creating the template"); throw new CompileException("Fatal error while creating the template");
} }
} }
@ -552,7 +550,8 @@ class Template extends Render
* @static * @static
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @throws \LogicException * @throws \LogicException
* @throws TokenizeException * @throws \RuntimeException
* @throws Error\TokenizeException
* @return string * @return string
*/ */
public function parseAct(Tokenizer $tokens) public function parseAct(Tokenizer $tokens)
@ -573,6 +572,15 @@ class Template extends Render
$name = $action . "." . $name; $name = $action . "." . $name;
} }
return $this->parseMacroCall($tokens, $name); return $this->parseMacroCall($tokens, $name);
} elseif($tokens->is(T_DOUBLE_COLON, T_NS_SEPARATOR)) { // static method call
$tokens->back();
$p = $tokens->p;
$static = $this->parseStatic($tokens);
if($tokens->is("(")) {
return $this->out($this->parseExpr($tokens->seek($p)));
} else {
return $this->out(Compiler::smartFuncParser($static, $tokens, $this));
}
} }
if ($tag = $this->_fenom->getTag($action, $this)) { // call some function if ($tag = $this->_fenom->getTag($action, $this)) { // call some function
@ -766,6 +774,10 @@ class Template extends Render
throw new \Exception("Function " . $tokens->getAndNext() . " not found"); throw new \Exception("Function " . $tokens->getAndNext() . " not found");
} }
$code = $unary . $func . $this->parseArgs($tokens->next()); $code = $unary . $func . $this->parseArgs($tokens->next());
} elseif($tokens->isNext(T_NS_SEPARATOR, T_DOUBLE_COLON)) {
$method = $this->parseStatic($tokens);
$args = $this->parseArgs($tokens);
$code = $unary . $method . $args;
} else { } else {
return false; return false;
} }
@ -1179,13 +1191,18 @@ class Template extends Render
public function parseModifier(Tokenizer $tokens, $value) public function parseModifier(Tokenizer $tokens, $value)
{ {
while ($tokens->is("|")) { while ($tokens->is("|")) {
$mods = $this->_fenom->getModifier($tokens->getNext(Tokenizer::MACRO_STRING), $this); $modifier = $tokens->getNext(Tokenizer::MACRO_STRING);
if (!$mods) { if($tokens->isNext(T_DOUBLE_COLON, T_NS_SEPARATOR)) {
throw new \Exception("Modifier " . $tokens->current() . " not found"); $mods = $this->parseStatic($tokens);
} else {
$mods = $this->_fenom->getModifier($modifier, $this);
if (!$mods) {
throw new \Exception("Modifier " . $tokens->current() . " not found");
}
$tokens->next();
} }
$tokens->next();
$args = array();
$args = array();
while ($tokens->is(":")) { while ($tokens->is(":")) {
if (!$args[] = $this->parseTerm($tokens->next())) { if (!$args[] = $this->parseTerm($tokens->next())) {
throw new UnexpectedTokenException($tokens); throw new UnexpectedTokenException($tokens);
@ -1288,23 +1305,48 @@ class Template extends Render
throw new InvalidUsageException("Macro '$name' require '$arg' argument"); throw new InvalidUsageException("Macro '$name' require '$arg' argument");
} }
} }
// $n = sprintf('%x_%x', crc32($this->_name), $this->i++);
if ($recursive) { if ($recursive) {
$recursive['recursive'] = true; $recursive['recursive'] = true;
return '$tpl->getMacro("' . $name . '")->__invoke('.Compiler::toArray($args).', $tpl);'; return '$tpl->getMacro("' . $name . '")->__invoke('.Compiler::toArray($args).', $tpl);';
} else { } else {
// $body = '? >' . $macro["body"] . '<?php';
$vars = $this->tmpVar(); $vars = $this->tmpVar();
return $vars . ' = $var; $var = ' . Compiler::toArray($args) . ';' . PHP_EOL . '?>' . return $vars . ' = $var; $var = ' . Compiler::toArray($args) . ';' . PHP_EOL . '?>' .
$macro["body"] . '<?php' . PHP_EOL . '$var = '.$vars.'; unset(' . $vars . ');'; $macro["body"] . '<?php' . PHP_EOL . '$var = '.$vars.'; unset(' . $vars . ');';
} }
} }
/**
* @param Tokenizer $tokens
* @throws \LogicException
* @throws \RuntimeException
* @return string
*/
public function parseStatic(Tokenizer $tokens)
{
if($this->_options & Fenom::DENY_STATICS) {
throw new \LogicException("Static methods are disabled");
}
$tokens->skipIf(T_NS_SEPARATOR);
$name = "";
if ($tokens->is(T_STRING)) {
$name .= $tokens->getAndNext();
while ($tokens->is(T_NS_SEPARATOR)) {
$name .= '\\' . $tokens->next()->get(T_STRING);
$tokens->next();
}
}
$tokens->need(T_DOUBLE_COLON)->next()->need(T_STRING);
$static = $name . "::" . $tokens->getAndNext();
if(!is_callable($static)) {
throw new \RuntimeException("Method $static doesn't exist");
}
return $static;
}
/** /**
* Parse argument list * Parse argument list
* (1 + 2.3, 'string', $var, [2,4]) * (1 + 2.3, 'string', $var, [2,4])
* *
* @static
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @throws TokenizeException * @throws TokenizeException
* @return string * @return string

View File

@ -617,4 +617,16 @@ class Tokenizer
{ {
return $this->curr ? $this->curr[2] : false; return $this->curr ? $this->curr[2] : false;
} }
/**
* Seek to custom element
* @param int $p
* @return $this
*/
public function seek($p)
{
$this->p = $p;
unset($this->prev, $this->curr, $this->next);
return $this;
}
} }

View File

@ -780,12 +780,39 @@ class TemplateTest extends TestCase
); );
} }
public function providerStatic() {
return array(
array('{Fenom\TemplateTest::multi x=3 y=4}', '12'),
array('{Fenom\TemplateTest::multi(3,4)}', '12'),
array('{12 + Fenom\TemplateTest::multi(3,4)}', '24'),
array('{12 + 3|Fenom\TemplateTest::multi:4}', '24'),
);
}
public function providerStaticInvalid() {
return array(
array('{Fenom\TemplateTest::multi x=3 y=4}', 'Fenom\Error\SecurityException', "Static methods are disabled", Fenom::DENY_STATICS),
array('{Fenom\TemplateTest::multi(3,4)}', 'Fenom\Error\SecurityException', "Static methods are disabled", Fenom::DENY_STATICS),
array('{12 + Fenom\TemplateTest::multi(3,4)}', 'Fenom\Error\SecurityException', "Static methods are disabled", Fenom::DENY_STATICS),
array('{12 + 3|Fenom\TemplateTest::multi:4}', 'Fenom\Error\SecurityException', "Static methods are disabled", Fenom::DENY_STATICS),
array('{Fenom\TemplateTest::multi_invalid x=3 y=4}', 'Fenom\Error\CompileException', 'Method Fenom\TemplateTest::multi_invalid doesn\'t exist'),
array('{Fenom\TemplateTest::multi_invalid(3,4)}', 'Fenom\Error\CompileException', 'Method Fenom\TemplateTest::multi_invalid doesn\'t exist'),
array('{12 + Fenom\TemplateTest::multi_invalid(3,4)}', 'Fenom\Error\CompileException', 'Method Fenom\TemplateTest::multi_invalid doesn\'t exist'),
array('{12 + 3|Fenom\TemplateTest::multi_invalid:4}', 'Fenom\Error\CompileException', 'Method Fenom\TemplateTest::multi_invalid doesn\'t exist'),
);
}
public function _testSandbox() public function _testSandbox()
{ {
try { try {
var_dump($this->fenom->setOptions(Fenom::FORCE_VERIFY)->addFilter(function ($txt) {return $txt;})->compileCode('- {$a} -')->fetch(array('a' => 1))); var_dump($this->fenom->compileCode('{Fenom\TemplateTest::multi(3,4)}')->getBody());
} catch (\Exception $e) { } catch (\Exception $e) {
print_r($e->getMessage() . "\n" . $e->getTraceAsString()); print_r($e->getMessage() . "\n" . $e->getTraceAsString());
while($e->getPrevious()) {
$e = $e->getPrevious();
print_r("\n\n".$e->getMessage() . "\n" . $e->getTraceAsString());
}
} }
exit; exit;
} }
@ -1037,5 +1064,27 @@ class TemplateTest extends TestCase
{ {
$this->exec($code, self::getVars(), $result); $this->exec($code, self::getVars(), $result);
} }
/**
* @group static
* @dataProvider providerStatic
*/
public function testStatic($code, $result)
{
$this->exec($code, self::getVars(), $result, true);
}
public static function multi($x, $y = 42) {
return $x * $y;
}
/**
* @group static-invalid
* @dataProvider providerStaticInvalid
*/
public function testStaticInvalid($code, $exception, $message, $options = 0)
{
$this->execError($code, $exception, $message, $options);
}
} }