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

View File

@ -1,5 +1,12 @@
Changelog 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) ## 2.11.0 (2016-06-09)

View File

@ -6,8 +6,8 @@ Fenom - Template Engine for PHP
* **Subject:** Template engine * **Subject:** Template engine
* **Syntax:** Smarty-like * **Syntax:** Smarty-like
* **Documentation:** **[English](./docs/en/readme.md)**, **[Russian](./docs/ru/readme.md)** * **Documentation:** **[English](./docs/en/readme.md)**, **[Russian](./docs/ru/readme.md)**
* **PHP version:** 5.4+ * **PHP version:** 8.0+
* **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) * **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) * **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) * **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` * **Composer:** `composer require fenom/fenom`
@ -22,16 +22,10 @@ Fenom - Template Engine for PHP
### Install ### Install
If you use composer in your project then you can to install Fenom as package. 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 ### Setup
There is two way to create Fenom instance: There is two-way to create Fenom instance:
* Long way: use operator `new` * Long way: use operator `new`
* Shot way: use static factory-method * Shot way: use static factory-method

View File

@ -11,15 +11,17 @@
} }
], ],
"require": { "require": {
"php": ">=5.3.0", "php": ">=8.0.0",
"ext-tokenizer": "*" "ext-tokenizer": "*"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "<6.0", "phpunit/phpunit": "9.*"
"satooshi/php-coveralls": "*"
}, },
"autoload": { "autoload": {
"psr-0": { "Fenom\\": "src/" }, "psr-4": { "Fenom\\": "src/Fenom" },
"classmap": [ "src/Fenom.php" ] "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" convertWarningsToExceptions="true"
processIsolation="false" processIsolation="false"
stopOnFailure="false" stopOnFailure="false"
syntaxCheck="false"
bootstrap="tests/autoload.php" bootstrap="tests/autoload.php"
> >
<php> <php>
@ -18,7 +17,7 @@
</php> </php>
<testsuites> <testsuites>
<testsuite > <testsuite name="tests">
<directory>./tests/</directory> <directory>./tests/</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
@ -29,14 +28,6 @@
</exclude> </exclude>
</groups> </groups>
<filter>
<whitelist>
<directory>./src/</directory>
</whitelist>
<blacklist>
<directory>./tests/</directory>
</blacklist>
</filter>
<logging> <logging>
<log type="coverage-clover" target="build/logs/clover.xml"/> <log type="coverage-clover" target="build/logs/clover.xml"/>
</logging> </logging>

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -24,13 +24,12 @@ class Compiler
/** /**
* Tag {include ...} * Tag {include ...}
* *
* @static
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $tag * @param Tag $tag
* @throws \LogicException * @throws \LogicException
* @return string * @return string
*/ */
public static function tagInclude(Tokenizer $tokens, Tag $tag) public static function tagInclude(Tokenizer $tokens, Tag $tag): string
{ {
$tpl = $tag->tpl; $tpl = $tag->tpl;
$name = false; $name = false;
@ -70,12 +69,13 @@ class Compiler
/** /**
* Tag {insert ...} * Tag {insert ...}
*
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $tag * @param Tag $tag
* @throws Error\InvalidUsageException
* @return string * @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 = $tag->tpl;
$tpl->parsePlainArg($tokens, $name); $tpl->parsePlainArg($tokens, $name);
@ -91,12 +91,11 @@ class Compiler
/** /**
* Open tag {if ...} * Open tag {if ...}
* *
* @static
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $scope * @param Tag $scope
* @return string * @return string
*/ */
public static function ifOpen(Tokenizer $tokens, Tag $scope) public static function ifOpen(Tokenizer $tokens, Tag $scope): string
{ {
$scope["else"] = false; $scope["else"] = false;
return 'if(' . $scope->tpl->parseExpr($tokens) . ') {'; return 'if(' . $scope->tpl->parseExpr($tokens) . ') {';
@ -105,13 +104,12 @@ class Compiler
/** /**
* Tag {elseif ...} * Tag {elseif ...}
* *
* @static
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $scope * @param Tag $scope
* @throws InvalidUsageException * @throws InvalidUsageException
* @return string * @return string
*/ */
public static function tagElseIf(Tokenizer $tokens, Tag $scope) public static function tagElseIf(Tokenizer $tokens, Tag $scope): string
{ {
if ($scope["else"]) { if ($scope["else"]) {
throw new InvalidUsageException('Incorrect use of the tag {elseif}'); throw new InvalidUsageException('Incorrect use of the tag {elseif}');
@ -126,7 +124,7 @@ class Compiler
* @param Tag $scope * @param Tag $scope
* @return string * @return string
*/ */
public static function tagElse($tokens, Tag $scope) public static function tagElse(Tokenizer $tokens, Tag $scope): string
{ {
$scope["else"] = true; $scope["else"] = true;
return '} else {'; return '} else {';
@ -135,14 +133,14 @@ class Compiler
/** /**
* Open tag {foreach ...} * Open tag {foreach ...}
* *
* @static
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $scope * @param Tag $scope
* @throws UnexpectedTokenException
* @throws InvalidUsageException
* @return string * @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["else"] = false;
$scope["key"] = null; $scope["key"] = null;
@ -200,7 +198,7 @@ class Compiler
* @param Tag $scope * @param Tag $scope
* @return string * @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; $scope["no-break"] = $scope["no-continue"] = $scope["else"] = true;
$after = $scope["after"] ? implode("; ", $scope["after"]) . ";" : ""; $after = $scope["after"] ? implode("; ", $scope["after"]) . ";" : "";
@ -213,7 +211,8 @@ class Compiler
* @return string * @return string
* @throws CompileException * @throws CompileException
*/ */
public static function foreachProp(Tag $scope, $prop) { public static function foreachProp(Tag $scope, string $prop): string
{
if(empty($scope["props"][$prop])) { if(empty($scope["props"][$prop])) {
$var_name = $scope["props"][$prop] = $scope->tpl->tmpVar()."_".$prop; $var_name = $scope["props"][$prop] = $scope->tpl->tmpVar()."_".$prop;
switch($prop) { switch($prop) {
@ -247,7 +246,7 @@ class Compiler
* @param Tag $scope * @param Tag $scope
* @return string * @return string
*/ */
public static function foreachClose($tokens, Tag $scope) public static function foreachClose(Tokenizer $tokens, Tag $scope): string
{ {
$before = $scope["before"] ? implode("; ", $scope["before"]) . ";" : ""; $before = $scope["before"] ? implode("; ", $scope["before"]) . ";" : "";
$head = $scope["body"] ? implode("; ", $scope["body"]) . ";" : ""; $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 Tokenizer $tokens
* @param Tag $scope * @param Tag $scope
* @return string * @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) . ') {'; return 'while(' . $scope->tpl->parseExpr($tokens) . ') {';
} }
@ -387,12 +278,11 @@ class Compiler
/** /**
* Open tag {switch} * Open tag {switch}
* *
* @static
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $scope * @param Tag $scope
* @return string * @return string
*/ */
public static function switchOpen(Tokenizer $tokens, Tag $scope) public static function switchOpen(Tokenizer $tokens, Tag $scope): string
{ {
$expr = $scope->tpl->parseExpr($tokens); $expr = $scope->tpl->parseExpr($tokens);
$scope["case"] = array(); $scope["case"] = array();
@ -408,7 +298,7 @@ class Compiler
* Resort cases for {switch} * Resort cases for {switch}
* @param Tag $scope * @param Tag $scope
*/ */
private static function _caseResort(Tag $scope) private static function _caseResort(Tag $scope): void
{ {
$content = $scope->cutContent(); $content = $scope->cutContent();
foreach ($scope["last"] as $case) { foreach ($scope["last"] as $case) {
@ -427,12 +317,11 @@ class Compiler
/** /**
* Tag {case ...} * Tag {case ...}
* *
* @static
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $tag * @param Tag $tag
* @return string * @return string
*/ */
public static function tagCase(Tokenizer $tokens, Tag $tag) public static function tagCase(Tokenizer $tokens, Tag $tag): string
{ {
self::_caseResort($tag); self::_caseResort($tag);
do { do {
@ -455,12 +344,11 @@ class Compiler
/** /**
* Tag {default} * Tag {default}
* *
* @static
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $scope * @param Tag $scope
* @return string * @return string
*/ */
public static function tagDefault($tokens, Tag $scope) public static function tagDefault(Tokenizer $tokens, Tag $scope): string
{ {
self::_caseResort($scope); self::_caseResort($scope);
$scope["last"][] = false; $scope["last"][] = false;
@ -470,12 +358,11 @@ class Compiler
/** /**
* Close tag {switch} * Close tag {switch}
* *
* @static
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $scope * @param Tag $scope
* @return string * @return string
*/ */
public static function switchClose($tokens, Tag $scope) public static function switchClose(Tokenizer $tokens, Tag $scope): string
{ {
self::_caseResort($scope); self::_caseResort($scope);
$expr = $scope["var"]; $expr = $scope["var"];
@ -494,13 +381,12 @@ class Compiler
/** /**
* Tag {continue} * Tag {continue}
* *
* @static
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $scope * @param Tag $scope
* @throws InvalidUsageException
* @return string * @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"])) { if (empty($scope["no-continue"])) {
return 'continue;'; return 'continue;';
@ -512,13 +398,12 @@ class Compiler
/** /**
* Tag {break} * Tag {break}
* *
* @static
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $scope * @param Tag $scope
* @throws InvalidUsageException
* @return string * @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"])) { if (empty($scope["no-break"])) {
return 'break;'; return 'break;';
@ -529,12 +414,13 @@ class Compiler
/** /**
* Dispatch {extends} tag * Dispatch {extends} tag
*
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $tag * @param Tag $tag
* @throws Error\InvalidUsageException * @throws Error\InvalidUsageException
* @return string * @return string
*/ */
public static function tagExtends(Tokenizer $tokens, Tag $tag) public static function tagExtends(Tokenizer $tokens, Tag $tag): void
{ {
$tpl = $tag->tpl; $tpl = $tag->tpl;
if ($tpl->extends) { if ($tpl->extends) {
@ -558,8 +444,9 @@ class Compiler
* Post compile action for {extends ...} tag * Post compile action for {extends ...} tag
* @param Template $tpl * @param Template $tpl
* @param string $body * @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->dynamic_extends) {
if (!$tpl->ext_stack) { if (!$tpl->ext_stack) {
@ -585,10 +472,9 @@ class Compiler
* Tag {use ...} * Tag {use ...}
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $tag * @param Tag $tag
* @throws Error\InvalidUsageException * @throws Error\InvalidUsageException|CompileException
* @return string
*/ */
public static function tagUse(Tokenizer $tokens, Tag $tag) public static function tagUse(Tokenizer $tokens, Tag $tag): void
{ {
$tpl = $tag->tpl; $tpl = $tag->tpl;
if ($tpl->getStackSize()) { if ($tpl->getStackSize()) {
@ -607,9 +493,8 @@ class Compiler
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $scope * @param Tag $scope
* @throws \RuntimeException * @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); $scope["cname"] = $scope->tpl->parsePlainArg($tokens, $name);
if (!$name) { if (!$name) {
@ -623,7 +508,7 @@ class Compiler
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $scope * @param Tag $scope
*/ */
public static function tagBlockClose($tokens, Tag $scope) public static function tagBlockClose(Tokenizer $tokens, Tag $scope): void
{ {
$tpl = $scope->tpl; $tpl = $scope->tpl;
$name = $scope["name"]; $name = $scope["name"];
@ -657,7 +542,7 @@ class Compiler
* @param Tag $scope * @param Tag $scope
* @return string * @return string
*/ */
public static function tagParent($tokens, Tag $scope) public static function tagParent(Tokenizer $tokens, Tag $scope): string
{ {
$block_scope = $scope->tpl->getParentScope('block'); $block_scope = $scope->tpl->getParentScope('block');
if (!$block_scope['use_parent']) { if (!$block_scope['use_parent']) {
@ -671,7 +556,7 @@ class Compiler
* *
* @return string * @return string
*/ */
public static function stdClose() public static function stdClose(): string
{ {
return '}'; return '}';
} }
@ -683,7 +568,7 @@ class Compiler
* @param Tag $tag * @param Tag $tag
* @return string * @return string
*/ */
public static function stdFuncParser(Tokenizer $tokens, Tag $tag) public static function stdFuncParser(Tokenizer $tokens, Tag $tag): string
{ {
if(is_string($tag->callback)) { if(is_string($tag->callback)) {
return $tag->out($tag->callback . "(" . self::toArray($tag->tpl->parseParams($tokens)) . ', $tpl, $var)'); return $tag->out($tag->callback . "(" . self::toArray($tag->tpl->parseParams($tokens)) . ', $tpl, $var)');
@ -699,8 +584,9 @@ class Compiler
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $tag * @param Tag $tag
* @return string * @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)) { if (strpos($tag->callback, "::") || is_array($tag->callback)) {
list($class, $method) = explode("::", $tag->callback, 2); list($class, $method) = explode("::", $tag->callback, 2);
@ -729,7 +615,7 @@ class Compiler
* @param Tag $tag * @param Tag $tag
* @return string * @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["params"] = self::toArray($tag->tpl->parseParams($tokens));
$tag->setOption(\Fenom::AUTO_ESCAPE, false); $tag->setOption(\Fenom::AUTO_ESCAPE, false);
@ -743,7 +629,7 @@ class Compiler
* @param Tag $tag * @param Tag $tag
* @return string * @return string
*/ */
public static function stdFuncClose($tokens, Tag $tag) public static function stdFuncClose(Tokenizer $tokens, Tag $tag): string
{ {
$tag->restore(\Fenom::AUTO_ESCAPE); $tag->restore(\Fenom::AUTO_ESCAPE);
if(is_string($tag->callback)) { if(is_string($tag->callback)) {
@ -756,10 +642,10 @@ class Compiler
/** /**
* Convert array of code to string array * Convert array of code to string array
* @param $params * @param array $params
* @return string * @return string
*/ */
public static function toArray($params) public static function toArray(array $params): string
{ {
$_code = array(); $_code = array();
foreach ($params as $k => $v) { foreach ($params as $k => $v) {
@ -773,8 +659,9 @@ class Compiler
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $scope * @param Tag $scope
* @return string * @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)) { if($tokens->is(T_VARIABLE)) {
$var = $scope->tpl->parseVariable($tokens); $var = $scope->tpl->parseVariable($tokens);
@ -819,12 +706,12 @@ class Compiler
* @param Tag $scope * @param Tag $scope
* @return string * @return string
*/ */
public static function setClose($tokens, Tag $scope) public static function setClose(Tokenizer $tokens, Tag $scope): string
{ {
return $scope["name"] . '=' . $scope["value"] . ';'; 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).';'; return $scope->tpl->parseExpr($tokens).';';
} }
@ -834,19 +721,20 @@ class Compiler
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $scope * @param Tag $scope
* @return string * @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()"); $scope["filter"] = $scope->tpl->parseModifier($tokens, "ob_get_clean()");
return "ob_start();"; return "ob_start();";
} }
/** /**
* @param $tokens * @param Tokenizer $tokens
* @param Tag $scope * @param Tag $scope
* @return string * @return string
*/ */
public static function filterClose($tokens, Tag $scope) public static function filterClose(Tokenizer $tokens, Tag $scope): string
{ {
return "echo " . $scope["filter"] . ";"; return "echo " . $scope["filter"] . ";";
} }
@ -859,7 +747,7 @@ class Compiler
* @throws Error\InvalidUsageException * @throws Error\InvalidUsageException
* @return string * @return string
*/ */
public static function tagCycle(Tokenizer $tokens, Tag $tag) public static function tagCycle(Tokenizer $tokens, Tag $tag): string
{ {
$tpl = $tag->tpl; $tpl = $tag->tpl;
if ($tokens->is("[")) { if ($tokens->is("[")) {
@ -882,11 +770,11 @@ class Compiler
/** /**
* Runtime cycle callback * Runtime cycle callback
* @param mixed $vals * @param array $vals
* @param $index * @param int $index
* @return mixed * @return mixed
*/ */
public static function cycle($vals, $index) public static function cycle(array $vals, int $index): mixed
{ {
return $vals[$index % count($vals)]; return $vals[$index % count($vals)];
} }
@ -896,11 +784,11 @@ class Compiler
* *
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $tag * @param Tag $tag
* @throws Error\UnexpectedTokenException
* @throws Error\InvalidUsageException
* @return string * @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; $tpl = $tag->tpl;
$import = array(); $import = array();
@ -965,7 +853,7 @@ class Compiler
* @param Tag $scope * @param Tag $scope
* @throws InvalidUsageException * @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["name"] = $tokens->get(Tokenizer::MACRO_STRING);
$scope["recursive"] = false; $scope["recursive"] = false;
@ -1010,7 +898,7 @@ class Compiler
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $scope * @param Tag $scope
*/ */
public static function macroClose($tokens, Tag $scope) public static function macroClose(Tokenizer $tokens, Tag $scope): void
{ {
if ($scope["recursive"]) { if ($scope["recursive"]) {
$scope["macro"]["recursive"] = true; $scope["macro"]["recursive"] = true;
@ -1026,7 +914,7 @@ class Compiler
* @param Tag $tag * @param Tag $tag
* @return string * @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); return 'echo ' . $tag->tpl->parseExpr($tokens);
} }
@ -1035,9 +923,9 @@ class Compiler
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $tag * @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(); $tokens->next();
$tag->setOption(\Fenom::AUTO_ESCAPE, $expected); $tag->setOption(\Fenom::AUTO_ESCAPE, $expected);
} }
@ -1045,7 +933,7 @@ class Compiler
/** /**
* Do nothing * Do nothing
*/ */
public static function nope() public static function nope(): void
{ {
} }
@ -1053,30 +941,33 @@ class Compiler
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $tag * @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(); $tokens->next();
$tag->setOption(\Fenom::AUTO_STRIP, $expected); $tag->setOption(\Fenom::AUTO_STRIP, $expected);
} }
/** /**
* Tag {ignore} * Tag {ignore}
*
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $tag * @param Tag $tag
*/ */
public static function ignoreOpen($tokens, Tag $tag) public static function ignoreOpen(Tokenizer $tokens, Tag $tag): void
{ {
$tag->tpl->ignore('ignore'); $tag->tpl->ignore('ignore');
} }
/** /**
* Tag {unset ...} * Tag {unset ...}
*
* @param Tokenizer $tokens * @param Tokenizer $tokens
* @param Tag $tag * @param Tag $tag
* @return string * @return string
* @throws CompileException
*/ */
public static function tagUnset(Tokenizer $tokens, Tag $tag) public static function tagUnset(Tokenizer $tokens, Tag $tag): string
{ {
$unset = array(); $unset = array();
while($tokens->valid()) { while($tokens->valid()) {
@ -1085,7 +976,7 @@ class Compiler
return 'unset('.implode(", ", $unset).')'; 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)); $name = str_replace(array('\'', '"'), '', $tokens->get(T_CONSTANT_ENCAPSED_STRING));
$tokens->next(); $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 { } else {
$expect = ""; $expect = "";
} }
if (!$tokens->curr) { if (!$tokens->currToken()) {
$this->message = "Unexpected end of " . ($where ? : "expression") . "$expect"; $this->message = "Unexpected end of " . ($where ? : "expression") . "$expect";
} else { } else {
$this->message = "Unexpected token '" . $tokens->current() . "' in " . ($where ? : "expression") . "$expect"; $this->message = "Unexpected token '" . $tokens->current() . "' in " . ($where ? : "expression") . "$expect";

View File

@ -19,19 +19,61 @@ class Modifier
/** /**
* Date format * Date format
* *
* @param string|int $date * @param int|string $date
* @param string $format * @param string $format
* @return string * @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)) { if (!is_numeric($date)) {
$date = strtotime($date); if ($date instanceof \DateTime) {
$date = $date->getTimestamp();
} else {
$date = strtotime($date);
}
if (!$date) { if (!$date) {
$date = time(); $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 * @param string $format
* @return string * @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)) { if (!is_numeric($date)) {
$date = strtotime($date); $date = strtotime($date);
@ -55,18 +97,18 @@ class Modifier
* *
* @param string $text * @param string $text
* @param string $type * @param string $type
* @param string $charset * @param string|null $charset
* @return string * @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)) { switch (strtolower($type)) {
case "url": case "url":
return urlencode($text); return urlencode($text);
case "html"; case "html";
return htmlspecialchars($text, ENT_COMPAT, $charset ? $charset : \Fenom::$charset); return htmlspecialchars($text, ENT_COMPAT, $charset ?: \Fenom::$charset);
case "js": 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: default:
return $text; return $text;
} }
@ -79,7 +121,7 @@ class Modifier
* @param string $type * @param string $type
* @return string * @return string
*/ */
public static function unescape($text, $type = 'html') public static function unescape(string $text, string $type = 'html'): string
{ {
switch (strtolower($type)) { switch (strtolower($type)) {
case "url": case "url":
@ -96,12 +138,12 @@ class Modifier
* *
* @param string $string text witch will be truncate * @param string $string text witch will be truncate
* @param int $length maximum symbols of result string * @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 $by_words
* @param bool $middle * @param bool $middle
* @return string * @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 ($middle) {
if (preg_match('#^(.{' . $length . '}).*?(.{' . $length . '})?$#usS', $string, $match)) { if (preg_match('#^(.{' . $length . '}).*?(.{' . $length . '})?$#usS', $string, $match)) {
@ -134,7 +176,7 @@ class Modifier
* @param bool $to_line strip line ends * @param bool $to_line strip line ends
* @return string * @return string
*/ */
public static function strip($str, $to_line = false) public static function strip(string $str, bool $to_line = false): string
{ {
$str = trim($str); $str = trim($str);
if ($to_line) { if ($to_line) {
@ -149,7 +191,7 @@ class Modifier
* @param mixed $item * @param mixed $item
* @return int * @return int
*/ */
public static function length($item) public static function length(mixed $item): int
{ {
if (is_string($item)) { if (is_string($item)) {
return strlen(preg_replace('#[\x00-\x7F]|[\x80-\xDF][\x00-\xBF]|[\xE0-\xEF][\x00-\xBF]{2}#s', ' ', $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 * @param mixed $haystack
* @return bool * @return bool
*/ */
public static function in($value, $haystack) public static function in(mixed $value, mixed $haystack): bool
{ {
if(is_scalar($value)) { if(is_scalar($value)) {
if (is_array($haystack)) { if (is_array($haystack)) {
return in_array($value, $haystack) || array_key_exists($value, $haystack); return in_array($value, $haystack) || array_key_exists($value, $haystack);
} elseif (is_string($haystack)) { } elseif (is_string($haystack)) {
return strpos($haystack, $value) !== false; return str_contains($haystack, $value);
} }
} }
return false; return false;
@ -184,7 +226,7 @@ class Modifier
* @param mixed $value * @param mixed $value
* @return bool * @return bool
*/ */
public static function isIterable($value) public static function isIterable(mixed $value): bool
{ {
return is_array($value) || ($value instanceof \Iterator); return is_array($value) || ($value instanceof \Iterator);
} }
@ -196,7 +238,7 @@ class Modifier
* @param string $replace The replacement value that replaces found search * @param string $replace The replacement value that replaces found search
* @return mixed * @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); return str_replace($search, $replace, $value);
} }
@ -207,7 +249,7 @@ class Modifier
* @param string $replacement * @param string $replacement
* @return mixed * @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); return preg_replace($pattern, $replacement, $value);
} }
@ -217,7 +259,7 @@ class Modifier
* @param string $pattern * @param string $pattern
* @return bool * @return bool
*/ */
public static function match($string, $pattern) public static function match(string $string, string $pattern): bool
{ {
return fnmatch($pattern, $string); return fnmatch($pattern, $string);
} }
@ -227,7 +269,7 @@ class Modifier
* @param string $pattern * @param string $pattern
* @return int * @return int
*/ */
public static function ematch($string, $pattern) public static function ematch(string $string, string $pattern): int
{ {
return preg_match($pattern, $string); return preg_match($pattern, $string);
} }
@ -237,23 +279,23 @@ class Modifier
* @param string $delimiter * @param string $delimiter
* @return array * @return array
*/ */
public static function split($value, $delimiter = ",") public static function split(mixed $value, string $delimiter = ","): array
{ {
if(is_string($value)) { if(is_string($value)) {
return explode($delimiter, $value); return explode($delimiter, $value);
} elseif(is_array($value)) { } elseif(is_array($value)) {
return $value; return $value;
} else { } else {
return array(); return [];
} }
} }
/** /**
* @param $value * @param mixed $value
* @param string $pattern * @param string $pattern
* @return array * @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)) { if(is_string($value)) {
return preg_split($pattern, $value); return preg_split($pattern, $value);
@ -265,11 +307,11 @@ class Modifier
} }
/** /**
* @param $value * @param mixed $value
* @param string $glue * @param string $glue
* @return string * @return string
*/ */
public static function join($value, $glue = ",") public static function join(mixed $value, string $glue = ","): string
{ {
if(is_array($value)) { if(is_array($value)) {
return implode($glue, $value); return implode($glue, $value);
@ -281,12 +323,13 @@ class Modifier
} }
/** /**
* @param string|int $from * @param int $from
* @param string|int $to * @param int $to
* @param int $step * @param int $step
* @return RangeIterator * @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) { if($from instanceof RangeIterator) {
return $from->setStep($to); return $from->setStep($to);
} else { } else {

View File

@ -15,16 +15,16 @@ namespace Fenom;
*/ */
class Provider implements ProviderInterface class Provider implements ProviderInterface
{ {
private $_path; private string $_path;
protected $_clear_cache = false; protected bool $_clear_cache = false;
/** /**
* Clean directory from files * Clean directory from files
* *
* @param string $path * @param string $path
*/ */
public static function clean($path) public static function clean(string $path)
{ {
if (is_file($path)) { if (is_file($path)) {
unlink($path); unlink($path);
@ -39,7 +39,7 @@ class Provider implements ProviderInterface
foreach ($iterator as $file) { foreach ($iterator as $file) {
/* @var \splFileInfo $file */ /* @var \splFileInfo $file */
if ($file->isFile()) { if ($file->isFile()) {
if (strpos($file->getBasename(), ".") !== 0) { if (!str_starts_with($file->getBasename(), ".")) {
unlink($file->getRealPath()); unlink($file->getRealPath());
} }
} elseif ($file->isDir()) { } elseif ($file->isDir()) {
@ -54,7 +54,7 @@ class Provider implements ProviderInterface
* *
* @param string $path * @param string $path
*/ */
public static function rm($path) public static function rm(string $path)
{ {
self::clean($path); self::clean($path);
if (is_dir($path)) { if (is_dir($path)) {
@ -64,9 +64,9 @@ class Provider implements ProviderInterface
/** /**
* @param string $template_dir directory of templates * @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)) { if ($_dir = realpath($template_dir)) {
$this->_path = $_dir; $this->_path = $_dir;
@ -80,17 +80,17 @@ class Provider implements ProviderInterface
* @see http://php.net/manual/en/function.clearstatcache.php * @see http://php.net/manual/en/function.clearstatcache.php
* @param bool $status * @param bool $status
*/ */
public function setClearCachedStats($status = true) { public function setClearCachedStats(bool $status = true) {
$this->_clear_cache = $status; $this->_clear_cache = $status;
} }
/** /**
* Get source and mtime of template by name * Get source and mtime of template by name
* @param string $tpl * @param string $tpl
* @param int $time load last modified time * @param float|null $time load last modified time
* @return string * @return string
*/ */
public function getSource($tpl, &$time) public function getSource(string $tpl, ?float &$time): string
{ {
$tpl = $this->_getTemplatePath($tpl); $tpl = $this->_getTemplatePath($tpl);
if($this->_clear_cache) { if($this->_clear_cache) {
@ -103,24 +103,24 @@ class Provider implements ProviderInterface
/** /**
* Get last modified of template by name * Get last modified of template by name
* @param string $tpl * @param string $tpl
* @return int * @return float
*/ */
public function getLastModified($tpl) public function getLastModified(string $tpl): float
{ {
$tpl = $this->_getTemplatePath($tpl); $tpl = $this->_getTemplatePath($tpl);
if($this->_clear_cache) { if($this->_clear_cache) {
clearstatcache(true, $tpl); clearstatcache(true, $tpl);
} }
return filemtime($tpl); return (float)filemtime($tpl);
} }
/** /**
* Get all names of templates from provider. * Get all names of templates from provider.
* *
* @param string $extension all templates must have this extension, default .tpl * @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(); $list = array();
$iterator = new \RecursiveIteratorIterator( $iterator = new \RecursiveIteratorIterator(
@ -140,14 +140,14 @@ class Provider implements ProviderInterface
/** /**
* Get template path * Get template path
* @param $tpl * @param string $tpl
* @return string * @return string
* @throws \RuntimeException * @throws \RuntimeException
*/ */
protected function _getTemplatePath($tpl) protected function _getTemplatePath(string $tpl): string
{ {
$path = realpath($this->_path . "/" . $tpl); $path = realpath($this->_path . "/" . $tpl);
if ($path && strpos($path, $this->_path) === 0) { if ($path && str_starts_with($path, $this->_path)) {
return $path; return $path;
} else { } else {
throw new \RuntimeException("Template $tpl not found"); throw new \RuntimeException("Template $tpl not found");
@ -158,9 +158,9 @@ class Provider implements ProviderInterface
* @param string $tpl * @param string $tpl
* @return bool * @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 * @param array $templates [template_name => modified, ...] By conversation, you may trust the template's name
* @return bool * @return bool
*/ */
public function verify(array $templates) public function verify(array $templates): bool
{ {
foreach ($templates as $template => $mtime) { foreach ($templates as $template => $mtime) {
$template = $this->_path . '/' . $template; $template = $this->_path . '/' . $template;
if($this->_clear_cache) { if($this->_clear_cache) {
clearstatcache(true, $template); clearstatcache(true, $template);
} }
if (@filemtime($template) !== $mtime) { if (@filemtime($template) != $mtime) {
return false; return false;
} }

View File

@ -20,20 +20,20 @@ interface ProviderInterface
* @param string $tpl * @param string $tpl
* @return bool * @return bool
*/ */
public function templateExists($tpl); public function templateExists(string $tpl): bool;
/** /**
* @param string $tpl * @param string $tpl
* @param int $time * @param float $time seconds with micro
* @return string * @return string
*/ */
public function getSource($tpl, &$time); public function getSource(string $tpl, float &$time): string;
/** /**
* @param string $tpl * @param string $tpl
* @return int * @return float seconds with micro
*/ */
public function getLastModified($tpl); public function getLastModified(string $tpl): float;
/** /**
* Verify templates (check mtime) * 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 * @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 * @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 * 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; namespace Fenom;
use Countable;
use Iterator;
class RangeIterator implements \Iterator, \Countable class RangeIterator implements Iterator, Countable
{ {
public $current; public int $current;
public $index = 0; public int $index = 0;
public $min; public int $min;
public $max; public int $max;
public $step; public int $step;
public function __construct($min, $max, $step = 1) public function __construct(int $min, int $max, int $step = 1)
{ {
$this->min = $min; $this->min = $min;
$this->max = $max; $this->max = $max;
@ -23,7 +25,7 @@ class RangeIterator implements \Iterator, \Countable
* @param int $step * @param int $step
* @return $this * @return $this
*/ */
public function setStep($step) public function setStep(int $step): static
{ {
if($step > 0) { if($step > 0) {
$this->current = min($this->min, $this->max); $this->current = min($this->min, $this->max);
@ -40,7 +42,7 @@ class RangeIterator implements \Iterator, \Countable
/** /**
* Return the current element * Return the current element
*/ */
public function current() public function current(): mixed
{ {
return $this->current; return $this->current;
} }
@ -48,7 +50,7 @@ class RangeIterator implements \Iterator, \Countable
/** /**
* Move forward to next element * Move forward to next element
*/ */
public function next() public function next(): void
{ {
$this->current += $this->step; $this->current += $this->step;
$this->index++; $this->index++;
@ -56,9 +58,9 @@ class RangeIterator implements \Iterator, \Countable
/** /**
* Return the key of the current element * Return the key of the current element
* @return int * @return mixed
*/ */
public function key() public function key(): mixed
{ {
return $this->index; return $this->index;
} }
@ -67,7 +69,7 @@ class RangeIterator implements \Iterator, \Countable
* Checks if current position is valid * Checks if current position is valid
* @return bool * @return bool
*/ */
public function valid() public function valid(): bool
{ {
return $this->current >= $this->min && $this->current <= $this->max; 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 * Rewind the Iterator to the first element
*/ */
public function rewind() public function rewind(): void
{ {
if($this->step > 0) { if($this->step > 0) {
$this->current = min($this->min, $this->max); $this->current = min($this->min, $this->max);
@ -88,7 +90,7 @@ class RangeIterator implements \Iterator, \Countable
/** /**
* Count elements of an object * Count elements of an object
*/ */
public function count() public function count(): int
{ {
return intval(($this->max - $this->min + 1) / $this->step); return intval(($this->max - $this->min + 1) / $this->step);
} }

View File

@ -10,6 +10,7 @@
namespace Fenom; namespace Fenom;
use Fenom; use Fenom;
use Fenom\Error\TemplateException;
/** /**
* Primitive template * Primitive template
@ -17,71 +18,72 @@ use Fenom;
*/ */
class Render extends \ArrayObject class Render extends \ArrayObject
{ {
private static $_props = array( private static array $_props = [
"name" => "runtime", "name" => "runtime",
"base_name" => "", "base_name" => "",
"scm" => false, "scm" => false,
"time" => 0, "time" => 0,
"depends" => array(), "depends" => [],
"macros" => array() "macros" => []
); ];
/** /**
* @var \Closure * @var \Closure|null
*/ */
protected $_code; protected ?\Closure $_code = null;
/** /**
* Template name * Template name
* @var string * @var string
*/ */
protected $_name = 'runtime'; protected mixed $_name = 'runtime';
/** /**
* Provider's schema * Provider's schema
* @var bool * @var string|null
*/ */
protected $_scm = false; protected ?string $_scm = null;
/** /**
* Basic template name * Basic template name
* @var string * @var string
*/ */
protected $_base_name = 'runtime'; protected string $_base_name = 'runtime';
/** /**
* @var Fenom * @var Fenom
*/ */
protected $_fenom; protected Fenom $_fenom;
/** /**
* Timestamp of compilation * Timestamp of compilation
* @var float * @var float
*/ */
protected $_time = 0.0; protected float $_time = 0.0;
/** /**
* @var array depends list * @var array depends list
*/ */
protected $_depends = array(); protected array $_depends = [];
/** /**
* @var int template options (see Fenom options) * @var int template options (see Fenom options)
*/ */
protected $_options = 0; protected int $_options = 0;
/** /**
* Template provider * Template provider
* @var ProviderInterface * @var ProviderInterface
*/ */
protected $_provider; protected ProviderInterface $_provider;
/** /**
* @var \Closure[] * @var \Closure[]
*/ */
protected $_macros; protected array $_macros;
/** /**
* @param Fenom $fenom * @param Fenom $fenom
* @param callable $code template body * @param \Closure $code template body
* @param array $props * @param array $props
*/ */
public function __construct(Fenom $fenom, \Closure $code, array $props = array()) public function __construct(Fenom $fenom, \Closure $code, array $props = array())
{ {
parent::__construct();
$this->_fenom = $fenom; $this->_fenom = $fenom;
$props += self::$_props; $props += self::$_props;
$this->_name = $props["name"]; $this->_name = $props["name"];
@ -97,25 +99,25 @@ class Render extends \ArrayObject
* Get template storage * Get template storage
* @return \Fenom * @return \Fenom
*/ */
public function getStorage() public function getStorage(): Fenom
{ {
return $this->_fenom; return $this->_fenom;
} }
/** /**
* Get depends list * Get list of dependencies.
* @return array * @return array
*/ */
public function getDepends() public function getDepends(): array
{ {
return $this->_depends; return $this->_depends;
} }
/** /**
* Get schema name * Get schema name
* @return string * @return string|null
*/ */
public function getScm() public function getScm(): ?string
{ {
return $this->_scm; return $this->_scm;
} }
@ -124,7 +126,7 @@ class Render extends \ArrayObject
* Get provider of template source * Get provider of template source
* @return ProviderInterface * @return ProviderInterface
*/ */
public function getProvider() public function getProvider(): ProviderInterface
{ {
return $this->_fenom->getProvider($this->_scm); return $this->_fenom->getProvider($this->_scm);
} }
@ -133,7 +135,7 @@ class Render extends \ArrayObject
* Get name without schema * Get name without schema
* @return string * @return string
*/ */
public function getBaseName() public function getBaseName(): string
{ {
return $this->_base_name; return $this->_base_name;
} }
@ -142,7 +144,7 @@ class Render extends \ArrayObject
* Get parse options * Get parse options
* @return int * @return int
*/ */
public function getOptions() public function getOptions(): int
{ {
return $this->_options; return $this->_options;
} }
@ -159,7 +161,7 @@ class Render extends \ArrayObject
* Get template name * Get template name
* @return string * @return string
*/ */
public function getName() public function getName(): string
{ {
return $this->_name; return $this->_name;
} }
@ -174,7 +176,7 @@ class Render extends \ArrayObject
* Validate template * Validate template
* @return bool * @return bool
*/ */
public function isValid() public function isValid(): bool
{ {
foreach ($this->_depends as $scm => $templates) { foreach ($this->_depends as $scm => $templates) {
$provider = $this->_fenom->getProvider($scm); $provider = $this->_fenom->getProvider($scm);
@ -197,7 +199,7 @@ class Render extends \ArrayObject
* @throws \RuntimeException * @throws \RuntimeException
* @return mixed * @return mixed
*/ */
public function getMacro($name) public function getMacro($name): mixed
{ {
if (empty($this->_macros[$name])) { if (empty($this->_macros[$name])) {
throw new \RuntimeException('macro ' . $name . ' not found'); throw new \RuntimeException('macro ' . $name . ' not found');
@ -208,11 +210,16 @@ class Render extends \ArrayObject
/** /**
* Execute template and write into output * Execute template and write into output
* @param array $values for template * @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; return $values;
} }
@ -222,7 +229,7 @@ class Render extends \ArrayObject
* @return string * @return string
* @throws \Exception * @throws \Exception
*/ */
public function fetch(array $values) public function fetch(array $values): string
{ {
ob_start(); ob_start();
try { try {
@ -236,17 +243,12 @@ class Render extends \ArrayObject
/** /**
* Stub * Stub
* @param $method * @param string $method
* @param $args * @param mixed $args
* @throws \BadMethodCallException * @throws \BadMethodCallException
*/ */
public function __call($method, $args) public function __call(string $method, mixed $args)
{ {
throw new \BadMethodCallException("Unknown method " . $method); 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 * @var Template
*/ */
public $tpl; public Template $tpl;
public $name; public string $name;
public $options = array(); public array $options = [];
public $line = 0; public int $line = 0;
public $level = 0; public int $level = 0;
public $callback; public mixed $callback;
public $escape; public bool $escape;
private $_offset = 0; private int $_offset = 0;
private $_closed = true; private bool $_closed = true;
private $_body; private string $_body;
private $_type = 0; private int $_type = 0;
private $_open; private mixed $_open;
private $_close; private mixed $_close;
private $_tags = array(); private array $_tags = [];
private $_floats = array(); private array $_floats = [];
private $_changed = array(); private array $_changed = [];
/** /**
* Create tag entity * Create tag entity
* @param string $name the tag name * @param string $name the tag name
* @param Template $tpl current template * @param Template $tpl current template
* @param string $info tag's information * @param array $info tag's information
* @param string $body template's code * @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->tpl = $tpl;
$this->name = $name; $this->name = $name;
$this->line = $tpl->getLine(); $this->line = $tpl->getLine();
@ -62,8 +63,8 @@ class Tag extends \ArrayObject
if ($this->_type & self::BLOCK) { if ($this->_type & self::BLOCK) {
$this->_open = $info["open"]; $this->_open = $info["open"];
$this->_close = $info["close"]; $this->_close = $info["close"];
$this->_tags = isset($info["tags"]) ? $info["tags"] : array(); $this->_tags = $info["tags"] ?? [];
$this->_floats = isset($info["float_tags"]) ? $info["float_tags"] : array(); $this->_floats = $info["float_tags"] ?? [];
$this->_closed = false; $this->_closed = false;
} else { } else {
$this->_open = $info["parser"]; $this->_open = $info["parser"];
@ -79,7 +80,7 @@ class Tag extends \ArrayObject
* @param string $option * @param string $option
* @throws \RuntimeException * @throws \RuntimeException
*/ */
public function tagOption($option) public function tagOption(string $option)
{ {
if (method_exists($this, 'opt' . $option)) { if (method_exists($this, 'opt' . $option)) {
$this->options[] = $option; $this->options[] = $option;
@ -93,7 +94,7 @@ class Tag extends \ArrayObject
* @param int $option option constant * @param int $option option constant
* @param bool $value true add option, false remove option * @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); $actual = (bool)($this->tpl->getOptions() & $option);
if ($actual != $value) { if ($actual != $value) {
@ -106,7 +107,7 @@ class Tag extends \ArrayObject
* Restore the option * Restore the option
* @param int $option * @param int $option
*/ */
public function restore($option) public function restore(int $option)
{ {
if (isset($this->_changed[$option])) { if (isset($this->_changed[$option])) {
$this->tpl->setOption($option, $this->_changed[$option]); $this->tpl->setOption($option, $this->_changed[$option]);
@ -126,7 +127,7 @@ class Tag extends \ArrayObject
* Check, if the tag closed * Check, if the tag closed
* @return bool * @return bool
*/ */
public function isClosed() public function isClosed(): bool
{ {
return $this->_closed; return $this->_closed;
} }
@ -137,7 +138,7 @@ class Tag extends \ArrayObject
* @param Tokenizer $tokenizer * @param Tokenizer $tokenizer
* @return mixed * @return mixed
*/ */
public function start($tokenizer) public function start(Tokenizer $tokenizer): mixed
{ {
foreach ($this->options as $option) { foreach ($this->options as $option) {
$option = 'opt' . $option; $option = 'opt' . $option;
@ -153,7 +154,7 @@ class Tag extends \ArrayObject
* @param int $level * @param int $level
* @return bool * @return bool
*/ */
public function hasTag($tag, $level) public function hasTag(string $tag, int $level): bool
{ {
if (isset($this->_tags[$tag])) { if (isset($this->_tags[$tag])) {
if ($level) { if ($level) {
@ -171,10 +172,10 @@ class Tag extends \ArrayObject
* *
* @param string $tag * @param string $tag
* @param Tokenizer $tokenizer * @param Tokenizer $tokenizer
* @throws \LogicException
* @return string * @return string
* @throws \LogicException
*/ */
public function tag($tag, $tokenizer) public function tag(string $tag, Tokenizer $tokenizer): string
{ {
if (isset($this->_tags[$tag])) { if (isset($this->_tags[$tag])) {
return call_user_func($this->_tags[$tag], $tokenizer, $this); return call_user_func($this->_tags[$tag], $tokenizer, $this);
@ -187,10 +188,10 @@ class Tag extends \ArrayObject
* Close callback * Close callback
* *
* @param Tokenizer $tokenizer * @param Tokenizer $tokenizer
* @throws \LogicException
* @return string * @return string
* @throws \LogicException
*/ */
public function end($tokenizer) public function end(Tokenizer $tokenizer): string
{ {
if ($this->_closed) { if ($this->_closed) {
throw new \LogicException("Tag {$this->name} already 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); $code = call_user_func($this->_close, $tokenizer, $this);
$this->restoreAll(); $this->restoreAll();
return $code; return (string)$code;
} else { } else {
throw new \LogicException("Can not use a inline tag {$this->name} as a block"); throw new \LogicException("Can not use a inline tag {$this->name} as a block");
} }
@ -224,7 +225,7 @@ class Tag extends \ArrayObject
* @throws \LogicException * @throws \LogicException
* @return string * @return string
*/ */
public function getContent() public function getContent(): string
{ {
return substr($this->_body, $this->_offset); return substr($this->_body, $this->_offset);
} }
@ -235,7 +236,7 @@ class Tag extends \ArrayObject
* @return string * @return string
* @throws \LogicException * @throws \LogicException
*/ */
public function cutContent() public function cutContent(): string
{ {
$content = substr($this->_body, $this->_offset); $content = substr($this->_body, $this->_offset);
$this->_body = substr($this->_body, 0, $this->_offset); $this->_body = substr($this->_body, 0, $this->_offset);
@ -258,7 +259,7 @@ class Tag extends \ArrayObject
* @param string $code * @param string $code
* @return string * @return string
*/ */
public function out($code) public function out(string $code): string
{ {
return $this->tpl->out($code, $this->escape); return $this->tpl->out($code, $this->escape);
} }

View File

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

View File

@ -19,9 +19,6 @@ use Fenom\Error\UnexpectedTokenException;
* - Line number of the token * - Line number of the token
* *
* @see http://php.net/tokenizer * @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 * @package Fenom
* @author Ivan Shalganov <a.cobest@gmail.com> * @author Ivan Shalganov <a.cobest@gmail.com>
@ -70,18 +67,18 @@ class Tokenizer
*/ */
const MACRO_COND = 1008; const MACRO_COND = 1008;
public $tokens; public array $tokens;
public $p = 0; public int $p = 0;
public $quotes = 0; public int $quotes = 0;
private $_max = 0; private int $_max;
private $_last_no = 0; private mixed $_last_no;
/** /**
* @see http://docs.php.net/manual/en/tokens.php * @see http://docs.php.net/manual/en/tokens.php
* @var array groups of tokens * @var array groups of tokens
*/ */
public static $macros = array( public static array $macros = [
self::MACRO_STRING => array( self::MACRO_STRING => [
\T_ABSTRACT => 1, \T_ARRAY => 1, \T_AS => 1, \T_BREAK => 1, \T_ABSTRACT => 1, \T_ARRAY => 1, \T_AS => 1, \T_BREAK => 1,
\T_CASE => 1, \T_CATCH => 1, \T_CLASS => 1, \T_CASE => 1, \T_CATCH => 1, \T_CLASS => 1,
\T_CLASS_C => 1, \T_CLONE => 1, \T_CONST => 1, \T_CONTINUE => 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_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_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_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_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_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_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_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_REQUIRE_ONCE => 1, \T_RETURN => 1, \T_STRING => 1,
\T_SWITCH => 1, \T_THROW => 1, 355 /* T_TRAIT */ => 1, 365 /* T_TRAIT_C */ => 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_TRY => 1, \T_UNSET => 1, \T_USE => 1, \T_VAR => 1,
\T_WHILE => 1, 267 /* T_YIELD */ => 1 \T_WHILE => 1, \T_YIELD => 1, \T_YIELD_FROM => 1
), ],
self::MACRO_INCDEC => array( self::MACRO_INCDEC => [
\T_INC => 1, \T_DEC => 1 \T_INC => 1, \T_DEC => 1
), ],
self::MACRO_UNARY => array( self::MACRO_UNARY => [
"!" => 1, "~" => 1, "-" => 1 "!" => 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_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_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, \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, "^" => 1, "%" => 1, "<" => 1, "^" => 1, "%" => 1,
"&" => 1 "&" => 1
), ],
self::MACRO_BOOLEAN => array( self::MACRO_BOOLEAN => [
\T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1,
\T_BOOLEAN_AND => 1, \T_BOOLEAN_OR => 1, \T_BOOLEAN_AND => 1, \T_BOOLEAN_OR => 1,
\T_LOGICAL_AND => 1 \T_LOGICAL_AND => 1
), ],
self::MACRO_MATH => array( self::MACRO_MATH => [
"+" => 1, "-" => 1, "*" => 1, "+" => 1, "-" => 1, "*" => 1,
"/" => 1, "^" => 1, "%" => 1, "/" => 1, "^" => 1, "%" => 1,
"&" => 1, "|" => 1 "&" => 1, "|" => 1
), ],
self::MACRO_COND => array( self::MACRO_COND => [
\T_IS_EQUAL => 1, \T_IS_IDENTICAL => 1, ">" => 1, \T_IS_EQUAL => 1, \T_IS_IDENTICAL => 1, ">" => 1,
"<" => 1, \T_SL => 1, \T_SR => 1, "<" => 1, \T_SL => 1, \T_SR => 1,
\T_IS_NOT_EQUAL => 1, \T_IS_NOT_IDENTICAL => 1, \T_IS_SMALLER_OR_EQUAL => 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_AND_EQUAL => 1, \T_DIV_EQUAL => 1, \T_MINUS_EQUAL => 1,
\T_MOD_EQUAL => 1, \T_MUL_EQUAL => 1, \T_OR_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_PLUS_EQUAL => 1, \T_SL_EQUAL => 1, \T_SR_EQUAL => 1,
\T_XOR_EQUAL => 1, '=' => 1, \T_XOR_EQUAL => 1, '=' => 1,
), ],
self::MACRO_SCALAR => array( self::MACRO_SCALAR => [
\T_LNUMBER => 1, \T_LNUMBER => 1,
\T_DNUMBER => 1, \T_DNUMBER => 1,
\T_CONSTANT_ENCAPSED_STRING => 1 \T_CONSTANT_ENCAPSED_STRING => 1
) ]
); ];
public static $description = array( public static array $description = [
self::MACRO_STRING => 'string', self::MACRO_STRING => 'string',
self::MACRO_INCDEC => 'increment/decrement operator', self::MACRO_INCDEC => 'increment/decrement operator',
self::MACRO_UNARY => 'unary operator', self::MACRO_UNARY => 'unary operator',
@ -156,27 +154,34 @@ class Tokenizer
self::MACRO_COND => 'conditional operator', self::MACRO_COND => 'conditional operator',
self::MACRO_EQUALS => 'equal operator', self::MACRO_EQUALS => 'equal operator',
self::MACRO_SCALAR => 'scalar value' self::MACRO_SCALAR => 'scalar value'
); ];
/** /**
* Special tokens * Special tokens
* @var array * @var array
*/ */
private static $spec = array( private static array $spec = [
'true' => 1, 'true' => 1,
'false' => 1, 'false' => 1,
'null' => 1, 'null' => 1,
'TRUE' => 1, 'TRUE' => 1,
'FALSE' => 1, 'FALSE' => 1,
'NULL' => 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); $_tokens = token_get_all("<?php " . $query);
$line = 1; $line = 1;
array_shift($_tokens); array_shift($_tokens);
@ -186,16 +191,39 @@ class Tokenizer
if ($token === '"' || $token === "'" || $token === "`") { if ($token === '"' || $token === "'" || $token === "`") {
$this->quotes++; $this->quotes++;
} }
$token = array( $token = [
$token, $token,
$token, $token,
$line, $line,
); ];
} elseif ($token[0] === \T_WHITESPACE) { } elseif ($token[0] === \T_WHITESPACE) {
$tokens[$i - 1][2] = $token[1]; $tokens[$i - 1][2] = $token[1];
continue; 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. } elseif ($token[0] === \T_DNUMBER) { // fix .1 and 1.
if(strpos($token[1], '.') === 0) { if(str_starts_with($token[1], '.')) {
$tokens[] = array( $tokens[] = array(
'.', '.',
'.', '.',
@ -239,9 +267,9 @@ class Tokenizer
/** /**
* Is incomplete mean some string not closed * 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); 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 * @link http://php.net/manual/en/iterator.current.php
* @return mixed Can return any type. * @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 * @link http://php.net/manual/en/iterator.next.php
* @return Tokenizer * @return Tokenizer
*/ */
public function next() public function next(): static
{ {
if ($this->p > $this->_max) { if ($this->p > $this->_max) {
return $this; return $this;
} }
$this->p++; $this->p++;
unset($this->prev, $this->curr, $this->next); $this->cleanTokenCache();
return $this; return $this;
} }
@ -277,10 +306,10 @@ class Tokenizer
* Check token type. If token type is one of expected types return true. Otherwise return false * Check token type. If token type is one of expected types return true. Otherwise return false
* *
* @param array $expects * @param array $expects
* @param string|int $token * @param int|string $token
* @return bool * @return bool
*/ */
private function _valid($expects, $token) private function _valid(array $expects, int|string $token): bool
{ {
foreach ($expects as $expect) { foreach ($expects as $expect) {
if (is_string($expect) || $expect < 1000) { 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. * If the next token is a valid one, move the position of cursor one step forward. Otherwise throws an exception.
* @param array $tokens * @param array $tokens
* @throws UnexpectedTokenException
* @return mixed * @return mixed
* @throws UnexpectedTokenException
*/ */
public function _next($tokens) public function _next(array $tokens): void
{ {
$this->next(); $this->next();
if (!$this->curr) { if (!$this->currToken()) {
throw new UnexpectedTokenException($this, $tokens); throw new UnexpectedTokenException($this, $tokens);
} }
if ($tokens) { if ($tokens) {
@ -323,7 +352,7 @@ class Tokenizer
* Fetch next specified token or throw an exception * Fetch next specified token or throw an exception
* @return mixed * @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()); $this->_next(func_get_args());
return $this->current(); return $this->current();
@ -333,9 +362,10 @@ class Tokenizer
* @param $token * @param $token
* @return bool * @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 * @return mixed
* @throws UnexpectedTokenException * @throws UnexpectedTokenException
*/ */
public function getAndNext( /* $token1, ... */) public function getAndNext( /* $token1, ... */): mixed
{ {
if ($this->curr) { $curr = $this->currToken();
$cur = $this->curr[1]; if ($curr) {
$cur = $curr[1];
$this->next(); $this->next();
return $cur; return $cur;
} else { } else {
@ -359,9 +390,10 @@ class Tokenizer
* @param $token1 * @param $token1
* @return bool * @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 * @param $token1
* @return bool * @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 * @param $token1
* @return bool * @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 * Get specified token
* *
* @param string|int $token1 * @param int|string $token1
* @throws UnexpectedTokenException
* @return mixed * @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])) { $curr = $this->currToken();
return $this->curr[1]; if ($curr && $this->_valid(func_get_args(), $curr[0])) {
return $curr[1];
} else { } else {
throw new UnexpectedTokenException($this, func_get_args()); throw new UnexpectedTokenException($this, func_get_args());
} }
@ -404,21 +439,21 @@ class Tokenizer
* Step back * Step back
* @return Tokenizer * @return Tokenizer
*/ */
public function back() public function back(): static
{ {
if ($this->p === 0) { if ($this->p === 0) {
return $this; return $this;
} }
$this->p--; $this->p--;
unset($this->prev, $this->curr, $this->next); $this->cleanTokenCache();
return $this; return $this;
} }
/** /**
* @param $token1 * @param int|string $token1
* @return bool * @return bool
*/ */
public function hasBackList($token1 /*, $token2 ...*/) public function hasBackList(int|string $token1 /*, $token2 ...*/): bool
{ {
$tokens = func_get_args(); $tokens = func_get_args();
$c = $this->p; $c = $this->p;
@ -431,27 +466,38 @@ class Tokenizer
return true; return true;
} }
/** public function prevToken(): mixed
* Lazy load properties
*
* @param string $key
* @return mixed
*/
public function __get($key)
{ {
switch ($key) { if ($this->_prev) {
case 'curr': return $this->_prev;
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;
} }
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; return $this->_max;
} }
@ -460,9 +506,10 @@ class Tokenizer
* Return the key of the current element * Return the key of the current element
* @return mixed scalar on success, or null on failure. * @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. * @return boolean The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure. * 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 * Get token name
* @static * @static
* @param int|string $token * @param mixed $token
* @return string * @return string|null
*/ */
public static function getName($token) public static function getName(mixed $token): ?string
{ {
if (is_string($token)) { if (is_string($token)) {
return $token; return $token;
@ -500,10 +547,11 @@ class Tokenizer
* @throws UnexpectedTokenException * @throws UnexpectedTokenException
* @return Tokenizer * @return Tokenizer
*/ */
public function skip( /*$token1, $token2, ...*/) public function skip( /*$token1, $token2, ...*/): static
{ {
if (func_num_args()) { 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(); $this->next();
return $this; return $this;
} else { } else {
@ -521,9 +569,10 @@ class Tokenizer
* @param int|string $token1 * @param int|string $token1
* @return Tokenizer * @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(); $this->next();
} }
return $this; return $this;
@ -536,9 +585,10 @@ class Tokenizer
* @return Tokenizer * @return Tokenizer
* @throws UnexpectedTokenException * @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; return $this;
} else { } else {
throw new UnexpectedTokenException($this, func_get_args()); throw new UnexpectedTokenException($this, func_get_args());
@ -551,7 +601,7 @@ class Tokenizer
* @param int $after count tokens after current token * @param int $after count tokens after current token
* @return array * @return array
*/ */
public function getSnippet($before = 0, $after = 0) public function getSnippet(int $before = 0, int $after = 0): array
{ {
$from = 0; $from = 0;
$to = $this->p; $to = $this->p;
@ -594,7 +644,7 @@ class Tokenizer
* @param int $after * @param int $after
* @return string * @return string
*/ */
public function getSnippetAsString($before = 0, $after = 0) public function getSnippetAsString(int $before = 0, int $after = 0): string
{ {
$str = ""; $str = "";
foreach ($this->getSnippet($before, $after) as $token) { 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 * Check if current is special value: true, TRUE, false, FALSE, null, NULL
* @return bool * @return bool
*/ */
public function isSpecialVal() public function isSpecialVal(): bool
{ {
return isset(self::$spec[$this->current()]); return isset(self::$spec[$this->current()]);
} }
@ -616,7 +666,7 @@ class Tokenizer
* Check if the token is last * Check if the token is last
* @return bool * @return bool
*/ */
public function isLast() public function isLast(): bool
{ {
return $this->p === $this->_max; return $this->p === $this->_max;
} }
@ -624,10 +674,10 @@ class Tokenizer
/** /**
* Move pointer to the end * Move pointer to the end
*/ */
public function end() public function end(): static
{ {
$this->p = $this->_max; $this->p = $this->_max;
unset($this->prev, $this->curr, $this->next); $this->cleanTokenCache();
return $this; return $this;
} }
@ -635,23 +685,26 @@ class Tokenizer
* Return line number of the current token * Return line number of the current token
* @return mixed * @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 * Is current token whitespaced, means previous token has whitespace characters
* @return bool * @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() 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 * @param int $p
* @return $this * @return $this
*/ */
public function seek($p) public function seek(int $p): static
{ {
$this->p = $p; $this->p = $p;
unset($this->prev, $this->curr, $this->next); $this->cleanTokenCache();
return $this; return $this;
} }
} }

View File

@ -3,13 +3,17 @@ namespace Fenom;
use Fenom, Fenom\Provider as FS; use Fenom, Fenom\Provider as FS;
class CustomFenom extends Fenom {
public mixed $prop;
}
class TestCase extends \PHPUnit\Framework\TestCase class TestCase extends \PHPUnit\Framework\TestCase
{ {
public $template_path = 'template'; public $template_path = 'template';
/** /**
* @var Fenom * @var CustomFenom
*/ */
public $fenom; public CustomFenom $fenom;
public $values; public $values;
@ -53,7 +57,7 @@ class TestCase extends \PHPUnit\Framework\TestCase
return FENOM_RESOURCES . '/compile'; return FENOM_RESOURCES . '/compile';
} }
public function setUp() public function setUp(): void
{ {
if (!file_exists($this->getCompilePath())) { if (!file_exists($this->getCompilePath())) {
mkdir($this->getCompilePath(), 0777, true); mkdir($this->getCompilePath(), 0777, true);
@ -61,7 +65,7 @@ class TestCase extends \PHPUnit\Framework\TestCase
FS::clean($this->getCompilePath()); 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->addProvider('persist', new Provider(FENOM_RESOURCES . '/provider'));
$this->fenom->addModifier('dots', __CLASS__ . '::dots'); $this->fenom->addModifier('dots', __CLASS__ . '::dots');
$this->fenom->addModifier('concat', __CLASS__ . '::concat'); $this->fenom->addModifier('concat', __CLASS__ . '::concat');
@ -89,7 +93,7 @@ class TestCase extends \PHPUnit\Framework\TestCase
public static function inlineFunction($params) public static function inlineFunction($params)
{ {
return isset($params["text"]) ? $params["text"] : ""; return $params["text"] ?? "";
} }
public static function blockFunction($params, $text) public static function blockFunction($params, $text)
@ -97,7 +101,7 @@ class TestCase extends \PHPUnit\Framework\TestCase
return $text; return $text;
} }
public static function setUpBeforeClass() public static function setUpBeforeClass(): void
{ {
if (!file_exists(FENOM_RESOURCES . '/template')) { if (!file_exists(FENOM_RESOURCES . '/template')) {
mkdir(FENOM_RESOURCES . '/template', 0777, true); 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); $dir = dirname($name);
if ($dir != "." && !is_dir(FENOM_RESOURCES . '/template/' . $dir)) { if ($dir != "." && !is_dir(FENOM_RESOURCES . '/template/' . $dir)) {
@ -168,7 +172,7 @@ class TestCase extends \PHPUnit\Framework\TestCase
$this->fenom->setOptions($options); $this->fenom->setOptions($options);
try { try {
$this->fenom->compileCode($code, "inline.tpl"); $this->fenom->compileCode($code, "inline.tpl");
} catch (\Exception $e) { } catch (\Throwable $e) {
$this->assertSame($exception, get_class($e), "Exception $code"); $this->assertSame($exception, get_class($e), "Exception $code");
$this->assertStringStartsWith($message, $e->getMessage()); $this->assertStringStartsWith($message, $e->getMessage());
$this->fenom->setOptions($opt); $this->fenom->setOptions($opt);
@ -180,7 +184,11 @@ class TestCase extends \PHPUnit\Framework\TestCase
public function assertRender($tpl, $result, array $vars = array(), $debug = false) 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) { if ($debug) {
print_r("\nDEBUG $tpl:\n" . $template->getBody()); 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( return array(
array('$one', 1), array('$one', 1),
@ -300,7 +308,8 @@ class Helper
const CONSTANT = "helper.class.const"; const CONSTANT = "helper.class.const";
public $word = 'helper'; public string $word = 'helper';
public Helper $self;
public function __construct($word) public function __construct($word)
{ {
@ -325,6 +334,13 @@ class Helper
public function getArray() { public function getArray() {
return array(1,2,3); return array(1,2,3);
} }
/**
* @throws \Exception
*/
public static function throws() {
throw new \Exception("helper exception");
}
} }
function helper_func($string, $pad = 10) { function helper_func($string, $pad = 10) {

View File

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

View File

@ -132,7 +132,8 @@ class AccessorTest extends TestCase
* @group issue260 * @group issue260
*/ */
public function testBug260() { 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); $this->execError($tpl, $exception, $message);
} }
public function getThree() { public static function getThree(): int
{
return 3; 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() { public static function providerSmartAccessor() {
return array( return array(
array('acc', '$tpl->getStorage()->test->values', \Fenom::ACCESSOR_VAR, '{$.acc.three}', '3'), array('acc', '\Fenom\AccessorTest::getThreeArray()', \Fenom::ACCESSOR_VAR, '{$.acc.three}', '3'),
array('acc', '$tpl->getStorage()->test->getThree', \Fenom::ACCESSOR_CALL, '{$.acc()}', '3'), array('acc', '\Fenom\AccessorTest::getThreeCb()', \Fenom::ACCESSOR_CALL, '{$.acc()}', '3'),
array('acc', 'three', \Fenom::ACCESSOR_PROPERTY, '{$.acc}', '3'), array('acc', 'prop', \Fenom::ACCESSOR_PROPERTY, '{$.acc}', 'something'),
array('acc', 'templateExists', \Fenom::ACCESSOR_METHOD, '{$.acc("persist:pipe.tpl")}', '1') array('acc', 'templateExists', \Fenom::ACCESSOR_METHOD, '{$.acc("persist:pipe.tpl")}', '1')
); );
} }
@ -259,8 +273,7 @@ class AccessorTest extends TestCase
* @param $result * @param $result
*/ */
public function testSmartAccessor($name, $accessor, $type, $code, $result) { public function testSmartAccessor($name, $accessor, $type, $code, $result) {
$this->fenom->test = $this; $this->fenom->prop = "something";
$this->fenom->three = 3;
$this->fenom->addAccessorSmart($name, $accessor, $type); $this->fenom->addAccessorSmart($name, $accessor, $type);
$this->assertRender($code, $result, $this->getVars()); $this->assertRender($code, $result, $this->getVars());
} }

View File

@ -6,7 +6,7 @@ namespace Fenom;
class CustomProviderTest extends TestCase class CustomProviderTest extends TestCase
{ {
public function setUp() public function setUp(): void
{ {
parent::setUp(); parent::setUp();
$this->fenom->addProvider("my", new Provider(FENOM_RESOURCES . '/provider')); $this->fenom->addProvider("my", new Provider(FENOM_RESOURCES . '/provider'));

View File

@ -21,7 +21,7 @@ class FunctionsTest extends TestCase
return $a + $i; return $a + $i;
} }
public function setUp() public function setUp(): void
{ {
parent::setUp(); parent::setUp();
$this->fenom->addFunctionSmart('sum', __CLASS__ . '::functionSum'); $this->fenom->addFunctionSmart('sum', __CLASS__ . '::functionSum');

View File

@ -4,7 +4,7 @@ namespace Fenom;
class MacrosTest extends TestCase class MacrosTest extends TestCase
{ {
public function setUp() public function setUp(): void
{ {
parent::setUp(); parent::setUp();
$this->tpl("math.tpl", $this->tpl("math.tpl",
@ -115,12 +115,10 @@ class MacrosTest extends TestCase
); );
} }
/**
* @expectedExceptionMessage Undefined macro 'plus'
* @expectedException \Fenom\Error\CompileException
*/
public function testImportMiss() public function testImportMiss()
{ {
$this->expectException(exception: \Fenom\Error\CompileException::class);
$this->expectExceptionMessage("Undefined macro 'plus'");
$tpl = $this->fenom->compile('import_miss.tpl'); $tpl = $this->fenom->compile('import_miss.tpl');
$this->assertSame( $this->assertSame(

View File

@ -11,7 +11,7 @@ class ProviderTest extends TestCase
*/ */
public $provider; public $provider;
public function setUp() public function setUp(): void
{ {
parent::setUp(); parent::setUp();
$this->tpl("template1.tpl", 'Template 1 {$a}'); $this->tpl("template1.tpl", 'Template 1 {$a}');
@ -30,6 +30,7 @@ class ProviderTest extends TestCase
public function testGetSource() public function testGetSource()
{ {
$time = 0.0;
clearstatcache(); clearstatcache();
$src = $this->provider->getSource("template1.tpl", $time); $src = $this->provider->getSource("template1.tpl", $time);
clearstatcache(); clearstatcache();
@ -37,11 +38,9 @@ class ProviderTest extends TestCase
$this->assertEquals(filemtime(FENOM_RESOURCES . '/template/template1.tpl'), $time); $this->assertEquals(filemtime(FENOM_RESOURCES . '/template/template1.tpl'), $time);
} }
/**
* @expectedException \RuntimeException
*/
public function testGetSourceInvalid() public function testGetSourceInvalid()
{ {
$this->expectException(\RuntimeException::class);
$this->provider->getSource("unexists.tpl", $time); $this->provider->getSource("unexists.tpl", $time);
} }
@ -52,11 +51,9 @@ class ProviderTest extends TestCase
$this->assertEquals(filemtime(FENOM_RESOURCES . '/template/template1.tpl'), $time); $this->assertEquals(filemtime(FENOM_RESOURCES . '/template/template1.tpl'), $time);
} }
/**
* @expectedException \RuntimeException
*/
public function testGetLastModifiedInvalid() public function testGetLastModifiedInvalid()
{ {
$this->expectException(\RuntimeException::class);
$this->provider->getLastModified("unexists.tpl"); $this->provider->getLastModified("unexists.tpl");
} }

View File

@ -12,7 +12,7 @@ class RenderTest extends TestCase
*/ */
public static $render; public static $render;
public static function setUpBeforeClass() public static function setUpBeforeClass(): void
{ {
self::$render = new Render(Fenom::factory("."), function ($tpl) { self::$render = new Render(Fenom::factory("."), function ($tpl) {
echo "It is render's function " . $tpl["render"]; 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"))); $this->assertSame("It is render's function fetch", self::$render->fetch(array("render" => "fetch")));
} }
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage template error
*/
public function testFetchException() public function testFetchException()
{ {
$this->expectException(Fenom\Error\TemplateException::class);
$render = new Render(Fenom::factory("."), function () { $render = new Render(Fenom::factory("."), function () {
echo "error"; echo "error";
throw new \RuntimeException("template 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 class TemplateTest extends TestCase
{ {
public function setUp() public function setUp(): void
{ {
parent::setUp(); parent::setUp();
$this->tpl('welcome.tpl', '<b>Welcome, {$username} ({$email})</b>'); $this->tpl('welcome.tpl', '<b>Welcome, {$username} ({$email})</b>');
@ -1202,8 +1202,8 @@ class TemplateTest extends TestCase
$this->values = $vars; $this->values = $vars;
$this->tpl("insert.tpl", $code); $this->tpl("insert.tpl", $code);
$tpl = $this->fenom->getTemplate('insert.tpl'); $tpl = $this->fenom->getTemplate('insert.tpl');
$this->assertSame($result, $tpl->fetch($vars)); $this->assertSame($result, $tpl->fetch($vars), $code);
$this->assertTrue($tpl->isValid()); $this->assertTrue($tpl->isValid(), $code);
} }
/** /**

View File

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

View File

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