diff --git a/src/Fenom.php b/src/Fenom.php index 328542f..cbc5e17 100644 --- a/src/Fenom.php +++ b/src/Fenom.php @@ -21,8 +21,8 @@ use Fenom\Template; */ class Fenom { - const VERSION = '2.12'; - const REV = 1; + const VERSION = '3.0'; + const REV = 2; /* Actions */ const INLINE_COMPILER = 1; const BLOCK_COMPILER = 5; @@ -64,18 +64,18 @@ class Fenom const ACCESSOR_METHOD = 'Fenom\Accessor::parserMethod'; const ACCESSOR_CHAIN = 'Fenom\Accessor::parserChain'; - public static $charset = "UTF-8"; + public static string $charset = "UTF-8"; /** * @var int maximum length of compiled filename (use sha1 of name if bigger) */ - public static $filename_length = 200; + public static int $filename_length = 200; /** * @var int[] of possible options, as associative array * @see setOptions */ - private static $_options_list = [ + private static array $_options_list = [ "disable_accessor" => self::DENY_ACCESSOR, "disable_methods" => self::DENY_METHODS, "disable_native_funcs" => self::DENY_NATIVE_FUNCS, @@ -94,27 +94,27 @@ class Fenom /** * @var callable[] */ - public array $pre_filters = []; + protected array $pre_filters = []; /** * @var callable[] */ - public array $filters = []; + protected array $filters = []; /** * @var callable[] */ - public array $tag_filters = []; + protected array $tag_filters = []; /** * @var string[] */ - public array $call_filters = []; + protected array $call_filters = []; /** * @var callable[] */ - public array $post_filters = []; + protected array $post_filters = []; /** * @var Fenom\Render[] Templates storage @@ -129,7 +129,7 @@ class Fenom /** * @var string compile prefix ID template */ - protected string $_compile_id; + protected string $_compile_id = ""; /** * @var string[] compile directory for custom provider @@ -749,9 +749,9 @@ class Fenom * * @param string $modifier * @param Template|null $template - * @return mixed + * @return callable|null */ - public function getModifier(string $modifier, Fenom\Template $template = null): string + public function getModifier(string $modifier, Fenom\Template $template = null): ?callable { if (isset($this->_modifiers[$modifier])) { return $this->_modifiers[$modifier]; @@ -937,9 +937,9 @@ class Fenom * Get an accessor * @param string $name * @param string|null $key - * @return callable|null + * @return callable|array|null */ - public function getAccessor(string $name,string $key = null): ?callable + public function getAccessor(string $name,string $key = null): mixed { if(isset($this->_accessors[$name])) { if($key) { @@ -998,10 +998,10 @@ class Fenom * @param string|array $template name of template. * If it is array of names of templates they will be extended from left to right. * @param array $vars array of data for template - * @return Fenom\Render + * @return array * @throws CompileException */ - public function display(string|array $template, array $vars = array()): Fenom\Render + public function display(string|array $template, array $vars = array()): array { return $this->getTemplate($template)->display($vars); } @@ -1093,12 +1093,12 @@ class Fenom /** * Load template from cache or create cache if it doesn't exists. * - * @param string $template + * @param string[]|string $template * @param int $opts * @return Fenom\Render * @throws CompileException */ - protected function _load(string $template, int $opts): Render + protected function _load(array|string $template, int $opts): Render { $file_name = $this->getCompileName($template, $opts); if (is_file($this->_compile_dir . "/" . $file_name)) { @@ -1229,4 +1229,12 @@ class Fenom } return $mask; } + + /** + * @return array + */ + public function getCallFilters(): array + { + return $this->call_filters; + } } diff --git a/src/Fenom/Accessor.php b/src/Fenom/Accessor.php index 23e05d5..0dbf535 100644 --- a/src/Fenom/Accessor.php +++ b/src/Fenom/Accessor.php @@ -134,7 +134,7 @@ class Accessor { if($tokens->is(T_DOUBLE_COLON)) { $const .= '::'.$tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext(); } - return '@constant('.var_export($const, true).')'; + return '(defined('.var_export($const, true).') ? constant('.var_export($const, true).') : "")'; } @@ -153,7 +153,7 @@ class Accessor { if($tokens->is(T_DOUBLE_COLON)) { $callable .= '::'.$tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext(); } - $call_filter = $tpl->getStorage()->call_filters; + $call_filter = $tpl->getStorage()->getCallFilters(); if($call_filter) { foreach($call_filter as $filter) { if(!fnmatch(addslashes($filter), $callable)) { diff --git a/src/Fenom/Compiler.php b/src/Fenom/Compiler.php index 082db5a..35f228a 100644 --- a/src/Fenom/Compiler.php +++ b/src/Fenom/Compiler.php @@ -24,7 +24,6 @@ class Compiler /** * Tag {include ...} * - * @static * @param Tokenizer $tokens * @param Tag $tag * @throws \LogicException @@ -70,6 +69,7 @@ class Compiler /** * Tag {insert ...} + * * @param Tokenizer $tokens * @param Tag $tag * @return string @@ -91,11 +91,9 @@ class Compiler /** * Open tag {if ...} * - * @static * @param Tokenizer $tokens * @param Tag $scope * @return string - * @throws \Exception */ public static function ifOpen(Tokenizer $tokens, Tag $scope): string { @@ -106,13 +104,12 @@ class Compiler /** * Tag {elseif ...} * - * @static * @param Tokenizer $tokens * @param Tag $scope * @throws InvalidUsageException * @return string */ - public static function tagElseIf(Tokenizer $tokens, Tag $scope) + public static function tagElseIf(Tokenizer $tokens, Tag $scope): string { if ($scope["else"]) { throw new InvalidUsageException('Incorrect use of the tag {elseif}'); @@ -127,7 +124,7 @@ class Compiler * @param Tag $scope * @return string */ - public static function tagElse($tokens, Tag $scope) + public static function tagElse(Tokenizer $tokens, Tag $scope): string { $scope["else"] = true; return '} else {'; @@ -136,14 +133,14 @@ class Compiler /** * Open tag {foreach ...} * - * @static * @param Tokenizer $tokens * @param Tag $scope - * @throws UnexpectedTokenException - * @throws InvalidUsageException * @return string + * @throws InvalidUsageException*@throws CompileException + * @throws UnexpectedTokenException + * @throws CompileException */ - public static function foreachOpen(Tokenizer $tokens, Tag $scope) + public static function foreachOpen(Tokenizer $tokens, Tag $scope): string { $scope["else"] = false; $scope["key"] = null; @@ -201,7 +198,7 @@ class Compiler * @param Tag $scope * @return string */ - public static function foreachElse($tokens, Tag $scope) + public static function foreachElse(Tokenizer $tokens, Tag $scope): string { $scope["no-break"] = $scope["no-continue"] = $scope["else"] = true; $after = $scope["after"] ? implode("; ", $scope["after"]) . ";" : ""; @@ -214,7 +211,8 @@ class Compiler * @return string * @throws CompileException */ - public static function foreachProp(Tag $scope, $prop) { + public static function foreachProp(Tag $scope, string $prop): string + { if(empty($scope["props"][$prop])) { $var_name = $scope["props"][$prop] = $scope->tpl->tmpVar()."_".$prop; switch($prop) { @@ -248,7 +246,7 @@ class Compiler * @param Tag $scope * @return string */ - public static function foreachClose($tokens, Tag $scope) + public static function foreachClose(Tokenizer $tokens, Tag $scope): string { $before = $scope["before"] ? implode("; ", $scope["before"]) . ";" : ""; $head = $scope["body"] ? implode("; ", $scope["body"]) . ";" : ""; @@ -268,119 +266,11 @@ class Compiler } /** - * @static - * @param Tokenizer $tokens - * @param Tag $scope - * @throws Error\UnexpectedTokenException - * @throws Error\InvalidUsageException - * @return string - * @codeCoverageIgnore - */ - public static function forOpen(Tokenizer $tokens, Tag $scope) - { - trigger_error("Fenom: tag {for} deprecated, use {foreach 1..4 as \$value} (in {$scope->tpl->getName()}:{$scope->line})", E_USER_DEPRECATED); - $p = array( - "index" => false, - "first" => false, - "last" => false, - "step" => 1, - "to" => false, -// "max" => false, -// "min" => false - ); - $scope["after"] = $before = $body = array(); - $i = array('', ''); - $c = ""; - $var = $scope->tpl->parseTerm($tokens, $is_var); - if (!$is_var) { - throw new UnexpectedTokenException($tokens); - } - $tokens->get("="); - $tokens->next(); - $val = $scope->tpl->parseExpr($tokens); - $p = $scope->tpl->parseParams($tokens, $p); - - if (is_numeric($p["step"])) { - if ($p["step"] > 0) { - $condition = "$var <= {$p['to']}"; - if ($p["last"]) { - $c = "($var + {$p['step']}) > {$p['to']}"; - } - } elseif ($p["step"] < 0) { - $condition = "$var >= {$p['to']}"; - if ($p["last"]) { - $c = "($var + {$p['step']}) < {$p['to']}"; - } - } else { - throw new InvalidUsageException("Invalid step value"); - } - } else { - $condition = "({$p['step']} > 0 && $var <= {$p['to']} || {$p['step']} < 0 && $var >= {$p['to']})"; - if ($p["last"]) { - $c = "({$p['step']} > 0 && ($var + {$p['step']}) <= {$p['to']} || {$p['step']} < 0 && ($var + {$p['step']}) >= {$p['to']})"; - } - } - - if ($p["first"]) { - $before[] = $p["first"] . ' = true'; - $scope["after"][] = $p["first"] . ' && (' . $p["first"] . ' = false )'; - } - if ($p["last"]) { - $before[] = $p["last"] . ' = false'; - $body[] = "if($c) {$p['last']} = true"; - } - - if ($p["index"]) { - $i[0] .= $p["index"] . ' = 0,'; - $i[1] .= $p["index"] . '++,'; - } - - $scope["else"] = false; - $scope["else_cond"] = "$var==$val"; - $before = $before ? implode("; ", $before) . ";" : ""; - $body = $body ? implode("; ", $body) . ";" : ""; - $scope["after"] = $scope["after"] ? implode("; ", $scope["after"]) . ";" : ""; - - return "$before for({$i[0]} $var=$val; $condition;{$i[1]} $var+={$p['step']}) { $body"; - } - - /** - * @static - * @param Tokenizer $tokens - * @param Tag $scope - * @return string - * @codeCoverageIgnore - */ - public static function forElse($tokens, Tag $scope) - { - $scope["no-break"] = $scope["no-continue"] = true; - $scope["else"] = true; - return " } if({$scope['else_cond']}) {"; - } - - /** - * @static - * @param Tokenizer $tokens - * @param Tag $scope - * @return string - * @codeCoverageIgnore - */ - public static function forClose($tokens, Tag $scope) - { - if ($scope["else"]) { - return '}'; - } else { - return " {$scope['after']} }"; - } - } - - /** - * @static * @param Tokenizer $tokens * @param Tag $scope * @return string */ - public static function whileOpen(Tokenizer $tokens, Tag $scope) + public static function whileOpen(Tokenizer $tokens, Tag $scope): string { return 'while(' . $scope->tpl->parseExpr($tokens) . ') {'; } @@ -388,12 +278,11 @@ class Compiler /** * Open tag {switch} * - * @static * @param Tokenizer $tokens * @param Tag $scope * @return string */ - public static function switchOpen(Tokenizer $tokens, Tag $scope) + public static function switchOpen(Tokenizer $tokens, Tag $scope): string { $expr = $scope->tpl->parseExpr($tokens); $scope["case"] = array(); @@ -409,7 +298,7 @@ class Compiler * Resort cases for {switch} * @param Tag $scope */ - private static function _caseResort(Tag $scope) + private static function _caseResort(Tag $scope): void { $content = $scope->cutContent(); foreach ($scope["last"] as $case) { @@ -428,12 +317,11 @@ class Compiler /** * Tag {case ...} * - * @static * @param Tokenizer $tokens * @param Tag $tag * @return string */ - public static function tagCase(Tokenizer $tokens, Tag $tag) + public static function tagCase(Tokenizer $tokens, Tag $tag): string { self::_caseResort($tag); do { @@ -456,12 +344,11 @@ class Compiler /** * Tag {default} * - * @static * @param Tokenizer $tokens * @param Tag $scope * @return string */ - public static function tagDefault($tokens, Tag $scope) + public static function tagDefault(Tokenizer $tokens, Tag $scope): string { self::_caseResort($scope); $scope["last"][] = false; @@ -471,12 +358,11 @@ class Compiler /** * Close tag {switch} * - * @static * @param Tokenizer $tokens * @param Tag $scope * @return string */ - public static function switchClose($tokens, Tag $scope) + public static function switchClose(Tokenizer $tokens, Tag $scope): string { self::_caseResort($scope); $expr = $scope["var"]; @@ -495,13 +381,12 @@ class Compiler /** * Tag {continue} * - * @static * @param Tokenizer $tokens * @param Tag $scope - * @throws InvalidUsageException * @return string + * @throws InvalidUsageException */ - public static function tagContinue($tokens, Tag $scope) + public static function tagContinue(Tokenizer $tokens, Tag $scope): string { if (empty($scope["no-continue"])) { return 'continue;'; @@ -513,13 +398,12 @@ class Compiler /** * Tag {break} * - * @static * @param Tokenizer $tokens * @param Tag $scope - * @throws InvalidUsageException * @return string + * @throws InvalidUsageException */ - public static function tagBreak($tokens, Tag $scope) + public static function tagBreak(Tokenizer $tokens, Tag $scope): string { if (empty($scope["no-break"])) { return 'break;'; @@ -530,12 +414,13 @@ class Compiler /** * Dispatch {extends} tag + * * @param Tokenizer $tokens * @param Tag $tag * @throws Error\InvalidUsageException * @return string */ - public static function tagExtends(Tokenizer $tokens, Tag $tag) + public static function tagExtends(Tokenizer $tokens, Tag $tag): void { $tpl = $tag->tpl; if ($tpl->extends) { @@ -559,8 +444,9 @@ class Compiler * Post compile action for {extends ...} tag * @param Template $tpl * @param string $body + * @throws CompileException */ - public static function extendBody($tpl, &$body) + public static function extendBody(Template $tpl, string &$body): void { if ($tpl->dynamic_extends) { if (!$tpl->ext_stack) { @@ -586,10 +472,9 @@ class Compiler * Tag {use ...} * @param Tokenizer $tokens * @param Tag $tag - * @throws Error\InvalidUsageException - * @return string + * @throws Error\InvalidUsageException|CompileException */ - public static function tagUse(Tokenizer $tokens, Tag $tag) + public static function tagUse(Tokenizer $tokens, Tag $tag): void { $tpl = $tag->tpl; if ($tpl->getStackSize()) { @@ -608,9 +493,8 @@ class Compiler * @param Tokenizer $tokens * @param Tag $scope * @throws \RuntimeException - * @return string */ - public static function tagBlockOpen(Tokenizer $tokens, Tag $scope) + public static function tagBlockOpen(Tokenizer $tokens, Tag $scope): void { $scope["cname"] = $scope->tpl->parsePlainArg($tokens, $name); if (!$name) { @@ -624,7 +508,7 @@ class Compiler * @param Tokenizer $tokens * @param Tag $scope */ - public static function tagBlockClose($tokens, Tag $scope) + public static function tagBlockClose(Tokenizer $tokens, Tag $scope): void { $tpl = $scope->tpl; $name = $scope["name"]; @@ -658,7 +542,7 @@ class Compiler * @param Tag $scope * @return string */ - public static function tagParent($tokens, Tag $scope) + public static function tagParent(Tokenizer $tokens, Tag $scope): string { $block_scope = $scope->tpl->getParentScope('block'); if (!$block_scope['use_parent']) { @@ -672,7 +556,7 @@ class Compiler * * @return string */ - public static function stdClose() + public static function stdClose(): string { return '}'; } @@ -684,7 +568,7 @@ class Compiler * @param Tag $tag * @return string */ - public static function stdFuncParser(Tokenizer $tokens, Tag $tag) + public static function stdFuncParser(Tokenizer $tokens, Tag $tag): string { if(is_string($tag->callback)) { return $tag->out($tag->callback . "(" . self::toArray($tag->tpl->parseParams($tokens)) . ', $tpl, $var)'); @@ -700,8 +584,9 @@ class Compiler * @param Tokenizer $tokens * @param Tag $tag * @return string + * @throws \ReflectionException */ - public static function smartFuncParser(Tokenizer $tokens, Tag $tag) + public static function smartFuncParser(Tokenizer $tokens, Tag $tag): string { if (strpos($tag->callback, "::") || is_array($tag->callback)) { list($class, $method) = explode("::", $tag->callback, 2); @@ -730,7 +615,7 @@ class Compiler * @param Tag $tag * @return string */ - public static function stdFuncOpen(Tokenizer $tokens, Tag $tag) + public static function stdFuncOpen(Tokenizer $tokens, Tag $tag): string { $tag["params"] = self::toArray($tag->tpl->parseParams($tokens)); $tag->setOption(\Fenom::AUTO_ESCAPE, false); @@ -744,7 +629,7 @@ class Compiler * @param Tag $tag * @return string */ - public static function stdFuncClose($tokens, Tag $tag) + public static function stdFuncClose(Tokenizer $tokens, Tag $tag): string { $tag->restore(\Fenom::AUTO_ESCAPE); if(is_string($tag->callback)) { @@ -757,10 +642,10 @@ class Compiler /** * Convert array of code to string array - * @param $params + * @param array $params * @return string */ - public static function toArray($params) + public static function toArray(array $params): string { $_code = array(); foreach ($params as $k => $v) { @@ -774,8 +659,9 @@ class Compiler * @param Tokenizer $tokens * @param Tag $scope * @return string + * @throws CompileException */ - public static function setOpen(Tokenizer $tokens, Tag $scope) + public static function setOpen(Tokenizer $tokens, Tag $scope): string { if($tokens->is(T_VARIABLE)) { $var = $scope->tpl->parseVariable($tokens); @@ -820,12 +706,12 @@ class Compiler * @param Tag $scope * @return string */ - public static function setClose($tokens, Tag $scope) + public static function setClose(Tokenizer $tokens, Tag $scope): string { return $scope["name"] . '=' . $scope["value"] . ';'; } - public static function tagDo($tokens, Tag $scope) + public static function tagDo(Tokenizer $tokens, Tag $scope): string { return $scope->tpl->parseExpr($tokens).';'; } @@ -835,19 +721,20 @@ class Compiler * @param Tokenizer $tokens * @param Tag $scope * @return string + * @throws CompileException */ - public static function filterOpen($tokens, Tag $scope) + public static function filterOpen(Tokenizer $tokens, Tag $scope): string { $scope["filter"] = $scope->tpl->parseModifier($tokens, "ob_get_clean()"); return "ob_start();"; } /** - * @param $tokens + * @param Tokenizer $tokens * @param Tag $scope * @return string */ - public static function filterClose($tokens, Tag $scope) + public static function filterClose(Tokenizer $tokens, Tag $scope): string { return "echo " . $scope["filter"] . ";"; } @@ -860,7 +747,7 @@ class Compiler * @throws Error\InvalidUsageException * @return string */ - public static function tagCycle(Tokenizer $tokens, Tag $tag) + public static function tagCycle(Tokenizer $tokens, Tag $tag): string { $tpl = $tag->tpl; if ($tokens->is("[")) { @@ -883,11 +770,11 @@ class Compiler /** * Runtime cycle callback - * @param mixed $vals - * @param $index + * @param array $vals + * @param int $index * @return mixed */ - public static function cycle($vals, $index) + public static function cycle(array $vals, int $index): mixed { return $vals[$index % count($vals)]; } @@ -897,11 +784,11 @@ class Compiler * * @param Tokenizer $tokens * @param Tag $tag - * @throws Error\UnexpectedTokenException - * @throws Error\InvalidUsageException * @return string + * @throws Error\InvalidUsageException|CompileException + * @throws Error\UnexpectedTokenException */ - public static function tagImport(Tokenizer $tokens, Tag $tag) + public static function tagImport(Tokenizer $tokens, Tag $tag): string { $tpl = $tag->tpl; $import = array(); @@ -966,7 +853,7 @@ class Compiler * @param Tag $scope * @throws InvalidUsageException */ - public static function macroOpen(Tokenizer $tokens, Tag $scope) + public static function macroOpen(Tokenizer $tokens, Tag $scope): void { $scope["name"] = $tokens->get(Tokenizer::MACRO_STRING); $scope["recursive"] = false; @@ -1011,7 +898,7 @@ class Compiler * @param Tokenizer $tokens * @param Tag $scope */ - public static function macroClose($tokens, Tag $scope) + public static function macroClose(Tokenizer $tokens, Tag $scope): void { if ($scope["recursive"]) { $scope["macro"]["recursive"] = true; @@ -1027,7 +914,7 @@ class Compiler * @param Tag $tag * @return string */ - public static function tagRaw(Tokenizer $tokens, Tag $tag) + public static function tagRaw(Tokenizer $tokens, Tag $tag): string { return 'echo ' . $tag->tpl->parseExpr($tokens); } @@ -1036,9 +923,9 @@ class Compiler * @param Tokenizer $tokens * @param Tag $tag */ - public static function escapeOpen(Tokenizer $tokens, Tag $tag) + public static function escapeOpen(Tokenizer $tokens, Tag $tag): void { - $expected = ($tokens->get(T_STRING) == "true" ? true : false); + $expected = $tokens->get(T_STRING) == "true"; $tokens->next(); $tag->setOption(\Fenom::AUTO_ESCAPE, $expected); } @@ -1046,7 +933,7 @@ class Compiler /** * Do nothing */ - public static function nope() + public static function nope(): void { } @@ -1054,30 +941,33 @@ class Compiler * @param Tokenizer $tokens * @param Tag $tag */ - public static function stripOpen(Tokenizer $tokens, Tag $tag) + public static function stripOpen(Tokenizer $tokens, Tag $tag): void { - $expected = ($tokens->get(T_STRING) == "true" ? true : false); + $expected = $tokens->get(T_STRING) == "true"; $tokens->next(); $tag->setOption(\Fenom::AUTO_STRIP, $expected); } /** * Tag {ignore} + * * @param Tokenizer $tokens * @param Tag $tag */ - public static function ignoreOpen($tokens, Tag $tag) + public static function ignoreOpen(Tokenizer $tokens, Tag $tag): void { $tag->tpl->ignore('ignore'); } /** * Tag {unset ...} + * * @param Tokenizer $tokens * @param Tag $tag * @return string + * @throws CompileException */ - public static function tagUnset(Tokenizer $tokens, Tag $tag) + public static function tagUnset(Tokenizer $tokens, Tag $tag): string { $unset = array(); while($tokens->valid()) { @@ -1086,7 +976,7 @@ class Compiler return 'unset('.implode(", ", $unset).')'; } - public static function tagPaste(Tokenizer $tokens, Tag $tag) + public static function tagPaste(Tokenizer $tokens, Tag $tag): string { $name = str_replace(array('\'', '"'), '', $tokens->get(T_CONSTANT_ENCAPSED_STRING)); $tokens->next(); diff --git a/src/Fenom/Provider.php b/src/Fenom/Provider.php index 905b166..1b7b39c 100644 --- a/src/Fenom/Provider.php +++ b/src/Fenom/Provider.php @@ -15,16 +15,16 @@ namespace Fenom; */ class Provider implements ProviderInterface { - private $_path; + private string $_path; - protected $_clear_cache = false; + protected bool $_clear_cache = false; /** * Clean directory from files * * @param string $path */ - public static function clean($path) + public static function clean(string $path) { if (is_file($path)) { unlink($path); @@ -39,7 +39,7 @@ class Provider implements ProviderInterface foreach ($iterator as $file) { /* @var \splFileInfo $file */ if ($file->isFile()) { - if (strpos($file->getBasename(), ".") !== 0) { + if (!str_starts_with($file->getBasename(), ".")) { unlink($file->getRealPath()); } } elseif ($file->isDir()) { @@ -54,7 +54,7 @@ class Provider implements ProviderInterface * * @param string $path */ - public static function rm($path) + public static function rm(string $path) { self::clean($path); if (is_dir($path)) { @@ -64,9 +64,9 @@ class Provider implements ProviderInterface /** * @param string $template_dir directory of templates - * @throws \LogicException if directory doesn't exists + * @throws \LogicException if directory doesn't exist */ - public function __construct($template_dir) + public function __construct(string $template_dir) { if ($_dir = realpath($template_dir)) { $this->_path = $_dir; @@ -80,17 +80,17 @@ class Provider implements ProviderInterface * @see http://php.net/manual/en/function.clearstatcache.php * @param bool $status */ - public function setClearCachedStats($status = true) { + public function setClearCachedStats(bool $status = true) { $this->_clear_cache = $status; } /** * Get source and mtime of template by name * @param string $tpl - * @param int $time load last modified time + * @param float|null $time load last modified time * @return string */ - public function getSource($tpl, &$time) + public function getSource(string $tpl, ?float &$time): string { $tpl = $this->_getTemplatePath($tpl); if($this->_clear_cache) { @@ -103,24 +103,24 @@ class Provider implements ProviderInterface /** * Get last modified of template by name * @param string $tpl - * @return int + * @return float */ - public function getLastModified($tpl) + public function getLastModified(string $tpl): float { $tpl = $this->_getTemplatePath($tpl); if($this->_clear_cache) { clearstatcache(true, $tpl); } - return filemtime($tpl); + return (float)filemtime($tpl); } /** * Get all names of templates from provider. * * @param string $extension all templates must have this extension, default .tpl - * @return array|\Iterator + * @return iterable */ - public function getList($extension = "tpl") + public function getList(string $extension = "tpl"): iterable { $list = array(); $iterator = new \RecursiveIteratorIterator( @@ -140,14 +140,14 @@ class Provider implements ProviderInterface /** * Get template path - * @param $tpl + * @param string $tpl * @return string * @throws \RuntimeException */ - protected function _getTemplatePath($tpl) + protected function _getTemplatePath(string $tpl): string { $path = realpath($this->_path . "/" . $tpl); - if ($path && strpos($path, $this->_path) === 0) { + if ($path && str_starts_with($path, $this->_path)) { return $path; } else { throw new \RuntimeException("Template $tpl not found"); @@ -158,9 +158,9 @@ class Provider implements ProviderInterface * @param string $tpl * @return bool */ - public function templateExists($tpl) + public function templateExists(string $tpl): bool { - return ($path = realpath($this->_path . "/" . $tpl)) && strpos($path, $this->_path) === 0; + return ($path = realpath($this->_path . "/" . $tpl)) && str_starts_with($path, $this->_path); } /** @@ -169,7 +169,7 @@ class Provider implements ProviderInterface * @param array $templates [template_name => modified, ...] By conversation, you may trust the template's name * @return bool */ - public function verify(array $templates) + public function verify(array $templates): bool { foreach ($templates as $template => $mtime) { $template = $this->_path . '/' . $template; diff --git a/src/Fenom/ProviderInterface.php b/src/Fenom/ProviderInterface.php index c0bbca0..4782f3e 100644 --- a/src/Fenom/ProviderInterface.php +++ b/src/Fenom/ProviderInterface.php @@ -20,20 +20,20 @@ interface ProviderInterface * @param string $tpl * @return bool */ - public function templateExists($tpl); + public function templateExists(string $tpl): bool; /** * @param string $tpl - * @param int $time + * @param float $time seconds with micro * @return string */ - public function getSource($tpl, &$time); + public function getSource(string $tpl, float &$time): string; /** * @param string $tpl - * @return int + * @return float seconds with micro */ - public function getLastModified($tpl); + public function getLastModified(string $tpl): float; /** * Verify templates (check mtime) @@ -41,11 +41,11 @@ interface ProviderInterface * @param array $templates [template_name => modified, ...] By conversation, you may trust the template's name * @return bool if true - all templates are valid else some templates are invalid */ - public function verify(array $templates); + public function verify(array $templates): bool; /** * Get all names of template from provider - * @return array|\Iterator + * @return iterable */ - public function getList(); + public function getList(): iterable; } diff --git a/src/Fenom/RangeIterator.php b/src/Fenom/RangeIterator.php index 82c5656..dd89248 100644 --- a/src/Fenom/RangeIterator.php +++ b/src/Fenom/RangeIterator.php @@ -14,7 +14,7 @@ class RangeIterator implements Iterator, Countable public int $max; public int $step; - public function __construct($min, $max, $step = 1) + public function __construct(int $min, int $max, int $step = 1) { $this->min = $min; $this->max = $max; @@ -42,7 +42,7 @@ class RangeIterator implements Iterator, Countable /** * Return the current element */ - public function current(): int + public function current(): mixed { return $this->current; } @@ -50,7 +50,7 @@ class RangeIterator implements Iterator, Countable /** * Move forward to next element */ - public function next() + public function next(): void { $this->current += $this->step; $this->index++; @@ -58,9 +58,9 @@ class RangeIterator implements Iterator, Countable /** * Return the key of the current element - * @return int + * @return mixed */ - public function key(): int + public function key(): mixed { return $this->index; } @@ -77,7 +77,7 @@ class RangeIterator implements Iterator, Countable /** * Rewind the Iterator to the first element */ - public function rewind() + public function rewind(): void { if($this->step > 0) { $this->current = min($this->min, $this->max); diff --git a/src/Fenom/Tag.php b/src/Fenom/Tag.php index 0456e5c..fac0e8e 100644 --- a/src/Fenom/Tag.php +++ b/src/Fenom/Tag.php @@ -205,7 +205,7 @@ class Tag extends \ArrayObject } $code = call_user_func($this->_close, $tokenizer, $this); $this->restoreAll(); - return $code; + return (string)$code; } else { throw new \LogicException("Can not use a inline tag {$this->name} as a block"); } diff --git a/src/Fenom/Template.php b/src/Fenom/Template.php index 99885c3..3442adb 100644 --- a/src/Fenom/Template.php +++ b/src/Fenom/Template.php @@ -66,7 +66,7 @@ class Template extends Render /** * @var string|null */ - public ?string $extends; + public ?string $extends = null; /** * @var string|null @@ -80,6 +80,7 @@ class Template extends Render public array $ext_stack = []; public bool $extend_body = false; + public string $dynamic_extends = ""; /** * Parent template @@ -483,7 +484,7 @@ class Template extends Render */ private function _getClosureSource(): string { - return "function (\$var, \$tpl) {\n?>{$this->_body}{$this->_body}_stack[] = $tag; } - return $code; + return (string)$code; } for ($j = $i = count($this->_stack) - 1; $i >= 0; $i--) { // call function's internal tag @@ -705,7 +706,7 @@ class Template extends Render * @param bool $is_var * @return string */ - public function parseExpr(Tokenizer $tokens, bool &$is_var = false): string + public function parseExpr(Tokenizer $tokens, ?bool &$is_var = false): string { $exp = array(); $var = false; // last term was: true - variable, false - mixed @@ -714,7 +715,7 @@ class Template extends Render while ($tokens->valid()) { // parse term $term = $this->parseTerm($tokens, $var, -1); // term of the expression - if ($term !== false) { + if ($term !== "") { if ($tokens->is('?', '!')) { if ($cond) { $term = array_pop($exp) . ' ' . $term; @@ -910,7 +911,7 @@ class Template extends Render $args = $this->parseArgs($tokens); $code = $unary . $this->parseChain($tokens, $method . $args); } else { - return false; + return ""; } break; case T_ISSET: @@ -933,7 +934,7 @@ class Template extends Render if ($unary) { throw new UnexpectedTokenException($tokens->back()); } else { - return false; + return ""; } } if (($allows & self::TERM_MODS) && $tokens->is('|')) { @@ -1050,7 +1051,7 @@ class Template extends Render * @param bool $is_var * @return string */ - public function parseAccessor(Tokenizer $tokens, bool &$is_var = false): string + public function parseAccessor(Tokenizer $tokens, ?bool &$is_var = false): string { $accessor = $tokens->need('$')->next()->need('.')->next()->current(); $parser = $this->getStorage()->getAccessor($accessor); @@ -1356,9 +1357,8 @@ class Template extends Render * * @param Tokenizer $tokens * @param $value - * @throws \LogicException - * @throws \Exception * @return string + * @throws CompileException */ public function parseModifier(Tokenizer $tokens, $value): string { @@ -1369,7 +1369,7 @@ class Template extends Render } else { $mods = $this->_fenom->getModifier($modifier, $this); if (!$mods) { - throw new \Exception("Modifier " . $tokens->current() . " not found"); + throw new CompileException("Modifier " . $tokens->current() . " not found"); } $tokens->next(); } @@ -1563,10 +1563,10 @@ class Template extends Render * Parse first unnamed argument * * @param Tokenizer $tokens - * @param string $static - * @return mixed|string + * @param string|null $static + * @return string */ - public function parsePlainArg(Tokenizer $tokens, string &$static): mixed + public function parsePlainArg(Tokenizer $tokens, ?string &$static = null): string { if ($tokens->is(T_CONSTANT_ENCAPSED_STRING)) { if ($tokens->isNext('|')) { diff --git a/src/Fenom/Tokenizer.php b/src/Fenom/Tokenizer.php index a073ed4..aa66ea7 100644 --- a/src/Fenom/Tokenizer.php +++ b/src/Fenom/Tokenizer.php @@ -181,7 +181,7 @@ class Tokenizer $this->_curr = null; $this->_next = null; $this->_prev = null; - $tokens = array(-1 => array(\T_WHITESPACE, '', '', 1)); + $tokens = [-1 => [\T_WHITESPACE, '', '', 1]]; $_tokens = token_get_all("quotes++; } - $token = array( + $token = [ $token, $token, $line, - ); + ]; } elseif ($token[0] === \T_WHITESPACE) { $tokens[$i - 1][2] = $token[1]; continue; + } elseif ($token[0] === \T_NAME_FULLY_QUALIFIED || $token[0] === \T_NAME_QUALIFIED || $token[0] === \T_NAME_RELATIVE) { + $parts = explode("\\", $token[1]); + for ($k = 0; $k < count($parts); $k++) { + if ($parts[$k] !== "") { + $tokens[] = [ + T_STRING, + $parts[$k], + "", + $line = $token[2] + ]; + $i++; + } + if (isset($parts[$k], $parts[$k+1])) { + $tokens[] = [ + "\\", + "\\", + "", + $line = $token[2] + ]; + $i++; + } + } + continue; } elseif ($token[0] === \T_DNUMBER) { // fix .1 and 1. if(str_starts_with($token[1], '.')) { $tokens[] = array( diff --git a/tests/TestCase.php b/tests/TestCase.php index 7c04289..c8509f4 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,13 +3,17 @@ namespace Fenom; use Fenom, Fenom\Provider as FS; +class CustomFenom extends Fenom { + public mixed $prop; +} + class TestCase extends \PHPUnit\Framework\TestCase { public $template_path = 'template'; /** - * @var Fenom + * @var CustomFenom */ - public $fenom; + public CustomFenom $fenom; public $values; @@ -61,7 +65,7 @@ class TestCase extends \PHPUnit\Framework\TestCase FS::clean($this->getCompilePath()); } - $this->fenom = Fenom::factory(FENOM_RESOURCES . '/' . $this->template_path, $this->getCompilePath()); + $this->fenom = CustomFenom::factory(FENOM_RESOURCES . '/' . $this->template_path, $this->getCompilePath()); $this->fenom->addProvider('persist', new Provider(FENOM_RESOURCES . '/provider')); $this->fenom->addModifier('dots', __CLASS__ . '::dots'); $this->fenom->addModifier('concat', __CLASS__ . '::concat'); @@ -168,7 +172,7 @@ class TestCase extends \PHPUnit\Framework\TestCase $this->fenom->setOptions($options); try { $this->fenom->compileCode($code, "inline.tpl"); - } catch (\Exception $e) { + } catch (\Throwable $e) { $this->assertSame($exception, get_class($e), "Exception $code"); $this->assertStringStartsWith($message, $e->getMessage()); $this->fenom->setOptions($opt); @@ -180,7 +184,11 @@ class TestCase extends \PHPUnit\Framework\TestCase public function assertRender($tpl, $result, array $vars = array(), $debug = false) { - $template = $this->fenom->compileCode($tpl); + try { + $template = $this->fenom->compileCode($tpl); + } catch (\Throwable $e) { + throw new \RuntimeException("Template failed: $tpl",0 , $e); + } if ($debug) { print_r("\nDEBUG $tpl:\n" . $template->getBody()); } @@ -300,7 +308,8 @@ class Helper const CONSTANT = "helper.class.const"; - public $word = 'helper'; + public string $word = 'helper'; + public Helper $self; public function __construct($word) { diff --git a/tests/cases/Fenom/AccessorTest.php b/tests/cases/Fenom/AccessorTest.php index 2c0684d..d4b3720 100644 --- a/tests/cases/Fenom/AccessorTest.php +++ b/tests/cases/Fenom/AccessorTest.php @@ -132,7 +132,8 @@ class AccessorTest extends TestCase * @group issue260 */ public function testBug260() { - $this->fenom->compileCode('{$.php.Fenom::factory()->addModifier("intval", "intval")}'); + $t = $this->fenom->compileCode('{$.php.Fenom::factory()->addModifier("intval", "intval")}'); + $this->assertInstanceOf(Template::class, $t); } @@ -236,15 +237,28 @@ class AccessorTest extends TestCase $this->execError($tpl, $exception, $message); } - public function getThree() { + public static function getThree(): int + { return 3; } + public static function getThreeArray(): array + { + return ["three" => 3]; + } + + public static function getThreeCb(): callable + { + return fn() => 3; + } + + public static int $three = 3; + public static function providerSmartAccessor() { return array( - array('acc', '$tpl->getStorage()->test->values', \Fenom::ACCESSOR_VAR, '{$.acc.three}', '3'), - array('acc', '$tpl->getStorage()->test->getThree', \Fenom::ACCESSOR_CALL, '{$.acc()}', '3'), - array('acc', 'three', \Fenom::ACCESSOR_PROPERTY, '{$.acc}', '3'), + array('acc', '\Fenom\AccessorTest::getThreeArray()', \Fenom::ACCESSOR_VAR, '{$.acc.three}', '3'), + array('acc', '\Fenom\AccessorTest::getThreeCb()', \Fenom::ACCESSOR_CALL, '{$.acc()}', '3'), + array('acc', 'prop', \Fenom::ACCESSOR_PROPERTY, '{$.acc}', 'something'), array('acc', 'templateExists', \Fenom::ACCESSOR_METHOD, '{$.acc("persist:pipe.tpl")}', '1') ); } @@ -259,8 +273,7 @@ class AccessorTest extends TestCase * @param $result */ public function testSmartAccessor($name, $accessor, $type, $code, $result) { - $this->fenom->test = $this; - $this->fenom->three = 3; + $this->fenom->prop = "something"; $this->fenom->addAccessorSmart($name, $accessor, $type); $this->assertRender($code, $result, $this->getVars()); } diff --git a/tests/cases/Fenom/CustomProviderTest.php b/tests/cases/Fenom/CustomProviderTest.php index 30c9c63..88ba4dd 100644 --- a/tests/cases/Fenom/CustomProviderTest.php +++ b/tests/cases/Fenom/CustomProviderTest.php @@ -6,7 +6,7 @@ namespace Fenom; class CustomProviderTest extends TestCase { - public function setUp() + public function setUp(): void { parent::setUp(); $this->fenom->addProvider("my", new Provider(FENOM_RESOURCES . '/provider')); diff --git a/tests/cases/Fenom/FunctionsTest.php b/tests/cases/Fenom/FunctionsTest.php index ed56f1e..64f27a1 100644 --- a/tests/cases/Fenom/FunctionsTest.php +++ b/tests/cases/Fenom/FunctionsTest.php @@ -21,7 +21,7 @@ class FunctionsTest extends TestCase return $a + $i; } - public function setUp() + public function setUp(): void { parent::setUp(); $this->fenom->addFunctionSmart('sum', __CLASS__ . '::functionSum'); diff --git a/tests/cases/Fenom/MacrosTest.php b/tests/cases/Fenom/MacrosTest.php index 89ff81d..197ad89 100644 --- a/tests/cases/Fenom/MacrosTest.php +++ b/tests/cases/Fenom/MacrosTest.php @@ -4,7 +4,7 @@ namespace Fenom; class MacrosTest extends TestCase { - public function setUp() + public function setUp(): void { parent::setUp(); $this->tpl("math.tpl", @@ -115,12 +115,10 @@ class MacrosTest extends TestCase ); } - /** - * @expectedExceptionMessage Undefined macro 'plus' - * @expectedException \Fenom\Error\CompileException - */ public function testImportMiss() { + $this->expectException(exception: \Fenom\Error\CompileException::class); + $this->expectExceptionMessage("Undefined macro 'plus'"); $tpl = $this->fenom->compile('import_miss.tpl'); $this->assertSame( diff --git a/tests/cases/Fenom/ProviderTest.php b/tests/cases/Fenom/ProviderTest.php index 77d8706..9e9a5af 100644 --- a/tests/cases/Fenom/ProviderTest.php +++ b/tests/cases/Fenom/ProviderTest.php @@ -11,7 +11,7 @@ class ProviderTest extends TestCase */ public $provider; - public function setUp() + public function setUp(): void { parent::setUp(); $this->tpl("template1.tpl", 'Template 1 {$a}'); @@ -30,6 +30,7 @@ class ProviderTest extends TestCase public function testGetSource() { + $time = 0.0; clearstatcache(); $src = $this->provider->getSource("template1.tpl", $time); clearstatcache(); @@ -37,11 +38,9 @@ class ProviderTest extends TestCase $this->assertEquals(filemtime(FENOM_RESOURCES . '/template/template1.tpl'), $time); } - /** - * @expectedException \RuntimeException - */ public function testGetSourceInvalid() { + $this->expectException(\RuntimeException::class); $this->provider->getSource("unexists.tpl", $time); } @@ -52,11 +51,9 @@ class ProviderTest extends TestCase $this->assertEquals(filemtime(FENOM_RESOURCES . '/template/template1.tpl'), $time); } - /** - * @expectedException \RuntimeException - */ public function testGetLastModifiedInvalid() { + $this->expectException(\RuntimeException::class); $this->provider->getLastModified("unexists.tpl"); } diff --git a/tests/cases/Fenom/RenderTest.php b/tests/cases/Fenom/RenderTest.php index 149aa45..aab8ccc 100644 --- a/tests/cases/Fenom/RenderTest.php +++ b/tests/cases/Fenom/RenderTest.php @@ -12,7 +12,7 @@ class RenderTest extends TestCase */ public static $render; - public static function setUpBeforeClass() + public static function setUpBeforeClass(): void { self::$render = new Render(Fenom::factory("."), function ($tpl) { echo "It is render's function " . $tpl["render"]; @@ -50,6 +50,8 @@ class RenderTest extends TestCase */ public function testFetchException() { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage("template error"); $render = new Render(Fenom::factory("."), function () { echo "error"; throw new \RuntimeException("template error"); diff --git a/tests/cases/Fenom/SandboxTest.php b/tests/cases/Fenom/SandboxTest.php deleted file mode 100644 index 6e42360..0000000 --- a/tests/cases/Fenom/SandboxTest.php +++ /dev/null @@ -1,35 +0,0 @@ -fenom->setOptions(\Fenom::FORCE_VERIFY); -// $this->fenom->addAccessorSmart('q', 'Navi::$q', \Fenom::ACCESSOR_VAR); -// $this->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}'); -// var_dump($this->fenom->compileCode('{set $a=$one|min:0..$three|max:4}')->getBody()); - -// try { -// var_dump($this->fenom->compileCode('{foreach $a as $k}A{$k:first}{foreachelse}B{/foreach}')->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 diff --git a/tests/cases/Fenom/ScopeTest.php b/tests/cases/Fenom/ScopeTest.php deleted file mode 100644 index 9b3a7d5..0000000 --- a/tests/cases/Fenom/ScopeTest.php +++ /dev/null @@ -1,36 +0,0 @@ -assertInstanceOf('Fenom\Tokenizer', $tokenizer); - $this->assertInstanceOf('Fenom\Scope', $scope); - $scope["value"] = true; - return "open-tag"; - } - - public function closeTag($tokenizer, $scope) - { - $this->assertInstanceOf('Fenom\Tokenizer', $tokenizer); - $this->assertInstanceOf('Fenom\Scope', $scope); - $this->assertTrue($scope["value"]); - return "close-tag"; - } - - public function testBlock() - { - /*$scope = new Scope($this->fenom, new Template($this->fenom), 1, array( - "open" => array($this, "openTag"), - "close" => array($this, "closeTag") - ), 0); - $tokenizer = new Tokenizer("1+1"); - $this->assertSame("open-tag /*#{$scope->id}#* /", $scope->open($tokenizer)); - $this->assertSame("close-tag", $scope->close($tokenizer)); - - $content = " some ?> content\n\nwith /*#9999999#* / many\n\tlines"; - $scope->tpl->_body = "start open($tokenizer)." ?>".$content; - $this->assertSame($content, $scope->getContent());*/ - } -} diff --git a/tests/cases/Fenom/TagOptionTest.php b/tests/cases/Fenom/TagOptionTest.php deleted file mode 100644 index 71d0854..0000000 --- a/tests/cases/Fenom/TagOptionTest.php +++ /dev/null @@ -1,14 +0,0 @@ -tpl('welcome.tpl', 'Welcome, {$username} ({$email})'); diff --git a/tests/cases/Fenom/TokenizerTest.php b/tests/cases/Fenom/TokenizerTest.php index 15213e7..50124cd 100644 --- a/tests/cases/Fenom/TokenizerTest.php +++ b/tests/cases/Fenom/TokenizerTest.php @@ -80,6 +80,25 @@ class TokenizerTest extends TestCase $this->assertSame('}', $tokens->end()->current()); } + public function testComplexTokens() + { + $text = "one\\two"; + $tokens = new Tokenizer($text); + $this->assertSame("one", $tokens->current()); + $this->assertSame("\\", $tokens->next()->current()); + $this->assertSame("two", $tokens->next()->current()); + $this->assertFalse($tokens->next()->valid()); + + $text = "\\one\\two"; + + $tokens = new Tokenizer($text); + $this->assertSame("\\", $tokens->current()); + $this->assertSame("one", $tokens->next()->current()); + $this->assertSame("\\", $tokens->next()->current()); + $this->assertSame("two", $tokens->next()->current()); + $this->assertFalse($tokens->next()->valid()); + } + public function testSkip() { $text = "1 foo: bar ( 3 + double ) ";