Merge pull request #341 from fenom-template/v3.0.0

Migrate to php8
This commit is contained in:
Ivan Shalganov 2023-02-27 09:38:43 +01:00 committed by GitHub
commit fbf4f46f1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1866 additions and 3134 deletions

View File

@ -1,10 +1,6 @@
name: PHP Composer
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
on: push
permissions:
contents: read
@ -15,22 +11,21 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v3
- name: Validate composer.json and composer.lock
run: composer validate --strict
- name: Validate composer.json and composer.lock
run: composer validate --strict
- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-
- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Run tests
run: vendor/bin/phpunit
- name: Run tests
run: vendor/bin/phpunit

3
.gitignore vendored
View File

@ -5,4 +5,5 @@ composer.phar
tests/resources/compile/*
!.gitkeep
tests/resources/template/*
sandbox/compiled/*
sandbox/compiled/*
/.phpunit.result.cache

View File

@ -1,5 +1,12 @@
Changelog
=========
## 3.0.0 (2023-02-23)
- Fenom supported php8+
- Remove `eval` from template compiler
- `strftime` -> `date` with fallback support.
- update tokenizer
- bugfixes and optimizations
## 2.11.0 (2016-06-09)

View File

@ -6,8 +6,8 @@ Fenom - Template Engine for PHP
* **Subject:** Template engine
* **Syntax:** Smarty-like
* **Documentation:** **[English](./docs/en/readme.md)**, **[Russian](./docs/ru/readme.md)**
* **PHP version:** 5.4+
* **State:** [![Build Status](https://travis-ci.org/fenom-template/fenom.svg?branch=master)](https://travis-ci.org/fenom-template/fenom) [![Coverage Status](https://coveralls.io/repos/fenom-template/fenom/badge.svg?branch=master)](https://coveralls.io/r/fenom-template/fenom?branch=master)
* **PHP version:** 8.0+
* **State:** [![PHP Composer](https://github.com/fenom-template/fenom/actions/workflows/php.yml/badge.svg?branch=master)](https://github.com/fenom-template/fenom/actions/workflows/php.yml) [![Coverage Status](https://coveralls.io/repos/fenom-template/fenom/badge.svg?branch=master)](https://coveralls.io/r/fenom-template/fenom?branch=master)
* **Version:** [![Latest Stable Version](https://poser.pugx.org/fenom/fenom/v/stable.png)](https://packagist.org/packages/fenom/fenom)
* **Packagist:** [fenom/fenom](https://packagist.org/packages/fenom/fenom) [![Total Downloads](https://poser.pugx.org/fenom/fenom/downloads.png)](https://packagist.org/packages/fenom/fenom)
* **Composer:** `composer require fenom/fenom`
@ -22,16 +22,10 @@ Fenom - Template Engine for PHP
### Install
If you use composer in your project then you can to install Fenom as package.
However, if you are not using composer you have to configure _autoloader_ to work with Fenom.
Fenom implements the `PSR-0` PHP standard to load classes which are located in the `src/` directory.
Templater already has own autoload-function, to register call method `Fenom::registerAutoload`:
```php
Fenom::registerAutoload();
```
### Setup
There is two way to create Fenom instance:
There is two-way to create Fenom instance:
* Long way: use operator `new`
* Shot way: use static factory-method

View File

@ -11,15 +11,17 @@
}
],
"require": {
"php": ">=5.3.0",
"php": ">=8.0.0",
"ext-tokenizer": "*"
},
"require-dev": {
"phpunit/phpunit": "<6.0",
"satooshi/php-coveralls": "*"
"phpunit/phpunit": "9.*"
},
"autoload": {
"psr-0": { "Fenom\\": "src/" },
"psr-4": { "Fenom\\": "src/Fenom" },
"classmap": [ "src/Fenom.php" ]
},
"autoload-dev": {
"classmap": [ "tests/cases" ]
}
}

3026
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,6 @@
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="tests/autoload.php"
>
<php>
@ -18,7 +17,7 @@
</php>
<testsuites>
<testsuite >
<testsuite name="tests">
<directory>./tests/</directory>
</testsuite>
</testsuites>
@ -29,14 +28,6 @@
</exclude>
</groups>
<filter>
<whitelist>
<directory>./src/</directory>
</whitelist>
<blacklist>
<directory>./tests/</directory>
</blacklist>
</filter>
<logging>
<log type="coverage-clover" target="build/logs/clover.xml"/>
</logging>

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@
*/
namespace Fenom;
use Fenom\Error\CompileException;
use Fenom\Error\UnexpectedTokenException;
/**
@ -16,7 +17,7 @@ use Fenom\Error\UnexpectedTokenException;
* @package Fenom
*/
class Accessor {
public static $vars = array(
public static array $vars = array(
'get' => '$_GET',
'post' => '$_POST',
'session' => '$_SESSION',
@ -32,10 +33,11 @@ class Accessor {
* @param string $var variable expression on PHP ('App::get("storage")->user')
* @param Tokenizer $tokens
* @param Template $tpl
* @param $is_var
* @param bool $is_var
* @return string
* @throws CompileException
*/
public static function parserVar($var, Tokenizer $tokens, Template $tpl, &$is_var)
public static function parserVar(string $var, Tokenizer $tokens, Template $tpl, bool &$is_var): string
{
$is_var = true;
return $tpl->parseVariable($tokens, $var);
@ -47,7 +49,7 @@ class Accessor {
* @param Template $tpl
* @return string
*/
public static function parserCall($call, Tokenizer $tokens, Template $tpl)
public static function parserCall(string $call, Tokenizer $tokens, Template $tpl): string
{
return $call.$tpl->parseArgs($tokens);
}
@ -56,10 +58,11 @@ class Accessor {
* @param string $prop fenom's property name
* @param Tokenizer $tokens
* @param Template $tpl
* @param $is_var
* @param bool $is_var
* @return string
* @throws CompileException
*/
public static function parserProperty($prop, Tokenizer $tokens, Template $tpl, &$is_var)
public static function parserProperty(string $prop, Tokenizer $tokens, Template $tpl, bool &$is_var): string
{
$is_var = true;
return self::parserVar('$tpl->getStorage()->'.$prop, $tokens, $tpl, $is_var);
@ -71,7 +74,7 @@ class Accessor {
* @param Template $tpl
* @return string
*/
public static function parserMethod($method, Tokenizer $tokens, Template $tpl)
public static function parserMethod(string $method, Tokenizer $tokens, Template $tpl): string
{
return self::parserCall('$tpl->getStorage()->'.$method, $tokens, $tpl);
}
@ -81,13 +84,14 @@ class Accessor {
* @param Tokenizer $tokens
* @param Template $tpl
* @return string
* @throws CompileException
*/
public static function getVar(Tokenizer $tokens, Template $tpl)
public static function getVar(Tokenizer $tokens, Template $tpl): string
{
$name = $tokens->prev[Tokenizer::TEXT];
$name = $tokens->prevToken()[Tokenizer::TEXT];
if(isset(self::$vars[$name])) {
$var = $tpl->parseVariable($tokens, self::$vars[$name]);
return "(isset($var) ? $var : null)";
return "(($var) ?? null)";
} else {
throw new UnexpectedTokenException($tokens->back());
}
@ -98,7 +102,7 @@ class Accessor {
* @param Tokenizer $tokens
* @return string
*/
public static function tpl(Tokenizer $tokens)
public static function tpl(Tokenizer $tokens): string
{
$method = $tokens->skip('.')->need(T_STRING)->getAndNext();
if(method_exists('Fenom\Render', 'get'.$method)) {
@ -111,7 +115,7 @@ class Accessor {
/**
* @return string
*/
public static function version()
public static function version(): string
{
return 'Fenom::VERSION';
}
@ -120,9 +124,9 @@ class Accessor {
* @param Tokenizer $tokens
* @return string
*/
public static function constant(Tokenizer $tokens)
public static function constant(Tokenizer $tokens): string
{
$const = array($tokens->skip('.')->need(Tokenizer::MACRO_STRING)->getAndNext());
$const = [$tokens->skip('.')->need(Tokenizer::MACRO_STRING)->getAndNext()];
while($tokens->is('.')) {
$const[] = $tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext();
}
@ -130,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).') : "")';
}
@ -139,9 +143,9 @@ class Accessor {
* @param Template $tpl
* @return string
*/
public static function call(Tokenizer $tokens, Template $tpl)
public static function call(Tokenizer $tokens, Template $tpl): string
{
$callable = array($tokens->skip('.')->need(Tokenizer::MACRO_STRING)->getAndNext());
$callable = [$tokens->skip('.')->need(Tokenizer::MACRO_STRING)->getAndNext()];
while($tokens->is('.')) {
$callable[] = $tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext();
}
@ -149,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)) {
@ -175,7 +179,7 @@ class Accessor {
* @param Template $tpl
* @return string
*/
public static function fetch(Tokenizer $tokens, Template $tpl)
public static function fetch(Tokenizer $tokens, Template $tpl): string
{
$tokens->skip('(');
$name = $tpl->parsePlainArg($tokens, $static);
@ -188,7 +192,7 @@ class Accessor {
$tokens->next();
if($tokens->is('[')){
$vars = $tpl->parseArray($tokens) . ' + $var';
}elseif($tokens->is(T_VARIABLE)){
} elseif($tokens->is(T_VARIABLE)){
$vars = $tpl->parseExpr($tokens) . ' + $var';
}
} else {
@ -202,9 +206,9 @@ class Accessor {
* Accessor {$.block.NAME}
* @param Tokenizer $tokens
* @param Template $tpl
* @return mixed
* @return string
*/
public static function block(Tokenizer $tokens, Template $tpl)
public static function block(Tokenizer $tokens, Template $tpl): string
{
if($tokens->is('.')) {
$name = $tokens->next()->get(Tokenizer::MACRO_STRING);

View File

@ -24,13 +24,12 @@ class Compiler
/**
* Tag {include ...}
*
* @static
* @param Tokenizer $tokens
* @param Tag $tag
* @throws \LogicException
* @return string
*/
public static function tagInclude(Tokenizer $tokens, Tag $tag)
public static function tagInclude(Tokenizer $tokens, Tag $tag): string
{
$tpl = $tag->tpl;
$name = false;
@ -70,12 +69,13 @@ class Compiler
/**
* Tag {insert ...}
*
* @param Tokenizer $tokens
* @param Tag $tag
* @throws Error\InvalidUsageException
* @return string
* @throws Error\InvalidUsageException|CompileException
*/
public static function tagInsert(Tokenizer $tokens, Tag $tag)
public static function tagInsert(Tokenizer $tokens, Tag $tag): string
{
$tpl = $tag->tpl;
$tpl->parsePlainArg($tokens, $name);
@ -91,12 +91,11 @@ class Compiler
/**
* Open tag {if ...}
*
* @static
* @param Tokenizer $tokens
* @param Tag $scope
* @return string
*/
public static function ifOpen(Tokenizer $tokens, Tag $scope)
public static function ifOpen(Tokenizer $tokens, Tag $scope): string
{
$scope["else"] = false;
return 'if(' . $scope->tpl->parseExpr($tokens) . ') {';
@ -105,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}');
@ -126,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 {';
@ -135,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;
@ -200,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"]) . ";" : "";
@ -213,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) {
@ -247,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"]) . ";" : "";
@ -267,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) . ') {';
}
@ -387,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();
@ -408,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) {
@ -427,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 {
@ -455,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;
@ -470,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"];
@ -494,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;';
@ -512,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;';
@ -529,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) {
@ -558,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) {
@ -585,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()) {
@ -607,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) {
@ -623,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"];
@ -657,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']) {
@ -671,7 +556,7 @@ class Compiler
*
* @return string
*/
public static function stdClose()
public static function stdClose(): string
{
return '}';
}
@ -683,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)');
@ -699,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);
@ -729,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);
@ -743,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)) {
@ -756,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) {
@ -773,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);
@ -819,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).';';
}
@ -834,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"] . ";";
}
@ -859,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("[")) {
@ -882,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)];
}
@ -896,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();
@ -965,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;
@ -1010,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;
@ -1026,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);
}
@ -1035,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);
}
@ -1045,7 +933,7 @@ class Compiler
/**
* Do nothing
*/
public static function nope()
public static function nope(): void
{
}
@ -1053,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()) {
@ -1085,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();

View File

@ -0,0 +1,10 @@
<?php
namespace Fenom\Error;
/**
* @package Fenom\Error
*/
class TemplateException extends \Exception
{
}

View File

@ -24,7 +24,7 @@ class UnexpectedTokenException extends \RuntimeException
} else {
$expect = "";
}
if (!$tokens->curr) {
if (!$tokens->currToken()) {
$this->message = "Unexpected end of " . ($where ? : "expression") . "$expect";
} else {
$this->message = "Unexpected token '" . $tokens->current() . "' in " . ($where ? : "expression") . "$expect";

View File

@ -19,19 +19,61 @@ class Modifier
/**
* Date format
*
* @param string|int $date
* @param int|string $date
* @param string $format
* @return string
*/
public static function dateFormat($date, $format = "%b %e, %Y")
public static function dateFormat(mixed $date, string $format = "%b %e, %Y"): string
{
if (!is_numeric($date)) {
$date = strtotime($date);
if ($date instanceof \DateTime) {
$date = $date->getTimestamp();
} else {
$date = strtotime($date);
}
if (!$date) {
$date = time();
}
}
return strftime($format, $date);
if (str_contains($format, "%")) {
// fallback mode
$from = [
// Day - no strf eq : S (created one called %O)
'%O', '%d', '%a', '%e', '%A', '%u', '%w', '%j',
// Week - no date eq : %U, %W
'%V',
// Month - no strf eq : n, t
'%B', '%m', '%b', '%-m',
// Year - no strf eq : L; no date eq : %C, %g
'%G', '%Y', '%y',
// Time - no strf eq : B, G, u; no date eq : %r, %R, %T, %X
'%P', '%p', '%l', '%I', '%H', '%M', '%S',
// Timezone - no strf eq : e, I, P, Z
'%z', '%Z',
// Full Date / Time - no strf eq : c, r; no date eq : %c, %D, %F, %x
'%s'
];
$to = [
'S', 'd', 'D', 'j', 'l', 'N', 'w', 'z',
'W',
'F', 'm', 'M', 'n',
'o', 'Y', 'y',
'a', 'A', 'g', 'h', 'H', 'i', 's',
'O', 'T',
'U'
];
$pattern = array_map(
function ( $s ) {
return '/(?<!\\\\|\%)' . $s . '/';
},
$from
);
$format = preg_replace($pattern, $to, $format);
}
return date($format, $date);
}
/**
@ -39,7 +81,7 @@ class Modifier
* @param string $format
* @return string
*/
public static function date($date, $format = "Y m d")
public static function date(string $date, string $format = "Y m d"): string
{
if (!is_numeric($date)) {
$date = strtotime($date);
@ -55,18 +97,18 @@ class Modifier
*
* @param string $text
* @param string $type
* @param string $charset
* @param string|null $charset
* @return string
*/
public static function escape($text, $type = 'html', $charset = null)
public static function escape(string $text, string $type = 'html', string $charset = null): string
{
switch (strtolower($type)) {
case "url":
return urlencode($text);
case "html";
return htmlspecialchars($text, ENT_COMPAT, $charset ? $charset : \Fenom::$charset);
return htmlspecialchars($text, ENT_COMPAT, $charset ?: \Fenom::$charset);
case "js":
return json_encode($text, 64 | 256); // JSON_UNESCAPED_SLASHES = 64, JSON_UNESCAPED_UNICODE = 256
return json_encode($text, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
default:
return $text;
}
@ -79,7 +121,7 @@ class Modifier
* @param string $type
* @return string
*/
public static function unescape($text, $type = 'html')
public static function unescape(string $text, string $type = 'html'): string
{
switch (strtolower($type)) {
case "url":
@ -96,12 +138,12 @@ class Modifier
*
* @param string $string text witch will be truncate
* @param int $length maximum symbols of result string
* @param string $etc place holder truncated symbols
* @param string $etc placeholder truncated symbols
* @param bool $by_words
* @param bool $middle
* @return string
*/
public static function truncate($string, $length = 80, $etc = '...', $by_words = false, $middle = false)
public static function truncate(string $string, int $length = 80, string $etc = '...', bool $by_words = false, bool $middle = false): string
{
if ($middle) {
if (preg_match('#^(.{' . $length . '}).*?(.{' . $length . '})?$#usS', $string, $match)) {
@ -134,7 +176,7 @@ class Modifier
* @param bool $to_line strip line ends
* @return string
*/
public static function strip($str, $to_line = false)
public static function strip(string $str, bool $to_line = false): string
{
$str = trim($str);
if ($to_line) {
@ -149,7 +191,7 @@ class Modifier
* @param mixed $item
* @return int
*/
public static function length($item)
public static function length(mixed $item): int
{
if (is_string($item)) {
return strlen(preg_replace('#[\x00-\x7F]|[\x80-\xDF][\x00-\xBF]|[\xE0-\xEF][\x00-\xBF]{2}#s', ' ', $item));
@ -168,13 +210,13 @@ class Modifier
* @param mixed $haystack
* @return bool
*/
public static function in($value, $haystack)
public static function in(mixed $value, mixed $haystack): bool
{
if(is_scalar($value)) {
if (is_array($haystack)) {
return in_array($value, $haystack) || array_key_exists($value, $haystack);
} elseif (is_string($haystack)) {
return strpos($haystack, $value) !== false;
return str_contains($haystack, $value);
}
}
return false;
@ -184,7 +226,7 @@ class Modifier
* @param mixed $value
* @return bool
*/
public static function isIterable($value)
public static function isIterable(mixed $value): bool
{
return is_array($value) || ($value instanceof \Iterator);
}
@ -196,7 +238,7 @@ class Modifier
* @param string $replace The replacement value that replaces found search
* @return mixed
*/
public static function replace($value, $search, $replace)
public static function replace(string $value, string $search, string $replace): string
{
return str_replace($search, $replace, $value);
}
@ -207,7 +249,7 @@ class Modifier
* @param string $replacement
* @return mixed
*/
public static function ereplace($value, $pattern, $replacement)
public static function ereplace(string $value, string $pattern, string $replacement): string
{
return preg_replace($pattern, $replacement, $value);
}
@ -217,7 +259,7 @@ class Modifier
* @param string $pattern
* @return bool
*/
public static function match($string, $pattern)
public static function match(string $string, string $pattern): bool
{
return fnmatch($pattern, $string);
}
@ -227,7 +269,7 @@ class Modifier
* @param string $pattern
* @return int
*/
public static function ematch($string, $pattern)
public static function ematch(string $string, string $pattern): int
{
return preg_match($pattern, $string);
}
@ -237,23 +279,23 @@ class Modifier
* @param string $delimiter
* @return array
*/
public static function split($value, $delimiter = ",")
public static function split(mixed $value, string $delimiter = ","): array
{
if(is_string($value)) {
return explode($delimiter, $value);
} elseif(is_array($value)) {
return $value;
} else {
return array();
return [];
}
}
/**
* @param $value
* @param mixed $value
* @param string $pattern
* @return array
*/
public static function esplit($value, $pattern = '/,\s*/S')
public static function esplit(mixed $value, string $pattern = '/,\s*/S'): array
{
if(is_string($value)) {
return preg_split($pattern, $value);
@ -265,11 +307,11 @@ class Modifier
}
/**
* @param $value
* @param mixed $value
* @param string $glue
* @return string
*/
public static function join($value, $glue = ",")
public static function join(mixed $value, string $glue = ","): string
{
if(is_array($value)) {
return implode($glue, $value);
@ -281,12 +323,13 @@ class Modifier
}
/**
* @param string|int $from
* @param string|int $to
* @param int $from
* @param int $to
* @param int $step
* @return RangeIterator
*/
public static function range($from, $to, $step = 1) {
public static function range(mixed $from, int $to, int $step = 1): RangeIterator
{
if($from instanceof RangeIterator) {
return $from->setStep($to);
} else {

View File

@ -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,14 +169,14 @@ 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;
if($this->_clear_cache) {
clearstatcache(true, $template);
}
if (@filemtime($template) !== $mtime) {
if (@filemtime($template) != $mtime) {
return false;
}

View File

@ -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;
}

View File

@ -2,17 +2,19 @@
namespace Fenom;
use Countable;
use Iterator;
class RangeIterator implements \Iterator, \Countable
class RangeIterator implements Iterator, Countable
{
public $current;
public $index = 0;
public $min;
public $max;
public $step;
public int $current;
public int $index = 0;
public int $min;
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;
@ -23,7 +25,7 @@ class RangeIterator implements \Iterator, \Countable
* @param int $step
* @return $this
*/
public function setStep($step)
public function setStep(int $step): static
{
if($step > 0) {
$this->current = min($this->min, $this->max);
@ -40,7 +42,7 @@ class RangeIterator implements \Iterator, \Countable
/**
* Return the current element
*/
public function current()
public function current(): mixed
{
return $this->current;
}
@ -48,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++;
@ -56,9 +58,9 @@ class RangeIterator implements \Iterator, \Countable
/**
* Return the key of the current element
* @return int
* @return mixed
*/
public function key()
public function key(): mixed
{
return $this->index;
}
@ -67,7 +69,7 @@ class RangeIterator implements \Iterator, \Countable
* Checks if current position is valid
* @return bool
*/
public function valid()
public function valid(): bool
{
return $this->current >= $this->min && $this->current <= $this->max;
}
@ -75,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);
@ -88,7 +90,7 @@ class RangeIterator implements \Iterator, \Countable
/**
* Count elements of an object
*/
public function count()
public function count(): int
{
return intval(($this->max - $this->min + 1) / $this->step);
}

View File

@ -10,6 +10,7 @@
namespace Fenom;
use Fenom;
use Fenom\Error\TemplateException;
/**
* Primitive template
@ -17,71 +18,72 @@ use Fenom;
*/
class Render extends \ArrayObject
{
private static $_props = array(
private static array $_props = [
"name" => "runtime",
"base_name" => "",
"scm" => false,
"time" => 0,
"depends" => array(),
"macros" => array()
);
"depends" => [],
"macros" => []
];
/**
* @var \Closure
* @var \Closure|null
*/
protected $_code;
protected ?\Closure $_code = null;
/**
* Template name
* @var string
*/
protected $_name = 'runtime';
protected mixed $_name = 'runtime';
/**
* Provider's schema
* @var bool
* @var string|null
*/
protected $_scm = false;
protected ?string $_scm = null;
/**
* Basic template name
* @var string
*/
protected $_base_name = 'runtime';
protected string $_base_name = 'runtime';
/**
* @var Fenom
*/
protected $_fenom;
protected Fenom $_fenom;
/**
* Timestamp of compilation
* @var float
*/
protected $_time = 0.0;
protected float $_time = 0.0;
/**
* @var array depends list
*/
protected $_depends = array();
protected array $_depends = [];
/**
* @var int template options (see Fenom options)
*/
protected $_options = 0;
protected int $_options = 0;
/**
* Template provider
* @var ProviderInterface
*/
protected $_provider;
protected ProviderInterface $_provider;
/**
* @var \Closure[]
*/
protected $_macros;
protected array $_macros;
/**
* @param Fenom $fenom
* @param callable $code template body
* @param \Closure $code template body
* @param array $props
*/
public function __construct(Fenom $fenom, \Closure $code, array $props = array())
{
parent::__construct();
$this->_fenom = $fenom;
$props += self::$_props;
$this->_name = $props["name"];
@ -97,25 +99,25 @@ class Render extends \ArrayObject
* Get template storage
* @return \Fenom
*/
public function getStorage()
public function getStorage(): Fenom
{
return $this->_fenom;
}
/**
* Get depends list
* Get list of dependencies.
* @return array
*/
public function getDepends()
public function getDepends(): array
{
return $this->_depends;
}
/**
* Get schema name
* @return string
* @return string|null
*/
public function getScm()
public function getScm(): ?string
{
return $this->_scm;
}
@ -124,7 +126,7 @@ class Render extends \ArrayObject
* Get provider of template source
* @return ProviderInterface
*/
public function getProvider()
public function getProvider(): ProviderInterface
{
return $this->_fenom->getProvider($this->_scm);
}
@ -133,7 +135,7 @@ class Render extends \ArrayObject
* Get name without schema
* @return string
*/
public function getBaseName()
public function getBaseName(): string
{
return $this->_base_name;
}
@ -142,7 +144,7 @@ class Render extends \ArrayObject
* Get parse options
* @return int
*/
public function getOptions()
public function getOptions(): int
{
return $this->_options;
}
@ -159,7 +161,7 @@ class Render extends \ArrayObject
* Get template name
* @return string
*/
public function getName()
public function getName(): string
{
return $this->_name;
}
@ -174,7 +176,7 @@ class Render extends \ArrayObject
* Validate template
* @return bool
*/
public function isValid()
public function isValid(): bool
{
foreach ($this->_depends as $scm => $templates) {
$provider = $this->_fenom->getProvider($scm);
@ -197,7 +199,7 @@ class Render extends \ArrayObject
* @throws \RuntimeException
* @return mixed
*/
public function getMacro($name)
public function getMacro($name): mixed
{
if (empty($this->_macros[$name])) {
throw new \RuntimeException('macro ' . $name . ' not found');
@ -208,11 +210,16 @@ class Render extends \ArrayObject
/**
* Execute template and write into output
* @param array $values for template
* @return Render
* @return array
* @throws TemplateException
*/
public function display(array $values)
public function display(array $values): array
{
$this->_code->__invoke($values, $this);
try {
$this->_code->__invoke($values, $this);
} catch (\Throwable $e) {
throw new Fenom\Error\TemplateException("unhandled exception in the template `{$this->getName()}`: {$e->getMessage()}", 0, $e);
}
return $values;
}
@ -222,7 +229,7 @@ class Render extends \ArrayObject
* @return string
* @throws \Exception
*/
public function fetch(array $values)
public function fetch(array $values): string
{
ob_start();
try {
@ -236,17 +243,12 @@ class Render extends \ArrayObject
/**
* Stub
* @param $method
* @param $args
* @param string $method
* @param mixed $args
* @throws \BadMethodCallException
*/
public function __call($method, $args)
public function __call(string $method, mixed $args)
{
throw new \BadMethodCallException("Unknown method " . $method);
}
public function __get($name)
{
return $this->$name = null;
}
}

View File

@ -23,33 +23,34 @@ class Tag extends \ArrayObject
/**
* @var Template
*/
public $tpl;
public $name;
public $options = array();
public $line = 0;
public $level = 0;
public $callback;
public $escape;
public Template $tpl;
public string $name;
public array $options = [];
public int $line = 0;
public int $level = 0;
public mixed $callback;
public bool $escape;
private $_offset = 0;
private $_closed = true;
private $_body;
private $_type = 0;
private $_open;
private $_close;
private $_tags = array();
private $_floats = array();
private $_changed = array();
private int $_offset = 0;
private bool $_closed = true;
private string $_body;
private int $_type = 0;
private mixed $_open;
private mixed $_close;
private array $_tags = [];
private array $_floats = [];
private array $_changed = [];
/**
* Create tag entity
* @param string $name the tag name
* @param Template $tpl current template
* @param string $info tag's information
* @param array $info tag's information
* @param string $body template's code
*/
public function __construct($name, Template $tpl, $info, &$body)
public function __construct(string $name, Template $tpl, array $info, string &$body)
{
parent::__construct();
$this->tpl = $tpl;
$this->name = $name;
$this->line = $tpl->getLine();
@ -62,8 +63,8 @@ class Tag extends \ArrayObject
if ($this->_type & self::BLOCK) {
$this->_open = $info["open"];
$this->_close = $info["close"];
$this->_tags = isset($info["tags"]) ? $info["tags"] : array();
$this->_floats = isset($info["float_tags"]) ? $info["float_tags"] : array();
$this->_tags = $info["tags"] ?? [];
$this->_floats = $info["float_tags"] ?? [];
$this->_closed = false;
} else {
$this->_open = $info["parser"];
@ -79,7 +80,7 @@ class Tag extends \ArrayObject
* @param string $option
* @throws \RuntimeException
*/
public function tagOption($option)
public function tagOption(string $option)
{
if (method_exists($this, 'opt' . $option)) {
$this->options[] = $option;
@ -93,7 +94,7 @@ class Tag extends \ArrayObject
* @param int $option option constant
* @param bool $value true add option, false remove option
*/
public function setOption($option, $value)
public function setOption(int $option, bool $value)
{
$actual = (bool)($this->tpl->getOptions() & $option);
if ($actual != $value) {
@ -106,7 +107,7 @@ class Tag extends \ArrayObject
* Restore the option
* @param int $option
*/
public function restore($option)
public function restore(int $option)
{
if (isset($this->_changed[$option])) {
$this->tpl->setOption($option, $this->_changed[$option]);
@ -126,7 +127,7 @@ class Tag extends \ArrayObject
* Check, if the tag closed
* @return bool
*/
public function isClosed()
public function isClosed(): bool
{
return $this->_closed;
}
@ -137,7 +138,7 @@ class Tag extends \ArrayObject
* @param Tokenizer $tokenizer
* @return mixed
*/
public function start($tokenizer)
public function start(Tokenizer $tokenizer): mixed
{
foreach ($this->options as $option) {
$option = 'opt' . $option;
@ -153,7 +154,7 @@ class Tag extends \ArrayObject
* @param int $level
* @return bool
*/
public function hasTag($tag, $level)
public function hasTag(string $tag, int $level): bool
{
if (isset($this->_tags[$tag])) {
if ($level) {
@ -171,10 +172,10 @@ class Tag extends \ArrayObject
*
* @param string $tag
* @param Tokenizer $tokenizer
* @throws \LogicException
* @return string
* @throws \LogicException
*/
public function tag($tag, $tokenizer)
public function tag(string $tag, Tokenizer $tokenizer): string
{
if (isset($this->_tags[$tag])) {
return call_user_func($this->_tags[$tag], $tokenizer, $this);
@ -187,10 +188,10 @@ class Tag extends \ArrayObject
* Close callback
*
* @param Tokenizer $tokenizer
* @throws \LogicException
* @return string
* @throws \LogicException
*/
public function end($tokenizer)
public function end(Tokenizer $tokenizer): string
{
if ($this->_closed) {
throw new \LogicException("Tag {$this->name} already closed");
@ -204,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");
}
@ -224,7 +225,7 @@ class Tag extends \ArrayObject
* @throws \LogicException
* @return string
*/
public function getContent()
public function getContent(): string
{
return substr($this->_body, $this->_offset);
}
@ -235,7 +236,7 @@ class Tag extends \ArrayObject
* @return string
* @throws \LogicException
*/
public function cutContent()
public function cutContent(): string
{
$content = substr($this->_body, $this->_offset);
$this->_body = substr($this->_body, 0, $this->_offset);
@ -258,7 +259,7 @@ class Tag extends \ArrayObject
* @param string $code
* @return string
*/
public function out($code)
public function out(string $code): string
{
return $this->tpl->out($code, $this->escape);
}

View File

@ -52,77 +52,88 @@ class Template extends Render
/**
* @var int shared counter
*/
public $i = 1;
public int $i = 1;
/**
* @var array of macros
*/
public $macros = array();
public array $macros = [];
/**
* @var array of blocks
*/
public $blocks = array();
public array $blocks = [];
/**
* @var string|null
*/
public $extends;
public ?string $extends = null;
/**
* @var string|null
*/
public $extended;
public ?string $extended;
/**
* Stack of extended templates
* @var array
*/
public $ext_stack = array();
public array $ext_stack = [];
public $extend_body = false;
public bool $extend_body = false;
public string $dynamic_extends = "";
/**
* Parent template
* @var Template
*/
public $parent;
public ?Template $parent;
/**
* Template PHP code
* @var string
*/
private $_body;
private $_compile_stage = 0;
private string $_body = "";
private int $_compile_stage = 0;
/**
* Call stack
* @var Tag[]
*/
private $_stack = array();
private array $_stack = [];
/**
* Template source
* @var string
*/
private $_src;
private string $_src;
/**
* @var int
*/
private $_line = 1;
private $_post = array();
private int $_line = 1;
/**
* @var bool|string
* @var callable[]
*/
private $_ignore = false;
private array $_post = [];
/**
* @var string|null
*/
private ?string $_ignore = null;
private $_before = array();
/**
* @var string[]
*/
private array $_before = [];
private $_filters = array();
private array $_filters = [];
/**
* @var int crc32 of the template name
*/
private $_crc = 0;
private int $_crc = 0;
/**
* @var callable[]
*/
private array $_tag_filters;
/**
* @param Fenom $fenom Template storage
@ -142,16 +153,16 @@ class Template extends Render
* Get tag stack size
* @return int
*/
public function getStackSize()
public function getStackSize(): int
{
return count($this->_stack);
}
/**
* @param string $tag
* @return bool|\Fenom\Tag
* @return Tag|null
*/
public function getParentScope($tag)
public function getParentScope(string $tag): ?Tag
{
for ($i = count($this->_stack) - 1; $i >= 0; $i--) {
if ($this->_stack[$i]->name == $tag) {
@ -159,7 +170,7 @@ class Template extends Render
}
}
return false;
return null;
}
/**
@ -167,8 +178,9 @@ class Template extends Render
* @param string $name
* @param bool $compile
* @return self
* @throws CompileException
*/
public function load($name, $compile = true)
public function load(string $name, bool $compile = true): static
{
$this->_name = $name;
$this->_crc = crc32($this->_name);
@ -193,8 +205,9 @@ class Template extends Render
* @param string $src template source
* @param bool $compile
* @return \Fenom\Template
* @throws CompileException
*/
public function source($name, $src, $compile = true)
public function source(string $name, string $src, bool $compile = true): static
{
$this->_name = $name;
$this->_src = $src;
@ -309,7 +322,8 @@ class Template extends Render
$this->_compile_stage = self::COMPILE_STAGE_POST_FILTERED;
}
public function isStageDone($stage_no) {
public function isStageDone(int $stage_no): bool
{
return $this->_compile_stage >= $stage_no;
}
@ -318,7 +332,7 @@ class Template extends Render
* @param int $option
* @param bool $value
*/
public function setOption($option, $value)
public function setOption(int $option, bool $value)
{
if ($value) {
$this->_options |= $option;
@ -329,21 +343,21 @@ class Template extends Render
/**
* Execute some code at loading cache
* @param $code
* @param string $code
* @return void
*/
public function before($code)
public function before(string $code): void
{
$this->_before[] = $code;
}
/**
* Generate name of temporary internal template variable (may be random)
* Generate name of temporary internal template variable (maybe random)
* @return string
*/
public function tmpVar()
public function tmpVar(): string
{
return sprintf('$t%x_%x', $this->_crc ? $this->_crc : mt_rand(0, 0x7FFFFFFF), $this->i++);
return sprintf('$t%x_%x', $this->_crc ?: mt_rand(0, 0x7FFFFFFF), $this->i++);
}
/**
@ -351,12 +365,12 @@ class Template extends Render
*
* @param string $text
*/
private function _appendText($text)
private function _appendText(string $text)
{
$this->_line += substr_count($text, "\n");
$strip = $this->_options & Fenom::AUTO_STRIP;
if ($this->_filters) {
if (strpos($text, "<?") === false) {
if (!str_contains($text, "<?")) {
foreach ($this->_filters as $filter) {
$text = call_user_func($filter, $this, $text);
}
@ -385,9 +399,9 @@ class Template extends Render
* Append PHP code to template body
*
* @param string $code
* @param $source
* @param string $source
*/
private function _appendCode($code, $source)
private function _appendCode(string $code, string $source)
{
if (!$code) {
return;
@ -398,17 +412,17 @@ class Template extends Render
}
/**
* @param $tag_name
* @param string $tag_name
*/
public function ignore($tag_name)
public function ignore(string $tag_name)
{
$this->_ignore = $tag_name;
}
/**
* @param callable[] $cb
* @param callable $cb
*/
public function addPostCompile($cb)
public function addPostCompile(callable $cb)
{
$this->_post[] = $cb;
}
@ -418,7 +432,7 @@ class Template extends Render
*
* @return string
*/
public function getBody()
public function getBody(): string
{
return $this->_body;
}
@ -428,7 +442,7 @@ class Template extends Render
*
* @return string
*/
public function getTemplateCode()
public function getTemplateCode(): string
{
$before = $this->_before ? implode("\n", $this->_before) . "\n" : "";
return "<?php \n" .
@ -449,7 +463,7 @@ class Template extends Render
* Make array with macros code
* @return string
*/
private function _getMacrosArray()
private function _getMacrosArray(): string
{
if ($this->macros) {
$macros = array();
@ -468,9 +482,9 @@ class Template extends Render
* Return closure code
* @return string
*/
private function _getClosureSource()
private function _getClosureSource(): string
{
return "function (\$var, \$tpl) {\n?>{$this->_body}<?php\n}";
return "function (mixed \$var, mixed \$tpl) {\n?>{$this->_body}<?php\n}";
}
/**
@ -478,11 +492,11 @@ class Template extends Render
*
* @param array $values input values
* @throws CompileException
* @return Render
* @return array
*/
public function display(array $values)
public function display(array $values): array
{
if (!$this->_code) {
if (!$this->_code) { // TODO: remove
// evaluate template's code
eval("\$this->_code = " . $this->_getClosureSource() . ";\n\$this->_macros = " . $this->_getMacrosArray() . ';');
if (!$this->_code) {
@ -506,10 +520,10 @@ class Template extends Render
* Output the value
*
* @param string $data
* @param null|bool $escape
* @param bool|null $escape
* @return string
*/
public function out($data, $escape = null)
public function out(string $data, ?bool $escape = null): string
{
if ($escape === null) {
$escape = $this->_options & Fenom::AUTO_ESCAPE;
@ -524,8 +538,9 @@ class Template extends Render
/**
* Import block from another template
* @param string $tpl
* @throws CompileException
*/
public function importBlocks($tpl)
public function importBlocks(string $tpl)
{
$donor = $this->_fenom->compile($tpl, false);
foreach ($donor->blocks as $name => $block) {
@ -541,8 +556,9 @@ class Template extends Render
* Extends the template
* @param string $tpl
* @return \Fenom\Template parent
* @throws CompileException
*/
public function extend($tpl)
public function extend(string $tpl): Template
{
if (!$this->isStageDone(self::COMPILE_STAGE_PARSED)) {
$this->compile();
@ -573,7 +589,7 @@ class Template extends Render
* @throws CompileException
* @return string executable PHP code
*/
public function parseTag(Tokenizer $tokens)
public function parseTag(Tokenizer $tokens): string
{
try {
if ($tokens->is(Tokenizer::MACRO_STRING)) {
@ -601,7 +617,7 @@ class Template extends Render
* @return string
* @throws TokenizeException
*/
public function parseEndTag(Tokenizer $tokens)
public function parseEndTag(Tokenizer $tokens): string
{
$name = $tokens->getNext(Tokenizer::MACRO_STRING);
$tokens->next();
@ -626,12 +642,12 @@ class Template extends Render
* @throws Error\TokenizeException
* @return string
*/
public function parseAct(Tokenizer $tokens)
public function parseAct(Tokenizer $tokens): string
{
$action = $tokens->get(Tokenizer::MACRO_STRING);
$tokens->next();
if ($tokens->is("(", T_DOUBLE_COLON, T_NS_SEPARATOR) && !$tokens->isWhiteSpaced()
) { // just invoke function or static method
if ($tokens->is("(", T_DOUBLE_COLON, "\\") && !$tokens->isWhiteSpaced()) {
// just invoke function or static method
$tokens->back();
return $this->out($this->parseExpr($tokens));
} elseif ($tokens->is('.')) {
@ -652,9 +668,9 @@ class Template extends Render
if ($tag->isClosed()) {
$tag->restoreAll();
} else {
array_push($this->_stack, $tag);
$this->_stack[] = $tag;
}
return $code;
return (string)$code;
}
for ($j = $i = count($this->_stack) - 1; $i >= 0; $i--) { // call function's internal tag
@ -678,7 +694,7 @@ class Template extends Render
* Get current template line
* @return int
*/
public function getLine()
public function getLine(): int
{
return $this->_line;
}
@ -688,10 +704,9 @@ class Template extends Render
*
* @param Tokenizer $tokens
* @param bool $is_var
* @throws \Exception
* @return string
*/
public function parseExpr(Tokenizer $tokens, &$is_var = false)
public function parseExpr(Tokenizer $tokens, ?bool &$is_var = false): string
{
$exp = array();
$var = false; // last term was: true - variable, false - mixed
@ -700,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;
@ -801,10 +816,9 @@ class Template extends Render
* @param Tokenizer $tokens
* @param bool $is_var is parsed term - plain variable
* @param int $allows
* @throws \Exception
* @return bool|string
* @return string
*/
public function parseTerm(Tokenizer $tokens, &$is_var = false, $allows = -1)
public function parseTerm(Tokenizer $tokens, ?bool &$is_var = false, int $allows = -1): string
{
$is_var = false;
if ($tokens->is(Tokenizer::MACRO_UNARY)) {
@ -892,12 +906,12 @@ class Template extends Render
$call = $func . $this->parseArgs($tokens->next());
}
$code = $unary . $this->parseChain($tokens, $call);
} elseif ($tokens->isNext(T_NS_SEPARATOR, T_DOUBLE_COLON)) {
} elseif ($tokens->isNext("\\", T_DOUBLE_COLON)) {
$method = $this->parseStatic($tokens);
$args = $this->parseArgs($tokens);
$code = $unary . $this->parseChain($tokens, $method . $args);
} else {
return false;
return "";
}
break;
case T_ISSET:
@ -920,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('|')) {
@ -941,7 +955,7 @@ class Template extends Render
* @param string $code start point (it is $var)
* @return string
*/
public function parseChain(Tokenizer $tokens, $code)
public function parseChain(Tokenizer $tokens, string $code): string
{
do {
if ($tokens->is('(')) {
@ -951,8 +965,8 @@ class Template extends Render
$code .= '->' . $tokens->next()->getAndNext();
}
if ($tokens->current() === "." || $tokens->current() === "[") {
$code = substr($code, 0, -strlen($tokens->prev[1]));
$code .= $this->parseVariable($tokens, $tokens->prev[1]);
$code = substr($code, 0, -strlen($tokens->prevToken()[1]));
$code .= $this->parseVariable($tokens, $tokens->prevToken()[1]);
}
} while ($tokens->is('(', T_OBJECT_OPERATOR));
@ -962,11 +976,11 @@ class Template extends Render
/**
* Parse variable name: $a, $a.b, $a.b['c'], $a:index
* @param Tokenizer $tokens
* @param $var
* @return string
* @throws Error\UnexpectedTokenException
* @param string|null $var
* @return string|null
* @throws CompileException
*/
public function parseVariable(Tokenizer $tokens, $var = null)
public function parseVariable(Tokenizer $tokens, ?string $var = null): ?string
{
if (!$var) {
if ($tokens->isNext('@')) {
@ -1037,7 +1051,7 @@ class Template extends Render
* @param bool $is_var
* @return string
*/
public function parseAccessor(Tokenizer $tokens, &$is_var = false)
public function parseAccessor(Tokenizer $tokens, ?bool &$is_var = false): string
{
$accessor = $tokens->need('$')->next()->need('.')->next()->current();
$parser = $this->getStorage()->getAccessor($accessor);
@ -1050,16 +1064,16 @@ class Template extends Render
', "callback"), ' . var_export($accessor, true) . ', $tpl, $var)';
} else {
return call_user_func_array(
$parser['parser'], array(
$parser['accessor'],
$tokens->next(),
$this,
&$is_var
)
$parser['parser'], [
$parser['accessor'],
$tokens->next(),
$this,
&$is_var
]
);
}
} else {
return call_user_func_array($parser, array($tokens->next(), $this, &$is_var));
return call_user_func_array($parser, [$tokens->next(), $this, &$is_var]);
}
} else {
throw new \RuntimeException("Unknown accessor '\$.$accessor'");
@ -1075,7 +1089,7 @@ class Template extends Render
* @return string
* @throws UnexpectedTokenException
*/
public function parseTernary(Tokenizer $tokens, $var, $is_var)
public function parseTernary(Tokenizer $tokens, $var, $is_var): string
{
$empty = $tokens->is('?');
$tokens->next();
@ -1094,12 +1108,7 @@ class Template extends Render
return '((' . $var . ' !== null) ? ' . $var . ' : (' . $this->parseExpr($tokens) . '))';
}
}
} elseif ($tokens->is(
Tokenizer::MACRO_BINARY,
Tokenizer::MACRO_BOOLEAN,
Tokenizer::MACRO_MATH
) || !$tokens->valid()
) {
} elseif ($tokens->is(Tokenizer::MACRO_BINARY, Tokenizer::MACRO_BOOLEAN, Tokenizer::MACRO_MATH ) || !$tokens->valid()) {
if ($empty) {
if ($is_var) {
return '!empty(' . $var . ')';
@ -1135,14 +1144,14 @@ class Template extends Render
/**
* Parse 'is' and 'is not' operators
* @see _tests
* @param Tokenizer $tokens
* @param string $value
* @param bool $variable
* @throws InvalidUsageException
* @return string
* @throws InvalidUsageException
* @see _tests
*/
public function parseIs(Tokenizer $tokens, $value, $variable = false)
public function parseIs(Tokenizer $tokens, string $value, bool $variable = false): string
{
$tokens->next();
if ($tokens->current() == 'not') {
@ -1169,7 +1178,7 @@ class Template extends Render
return $invert . '(' . $value . ' instanceof \\' . $this->parseName($tokens) . ')';
} elseif ($tokens->is(T_VARIABLE, '[', Tokenizer::MACRO_SCALAR, '"')) {
return '(' . $value . ' ' . $equal . '= ' . $this->parseTerm($tokens) . ')';
} elseif ($tokens->is(T_NS_SEPARATOR)) { //
} elseif ($tokens->is("\\")) { //
return $invert . '(' . $value . ' instanceof \\' . $this->parseName($tokens) . ')';
} else {
throw new InvalidUsageException("Unknown argument");
@ -1180,11 +1189,11 @@ class Template extends Render
* Parse 'in' and 'not in' operators
* @param Tokenizer $tokens
* @param string $value
* @throws InvalidUsageException
* @throws UnexpectedTokenException
* @return string
* @throws UnexpectedTokenException
* @throws InvalidUsageException
*/
public function parseIn(Tokenizer $tokens, $value)
public function parseIn(Tokenizer $tokens, string $value): string
{
$checkers = array(
"string" => 'is_int(strpos(%2$s, %1$s))',
@ -1239,13 +1248,13 @@ class Template extends Render
* @param Tokenizer $tokens
* @return string
*/
public function parseName(Tokenizer $tokens)
public function parseName(Tokenizer $tokens): string
{
$tokens->skipIf(T_NS_SEPARATOR);
$tokens->skipIf("\\");
$name = "";
if ($tokens->is(T_STRING)) {
$name .= $tokens->getAndNext();
while ($tokens->is(T_NS_SEPARATOR)) {
while ($tokens->is("\\")) {
$name .= '\\' . $tokens->next()->get(T_STRING);
$tokens->next();
}
@ -1260,7 +1269,7 @@ class Template extends Render
* @throws Error\UnexpectedTokenException
* @return string
*/
public function parseScalar(Tokenizer $tokens)
public function parseScalar(Tokenizer $tokens): string
{
$token = $tokens->key();
switch ($token) {
@ -1284,7 +1293,7 @@ class Template extends Render
* @throws UnexpectedTokenException
* @return string
*/
public function parseQuote(Tokenizer $tokens)
public function parseQuote(Tokenizer $tokens): string
{
if ($tokens->is('"')) {
$stop = $tokens->current();
@ -1348,29 +1357,30 @@ class Template extends Render
*
* @param Tokenizer $tokens
* @param $value
* @throws \LogicException
* @throws \Exception
* @return string
* @throws CompileException
*/
public function parseModifier(Tokenizer $tokens, $value)
public function parseModifier(Tokenizer $tokens, $value): string
{
while ($tokens->is("|")) {
$modifier = $tokens->getNext(Tokenizer::MACRO_STRING);
if ($tokens->isNext(T_DOUBLE_COLON, T_NS_SEPARATOR)) {
if ($tokens->isNext(T_DOUBLE_COLON, "\\")) {
$mods = $this->parseStatic($tokens);
} 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();
}
$args = array();
while ($tokens->is(":")) {
if (($args[] = $this->parseTerm($tokens->next(), $is_var, 0)) === false) {
$term = $this->parseTerm($tokens->next(), $is_var, 0);
if ($term === "") {
throw new UnexpectedTokenException($tokens);
}
$args[] = $term;
}
if (!is_string($mods)) { // dynamic modifier
@ -1392,11 +1402,11 @@ class Template extends Render
* [1, 2.3, 5+7/$var, 'string', "str {$var+3} ing", $var2, []]
*
* @param Tokenizer $tokens
* @param int $count amount of elements
* @throws Error\UnexpectedTokenException
* @param int $count number of elements
* @return string
* @throws Error\UnexpectedTokenException
*/
public function parseArray(Tokenizer $tokens, &$count = 0)
public function parseArray(Tokenizer $tokens, int &$count = 0): string
{
if ($tokens->is("[")) {
$arr = array();
@ -1433,7 +1443,7 @@ class Template extends Render
* @return string
* @throws InvalidUsageException
*/
public function parseMacroCall(Tokenizer $tokens, $name)
public function parseMacroCall(Tokenizer $tokens, $name): string
{
$recursive = false;
$macro = false;
@ -1481,18 +1491,18 @@ class Template extends Render
* @param Tokenizer $tokens
* @throws \LogicException
* @throws \RuntimeException
* @return string
* @return callable
*/
public function parseStatic(Tokenizer $tokens)
public function parseStatic(Tokenizer $tokens): callable
{
if ($this->_options & Fenom::DENY_STATICS) {
throw new \LogicException("Static methods are disabled");
}
$tokens->skipIf(T_NS_SEPARATOR);
$tokens->skipIf("\\");
$name = "";
if ($tokens->is(T_STRING)) {
$name .= $tokens->getAndNext();
while ($tokens->is(T_NS_SEPARATOR)) {
while ($tokens->is("\\")) {
$name .= '\\' . $tokens->next()->get(T_STRING);
$tokens->next();
}
@ -1512,7 +1522,7 @@ class Template extends Render
* @param Tokenizer $tokens
* @return string
*/
public function parseArgs(Tokenizer $tokens)
public function parseArgs(Tokenizer $tokens): string
{
$_args = "(";
$tokens->next();
@ -1555,10 +1565,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, &$static)
public function parsePlainArg(Tokenizer $tokens, ?string &$static = null): string
{
if ($tokens->is(T_CONSTANT_ENCAPSED_STRING)) {
if ($tokens->isNext('|')) {
@ -1578,10 +1588,8 @@ class Template extends Render
* Parse parameters as $key=$value
* param1=$var param2=3 ...
*
* @static
* @param Tokenizer $tokens
* @param array $defaults
* @throws \Exception
* @param array|null $defaults
* @return array
*/
public function parseParams(Tokenizer $tokens, array $defaults = null)

View File

@ -19,9 +19,6 @@ use Fenom\Error\UnexpectedTokenException;
* - Line number of the token
*
* @see http://php.net/tokenizer
* @property array $prev the previous token
* @property array $curr the current token
* @property array $next the next token
*
* @package Fenom
* @author Ivan Shalganov <a.cobest@gmail.com>
@ -70,18 +67,18 @@ class Tokenizer
*/
const MACRO_COND = 1008;
public $tokens;
public $p = 0;
public $quotes = 0;
private $_max = 0;
private $_last_no = 0;
public array $tokens;
public int $p = 0;
public int $quotes = 0;
private int $_max;
private mixed $_last_no;
/**
* @see http://docs.php.net/manual/en/tokens.php
* @var array groups of tokens
*/
public static $macros = array(
self::MACRO_STRING => array(
public static array $macros = [
self::MACRO_STRING => [
\T_ABSTRACT => 1, \T_ARRAY => 1, \T_AS => 1, \T_BREAK => 1,
\T_CASE => 1, \T_CATCH => 1, \T_CLASS => 1,
\T_CLASS_C => 1, \T_CLONE => 1, \T_CONST => 1, \T_CONTINUE => 1,
@ -92,23 +89,24 @@ class Tokenizer
\T_EXTENDS => 1, \T_FILE => 1, \T_FINAL => 1, \T_FOR => 1,
\T_FOREACH => 1, \T_FUNCTION => 1, \T_FUNC_C => 1, \T_GLOBAL => 1,
\T_GOTO => 1, \T_HALT_COMPILER => 1, \T_IF => 1, \T_IMPLEMENTS => 1,
\T_INCLUDE => 1, \T_INCLUDE_ONCE => 1, \T_INSTANCEOF => 1, 341 /* T_INSTEADOF */ => 1,
\T_INCLUDE => 1, \T_INCLUDE_ONCE => 1, \T_INSTANCEOF => 1, \T_INSTEADOF => 1,
\T_INTERFACE => 1, \T_ISSET => 1, \T_LINE => 1, \T_LIST => 1,
\T_LOGICAL_AND => 1, \T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_METHOD_C => 1,
\T_MATCH => 1,
\T_NAMESPACE => 1, \T_NS_C => 1, \T_NEW => 1, \T_PRINT => 1,
\T_PRIVATE => 1, \T_PUBLIC => 1, \T_PROTECTED => 1, \T_REQUIRE => 1,
\T_REQUIRE_ONCE => 1, \T_RETURN => 1, \T_RETURN => 1, \T_STRING => 1,
\T_SWITCH => 1, \T_THROW => 1, 355 /* T_TRAIT */ => 1, 365 /* T_TRAIT_C */ => 1,
\T_REQUIRE_ONCE => 1, \T_RETURN => 1, \T_STRING => 1,
\T_SWITCH => 1, \T_THROW => 1, \T_TRAIT => 1, \T_TRAIT_C => 1,
\T_TRY => 1, \T_UNSET => 1, \T_USE => 1, \T_VAR => 1,
\T_WHILE => 1, 267 /* T_YIELD */ => 1
),
self::MACRO_INCDEC => array(
\T_WHILE => 1, \T_YIELD => 1, \T_YIELD_FROM => 1
],
self::MACRO_INCDEC => [
\T_INC => 1, \T_DEC => 1
),
self::MACRO_UNARY => array(
],
self::MACRO_UNARY => [
"!" => 1, "~" => 1, "-" => 1
),
self::MACRO_BINARY => array(
],
self::MACRO_BINARY => [
\T_BOOLEAN_AND => 1, \T_BOOLEAN_OR => 1, \T_IS_GREATER_OR_EQUAL => 1,
\T_IS_EQUAL => 1, \T_IS_IDENTICAL => 1, \T_IS_NOT_EQUAL => 1,
\T_IS_NOT_IDENTICAL => 1, \T_IS_SMALLER_OR_EQUAL => 1, \T_LOGICAL_AND => 1,
@ -117,36 +115,36 @@ class Tokenizer
"*" => 1, "/" => 1, ">" => 1,
"<" => 1, "^" => 1, "%" => 1,
"&" => 1
),
self::MACRO_BOOLEAN => array(
],
self::MACRO_BOOLEAN => [
\T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1,
\T_BOOLEAN_AND => 1, \T_BOOLEAN_OR => 1,
\T_LOGICAL_AND => 1
),
self::MACRO_MATH => array(
],
self::MACRO_MATH => [
"+" => 1, "-" => 1, "*" => 1,
"/" => 1, "^" => 1, "%" => 1,
"&" => 1, "|" => 1
),
self::MACRO_COND => array(
],
self::MACRO_COND => [
\T_IS_EQUAL => 1, \T_IS_IDENTICAL => 1, ">" => 1,
"<" => 1, \T_SL => 1, \T_SR => 1,
\T_IS_NOT_EQUAL => 1, \T_IS_NOT_IDENTICAL => 1, \T_IS_SMALLER_OR_EQUAL => 1,
),
self::MACRO_EQUALS => array(
],
self::MACRO_EQUALS => [
\T_AND_EQUAL => 1, \T_DIV_EQUAL => 1, \T_MINUS_EQUAL => 1,
\T_MOD_EQUAL => 1, \T_MUL_EQUAL => 1, \T_OR_EQUAL => 1,
\T_PLUS_EQUAL => 1, \T_SL_EQUAL => 1, \T_SR_EQUAL => 1,
\T_XOR_EQUAL => 1, '=' => 1,
),
self::MACRO_SCALAR => array(
],
self::MACRO_SCALAR => [
\T_LNUMBER => 1,
\T_DNUMBER => 1,
\T_CONSTANT_ENCAPSED_STRING => 1
)
);
]
];
public static $description = array(
public static array $description = [
self::MACRO_STRING => 'string',
self::MACRO_INCDEC => 'increment/decrement operator',
self::MACRO_UNARY => 'unary operator',
@ -156,27 +154,34 @@ class Tokenizer
self::MACRO_COND => 'conditional operator',
self::MACRO_EQUALS => 'equal operator',
self::MACRO_SCALAR => 'scalar value'
);
];
/**
* Special tokens
* @var array
*/
private static $spec = array(
private static array $spec = [
'true' => 1,
'false' => 1,
'null' => 1,
'TRUE' => 1,
'FALSE' => 1,
'NULL' => 1
);
];
private ?array $_next;
private ?array $_prev;
private ?array $_curr;
/**
* @param $query
* @param string $query
*/
public function __construct($query)
public function __construct(string $query)
{
$tokens = array(-1 => array(\T_WHITESPACE, '', '', 1));
$this->_curr = null;
$this->_next = null;
$this->_prev = null;
$tokens = [-1 => [\T_WHITESPACE, '', '', 1]];
$_tokens = token_get_all("<?php " . $query);
$line = 1;
array_shift($_tokens);
@ -186,16 +191,39 @@ class Tokenizer
if ($token === '"' || $token === "'" || $token === "`") {
$this->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(strpos($token[1], '.') === 0) {
if(str_starts_with($token[1], '.')) {
$tokens[] = array(
'.',
'.',
@ -239,9 +267,9 @@ class Tokenizer
/**
* Is incomplete mean some string not closed
*
* @return int
* @return bool
*/
public function isIncomplete()
public function isIncomplete(): bool
{
return ($this->quotes % 2) || ($this->tokens[$this->_max][0] === T_ENCAPSED_AND_WHITESPACE);
}
@ -252,9 +280,10 @@ class Tokenizer
* @link http://php.net/manual/en/iterator.current.php
* @return mixed Can return any type.
*/
public function current()
public function current(): mixed
{
return $this->curr ? $this->curr[1] : null;
$curr = $this->currToken();
return $curr ? $curr[1] : null;
}
/**
@ -263,13 +292,13 @@ class Tokenizer
* @link http://php.net/manual/en/iterator.next.php
* @return Tokenizer
*/
public function next()
public function next(): static
{
if ($this->p > $this->_max) {
return $this;
}
$this->p++;
unset($this->prev, $this->curr, $this->next);
$this->cleanTokenCache();
return $this;
}
@ -277,10 +306,10 @@ class Tokenizer
* Check token type. If token type is one of expected types return true. Otherwise return false
*
* @param array $expects
* @param string|int $token
* @param int|string $token
* @return bool
*/
private function _valid($expects, $token)
private function _valid(array $expects, int|string $token): bool
{
foreach ($expects as $expect) {
if (is_string($expect) || $expect < 1000) {
@ -300,13 +329,13 @@ class Tokenizer
/**
* If the next token is a valid one, move the position of cursor one step forward. Otherwise throws an exception.
* @param array $tokens
* @throws UnexpectedTokenException
* @return mixed
* @throws UnexpectedTokenException
*/
public function _next($tokens)
public function _next(array $tokens): void
{
$this->next();
if (!$this->curr) {
if (!$this->currToken()) {
throw new UnexpectedTokenException($this, $tokens);
}
if ($tokens) {
@ -323,7 +352,7 @@ class Tokenizer
* Fetch next specified token or throw an exception
* @return mixed
*/
public function getNext( /*int|string $token1, int|string $token2, ... */)
public function getNext( /*int|string $token1, int|string $token2, ... */): mixed
{
$this->_next(func_get_args());
return $this->current();
@ -333,9 +362,10 @@ class Tokenizer
* @param $token
* @return bool
*/
public function isNextToken($token)
public function isNextToken($token): bool
{
return $this->next ? $this->next[1] == $token : false;
$next = $this->nextToken();
return $next && $next[1] == $token;
}
/**
@ -343,10 +373,11 @@ class Tokenizer
* @return mixed
* @throws UnexpectedTokenException
*/
public function getAndNext( /* $token1, ... */)
public function getAndNext( /* $token1, ... */): mixed
{
if ($this->curr) {
$cur = $this->curr[1];
$curr = $this->currToken();
if ($curr) {
$cur = $curr[1];
$this->next();
return $cur;
} else {
@ -359,9 +390,10 @@ class Tokenizer
* @param $token1
* @return bool
*/
public function isNext($token1 /*, ...*/)
public function isNext($token1 /*, ...*/): bool
{
return $this->next && $this->_valid(func_get_args(), $this->next[0]);
$next = $this->nextToken();
return $next && $this->_valid(func_get_args(), $next[0]);
}
/**
@ -369,9 +401,10 @@ class Tokenizer
* @param $token1
* @return bool
*/
public function is($token1 /*, ...*/)
public function is($token1 /*, ...*/): bool
{
return $this->curr && $this->_valid(func_get_args(), $this->curr[0]);
$curr = $this->currToken();
return $curr && $this->_valid(func_get_args(), $curr[0]);
}
/**
@ -379,22 +412,24 @@ class Tokenizer
* @param $token1
* @return bool
*/
public function isPrev($token1 /*, ...*/)
public function isPrev($token1 /*, ...*/): bool
{
return $this->prev && $this->_valid(func_get_args(), $this->prev[0]);
$prev = $this->prevToken();
return $prev && $this->_valid(func_get_args(), $prev[0]);
}
/**
* Get specified token
*
* @param string|int $token1
* @throws UnexpectedTokenException
* @param int|string $token1
* @return mixed
*@throws UnexpectedTokenException
*/
public function get($token1 /*, $token2 ...*/)
public function get(int|string $token1 /*, $token2 ...*/): mixed
{
if ($this->curr && $this->_valid(func_get_args(), $this->curr[0])) {
return $this->curr[1];
$curr = $this->currToken();
if ($curr && $this->_valid(func_get_args(), $curr[0])) {
return $curr[1];
} else {
throw new UnexpectedTokenException($this, func_get_args());
}
@ -404,21 +439,21 @@ class Tokenizer
* Step back
* @return Tokenizer
*/
public function back()
public function back(): static
{
if ($this->p === 0) {
return $this;
}
$this->p--;
unset($this->prev, $this->curr, $this->next);
$this->cleanTokenCache();
return $this;
}
/**
* @param $token1
* @param int|string $token1
* @return bool
*/
public function hasBackList($token1 /*, $token2 ...*/)
public function hasBackList(int|string $token1 /*, $token2 ...*/): bool
{
$tokens = func_get_args();
$c = $this->p;
@ -431,27 +466,38 @@ class Tokenizer
return true;
}
/**
* Lazy load properties
*
* @param string $key
* @return mixed
*/
public function __get($key)
public function prevToken(): mixed
{
switch ($key) {
case 'curr':
return $this->curr = ($this->p <= $this->_max) ? $this->tokens[$this->p] : null;
case 'next':
return $this->next = ($this->p + 1 <= $this->_max) ? $this->tokens[$this->p + 1] : null;
case 'prev':
return $this->prev = $this->p ? $this->tokens[$this->p - 1] : null;
default:
return $this->$key = null;
if ($this->_prev) {
return $this->_prev;
}
return $this->_prev = $this->p ? $this->tokens[$this->p - 1] : null;
}
public function count()
public function currToken(): mixed
{
if ($this->_curr !== null) {
return $this->_curr;
}
return $this->_curr = ($this->p <= $this->_max) ? $this->tokens[$this->p] : null;
}
public function nextToken(): mixed
{
if ($this->_next) {
return $this->_next;
}
return $this->_next = ($this->p + 1 <= $this->_max) ? $this->tokens[$this->p + 1] : null;
}
protected function cleanTokenCache(): void
{
$this->_prev = null;
$this->_curr = null;
$this->_next = null;
}
public function count(): int
{
return $this->_max;
}
@ -460,9 +506,10 @@ class Tokenizer
* Return the key of the current element
* @return mixed scalar on success, or null on failure.
*/
public function key()
public function key(): mixed
{
return $this->curr ? $this->curr[0] : null;
$curr = $this->currToken();
return $curr ? $curr[0] : null;
}
/**
@ -470,18 +517,18 @@ class Tokenizer
* @return boolean The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure.
*/
public function valid()
public function valid(): bool
{
return (bool)$this->curr;
return (bool)$this->currToken();
}
/**
* Get token name
* @static
* @param int|string $token
* @return string
* @param mixed $token
* @return string|null
*/
public static function getName($token)
public static function getName(mixed $token): ?string
{
if (is_string($token)) {
return $token;
@ -500,10 +547,11 @@ class Tokenizer
* @throws UnexpectedTokenException
* @return Tokenizer
*/
public function skip( /*$token1, $token2, ...*/)
public function skip( /*$token1, $token2, ...*/): static
{
if (func_num_args()) {
if ($this->curr && $this->_valid(func_get_args(), $this->curr[0])) {
$curr = $this->currToken();
if ($curr && $this->_valid(func_get_args(), $curr[0])) {
$this->next();
return $this;
} else {
@ -521,9 +569,10 @@ class Tokenizer
* @param int|string $token1
* @return Tokenizer
*/
public function skipIf($token1 /*, $token2, ...*/)
public function skipIf(int|string $token1 /*, $token2, ...*/): static
{
if ($this->curr && $this->_valid(func_get_args(), $this->curr[0])) {
$curr = $this->currToken();
if ($curr && $this->_valid(func_get_args(), $curr[0])) {
$this->next();
}
return $this;
@ -536,9 +585,10 @@ class Tokenizer
* @return Tokenizer
* @throws UnexpectedTokenException
*/
public function need($token1 /*, $token2, ...*/)
public function need(int|string $token1 /*, $token2, ...*/): static
{
if ($this->curr && $this->_valid(func_get_args(), $this->curr[0])) {
$curr = $this->currToken();
if ($curr && $this->_valid(func_get_args(), $curr[0])) {
return $this;
} else {
throw new UnexpectedTokenException($this, func_get_args());
@ -551,7 +601,7 @@ class Tokenizer
* @param int $after count tokens after current token
* @return array
*/
public function getSnippet($before = 0, $after = 0)
public function getSnippet(int $before = 0, int $after = 0): array
{
$from = 0;
$to = $this->p;
@ -594,7 +644,7 @@ class Tokenizer
* @param int $after
* @return string
*/
public function getSnippetAsString($before = 0, $after = 0)
public function getSnippetAsString(int $before = 0, int $after = 0): string
{
$str = "";
foreach ($this->getSnippet($before, $after) as $token) {
@ -607,7 +657,7 @@ class Tokenizer
* Check if current is special value: true, TRUE, false, FALSE, null, NULL
* @return bool
*/
public function isSpecialVal()
public function isSpecialVal(): bool
{
return isset(self::$spec[$this->current()]);
}
@ -616,7 +666,7 @@ class Tokenizer
* Check if the token is last
* @return bool
*/
public function isLast()
public function isLast(): bool
{
return $this->p === $this->_max;
}
@ -624,10 +674,10 @@ class Tokenizer
/**
* Move pointer to the end
*/
public function end()
public function end(): static
{
$this->p = $this->_max;
unset($this->prev, $this->curr, $this->next);
$this->cleanTokenCache();
return $this;
}
@ -635,23 +685,26 @@ class Tokenizer
* Return line number of the current token
* @return mixed
*/
public function getLine()
public function getLine(): mixed
{
return $this->curr ? $this->curr[3] : $this->_last_no;
$curr = $this->currToken();
return $curr ? $curr[3] : $this->_last_no;
}
/**
* Is current token whitespaced, means previous token has whitespace characters
* @return bool
*/
public function isWhiteSpaced()
public function isWhiteSpaced(): bool
{
return $this->prev ? (bool)$this->prev[2] : false;
$prev = $this->prevToken();
return $prev && $prev[2];
}
public function getWhitespace()
{
return $this->curr ? $this->curr[2] : false;
$curr = $this->currToken();
return $curr ? $curr[2] : false;
}
/**
@ -659,10 +712,10 @@ class Tokenizer
* @param int $p
* @return $this
*/
public function seek($p)
public function seek(int $p): static
{
$this->p = $p;
unset($this->prev, $this->curr, $this->next);
$this->cleanTokenCache();
return $this;
}
}

View File

@ -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;
@ -53,7 +57,7 @@ class TestCase extends \PHPUnit\Framework\TestCase
return FENOM_RESOURCES . '/compile';
}
public function setUp()
public function setUp(): void
{
if (!file_exists($this->getCompilePath())) {
mkdir($this->getCompilePath(), 0777, true);
@ -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');
@ -89,7 +93,7 @@ class TestCase extends \PHPUnit\Framework\TestCase
public static function inlineFunction($params)
{
return isset($params["text"]) ? $params["text"] : "";
return $params["text"] ?? "";
}
public static function blockFunction($params, $text)
@ -97,7 +101,7 @@ class TestCase extends \PHPUnit\Framework\TestCase
return $text;
}
public static function setUpBeforeClass()
public static function setUpBeforeClass(): void
{
if (!file_exists(FENOM_RESOURCES . '/template')) {
mkdir(FENOM_RESOURCES . '/template', 0777, true);
@ -106,7 +110,7 @@ class TestCase extends \PHPUnit\Framework\TestCase
}
}
public function tpl($name, $code)
public function tpl($name, $code): float
{
$dir = dirname($name);
if ($dir != "." && !is_dir(FENOM_RESOURCES . '/template/' . $dir)) {
@ -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());
}
@ -233,7 +241,7 @@ class TestCase extends \PHPUnit\Framework\TestCase
);
}
public function providerVariables()
public static function providerVariables()
{
return array(
array('$one', 1),
@ -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)
{
@ -325,6 +334,13 @@ class Helper
public function getArray() {
return array(1,2,3);
}
/**
* @throws \Exception
*/
public static function throws() {
throw new \Exception("helper exception");
}
}
function helper_func($string, $pad = 10) {

View File

@ -1,11 +1,7 @@
<?php
require(__DIR__ . "/../src/Fenom.php");
Fenom::registerAutoload();
if(!class_exists('\PHPUnit_Framework_TestCase') && class_exists('\PHPUnit\Framework\TestCase')) {
class_alias('\PHPUnit\Framework\TestCase', '\PHPUnit_Framework_TestCase');
}
//Fenom::registerAutoload();
define('FENOM_RESOURCES', __DIR__ . "/resources");

View File

@ -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());
}

View File

@ -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'));

View File

@ -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');

View File

@ -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(

View File

@ -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");
}

View File

@ -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"];
@ -44,12 +44,9 @@ class RenderTest extends TestCase
$this->assertSame("It is render's function fetch", self::$render->fetch(array("render" => "fetch")));
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage template error
*/
public function testFetchException()
{
$this->expectException(Fenom\Error\TemplateException::class);
$render = new Render(Fenom::factory("."), function () {
echo "error";
throw new \RuntimeException("template error");

View File

@ -1,35 +0,0 @@
<?php
namespace Fenom;
class SandboxTest extends TestCase {
/**
* @group sb
*/
public function test()
{
// return;
// $this->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 '<?php ' . $tag->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;
}
}

View File

@ -1,36 +0,0 @@
<?php
namespace Fenom;
class ScopeTest extends TestCase
{
public function openTag($tokenizer, $scope)
{
$this->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 <?php ".$scope->open($tokenizer)." ?>".$content;
$this->assertSame($content, $scope->getContent());*/
}
}

View File

@ -1,14 +0,0 @@
<?php
namespace Fenom;
class TagOptionTest extends TestCase
{
public function testTrim()
{
}
}

View File

@ -13,7 +13,7 @@ use Fenom\Template,
class TemplateTest extends TestCase
{
public function setUp()
public function setUp(): void
{
parent::setUp();
$this->tpl('welcome.tpl', '<b>Welcome, {$username} ({$email})</b>');
@ -1202,8 +1202,8 @@ class TemplateTest extends TestCase
$this->values = $vars;
$this->tpl("insert.tpl", $code);
$tpl = $this->fenom->getTemplate('insert.tpl');
$this->assertSame($result, $tpl->fetch($vars));
$this->assertTrue($tpl->isValid());
$this->assertSame($result, $tpl->fetch($vars), $code);
$this->assertTrue($tpl->isValid(), $code);
}
/**

View File

@ -45,7 +45,7 @@ class TokenizerTest extends TestCase
' ',
1
),
$tokens->curr
$tokens->currToken()
);
$this->assertSame("resolve", $tokens->getNext($tokens::MACRO_UNARY, T_STRING));
@ -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 ) ";
@ -106,7 +125,6 @@ class TokenizerTest extends TestCase
$this->assertSame($tokens, $tokens->next());
$tokens->p = -1000;
$this->assertSame($tokens, $tokens->back());
$this->assertNull($tokens->undef);
}
public function testFixFloats() {

View File

@ -24,7 +24,7 @@ class FenomTest extends \Fenom\TestCase
$time = $this->tpl('temp.tpl', 'Template 1 a');
$fenom = new Fenom($provider = new \Fenom\Provider(FENOM_RESOURCES . '/template'));
$fenom->setCompileDir(FENOM_RESOURCES . '/compile');
$this->assertInstanceOf('Fenom\Template', $tpl = $fenom->getTemplate('temp.tpl'));
$this->assertInstanceOf('Fenom\Render', $tpl = $fenom->getTemplate('temp.tpl'));
$this->assertSame($provider, $tpl->getProvider());
$this->assertSame('temp.tpl', $tpl->getBaseName());
$this->assertSame('temp.tpl', $tpl->getName());
@ -40,7 +40,7 @@ class FenomTest extends \Fenom\TestCase
FENOM_RESOURCES . '/compile',
Fenom::AUTO_ESCAPE
);
$this->assertInstanceOf('Fenom\Template', $tpl = $fenom->getTemplate('temp.tpl'));
$this->assertInstanceOf('Fenom\Render', $tpl = $fenom->getTemplate('temp.tpl'));
$this->assertSame($provider, $tpl->getProvider());
$this->assertSame('temp.tpl', $tpl->getBaseName());
$this->assertSame('temp.tpl', $tpl->getName());
@ -48,25 +48,12 @@ class FenomTest extends \Fenom\TestCase
$fenom->clearAllCompiles();
}
/**
* @expectedException LogicException
* @expectedExceptionMessage Cache directory /invalid/path is not writable
*/
public function testFactoryInvalid()
{
$this->expectException(LogicException::class, "Cache directory /invalid/path is not writable");
Fenom::factory(FENOM_RESOURCES . '/template', '/invalid/path');
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage Source must be a valid path or provider object
*/
public function testFactoryInvalid2()
{
Fenom::factory(new StdClass);
}
public function testCompileFile()
{
$a = array(