diff --git a/docs/ru/configuration.md b/docs/ru/configuration.md index 22b22b6..0ed4973 100644 --- a/docs/ru/configuration.md +++ b/docs/ru/configuration.md @@ -48,5 +48,9 @@ $fenom->setOptions(array( $fenom->setOptions(Fenom::AUTO_RELOAD | Fenom::FORCE_INCLUDE); ``` +```php +$fenom->addCallFilter('View\Widget\*::get*') +``` + **Замечание** По умолчанию все параметры деактивированы. diff --git a/docs/ru/syntax.md b/docs/ru/syntax.md index 50d4027..6722951 100644 --- a/docs/ru/syntax.md +++ b/docs/ru/syntax.md @@ -97,15 +97,12 @@ * `$.tpl.depends` возвращает массив шаблонов на которые ссылается текущий шаблон. * `$.tpl.time` возвращает штамп времени когда шаблон последний раз менялся * `$.version` возвращает версию Fenom. -* `$.const.*` обращение к PHP константе: `$.const.PHP_EOL` обращение к константе `PHP_EOL`. Поддерживается пространство имен +* `$.const` обращение к PHP константе: `$.const.PHP_EOL` обращение к константе `PHP_EOL`. Поддерживается пространство имен которое разделяется через точку: `$.const.Storage.FS::DIR_SEPARATOR` обращение к PHP константе `Storage\FS::DIR_SEPARATOR` если такой констатнты нет будет взята константа `Storage\FS\DIR_SEPARATOR`. -* `$.php.*` обращение к статическомому методу. `$.php.Storage.FS::put($filename, $data)` обращение к методу `Storage\FS::put($filename, $data)`. +* `$.php` обращение к статическомому методу. `$.php.Storage.FS::put($filename, $data)` обращение к методу `Storage\FS::put($filename, $data)`. `$.php.Storage.FS.put($filename, $data)` `Storage\FS\put($filename, $data)` -* `$.tag.*` обращение к тегу. `$.tag.mailto($filename, $data)` {mailto ""}. -* `$.func.*` * `$.fetch($name, $values)` -* `$.macro` `$.macro.math.plus` `$.macro.math.plus(...)` ## Скалярные значения @@ -116,7 +113,21 @@ #### Двойные кавычки Если строка заключена в двойные кавычки `"`, Fenom распознает большее количество управляющих последовательностей для специальных символов: -`\n`, `\r`, `\t`, `\v`, `\e`, `\f`, `\\`, `\$`, `\"`, `\[0-7]{1,3}`, `\x[0-9A-Fa-f]{1,2}`. + +| Последовательность | Значение | +|---------------------|----------| +| `\n` | новая строка (LF или 0x0A (10) в ASCII) +| `\r` | возврат каретки (CR или 0x0D (13) в ASCII) +| `\t` | горизонтальная табуляция (HT или 0x09 (9) в ASCII) +| `\v` | вертикальная табуляция (VT или 0x0B (11) в ASCII) + +| `\f` | подача страницы (FF или 0x0C (12) в ASCII) +| `\\` | обратная косая черта +| `\$` | знак доллара +| `\"` | двойная кавычка +| `\[0-7]{1,3}` | последовательность символов, соответствующая регулярному выражению символа в восьмеричной системе счисления +| `\x[0-9A-Fa-f]{1,2}`| последовательность символов, соответствующая регулярному выражению символа в шестнадцатеричной системе счисления + Но самым важным свойством строк в двойных кавычках является обработка переменных. Существует два типа синтаксиса: простой и сложный. Простой синтаксис более легок и удобен. Он дает возможность обработки переменной, значения массива или свойства объекта с минимумом усилий. diff --git a/src/Fenom.php b/src/Fenom.php index bb79e53..265b496 100644 --- a/src/Fenom.php +++ b/src/Fenom.php @@ -620,12 +620,8 @@ class Fenom * @param callable|string $parser_close * @return Fenom */ - public function addBlockFunction( - $function, - $callback, - $parser_open = self::DEFAULT_FUNC_OPEN, - $parser_close = self::DEFAULT_FUNC_CLOSE - ) { + public function addBlockFunction($function, $callback, $parser_open = self::DEFAULT_FUNC_OPEN, $parser_close = self::DEFAULT_FUNC_CLOSE) + { $this->_actions[$function] = array( 'type' => self::BLOCK_FUNCTION, 'open' => $parser_open, @@ -1034,7 +1030,7 @@ class Fenom } /** - * Flush internal memory template cache + * Flush internal template in-memory-cache */ public function flush() { @@ -1047,6 +1043,7 @@ class Fenom public function clearAllCompiles() { \Fenom\Provider::clean($this->_compile_dir); + $this->flush(); } /** diff --git a/src/Fenom/Accessor.php b/src/Fenom/Accessor.php index 0b4c8a6..2330537 100644 --- a/src/Fenom/Accessor.php +++ b/src/Fenom/Accessor.php @@ -33,7 +33,8 @@ class Accessor { * @param Tokenizer $tokens * @param Template $tpl */ - public static function getVar(Tokenizer $tokens, Template $tpl) { + public static function getVar(Tokenizer $tokens, Template $tpl) + { $name = $tokens->prev[Tokenizer::TEXT]; if(isset(self::$vars[$name])) { $var = $tpl->parseVariable($tokens, self::$vars[$name]); @@ -47,7 +48,8 @@ class Accessor { * Accessor for template information * @param Tokenizer $tokens */ - public static function tpl(Tokenizer $tokens) { + public static function tpl(Tokenizer $tokens) + { $method = $tokens->skip('.')->need(T_STRING)->getAndNext(); if(method_exists('Fenom\Render', 'get'.$method)) { return '$tpl->get'.ucfirst($method).'()'; @@ -56,7 +58,8 @@ class Accessor { } } - public static function version() { + public static function version() + { return 'Fenom::VERSION'; } @@ -64,7 +67,8 @@ class Accessor { * @param Tokenizer $tokens * @return string */ - public static function constant(Tokenizer $tokens) { + public static function constant(Tokenizer $tokens) + { $const = array($tokens->skip('.')->need(Tokenizer::MACRO_STRING)->getAndNext()); while($tokens->is('.')) { $const[] = $tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext(); @@ -82,7 +86,8 @@ class Accessor { * @param Template $tpl * @return string */ - public static function php(Tokenizer $tokens, Template $tpl) { + public static function php(Tokenizer $tokens, Template $tpl) + { $callable = array($tokens->skip('.')->need(Tokenizer::MACRO_STRING)->getAndNext()); while($tokens->is('.')) { $callable[] = $tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext(); @@ -91,8 +96,16 @@ class Accessor { if($tokens->is(T_DOUBLE_COLON)) { $callable .= '::'.$tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext(); } + $call_filter = $tpl->getStorage()->call_filters; + if($call_filter) { + foreach($call_filter as $filter) { + if(!fnmatch(addslashes($filter), $callable)) { + throw new \LogicException("Callback ".str_replace('\\', '.', $callable)." is not available by settings"); + } + } + } if(!is_callable($callable)) { - throw new \LogicException("PHP method ".str_replace('\\', '.', $callable).' does not exists.'); + throw new \RuntimeException("PHP method ".str_replace('\\', '.', $callable).' does not exists.'); } if($tokens->is('(')) { $arguments = 'array'.$tpl->parseArgs($tokens).''; @@ -103,11 +116,28 @@ class Accessor { } - public static function tag(Tokenizer $tokens, Template $tpl) { - $tag = $tokens->get(Tokenizer::MACRO_STRING); - $info = $tpl->getStorage()->getTag($tag, $tpl); - if($info['type'] !== \Fenom::INLINE_FUNCTION) { - throw new \LogicException("Only inline functions allowed in accessor"); + /** + * Accessor {$.fetch(...)} + * @param Tokenizer $tokens + * @param Template $tpl + * @return string + */ + public static function fetch(Tokenizer $tokens, Template $tpl) + { + $tokens->skip('('); + $name = $tpl->parsePlainArg($tokens, $static); + if($static) { + if(!$tpl->getStorage()->templateExists($static)) { + throw new \RuntimeException("Template $static not found"); + } } + if($tokens->is(',')) { + $tokens->skip()->need('['); + $vars = $tpl->parseArray($tokens) . ' + $var'; + } else { + $vars = '$var'; + } + $tokens->skip(')'); + return '$tpl->getStorage()->fetch('.$name.', '.$vars.')'; } } \ No newline at end of file diff --git a/src/Fenom/Compiler.php b/src/Fenom/Compiler.php index 3d18c4c..32a07c3 100644 --- a/src/Fenom/Compiler.php +++ b/src/Fenom/Compiler.php @@ -241,7 +241,7 @@ class Compiler */ public static function forOpen(Tokenizer $tokens, Tag $scope) { - $p = array( + $p = array( "index" => false, "first" => false, "last" => false, diff --git a/tests/TestCase.php b/tests/TestCase.php index 872e87c..d20effd 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -161,13 +161,13 @@ class TestCase extends \PHPUnit_Framework_TestCase $this->fail("Code $code must be invalid"); } - public function assertRender($tpl, $result, $debug = false) + public function assertRender($tpl, $result, array $vars = array(), $debug = false) { $template = $this->fenom->compileCode($tpl); if ($debug) { print_r("\nDEBUG $tpl:\n" . $template->getBody()); } - $this->assertSame($result, $template->fetch($this->values)); + $this->assertSame($result, $template->fetch($vars + $this->values)); return $template; } diff --git a/tests/cases/Fenom/AccessorTest.php b/tests/cases/Fenom/AccessorTest.php index 9587ff4..53949d3 100644 --- a/tests/cases/Fenom/AccessorTest.php +++ b/tests/cases/Fenom/AccessorTest.php @@ -97,8 +97,23 @@ class AccessorTest extends TestCase public static function providerPHP() { return array( array('$.php.strrev("string")', strrev("string")), + array('$.php.strrev("string")', strrev("string"), 'str*'), + array('$.php.strrev("string")', strrev("string"), 'strrev'), + array('$.php.get_current_user', get_current_user()), array('$.php.Fenom.helper_func("string", 12)', helper_func("string", 12)), + array('$.php.Fenom.helper_func("string", 12)', helper_func("string", 12), 'Fenom\\*'), + array('$.php.Fenom.helper_func("string", 12)', helper_func("string", 12), 'Fenom\helper_func'), + array('$.php.Fenom.helper_func("string", 12)', helper_func("string", 12), '*helper_func'), + array('$.php.Fenom.helper_func("string", 12)', helper_func("string", 12), '*'), array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string")), + array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\*'), + array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\TestCase*'), + array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\TestCase::*'), + array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\*::dots'), + array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\*::*'), + array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\TestCase::dots'), + array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), '*::dots'), + array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), '*'), ); } @@ -106,10 +121,45 @@ class AccessorTest extends TestCase * @dataProvider providerPHP * @group php */ - public function testPHP($tpl, $result) { + public function testPHP($tpl, $result, $mask = null) { + if($mask) { + $this->fenom->addCallFilter($mask); + } $this->assertRender('{'.$tpl.'}', $result); } + public static function providerPHPInvalid() { + return array( + array('$.php.aaa("string")', 'Fenom\Error\CompileException', 'PHP method aaa does not exists'), + array('$.php.strrev("string")', 'Fenom\Error\SecurityException', 'Callback strrev is not available by settings', 'strrevZ'), + array('$.php.strrev("string")', 'Fenom\Error\SecurityException', 'Callback strrev is not available by settings', 'str*Z'), + array('$.php.strrev("string")', 'Fenom\Error\SecurityException', 'Callback strrev is not available by settings', '*Z'), + array('$.php.Fenom.aaa("string")', 'Fenom\Error\CompileException', 'PHP method Fenom.aaa does not exists'), + array('$.php.Fenom.helper_func("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.helper_func is not available by settings', 'Reflection\*'), + array('$.php.Fenom.helper_func("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.helper_func is not available by settings', 'Fenom\*Z'), + array('$.php.Fenom.helper_func("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.helper_func is not available by settings', 'Fenom\*::*'), + array('$.php.TestCase::aaa("string")', 'Fenom\Error\CompileException', 'PHP method TestCase::aaa does not exists'), + array('$.php.Fenom.TestCase::aaa("string")', 'Fenom\Error\CompileException', 'PHP method Fenom.TestCase::aaa does not exists'), + array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Reflection\*'), + array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Fenom\*Z'), + array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Fenom\*::get*'), + array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Fenom\TestCase::get*'), + array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Fenom\TestCase::*Z'), + array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', '*::*Z'), + ); + } + + /** + * @dataProvider providerPHPInvalid + * @group php + */ + public function testPHPInvalid($tpl, $exception, $message, $methods = null) { + if($methods) { + $this->fenom->addCallFilter($methods); + } + $this->execError('{'.$tpl.'}', $exception, $message); + } + public static function providerAccessor() { @@ -133,7 +183,6 @@ class AccessorTest extends TestCase ); } - public static function providerAccessorInvalid() { return array( @@ -141,4 +190,41 @@ class AccessorTest extends TestCase array('{$.get.one}', 'Fenom\Error\SecurityException', 'Accessor are disabled', \Fenom::DENY_ACCESSOR), ); } + + public static function providerFetch() + { + return array( + array('{$.fetch("welcome.tpl")}'), + array('{set $tpl = "welcome.tpl"}{$.fetch($tpl)}'), + array('{$.fetch("welcome.tpl", ["username" => "Bzick", "email" => "bzick@dev.null"])}'), + array('{set $tpl = "welcome.tpl"}{$.fetch($tpl, ["username" => "Bzick", "email" => "bzick@dev.null"])}'), + ); + } + + /** + * @group fetch + * @dataProvider providerFetch + */ + public function testFetch($code) + { + $this->tpl('welcome.tpl', 'Welcome, {$username} ({$email})'); + $values = array('username' => 'Bzick', 'email' => 'bzick@dev.null'); + $this->assertRender($code, $this->fenom->fetch('welcome.tpl', $values), $values); + } + + public static function providerFetchInvalid() + { + return array( + array('{$.fetch("welcome_.tpl")}', 'Fenom\Error\CompileException', "Template welcome_.tpl not found"), + array('{$.fetch("welcome_.tpl", [])}', 'Fenom\Error\CompileException', "Template welcome_.tpl not found"), + ); + } + + /** + * @group fetchInvalid + * @dataProvider providerFetchInvalid + */ + public function testFetchInvalidTpl($tpl, $exception, $message) { + $this->execError($tpl, $exception, $message); + } } \ No newline at end of file diff --git a/tests/cases/Fenom/SandboxTest.php b/tests/cases/Fenom/SandboxTest.php new file mode 100644 index 0000000..30492c7 --- /dev/null +++ b/tests/cases/Fenom/SandboxTest.php @@ -0,0 +1,29 @@ +assertEquals([1, 2, 4, "as" => 767, "df" => ["qert"]], [1, 2, 4, "as" => 767, "df" => ["qet"]]); +// $this->fenom->addBlockCompiler('php', 'Fenom\Compiler::nope', function ($tokens, Tag $tag) { +// return 'cutContent(); +// }); +// $this->tpl('welcome.tpl', '{$a}'); +// try { +// var_dump($this->fenom->compileCode('{$.fetch("welcome.tpl", ["a" => 1])}')->getBody()); +// } catch (\Exception $e) { +// print_r($e->getMessage() . "\n" . $e->getTraceAsString()); +// while ($e->getPrevious()) { +// $e = $e->getPrevious(); +// print_r("\n\n" . $e->getMessage() . " in {$e->getFile()}:{$e->getLine()}\n" . $e->getTraceAsString()); +// } +// } +// exit; + } + +} \ No newline at end of file