Compare commits

...

151 Commits

Author SHA1 Message Date
Ivan Shalganov fbf4f46f1f
Merge pull request #341 from fenom-template/v3.0.0
Migrate to php8
2023-02-27 09:38:43 +01:00
ivan shalganov e1601dce6c migrate to php8 2023-02-23 22:09:39 +01:00
ivan shalganov 65c33ac2e3 Merge remote-tracking branch 'origin/master' into v3.0.0
# Conflicts:
#	.github/workflows/php.yml
2023-02-23 22:06:19 +01:00
ivan shalganov 74fbcd1534 migrate to php8 2023-02-23 22:03:07 +01:00
ivan shalganov 9269c96fc3 migrate to php8 2023-02-23 22:01:33 +01:00
Ivan Shalganov c7b8835ff7
Create .github/workflows/php.yml 2023-02-23 21:57:33 +01:00
ivan shalganov f8863ee0ad migrate to php8 2023-02-23 21:53:03 +01:00
ivan shalganov e8647cfb02 migrate to php8 2023-02-23 21:50:12 +01:00
ivan shalganov b04c38a533 migrate to php8 2023-02-23 21:03:19 +01:00
ivan shalganov 3af87c3419 migrate to php8 2023-02-21 22:09:00 +01:00
ivan shalganov a4fbc2a6aa migrate to php8 2023-02-19 22:14:08 +01:00
ivan shalganov ba1bc18aba migrate to php8 2023-02-06 23:49:54 +01:00
ivan shalganov 4cdfd306d9 migrate to php8 2023-02-05 22:18:18 +01:00
ivan shalganov 84dac62a85 migrate to php8 2023-02-05 21:59:04 +01:00
Ivan Shalganov 065ccaec23
Merge pull request #335 from WinterSilence/patch-2
Fix `Fenom::isAllowedFunction()`
2022-06-13 15:52:24 +03:00
Ivan Shalganov 6650668b72
Merge pull request #337 from WinterSilence/patch-3
Fix method name in `docs/ru/ext/extend.md`
2022-06-13 15:51:26 +03:00
Anton b24d9d9e1e
Update extend.md 2022-06-12 12:10:23 +03:00
Anton 5e14c6bf90
Update Fenom.php 2022-06-12 11:46:43 +03:00
Anton 79283c6f7f
Fix `Fenom::isAllowedFunction()`
- Checks if function in `ini_get('disable_functions')`
- Replace `is_callable()` to `function_exists()` to ignore invokable classes
2022-06-12 10:44:00 +03:00
Ivan Shalganov 8fb0a70311
Merge pull request #329 from EMDM45/patch-1
Update Accessor.php
2021-11-12 00:08:13 +03:00
Ivan Shalganov f3722a5f37
Merge pull request #330 from get-web/patch-1
Update set.md
2021-10-25 21:23:31 +03:00
Vitalii P 87324033e7
Update set.md 2021-10-24 15:38:18 +03:00
EMDM45 317c775f50
Update Accessor.php 2021-10-22 12:42:41 +05:00
Ivan Shalganov decad1d1fa
Update license to BSD-3-Clause 2021-10-12 15:43:18 +03:00
Ivan Shalganov f0cb251843
Update Fenom.php 2020-07-06 20:02:31 +03:00
Ivan Shalganov cb1f25d8af TravisCI: fix testing 2020-07-06 11:35:15 +03:00
Ivan Shalganov 5f9d01ae6f TravisCI: add PHP 7.3 and 7.4 2020-07-06 11:33:51 +03:00
Ivan Shalganov daf9d3cb20 Fix travis CI 2020-07-06 11:32:35 +03:00
Ivan Shalganov 52fea74cc4 `notice` appears when expression unexpectedly ends 2020-07-06 11:31:44 +03:00
Ivan Shalganov 493fcce6e0 `notice` appears when expression unexpectedly ends 2020-07-06 11:31:08 +03:00
Ivan Shalganov 07ff960324 Travis does not support PHP 5.4 and PHP 5.5 2020-07-06 11:20:07 +03:00
Maxim Kostjukevich 47fb3b5e0a
Merge pull request #311 from Electrica/patch-2
Update internal.md
2020-04-27 10:22:44 +04:00
Maxim Kostjukevich 4dd1e1870c
Update Tokenizer.php 2020-04-27 10:18:25 +04:00
Maxim Kostjukevich 55b84888e8
Update operators.md 2020-04-27 10:10:55 +04:00
Миша b4ff112237
Update internal.md
тЬся
соответсТвует
поправил
2019-12-21 16:43:22 +06:00
Ivan Shalganov fc188a5822
Merge pull request #302 from OrderTarget/master
Modifier callable docBlock fix
2018-08-16 10:21:45 +03:00
OrderTarget e4e9b2e73c
Modifier callable docBlock fix 2018-07-20 15:31:17 +03:00
Maxim Kostjukevich 5b46e54133
Merge pull request #293 from Loki3000/master
fix error in docs
2018-06-15 08:13:03 +03:00
Maxim Kostjukevich ebbec8c6be
Merge pull request #297 from pafnuty/master
Fix typos in ru/dev/readme.md
2018-06-15 08:10:37 +03:00
Pavel_Belousov d477607831 Fix typos in ru/dev/readme.md 2018-04-28 10:47:22 +04:00
Ivan Shalganov dbc9388b2f
Merge pull request #296 from sreenadh/patch-1
Community Guidelines
2018-04-23 09:25:28 +03:00
Sreenadh H 85bdf5ca94
Create CONTRIBUTING.md (#2) 2018-04-23 08:32:18 +05:30
Sreenadh H c1531ea81a
Create ISSUE_TEMPLATE.md 2018-04-23 08:28:53 +05:30
Ivan Shalganov 9a1e56d164
Merge pull request #292 from tsukasa-mixer/master
parseChain array result support
2018-03-28 12:36:06 +03:00
Maksim K d141b14017 up travis 2018-03-26 18:35:11 +03:00
Maksim K 3c083cc0dd up travis and readme 2018-03-26 18:33:45 +03:00
Loki3000 3e1fa19c70
fix doc errors 2018-03-15 18:20:44 +03:00
Loki3000 00112bb4d5
fix doc errors 2018-03-15 18:19:55 +03:00
Loki3000 80a537de8c
non existent parameter 2018-03-15 17:02:10 +03:00
Loki3000 de8dbb4d53
non existent parameter 2018-03-15 17:01:29 +03:00
Loki3000 40f50d20fd
fix error 2018-03-15 11:49:33 +03:00
Maksim 5dd197ffac
parseChain array result support
{$var->func()->func()->props->func().array_key}
2018-03-14 22:41:37 +03:00
Maxim Kostjukevich 7b0dab9247
Merge pull request #291 from JorgenPo/patch-1
Исправлен перевод документации
2018-02-09 17:23:52 +02:00
Георгий Попов d8d9833ec8
Add missing translation 2018-02-03 16:16:26 +03:00
Max Kostjukevich ab6c5ea47c Merge pull request #280 from sartatpdev999/patch-7
Fix typo error
2017-07-31 07:34:08 +03:00
Max Kostjukevich e251305ef5 Merge pull request #281 from sartatpdev999/patch-8
Fix spelling error
2017-07-31 07:33:58 +03:00
Max Kostjukevich 999a23995f Merge pull request #284 from sartatpdev999/patch-10
Fix typo & spelling errors
2017-07-31 07:33:42 +03:00
Max Kostjukevich cc2a56339d Merge pull request #285 from sartatpdev999/patch-11
Fix multiple errors
2017-07-31 07:33:17 +03:00
sartatpdev999 ec17a083d4 Extra fix
Extra fix
2017-07-31 11:21:56 +07:00
sartatpdev999 c481365d9c Fix multiple errors
Fix multiple errors
2017-07-31 11:15:31 +07:00
sartatpdev999 eedf775238 Fix typo & spelling errors 2017-07-28 10:14:49 +07:00
Ivan Shalganov 7b38f936c9 Merge pull request #283 from sartatpdev999/patch-9
Fix typo error
2017-07-27 13:56:09 +03:00
sartatpdev999 d7a8a2372a Fix typo error
можнно > можно
2017-07-27 16:27:50 +07:00
sartatpdev999 9457ac60df Fix spelling error
рекоммендуется > рекомендуется
2017-07-26 14:50:55 +07:00
sartatpdev999 75c56ebe81 Fix typo error
фунций > функций
2017-07-26 14:40:02 +07:00
Max Kostjukevich cce0e578f1 Merge pull request #279 from sartatpdev999/patch-5
Fix typo error
2017-07-26 09:37:25 +03:00
Max Kostjukevich 293893a9c8 Merge pull request #278 from sartatpdev999/patch-6
Fix spelling error & more correct translation
2017-07-26 09:36:52 +03:00
sartatpdev999 7d9b474de1 Fix spelling error & more correct translation
возвращяет > возвращает
коллбек, который будет вызван для изменения данных > функция обратного вызова, которая будет вызвана для изменения данных
2017-07-26 12:19:55 +07:00
sartatpdev999 73bb8791a5 Fix typo error
добавте > добавьте
2017-07-26 12:16:21 +07:00
Max Kostjukevich 0e5d0a27cd Merge pull request #277 from sartatpdev999/patch-4
Fix typo
2017-07-21 13:35:36 +03:00
sartatpdev999 cbab4d3184 Fix typo
интрвелами > интервалами
2017-07-21 17:21:33 +07:00
Ivan Shalganov 2d968ab8d0 Merge pull request #274 from bezumkin/patch-1
Fixed method tagPaste
2017-06-29 13:49:38 +03:00
Vasily Naumkin 6a7291fac6 Fixed method tagPaste
We need to remove quotes from names of blocks to get them from array.
2017-06-26 12:47:19 +03:00
bzick 8730343442 Fix #272 2017-05-23 11:21:35 +03:00
Ivan Shalganov 7d0db43050 Merge pull request #270 from fenom-template/develop
Fix bugs; phpunit things
2017-04-22 22:48:52 +03:00
bzick 44ac3b16e5 Phpunit things 2017-04-22 16:39:33 +03:00
bzick 4d9a82314b Phpunit things 2017-04-22 16:35:42 +03:00
bzick 2fac2bbc9f Phpunit things 2017-04-22 16:34:19 +03:00
bzick a591724d07 Phpunit things 2017-04-22 16:29:44 +03:00
bzick 4666fdced5 Phpunit things 2017-04-22 16:25:04 +03:00
bzick 66d9201a61 Phpunit things 2017-04-22 16:20:55 +03:00
bzick abf3bd5414 Fix #260 2017-04-22 15:59:20 +03:00
bzick 902284a76b Remove CI for old PHP (because PHPUnit...) 2017-04-22 15:25:00 +03:00
bzick 9670f0433a Merge branch 'master' into develop 2017-04-22 15:23:52 +03:00
bzick 28b4074d66 phpunit 5.* 2017-04-22 15:00:48 +03:00
Ivan Shalganov 449b76e554 Merge pull request #267 from 1f7/update_adapters_md
Update adapters.md
2017-03-12 22:49:05 +03:00
1f7 91c4dc611a Update adapters.md 2017-03-05 05:44:16 +03:00
bzick e97ef95ef5 CI: add --quiet mode for composer 2016-11-18 22:30:44 +03:00
bzick 23646edb6e PHP 7.1 supports 2016-11-18 22:26:03 +03:00
bzick d139a1ebf3 Fix #256 2016-11-18 22:07:14 +03:00
bzick 35455b761f Fixed: Unrecognized option "src_dir" under "coveralls" 2016-10-10 11:58:50 +03:00
bzick b92e0bbad3 Fix #241 + tests 2016-10-09 23:41:07 +03:00
Max Kostjukevich 05cda5426d Update extend.md
опечатки
2016-09-16 09:16:30 +03:00
bzick cd4f688088 Fix #249 2016-08-27 22:30:34 +03:00
bzick 514b0b6769 Fix #247 2016-08-22 17:09:18 +03:00
bzick 3171d80d8b Fix #247 2016-08-22 17:09:18 +03:00
Max Kostjukevich 58f8c0facb Merge pull request #246 from Sleuthhound/patch-4
Замечание
2016-08-13 19:23:32 +03:00
Anton Popov d64706b675 Замечание
http://php.net/manual/ru/function.fnmatch.php
2016-08-13 20:53:11 +05:00
Max Kostjukevich a4c8ae17ba Merge pull request #245 from ig0r74/patch-1
Update adapters.md
2016-08-12 19:53:04 +03:00
Igor ade29f5d67 Update adapters.md 2016-08-12 19:14:12 +03:00
Max Kostjukevich aa587a6293 Merge pull request #239 from Xesau/patch-1
Translated one sentence from Russian to English...
2016-07-06 18:06:47 +03:00
Xesau 0d599cb211 Translated one sentence from Russian to English...
and added documentation for {parent}
2016-07-05 20:10:23 +02:00
Max Kostjukevich f160664f0e Merge pull request #238 from vasia123/patch-2
fix typo
2016-06-29 21:22:36 +03:00
Vasily Krakovetsky 23cf3e72ad fix typo 2016-06-29 21:20:30 +03:00
bzick 7fffa19fa9 Fix valid filename and length 2016-06-10 15:53:16 +03:00
Ivan Shalganov eef14ecf06 Update CHANGELOG.md 2016-06-09 22:58:05 +03:00
Ivan Shalganov a3a84ea606 Merge pull request #237 from fenom-template/develop
2.11
2016-06-09 22:46:27 +03:00
bzick 76e1b5a48c Change depends satooshi/php-coveralls 2016-06-09 18:05:22 +03:00
bzick f486c7d5d4 Different packages for PHP55 and PHP70 2016-06-09 18:01:25 +03:00
bzick c87d831189 Update composer.lock 2016-06-09 17:57:42 +03:00
bzick 1e724e4c70 Tests++ 2016-06-09 16:00:18 +03:00
bzick a910b71a81 Fix comp. with 5.3 2016-06-09 14:30:20 +03:00
bzick e357a38946 Fix comp. with 5.3 2016-06-09 14:28:08 +03:00
bzick 626334017b Small fix of #229 2016-06-09 14:24:35 +03:00
bzick 24621099bf Add accessor callback; Add ?? operator 2016-06-09 13:45:26 +03:00
bzick 19ba9d3031 #231 2016-06-09 13:45:26 +03:00
bzick e557123e3c Done #231; Improve compile mechanism 2016-06-09 13:45:26 +03:00
Max Kostjukevich 180c171b1e fix #229
Fix error extends template call function
2016-06-08 13:28:02 +03:00
Max Kostjukevich 8e57ef2853 Merge pull request #235 from Daisuke-sama/patch-4
Update extends.md
2016-06-08 13:22:16 +03:00
Max Kostjukevich ed1c9f4837 Merge pull request #234 from Daisuke-sama/patch-3
Update extends.md
2016-06-08 13:21:57 +03:00
Daisuke-sama f8a2df3041 Update extends.md 2016-05-29 16:41:11 +02:00
Daisuke-sama 36b5c213a2 Update extends.md
Раздел "{$.block}" - опечатка.
2016-05-26 15:48:25 +02:00
Max Kostjukevich c6d6bc6e12 Merge pull request #233 from Daisuke-sama/patch-2
Update syntax.md
2016-05-26 16:34:51 +03:00
Max Kostjukevich c1ca6d2246 Merge pull request #232 from Daisuke-sama/patch-1
Update configuration.md
2016-05-26 16:34:37 +03:00
Daisuke-sama a005732286 Update syntax.md
1. Исправлена ссылка в разделе "Использование переменных"
2. Описка в разделе "Модификаторы"
3. Опечатка в разделе "Параметры тегов"
2016-05-26 15:27:23 +02:00
Daisuke-sama 4d76ce7abf Update configuration.md 2016-05-26 14:45:15 +02:00
bzick dec06c888e Done #228 2016-05-08 18:26:01 +03:00
bzick d23e2b4c4f Cleanup 2016-05-08 00:58:53 +03:00
bzick 32ce405de2 Docs++ 2016-05-08 00:57:02 +03:00
Ivan Shalganov 48f42c82a9 Merge pull request #227 from fenom-template/develop
2.9.0
2016-05-08 00:44:38 +03:00
Ivan Shalganov 0d1e7e56a6 Merge pull request #226 from fenom-template/master
Sync develop
2016-05-08 00:38:42 +03:00
bzick f315d937a8 Docs++ 2016-05-08 00:38:17 +03:00
Ivan Shalganov 279459be55 Merge pull request #224 from Sleuthhound/patch-3
Доступ к последовательности методов
2016-05-08 00:34:50 +03:00
bzick e289b9b3b1 Docs++ 2016-05-08 00:33:23 +03:00
bzick ba4ba548ff Foreach props, range iterator and more 2016-05-06 23:04:08 +03:00
Ivan Shalganov 9d1f0c4cae Merge pull request #222 from Sleuthhound/patch-1
Глобальная переменная в качестве параметра
2016-05-04 11:52:36 +03:00
Anton Popov 165d4816d6 Update Fenom.php 2016-04-19 14:35:25 +05:00
Anton Popov bfb64f150a Глобальная переменная в качестве параметра
Использование глобальных переменных в качестве параметров функций и методов:
object->method($.post.id)
2016-04-19 14:06:49 +05:00
bzick 1559adf030 Docs++ 2016-04-14 12:58:16 +03:00
bzick 45c9f27ae6 Docs++ 2016-04-13 11:34:38 +03:00
bzick eb75ae0bbb Docs++ 2016-04-12 12:28:57 +03:00
bzick 497f2e5b4b Docs++ 2016-04-12 09:37:40 +03:00
bzick 1c73d35681 Cleanup extending algorithm 2016-04-11 20:23:28 +03:00
bzick d6aa777fd0 Docs #209 [ru] 2016-04-11 20:19:31 +03:00
bzick 027075d7b5 Add #209: block's accessor $.blocks 2016-04-11 19:44:59 +03:00
Max Kostjukevich 2d3c89ec1e Merge pull request #219 from vasia123/patch-1
fix typo
2016-03-28 11:34:40 +03:00
Vasily Krakovetsky 1a3a3739ce fix typo 2016-03-28 11:13:26 +03:00
Max Kostjukevich 2ed57c959c Merge pull request #218 from DaaGER/patch-2
Update start.md
2016-03-15 14:27:38 +02:00
DaaGER 0dfcdfbcf7 Update start.md 2016-03-15 15:11:13 +03:00
Ivan Shalganov d52aaa57a2 Merge pull request #212 from fenom-template/develop
Fix #195; Catch Throwable exceptions;
2016-02-23 13:21:43 +03:00
bzick 3766505e5c Fix #195; Catch Throwable exceptions; 2016-02-23 13:16:48 +03:00
76 changed files with 3469 additions and 2430 deletions

View File

@ -1,5 +1,4 @@
service_name: travis-ci
src_dir: src
coverage_clover: build/logs/clover.xml

34
.github/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,34 @@
Contributing
============
Issue tracker
-------------
The Issue tracker serves mainly as a place to report bugs and request new features.
Please do not abuse it as a general questions or troubleshooting location.
For these questions you can always use the
[fenom tag](https://stackoverflow.com/questions/tagged/fenom) at [Stack Overflow](https://stackoverflow.com/).
* Please provide a small example in php/html that reproduces your situation
* Please report one feature or one bug per issue
* Failing to provide necessary information or not using the issue template may cause the issue to be closed without consideration.
Pull requests
-------------
Pull requests should be always based on the default [development](https://github.com/fenom-template/fenom/tree/master)
branch except for backports to older versions.
Some guidelines:
* Use an aptly named feature branch for the Pull request.
* Only files and lines affecting the scope of the Pull request must be affected.
* Make small, *atomic* commits that keep the smallest possible related code changes together.
* Code should be accompanied by a unit test testing expected behaviour.
When updating a PR, do not create a new one, just `git push --force` to your former feature branch, the PR will
update itself.

18
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,18 @@
> Please use https://stackoverflow.com/questions/tagged/fenom for all your general questions or troubleshooting!
>
> *Warning*: Failing to provide necessary information may cause the issue to be closed without consideration
### I found this bug / would like to have this new functionality
### This is Fenom and PHP version and environment (server/fpm/cli etc) I am using
### This is a template code snippet I use
```
{var x=10}
{Calc x=10 y=20}
```

31
.github/workflows/php.yml vendored Normal file
View File

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

3
.gitignore vendored
View File

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

View File

@ -3,17 +3,19 @@ language: php
sudo: false
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- 7.2
- 7.3
- 7.4
before_script:
- composer install --dev
- composer global require satooshi/php-coveralls
- composer update --quiet
script:
- phpunit
- vendor/bin/phpunit phpunit -c phpunit.xml.dist
after_script:
- php vendor/bin/coveralls
- coveralls

View File

@ -1,5 +1,36 @@
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)
- Added method to get the name of the cache template `$fenom->getCacheName($template_name)`(#231)
- Fix bug with before-code in template inheritance (#229)
- Added `??` operator.
- Improve compile mechanism
- ++Docs
- ++Test
## 2.10.0 (2016-05-08)
- Add tag `{do ...}`
- ++Docs
- ++Tests
## 2.9.0 (2016-05-08)
- Add `$.block`
- Refactory range
- Refactory blocks
- Docs
...
## 2.6.0 (2015-02-22)

View File

@ -1,31 +1,63 @@
Fenom - Template Engine for PHP
===============================
> Composer [package](https://packagist.org/packages/fenom/fenom): `{"fenom/fenom": "2.*"}`. <br />
> For old version: `{"fenom/fenom": "1.*"}`. <br />
> [List](https://github.com/fenom-template/fenom/wiki/Migrate-from-1.4.9-to-2.0) of incompatibilities between **1** and **2** versions.
**Fenóm** - lightweight and fast template engine for PHP.
[![Latest Stable Version](https://poser.pugx.org/fenom/fenom/v/stable.png)](https://packagist.org/packages/fenom/fenom)
[![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)
[![Total Downloads](https://poser.pugx.org/fenom/fenom/downloads.png)](https://packagist.org/packages/fenom/fenom)
* **Subject:** Template engine
* **Syntax:** Smarty-like
* **Documentation:** **[English](./docs/en/readme.md)**, **[Russian](./docs/ru/readme.md)**
* **PHP version:** 8.0+
* **State:** [![PHP Composer](https://github.com/fenom-template/fenom/actions/workflows/php.yml/badge.svg?branch=master)](https://github.com/fenom-template/fenom/actions/workflows/php.yml) [![Coverage Status](https://coveralls.io/repos/fenom-template/fenom/badge.svg?branch=master)](https://coveralls.io/r/fenom-template/fenom?branch=master)
* **Version:** [![Latest Stable Version](https://poser.pugx.org/fenom/fenom/v/stable.png)](https://packagist.org/packages/fenom/fenom)
* **Packagist:** [fenom/fenom](https://packagist.org/packages/fenom/fenom) [![Total Downloads](https://poser.pugx.org/fenom/fenom/downloads.png)](https://packagist.org/packages/fenom/fenom)
* **Composer:** `composer require fenom/fenom`
* **Discussion:** [Fenom Forum](https://groups.google.com/forum/#!forum/php-ion)
* **Versioning:** [semver2](http://semver.org/)
* **Performance:** see [benchmark](./docs/en/benchmark.md)
## [Quick start](./docs/en/start.md) :: [Documentation](./docs/readme.md) [[en](./docs/en/readme.md)|[ru](./docs/ru/readme.md)] :: [Benchmark](./docs/en/benchmark.md)
<!-- :: [Articles](./docs/articles.md) -->
***
### What is it
## Quick Start
**Fenóm** — lightweight template engine for PHP.
### Install
It means:
If you use composer in your project then you can to install Fenom as package.
* Known Smarty-like [syntax](./docs/en/syntax.md) with improvements.
* Very [fast](./docs/en/benchmark.md).
* [Lightweight](./docs/en/benchmark.md).
* Very [flexible](./docs/en/configuration.md#extends).
* Progressive parser without regular expressions.
* High [code coverage](https://coveralls.io/r/bzick/fenom?branch=master).
* Easy to understand [how it works](./docs/en/dev/readme.md).
* Easy to [use](./docs/en/start.md).
* Maximum [protection](./docs/en/configuration.md#configure).
### Setup
There is two-way to create Fenom instance:
* Long way: use operator `new`
* Shot way: use static factory-method
**Long way.** Create you own template provider or default provider `Fenom\Provider` (that is provider read [there](./)).
Using provider instance create Fenom instance:
```php
$fenom = new Fenom(new Fenom\Provider($template_dir));
```
After that, set compile directory:
```php
$fenom->setCompileDir($template_cache_dir);
```
This directory will be used for storing compiled templates, therefore it should be writable for Fenom.
Now Fenom is ready to work and now you can to configure it:
```php
$fenom->setOptions($options);
```
**Short way.** Creating an object via factory method with arguments from long way.
```php
$fenom = Fenom::factory($template_dir, $template_cache_dir, $options);
```
Now Fenom is ready to work.
### Usage
### Example

View File

@ -3,7 +3,7 @@
"type": "library",
"description": "Fenom - excellent template engine for PHP",
"keywords": ["fenom", "template", "templating", "templater"],
"license": "BSD-3",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Ivan Shalganov",
@ -11,15 +11,17 @@
}
],
"require": {
"php": ">=5.3.0",
"php": ">=8.0.0",
"ext-tokenizer": "*"
},
"require-dev": {
"phpunit/phpunit": "*",
"satooshi/php-coveralls": "dev-master"
"phpunit/phpunit": "9.*"
},
"autoload": {
"psr-0": { "Fenom\\": "src/" },
"psr-4": { "Fenom\\": "src/Fenom" },
"classmap": [ "src/Fenom.php" ]
},
"autoload-dev": {
"classmap": [ "tests/cases" ]
}
}

2142
composer.lock generated

File diff suppressed because it is too large Load Diff

3
docs/en/adapters.md Normal file → Executable file
View File

@ -5,4 +5,5 @@ Adapters
* [Fenom + Kohana](https://github.com/2bj/kofenom) — Kofenom
* Fenom + Symphony
* Fenom + Symphony2
* Fenom + Zend Framework
* Fenom + Zend Framework
* [Fenom + Slim Framework 3](https://github.com/runcmf/runbb-ext-renderer) — RunBB forum extension

View File

@ -38,7 +38,7 @@ Options may by associative array like `'option_name' => true` or bitwise mask.
```php
$fenom->setOptions(array(
"compile_check" => true,
"force_compile" => true,
"force_include" => true
));
// same

View File

@ -8,12 +8,12 @@ By default format is: `%b %e, %Y`.
```smarty
{var $ts = time()}
{$ts|date_format:"%Y/%m/%d %H:%M:%s"} outputs 2013/02/08 21:01:43
{$ts|date_format:"%Y/%m/%d %H:%M:%S"} outputs 2013/02/08 21:01:43
{$ts|date_format:"-1 day"} outputs 2013/02/07 21:01:43
{var $date = "2008-12-08"}
{$ts|date_format:"%Y/%m/%d %H:%M:%s"} outputs 2008/12/08 00:00:00
{$ts|date_format:"%Y/%m/%d %H:%M:%S"} outputs 2008/12/08 00:00:00
```
[Allowed quantificators](http://docs.php.net/strftime#refsect1-function.strftime-parameters) in **date_format**

View File

@ -55,7 +55,7 @@ Operators
{if $a & 1} {var $b = 4 | $flags} {/if}
```
### Assignment Operators
### Assignment operators
* `$a = $b` - assignment
* `$a += $b` - assignment with addition
@ -81,9 +81,11 @@ Operators
* `--$a` - decrement the variable and use it
* `$a--` - use the variable and decrement it
### String operator
### String operators
* `$a ~ $b` - return concatenation of variables `$a` and `$b`
* `$a ~ $b` - return concatenation of variables `$a` and `$b`
* `$a ~~ $b` - return concatenation of variables `$a` and `$b` separated by a space
* `$a ~= $b` - assignment with concatenation
### Ternary operators

View File

@ -23,22 +23,26 @@ Documentation
[Usage](./syntax.md#tags)
* [set](./tags/var.md), `add` and `var` — define variables
* [if](./tags/if.md), `elseif` and `else` — conditional statement
* [foreach](./tags/foreach.md), `foreaelse`, `break` and `continue` — traversing items in an array or object
* [for](./tags/for.md), `forelse`, `break` and `continue` — loop statement
* [switch](./tags/switch.md), `case`, `default`
* [set](./tags/set.md), [add](./tags/set.md#add) and `var` — define variables
* [if](./tags/if.md), [elseif](./tags/if.md#elseif) and [else](./tags/if.md#else) — conditional statement
* [foreach](./tags/foreach.md), [foreaelse](./tags/foreach.md#foreaelse),
[break](./tags/foreach.md#break) and [continue](./tags/foreach.md#continue) — traversing items in an array or object
* [switch](./tags/switch.md), [case](./tags/switch.md#case) — complex condition statement
* [cycle](./tags/cycle.md) — cycles on an array of values
* [include](./tags/include.md), `insert` — includes and evaluates the specified template
* [extends](./tags/extends.md), `use`, `block` and `parent` — template inheritance
* [include](./tags/include.md), [insert](./tags/include.md#insert) — includes and evaluates the specified template
* [extends](./tags/extends.md), [use](./tags/extends.md#use),
[block](./tags/extends.md#block), [parent](./tags/extends.md#parent) and [paste](./tags/extends.md#paste) — template inheritance
* [filter](./tags/filter.md) — apply modifier on a block of template data
* [ignore](./tags/ignore.md) — ignore Fenom syntax
* [macro](./tags/macro.md) and `import` — template functions
* [macro](./tags/macro.md) and [import](./tags/macro.md#import) — template functions
* [autoescape](./tags/autoescape.md) — escape template fragment
* [raw](./tags/raw.md) — unescape template fragment
* [unset](./tags/unset.md) — unset a given variables
* or [add](./ext/extend.md#add-tags) yours
Deprecated tags
* [for](./tags/for.md), `forelse`, `break` and `continue` — loop statement
***

View File

@ -1,13 +1,15 @@
Tag {cycle}
===========
`{cycle}` is used to alternate a set of values.
```smarty
{for $i=$a.c..}
{foreach 1..10}
<div class="{cycle ["odd", "even"]}">
{/for}
{/foreach}
{for $i=$a.c..}
{foreach 1..10}
<div class="{cycle ["odd", "even"] index=$i}">
{/for}
{/foreach}
```

9
docs/en/tags/do.md Normal file
View File

@ -0,0 +1,9 @@
Tag {do}
========
Evaluates any expression and doesn't print anything
```smarty
{do $count++}
{do $object->method()}
```

View File

@ -1,82 +1,70 @@
Tag {extends} [RU]
==================
Tag {extends}
=============
Тег `{extends}` реализует наследование шаблонов, иерархия, обратная {include}. То есть шаблон сам выбирает своего родителя.
`{extends}` tags are used in child templates in template inheritance for extending parent templates.
The `{extends}` tag must be on before any block.
Also if a child template extends a parent template with the `{extends}` tag it may contain only `{block}` tags. Any other template content is ignored.
### {extends}
Родительский шаблон можно задать единожды и до объявления какого-либо блока.
```smarty
{extends 'parent.tpl'}
```
Имя родительского шаблона может быть задан динамически, в этом случае производительность отрисовки может снизиться.
```smarty
{extends $parent_tpl}
```
### {block}
Блок указывает фрагмент шаблона, который будет передан родителю. Имя блока может быть задано как явно
```smarty
{block bk1}content 1{/block}
...
{block 'bk2'}content 2{/block}
```
так и не явно, но в данном случае пострадает производительность
```smarty
{block "bk{$number}"}content {$number}{/block}
...
{if $condition}
{block "bk-if"}content, then 'if' is true{/block}
{else}
{block "bk{$fail}"}content, then 'if' is false{/block}
{/if}
```
### {use}
Что бы импортировать блоки из другого шаблона используйте тег {use}:
Import the blocks defined in another file. Specifying blocks in this template will override those from the other file.
```smarty
{use 'blocks.tpl'}
```
{use 'blocks.tpl'} merge blocks from blocks.tpl template
{block 'alpha'} rewrite block alpha from blocks.tpl template, if it exists
...
{/block}
```
### {parent}
Planned. Not supported yet. Feature #5.
Uses the code from the block as defined in the parent.
```smarty
{block 'block1'}
{extends 'parent.tpl'}
{block 'header'}
content ...
{parent}
{parent} pase code from block 'header' from parent.tpl
content ...
{/block}
```
### Performance
### {paste}
Алгоритм реализации наследования шаблонов может работать в разных режимах, в зависимости от условий.
Каждый режим имеет свою производительность.
Pastes the code of any block
1. **Максимальная** производительность:
* Имена шаблонов в теге {extends } заданы явно, без использования переменных и условий.
* Имена блоков заданы явно, без использования переменных, условий и не вложены ни в какой другой тег.
2. **Средняя** производительность:
* Имена шаблонов в теге {extends } заданы явно, без использования переменных и условий.
* Имена блоков заданы **не** явно, с использованием переменныч, условий или могут быть вложенные в другие теги.
3. **Низкая** производительность:
* Имена шаблонов в теге {extends } заданы **не** явно, с использованием переменных и условий.
* Имена блоков заданы явно, без использования переменных, условий и не вложены ни в какой другой тег.
4. **Минимальная** производительность:
* Имена шаблонов в теге {extends } заданы **не** явно, с использованием переменных и условий.
* Имена блоков заданы **не** явно, с использованием переменных, условий или могут быть вложенные в другие теги.
```smarty
{block 'b1'}
...
{/block}
Режим может идти только на понижение, при изменении условий во время прохождения по иерархии шаблонов.
При любом режиме работы не используется буферизация данных, то есть данные выводятся сразу.
{block 'b2'}
...
{paste 'b1'} paste code from b1
{/block}
```
### {$.block}
Checks if clock exists
```smarty
{if $.block.header}
block header exists
{/if}
```

View File

@ -1,10 +1,12 @@
Tags {filter}
=============
Позволяет применить модификаторы на фрагмент шаблона
Apply modifier to template area.
```smarty
{filter|strip_tags|truncate:20}
Remove all HTML <b>tags</b> and truncate {$text} to 20 symbols
{/filter}
```
```
**Note**: output buffering used. May be used a lot of memory if you output a lot of data.

View File

@ -1,5 +1,7 @@
Tag {foreach} [RU]
==================
Tag {foreach}
=============
The tag `{foreach}` construct provides an easy way to iterate over arrays and ranges.
```smarty
{foreach $list as [$key =>] $value [index=$index] [first=$first] [last=$last]}
@ -15,7 +17,8 @@ Tag {foreach} [RU]
### {foreach}
Перебор значений массива $list
On each iteration, the value of the current element is assigned to `$value` and the internal array pointer is
advanced by one (so on the next iteration, you'll be looking at the next element).
```smarty
{foreach $list as $value}
@ -23,7 +26,7 @@ Tag {foreach} [RU]
{/foreach}
```
Перебор ключей и значений массива $list
The next form will additionally assign the current element's key to the `$key` variable on each iteration.
```smarty
{foreach $list as $key => $value}
@ -31,47 +34,66 @@ Tag {foreach} [RU]
{/foreach}
```
Получение номера (индекса) итерации
Gets the current array index, starting with zero.
```smarty
{foreach $list as $value}
<div>№{$value@index}: {$value}</div>
{/foreach}
or
{foreach $list as $value index=$index}
<div>№{$index}: {$value}</div>
{/foreach}
```
Определение первого элемента
Detect first iteration:
```smarty
{foreach $list as $value}
<div>{if $value@first} first item {/if} {$value}</div>
{/foreach}
or
{foreach $list as $value first=$first}
<div>{if $first} first item {/if} {$value}</div>
{/foreach}
```
Переменная `$first` будет иметь значение **TRUE**, если текущая итерация является первой.
Определение последнего элемента
`$first` is `TRUE` if the current `{foreach}` iteration is first iteration.
Detect last iteration:
```smarty
{foreach $list as $value}
<div>{if $value@last} last item {/if} {$value}</div>
{/foreach}
or
{foreach $list as $value last=$last}
<div>{if $last} last item {/if} {$value}</div>
{/foreach}
```
Переменная `$last` будет иметь значение **TRUE**, если текущая итерация является последней.
`$last` is set to `TRUE` if the current `{foreach}` iteration is last iteration.
### {break}
Тег `{break}` используется для выхода из цикла до достижения последней итерации. Если в цикле встречается тег `{break}`, цикл завершает свою работу, и далее, выполняется код, следующий сразу за блоком цикла
Tag `{break}` aborts the iteration.
### {continue}
Тег `{continue}` используется для прерывания текущей итерации. Если в цикле встречается тег `{continue}`, часть цикла, следующая после тега, не выполняется, и начинается следующая итерация. Если текущая итерация была последней, цикл завершается.
Tag `{continue}` leaves the current iteration and begins with the next iteration.
### {foreachelse}
Тег {foreachelse} ограничивает код, который должен быть выполнен, если итерируемый объект пуст.
`{foreachelse}` is executed when there are no values in the array variable.
```smarty
{var $list = []}
{set $list = []}
{foreach $list as $value}
<div>{if $last} last item {/if} {$value}</div>
{foreachelse}
@ -79,8 +101,4 @@ Tag {foreach} [RU]
{/foreach}
```
В блоке `{foreachelse}...{/foreach}` использование `{break}`, `{continue}` выбросит исключение `Fenom\CompileException` при компиляции
### Notice
Использование last требует от `$list` быть **countable**.
`{foreachelse}` does not support tags `{break}` and `{continue}`.

View File

@ -1,7 +1,9 @@
Tag {if} [RU]
=============
Tag {if}
========
Реализация оператора [if](http://docs.php.net/if) из PHP
Tag {if} have much the same flexibility as PHP [if](http://docs.php.net/if) statements,
with a few added features for the template engine.
All operators, allowed functions and variables are recognized in conditions.
```smarty
{if <expression>}
@ -21,8 +23,6 @@ Tag {if} [RU]
{/if}
```
Код, расположенный в теге `{if}` будет выполнен/выведен если выражение *<expression>* возвращает значение приводимое к **TRUE**
### {elseif}
```smarty
@ -33,8 +33,6 @@ Tag {if} [RU]
{/if}
```
Код, расположенный после тега `{elseif}` будет выполнен/выведен, если выражение <expression1> вернуло значение приводимое к **FALSE**, <expression2> - приводимое к **TRUE**
### {else}
```smarty
@ -43,7 +41,4 @@ Tag {if} [RU]
{else}
{*...some code...*}
{/if}
```
Код, расположенный после тега `{else}` будет выполнен/выведен, если выражение <expression> вернуло значение приводимое к **FALSE**
В тестируемых выражениях могут быть использованы логические операторы , что позволяет обрабатывать сочетания нескольких условий.
```

View File

@ -1,12 +1,12 @@
Tag {macro} [RU]
================
Tag {macro}
===========
Макросы - фрагмент шаблона который можно повторить сколь угодно раз и в каком угодно месте.
Макросы не имеют общего пространства имен с шаблоном и могут оперировать только переданными переменными.
Macros are comparable with functions in regular programming languages.
They are useful to put often used HTML idioms into reusable elements to not repeat yourself.
### {macro}
Обявление макроса происходит при помощи блочного тега `{macro}`
Macros can be defined in any template using tag `{macro}`.
```smarty
{macro plus($x, $y, $z=0)}
@ -14,14 +14,10 @@ Tag {macro} [RU]
{/macro}
```
Вызов макроса происходит при помощи строкового тега `{macro}`. Аргументы передаются стандартно, как атрибуты в HTML тегах
```smarty
{macro.plus x=$num y=100}
```
Во время рекурсивного вызова используйте суффикс macro что бы обратиться к текущему макросу:
```smarty
{macro plus($x, $y, $z=0)}
...
@ -32,13 +28,17 @@ Tag {macro} [RU]
### {import}
Для использования маросов в другом шаблоне необходимо их импортировать при помощи тега `{import}`
Macros can be defined in any template, and need to be "imported" before being used.
The above import call imports the "math.tpl" file (which can contain only macros, or a template and some macros),
and import the functions as items of the `macro` namespace.
```smarty
{import 'math.tpl'}
{macro.plus x=1 y=3}
```
При импорте можно указать дргое пространство имен что бы можно было использовать одноименные макросы из разных шаблонов
Use another namespace instead of `macro`
```smarty
{import 'math.tpl' as math}
@ -46,9 +46,6 @@ Tag {macro} [RU]
{math.plus x=5 y=100}
```
Пространство имен макросов может совпадать с названием какого-либо тега, в данном случае ничего плохого не произойдет: будет вызван макрос, а тег не исчезнит
При необходимости можно импортировать только необходимые макросы, явно указав в теге `{import}`
```smarty
{import [plus, minus, exp] from 'math.tpl' as math}
```

84
docs/en/tags/set.md Normal file
View File

@ -0,0 +1,84 @@
Tag {set}
=========
The tag {set} is used for assigning template variables during the execution of a template.
```smarty
{set $var=EXPR}
```
```smarty
{set $var}
... any content ...
{/set}
```
```smarty
{set $var|modifiers}
... any content ...
{/set}
```
Variable names follow the same rules as other labels in PHP.
A valid variable name starts with a letter or underscore, followed by any number of letters, numbers, or underscores.
```smarty
{set $v = 5}
{set $v = "value"}
{set $v = $x+$y}
{set $v = 4}
{set $v = $z++ + 1}
{set $v = --$z}
{set $v = $y/$x}
{set $v = $y-$x}
{set $v = $y*$x-2}
{set $v = ($y^$x)+7}
```
Works this array too
```smarty
{set $v = [1,2,3]}
{set $v = []}
{set $v = ["one"|upper => 1, 4 => $x, "three" => 3]}
{set $v = ["key1" => $y*$x-2, "key2" => ["z" => $z]]}
```
Getting function result into variable
```smarty
{set $v = count([1,2,3])+7}
```
Fetch the output of the template into variable
```smarty
{set $v}
Some long {$text|trim}
{/set}
{set $v|escape} {* apply modifier to variable*}
Some long {$text|trim}
{/set}
```
### {add}
The tag {add} the same tag as {set} except that sets the value of the variable if it does not exist.
```smarty
{add $var = 'value'}
```
instead of
```smarty
{if $var is not set}
{set $var = 'value'}
{/if}
```
### {var}
Old name of tag {set}. Currently tag {var} the same tag as {set}.

View File

@ -1,64 +0,0 @@
Tag {var}
=========
The tag {var} is used for assigning template variables during the execution of a template.
```smarty
{var $var=EXPR}
```
```smarty
{var $var}
... any content ...
{/var}
```
```smarty
{var $var|modifiers}
... any content ...
{/var}
```
Variable names follow the same rules as other labels in PHP.
A valid variable name starts with a letter or underscore, followed by any number of letters, numbers, or underscores.
```smarty
{var $v = 5}
{var $v = "value"}
{var $v = $x+$y}
{var $v = 4}
{var $v = $z++ + 1}
{var $v = --$z}
{var $v = $y/$x}
{var $v = $y-$x}
{var $v = $y*$x-2}
{var $v = ($y^$x)+7}
```
Creating array
```smarty
{var $v = [1,2,3]}
{var $v = []}
{var $v = ["one"|upper => 1, 4 => $x, "three" => 3]}
{var $v = ["key1" => $y*$x-2, "key2" => ["z" => $z]]}
```
Getting function result into variable
```smarty
{var $v = count([1,2,3])+7}
```
Collect the output of the template into a variable
```smarty
{var $v}
Some long {$text|trim}
{/var}
{var $v|escape} {* apply modifier to variable*}
Some long {$text|trim}
{/var}
```

4
docs/ru/adapters.md Normal file → Executable file
View File

@ -5,4 +5,6 @@
* [Fenom + Kohana](https://github.com/2bj/kofenom) — Kofenom
* Fenom + Symphony
* Fenom + Symphony2
* Fenom + Zend Framework 2
* Fenom + Zend Framework 2
* [Fenom + MODX Revolution](https://docs.modx.pro/components/pdotools/parser#Шаблонизатор-Fenom)
* [Fenom + Slim Framework 3](https://github.com/runcmf/runbb-ext-renderer) — RunBB forum extension

View File

@ -24,25 +24,26 @@ $fenom->setOptions($options);
```
В обоих случаях аргумет `$options` может быть массивом или битовой маской.
В массиве ключем должно быть название параметра, а значением — булевый флаг `true` (активировать) или `false` (деактивировать).
Битавая маска должна состоять из значений констант, согласно следующей таблице
Битовая маска должна состоять из значений констант, согласно следующей таблице
| Название параметра | Константа | Описание | Эффект |
| ---------------------- | ------------------------- | ------------ | ------- |
| *disable_methods* | `Fenom::DENY_METHODS` | отключает возможность вызова методов в шаблоне | |
| *disable_native_funcs* | `Fenom::DENY_NATIVE_FUNCS`| отключает возможность использования фунций PHP, за исключением разрешенных | |
| *disable_native_funcs* | `Fenom::DENY_NATIVE_FUNCS`| отключает возможность использования функций PHP, за исключением разрешенных | |
| *auto_reload* | `Fenom::AUTO_RELOAD` | автоматически пересобирать кеш шаблона если шаблон изменился | понижает производительность |
| *force_compile* | `Fenom::FORCE_COMPILE` | каждый раз пересобирать кеш шаблонов (рекоммендуется только для отладки)| очень сильно понижает производительность |
| *force_compile* | `Fenom::FORCE_COMPILE` | каждый раз пересобирать кеш шаблонов (рекомендуется только для отладки)| очень сильно понижает производительность |
| *disable_cache* | `Fenom::DISABLE_CACHE` | не кешировать компилированный шаблон | эпично понижает производительность |
| *force_include* | `Fenom::FORCE_INCLUDE` | стараться по возможности вставить код дочернего шаблона в родительский при подключении шаблона | повышает производительность, увеличивает размер файлов в кеше, уменьшает количество файлов в кеше |
| *auto_escape* | `Fenom::AUTO_ESCAPE` | автоматически экранировать HTML сущности при выводе переменных в шаблон | понижает производительность |
| *force_verify* | `Fenom::FORCE_VERIFY` | автоматически проверять существование переменной перед использованием в шаблоне | понижает производительность |
| *disable_php_calls* | `Fenom::DENY_PHP_CALLS` | отключает возможность вызова статических методов и функций в шаблоне | |
| *disable_statics* | `Fenom::DENY_STATICS` | устаревшее название disable_php_calls | |
| *disable_call* | `Fenom::DENY_CALL` | отключает возможность вызова статических методов и функций в шаблоне | |
| *disable_php_calls* | `Fenom::DENY_PHP_CALLS` | устаревшее название disable_call | |
| *disable_statics* | `Fenom::DENY_STATICS` | устаревшее название disable_call | |
| *strip* | `Fenom::AUTO_STRIP` | удаляет лишиние пробелы в шаблоне | уменьшает размер кеша |
```php
$fenom->setOptions(array(
"compile_check" => true,
"force_compile" => true,
"force_include" => true
));
// тоже самое что и

View File

@ -59,7 +59,7 @@ How it work
* В конце проверяется токенайзер на наличие не используемых токенов, если таковые есть - выбрасывается ошибка.
* [Проверяется](https://github.com/bzick/fenom/blob/1.2.2/src/Fenom/Template.php#L264) стек на наличие не закрытых блоковых тегов
* PHP код проходит [post-фильтры](https://github.com/bzick/fenom/blob/1.2.2/src/Fenom/Template.php#L282)
* Код шаблона [сохраняеться](https://github.com/bzick/fenom/blob/1.2.2/src/Fenom.php#L799) на файлувую систему
* Код шаблона [сохраняется](https://github.com/bzick/fenom/blob/1.2.2/src/Fenom.php#L799) на файлувую систему
* Код шаблона выполняется для использования
### Как работает токенайзер
@ -78,7 +78,7 @@ How it work
* Проверяет, не является выражение в токенайзере [тегом ignore](https://github.com/bzick/fenom/blob/1.2.2/src/Fenom/Template.php#L492).
* Проверяет, не является выражение в токенайзере [закрывающим тегом](https://github.com/bzick/fenom/blob/1.2.2/src/Fenom/Template.php#L499).
* Проверяет, не является выражение в токенайзере [скалярным значением](https://github.com/bzick/fenom/blob/1.2.2/src/Fenom/Template.php#L566).
* По имени тега из [списка тегов](https://github.com/bzick/fenom/blob/1.2.2/src/Fenom.php#L140) выбирается массив и запускается [соответсвующий](https://github.com/bzick/fenom/blob/1.2.2/src/Fenom/Template.php#L582) парсер.
* По имени тега из [списка тегов](https://github.com/bzick/fenom/blob/1.2.2/src/Fenom.php#L140) выбирается массив и запускается [соответствующий](https://github.com/bzick/fenom/blob/1.2.2/src/Fenom/Template.php#L582) парсер.
* Парсер возвращает PHP код
### Как работают парсеры

View File

@ -3,20 +3,20 @@ Develop
Если у Вас есть что обсудить или предложить создайте issue на Github или сделайте запрос на сливание.
По вопросам можете слать письмо на email : a.cobest@gmail.com (Русский и английские языки)
По вопросам можете слать письмо на email: a.cobest@gmail.com (Русский и английские языки)
## Соглашение по наименованию версий
Версии именуются согласо [Semantic Versioning 2.0.0](http://semver.org/).
Версии именуются согласно [Semantic Versioning 2.0.0](http://semver.org/).
## Соглашение по GIT
Ветка `master` содержит стабильную последнюю версию проекта.
В ветку `master` может сливаться новая версия проекта из `develop` или исправления.
Ветка `develop`, для разработки, содержит не стабильную версию проекта. Принимает новшевства, изменения и исправления.
Ветка `develop`, для разработки, содержит не стабильную версию проекта. Принимает новшества, изменения и исправления.
## Принцип работы
Разобраться в принципе работы поможе [эта схема](schema.md).
Разобраться в принципе работы поможет [эта схема](schema.md).

View File

@ -4,7 +4,7 @@
# Добавление тегов
В шаблонизаторе принято различать два типа тегов: омпиляторы_ и ункции_.
Компиляторы вызываются во время преобразования кода шаблона в PHP код и возвращяют PHP код который будет вставлен вместо тега.
Компиляторы вызываются во время преобразования кода шаблона в PHP код и возвращают PHP код который будет вставлен вместо тега.
А функции вызываются непременно в момент выполнения шаблона и возвращают непосредственно данные которые будут отображены.
Среди тегов как и в HTML есть строчные и блоковые теги.
@ -27,7 +27,7 @@ $fenom->addFunction("some_function", $some_function, function (Fenom\Tokenizer $
Существует более простой способ добавления произвольной функции:
```php
$fenom->addFunctionSmarty(string $function_name, callable $callback);
$fenom->addFunctionSmart(string $function_name, callable $callback);
```
В данном случае парсер сканирует список аргументов коллбека и попробует сопоставить с аргументами тега.
@ -97,7 +97,7 @@ $fenom->addModifier(string $modifier, callable $callback);
```
* `$modifier` - название модификатора, которое будет использоваться в шаблоне
* `$callback` - коллбек, который будет вызван для изменения данных
* `$callback` - функция обратного вызова, которая будет вызвана для изменения данных
Например:
@ -141,7 +141,7 @@ $fenom->addAccessor('project', function (Fenom\Tokenizer $tokens) { /* code */ }
## Готовые решения
Орпеделить парсер для глобальной переменной весьма трудозатратно и требует полного понимания как работают парсеры в Fenom.
Это не удобно. Поэтому есть несколько предзаготовленных (умных) парсеоров, которые берут рутину на себя, а пользоватею остается указать ключевые параметры.
Это не удобно. Поэтому есть несколько предзаготовленных (умных) парсеров, которые берут рутину на себя, а пользователю остается указать ключевые параметры.
Умные парсеты добавляются через метод `Fenom::addAccessorSmart(string $name, string $accessor, string $parser)`,
где `$name` имя глобальной переменной, `$accessor` — параметр к парсеру, `$parser` — предопределенный парсер.

View File

@ -14,12 +14,12 @@
```smarty
{var $ts = time()}
{$ts|date_format:"%Y/%m/%d %H:%M:%s"} выведет 2013/02/08 21:01:43
{$ts|date_format:"%Y/%m/%d %H:%M:%S"} выведет 2013/02/08 21:01:43
{$ts|date_format:"-1 day"} выведет вчерашний день, например 2013/02/07 21:01:43
{var $date = "2008-12-08"}
{$ts|date_format:"%Y/%m/%d %H:%M:%s"} выведет 2008/12/08 00:00:00
{$ts|date_format:"%Y/%m/%d %H:%M:%S"} выведет 2008/12/08 00:00:00
```
[Конверсионные указатели](http://docs.php.net/ru/strftime#refsect1-function.strftime-parameters) в модификаторе **date_format**:

View File

@ -1,6 +1,8 @@
Модификатор in
===========
Модификатор является реализацией оператора содержания [in](../operators.md#Оператор-содержания).
```smarty

View File

@ -23,4 +23,7 @@
{if $color|match:"*gr[ae]y"}
какой-то оттенок серого
{/if}
```
```
**Замечание:**
максимальная длинна проверяемой строки не должна превышать 4096 символов.

View File

@ -135,9 +135,21 @@ Fenom поддерживает префиксные и постфиксные о
* `$a ~~ $b` - возвращает результат объединения сток `$a` и `$b` через пробел
* `$a ~= $b` - присвоение с объединением
Примеры
```smarty
{"A" ~ "B"} -> AB
{"A" ~~ "B"} -> A B
{add $v = "A"}
{set $v ~= "B"}
{$v} -> AB
```
### Оператор интервала
Оператор `..` позволяет создать массив данных, не выходящие за указанные ограничения.
Оператор `..` позволяет создать массив данных, не выходящих за указанные пределы.
```smarty
{set $a = 1..4}
@ -161,11 +173,11 @@ Fenom поддерживает префиксные и постфиксные о
{set $a = $min..$max}
```
создаст массив из значений где первый и минимальный элемент будет значение `$min`,
а максимальный и последний элемент будет занчение `$max`
создаст массив из значений где первый (минимальный) элемент будет иметь значение `$min`,
а максимальный (последний) элемент будет иметь значение `$max`
**Замечание:**
оганичения должны быть одного типа, интервал `1..'f'` преобразует `f` в `0` и будет сгенерировано `[1,0]`
ограничения должны быть одного типа, интервал `1..'f'` преобразует `f` в `0` и будет сгенерировано `[1,0]`
### Тернарные операторы
@ -216,8 +228,8 @@ Fenom поддерживает префиксные и постфиксные о
* `$a ? $b : $c` - вернет `$b` если `$a` не пустое, иначе вернет `$c`.
* `$a ! $b : $c` - вернет `$b` если `$a` существует, иначе вернет `$c`.
* `$a ?: $c` - вернет `$a` если `$a` не пустое, иначе вернет `$b`.
* `$a !: $c` - вернет `$a` если `$a` существует, иначе вернет `$b`.
* `$a ?: $b` - вернет `$a` если `$a` не пустое, иначе вернет `$b`.
* `$a !: $b` - вернет `$a` если `$a` существует, иначе вернет `$b`.
```smarty
{var $a = true}

View File

@ -26,22 +26,27 @@
[Использование](./syntax.md#Теги) тегов.
* [set](./tags/set.md), `add` и `var` — определение значения переменной
* [if](./tags/if.md), `elseif` и `else` — условный оператор
* [foreach](./tags/foreach.md), `foreachelse`, `break` and `continue` — перебор элементов массива или объекта
* [for](./tags/for.md), `forelse`, `break` and `continue` — цикл
* [switch](./tags/switch.md), `case` — групповой условный оператор
* [set](./tags/set.md), [add](./tags/set.md#add) и [var](./tags/set.md#var) — определение значения переменной
* [if](./tags/if.md), [elseif](./tags/if.md#elseif) и [else](./tags/if.md#else) — условный оператор
* [foreach](./tags/foreach.md), [foreachelse](./tags/foreach.md#foreachelse),
[break](./tags/foreach.md#break) и [continue](./tags/foreach.md#continue) — перебор элементов массива или объекта
* [switch](./tags/switch.md) и [case](./tags/switch.md#case) — групповой условный оператор
* [cycle](./tags/cycle.md) — циклицеский перебор массива значений
* [include](./tags/include.md), `insert` — вставляет и исполняет указанный шаблон
* [extends](./tags/extends.md), `use`, `block` и `parent` — [наследование](./inheritance.md) шаблонов
* [include](./tags/include.md), [insert](./tags/include.md#insert) — вставляет и исполняет указанный шаблон
* [extends](./tags/extends.md), [use](./tags/extends.md#use),
[block](./tags/extends.md#block), [parent](./tags/extends.md#parent) и
[paste](./tags/extends.md#paste) — [наследование](./inheritance.md) шаблонов
* [filter](./tags/filter.md) — применение модификаторов к фрагменту шаблона
* [ignore](./tags/ignore.md) — игнорирование тегов Fenom
* [macro](./tags/macro.md) и `import` — пользовательские функции шаблонов
* [macro](./tags/macro.md) и [import](./tags/macro.md#macro) — пользовательские функции шаблонов
* [autoescape](./tags/autoescape.md) — экранирует фрагмент шаблона
* [raw](./tags/raw.md) — отключает экранирование фрагмента шаблона
* [unset](./tags/unset.md) — удаляет переменные
* или [добавьте](./ext/extend.md#Добавление-тегов) свои
Устаревшие теги
* [for](./tags/for.md), `forelse`, `break` and `continue` — цикл
***
@ -68,7 +73,7 @@
* [join](./mods/join.md) — объединяет массив в строку
* так же разрешены функции: `json_encode`, `json_decode`, `count`, `is_string`, `is_array`, `is_numeric`, `is_int`, `is_object`,
`strtotime`, `gettype`, `is_double`, `ip2long`, `long2ip`, `strip_tags`, `nl2br`
* или [добавте](./ext/extend.md#Добавление-модификаторов) свои
* или [добавьте](./ext/extend.md#Добавление-модификаторов) свои
***

View File

@ -38,7 +38,7 @@ $fenom = Fenom::factory('/path/to/templates', '/path/to/compiled/template', $opt
```
Пример создания Fenom через оператор `new`:
```php
$fenom = new Fenom(new Fenom/Provider('/path/to/templates'));
$fenom = new Fenom(new Fenom\Provider('/path/to/templates'));
$fenom->setCompileDir('/path/to/template/cache');
$fenom->setOptions($options);
```

View File

@ -33,7 +33,7 @@
```smarty
<div class="user">Hello, <a href="/users/{$user.id}">{$user.name}</a>.</div>
```
`{$user.id}` and `{$user['id']}` are same:
`{$user.id}` и `{$user['id']}` это одно и то же:
```smarty
<div class="user">Hello, <a href="/users/{$user['id']}">{$user['name']}</a>.</div>
```
@ -51,7 +51,7 @@
**Note**
Будте осторожны, Fenom не проверяет наличие метода в классе перед вызовом.
Что бы избежать фатальной ошибки определите метод `__call` у класса объекта.
Вызов методов в шаблоне можно вообще выключить в [настройках](./docs/configuration.md).
Вызов методов в шаблоне можно вообще выключить в [настройках](./configuration.md).
Ниже приведены комбинированые примеры работы с переменными:
@ -99,10 +99,11 @@
* `$.version` возвращает версию Fenom.
* `$.const` обращение к PHP константе: `$.const.PHP_EOL` обращение к константе `PHP_EOL`. Поддерживается пространство имен
которое разделяется через точку: `$.const.Storage.FS::DIR_SEPARATOR` обращение к PHP константе `Storage\FS::DIR_SEPARATOR`
если такой констатнты нет будет взята константа `Storage\FS\DIR_SEPARATOR`.
* `$.php` обращение к статическомому методу. `$.php.Storage.FS::put($filename, $data)` обращение к методу `Storage\FS::put($filename, $data)`.
`$.php.Storage.FS.put($filename, $data)` `Storage\FS\put($filename, $data)`
* так же вы можете [добавить](./ext/extend.md#Расширение-глобальной-переменной) свои системные переменные и функции
если такой константы нет будет взята константа `Storage\FS\DIR_SEPARATOR`.
* `$.call` обращение к статическому методу. `$.call.Storage.FS::put($filename, $data)` обращение к методу `Storage\FS::put($filename, $data)`.
Настройка `disable_call` отключает возможность обращения к `$.call`. Так же можно ограничить и указать список доступных к вызову классов и функций.
* `$.block` проверка на существование блоков которые были определены до момента обращения к акцессору. Например, `{$.blocks.BLOCK_NAME}`.
* так же вы можете [добавить](./ext/extend.md#Расширение-глобальной-переменной) свои или [удалить](./ext/extend.md#Расширение-глобальной-переменной) существующие системные переменные и функции
## Скалярные значения
@ -156,7 +157,7 @@
{"Hi, {$user.name}!"} выводит: Hi, Username!
{"Hi, {$user->name}!"} выводит: Hi, Username!
{"Hi, {$user->getName()}!"} выводит: Hi, Username!
{"Hi, {\$user->name}!"} выводит: Hi, {\$user->name}!
{"Hi, {\$user->name}!"} выводит: Hi, {$user->name}!
```
Допускаются также различные операции и модификаторы:
@ -335,7 +336,7 @@ Fenom предоставляет возможноть обращаться к ф
## Модификаторы
Модификаторы переменных могут быть прмменены к переменным, пользовательским функциям или строкам.
Модификаторы переменных могут быть применены к переменным, пользовательским функциям или строкам.
Для их применения надо после модифицируемого значения указать символ `|` (вертикальная черта) и название модификатора.
Так же модификаторы могут принимать параметры, которые влияют на их поведение.
Эти параметры следуют за названием модификатора и разделяются `:` (двоеточием).
@ -354,14 +355,14 @@ Fenom предоставляет возможноть обращаться к ф
## Теги
Все сущности шаблона можно разжелить на две группы:
Все сущности шаблона можно разделить на две группы:
* заполнитель (placeholder) — вывод переменной в шаблоне, например `{$name}`
* тег — конструкция, выполняющаяя некоторые действия, которая выглядит как именованный заполнитель (placeholder), например `{include $name}`
Теги также можно разделить на две группы:
* Функии. Тег функции вызывает пользовательскую во время выполнения шаблона, результат функции будет выведен вместо тега.
* Функции. Тег функции вызывает пользовательскую во время выполнения шаблона, результат функции будет выведен вместо тега.
Пользовательские функции являются дополнительными и могут быть индивидуальными. Они могут быть изменены по вашему желанию, также вы можете создать новые.
* Компиляторы. В отличии от функций компиляторы вызываются во время компиляции шаблона и возвращают PHP код, который описывает некоторое действие.
Компиляторы и формируют основные конструкции типа `if`, `foreach` и т.д.
@ -439,8 +440,8 @@ Outputs
| имя | код | тип тега | описание |
| ------- | ---- | --------- | ------------ |
| strip | s | блокоый | активирует удаление лишних пробелов на подобии модфикатора `strip` |
| ignore | i | блокоый | парсер будет игнорировать любой Fenom синтаксис на контент блокового тега |
| strip | s | блоковый | активирует удаление лишних пробелов наподобие модификатора `strip` |
| ignore | i | блоковый | парсер будет игнорировать любой Fenom синтаксис на контент блокового тега |
| raw | a | любой | отключает экранирование |
| escape | e | любой | принудительно активирует экранирование |

9
docs/ru/tags/do.md Normal file
View File

@ -0,0 +1,9 @@
Тег {do}
========
Выполнить произвольноые выражение без вывода результата на экран
```smarty
{do $count++}
{do $object->method()}
```

View File

@ -1,9 +1,9 @@
Тег {extends}
Тег `{extends}`
=============
Тег `{extends}` реализует [наследование](../inheritance.md) шаблонов, иерархия, обратная {include}. То есть шаблон сам выбирает своего родителя.
### {extends}
### `{extends}`
Родительский шаблон можно задать единожды и до объявления какого-либо блока.
@ -17,18 +17,18 @@
{extends $parent_tpl}
```
### {block}
### `{block}`
Блок указывает фрагмент шаблона, который будет передан родителю. Имя блока должно быть задано явно:
```smarty
{block bk1}content 1{/block}
{block 'bk1'}content 1{/block}
...
{block 'bk2'}content 2{/block}
```
### {use}
### `{use}`
Что бы импортировать блоки из другого шаблона используйте тег {use}:
@ -36,7 +36,7 @@
{use 'blocks.tpl'}
```
### {parent}
### `{parent}`
```smarty
{block 'block1'}
@ -44,4 +44,26 @@
{parent}
content ...
{/block}
```
```
### `{paste}`
Вставка кода блока в любое место через тег `{paste}`
```smarty
{block 'b1'}
...
{/block}
{paste 'b1'}
```
### `{$.block}`
Проверка наличия блока через глобальную переменную `$.block`
```smarty
{if $.block.header}
...
{/if}
```

View File

@ -5,7 +5,7 @@
`Foreach` работает только с массивами, объектами и интервалами.
```smarty
{foreach $list as [$key =>] $value [index=$index] [first=$first] [last=$last]}
{foreach $list [as [$key =>] $value] [index=$index] [first=$first] [last=$last]}
{* ...code... *}
{break}
{* ...code... *}
@ -25,7 +25,7 @@
<div>{$value}</div>
{/foreach}
{foreach 1..7 as $value} {* так же хорошо работает и с интрвелами *}
{foreach 1..7 as $value} {* так же хорошо работает и с интервалами *}
<div>№{$value}</div>
{/foreach}
```
@ -38,32 +38,51 @@
{/foreach}
```
Получение номера (индекса) итерации
Получение номера (индекса) итерации, начиная с 0
```smarty
{foreach $list as $value}
<div>№{$value@index}: {$value}</div>
{/foreach}
или
{foreach $list as $value index=$index}
<div>№{$index}: {$value}</div>
{/foreach}
```
Определение первого элемента
Определение первой итерации:
```smarty
{foreach $list as $value}
<div>{if $value@first} first item {/if} {$value}</div>
{/foreach}
или
{foreach $list as $value first=$first}
<div>{if $first} first item {/if} {$value}</div>
{/foreach}
```
Переменная `$first` будет иметь значение **TRUE**, если текущая итерация является первой.
Определение последнего элемента
Переменная `$value@first` будет иметь значение **TRUE**, если текущая итерация является первой.
Определение последней интерации:
```smarty
{foreach $list as $value}
<div>{if $value@last} last item {/if} {$value}</div>
{/foreach}
или
{foreach $list as $value last=$last}
<div>{if $last} last item {/if} {$value}</div>
{/foreach}
```
Переменная `$last` будет иметь значение **TRUE**, если текущая итерация является последней.
Переменная `$value:last` будет иметь значение **TRUE**, если текущая итерация является последней.
**Замечание:**
Использование `last` требует от `$list` быть **countable**.
@ -92,4 +111,4 @@
{/foreach}
```
В блоке `{foreachelse}...{/foreach}` использование `{break}`, `{continue}` выбросит исключение `Fenom\CompileException` при компиляции
В блоке `{foreachelse}...{/foreach}` использование `{break}`, `{continue}` выбросит исключение `Fenom\CompileException` при компиляции

View File

@ -26,7 +26,7 @@
### {insert}
В отличии от `{include}` тег `{insert}` не вызывает дочерний шаблон во время отрисовки, в ставляет код дочернего шаблона в родительский на момент компиляции.
В отличии от `{include}` тег `{insert}` не вызывает дочерний шаблон во время отрисовки, а вставляет код дочернего шаблона в родительский на момент компиляции.
Это позволяет сэкономить ресурсы на проверке и чтении шаблона. Однако такой формат подключения шаблона имеет ограничения.
Имя шаблона должно быть задано явно, без использования переменных и выражений:

View File

@ -55,7 +55,7 @@
Такой вариант создания позволяет применить модификаторы к данным перед тем как они будут сохранены в переменную:
```smarty
{set $v|escape} {* применение можификатора к значению *}
{set $v|escape} {* применение модификатора к значению *}
Some long {$text|trim}
{/set}
```
@ -67,7 +67,7 @@
```smarty
{add $var = 'value'}
```
Работу тега можнно описать следующим образом:
Работу тега можно описать следующим образом:
```smarty
{if $var is not set}
{set $var = 'value'}

View File

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

View File

@ -5,9 +5,14 @@ require_once __DIR__.'/../tests/tools.php';
\Fenom::registerAutoload();
$fenom = Fenom::factory(__DIR__.'/../tests/resources/provider', __DIR__.'/../tests/resources/compile');
$fenom->setOptions(Fenom::AUTO_RELOAD);
var_dump($fenom->fetch('extends/auto/parent.tpl'));
$fenom = Fenom::factory(__DIR__.'/templates', __DIR__.'/compiled');
//$fenom->setOptions(Fenom::AUTO_RELOAD | Fenom::FORCE_VERIFY | Fenom::FORCE_INCLUDE);
//var_dump($fenom->compile("nested.tpl", [])->getTemplateCode());
//exit;
//var_dump($fenom->compile('bug241/recursive.tpl', false)->getBody());
//var_dump($fenom->compile('bug249/bread.tpl', false)->getBody());
//var_dump($fenom->compile("bug158/main.tpl", [])->getTemplateCode());
//var_dump($fenom->display("bug158/main.tpl", []));
// $fenom->getTemplate("problem.tpl");
// $fenom->getTemplate("problem.tpl");
var_dump($fenom->compileCode('{$.php.Fenom::factory()->addDay()}')->getBody());

View File

@ -0,0 +1,2 @@
<link rel="apple-touch-icon" sizes="57x57" href="{$path_favicon}apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="{$path_favicon}apple-touch-icon-60x60.png">

View File

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html>
<head>
{include "bug215/favicon.tpl"}
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,3 @@
{if $n < 10}
{include 'bug241/recursive.tpl' n=$n - 1}
{/if}

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
</head>
<body>
{block 'content'}{/block}
</body>
</html>

View File

@ -0,0 +1 @@
{extends 'bug247/base.tpl'}

View File

@ -0,0 +1,38 @@
{*<div class='bread'>*}
{*<ul>*}
{*<li><a href='/'>Главная</a><li>*}
{*{foreach 1..$arr|length as $counter index=$i first=$first last=$last}*}
{*{if !$first}*}
{*<li class='delim'>/</li>*}
{*<li>{$last}*}
{*{if !$last}*}
{*<a href='{$arr[$i]['hidden_url']}'>{else}<span>*}
{*{/if}*}
{*{$arr[$i]['name']}{if !$last}</a>{else}</span>{/if}</li>{/if}*}
{*{/foreach}*}
{*</ul>*}
{*</div>*}
=== {(1..3)|length} ===
{foreach 1..3 as $c index=$i first=$first last=$last}
{$i}: {$last}
{/foreach}
<div class='bread'>
<ul>
<li><a href='/'>Главная</a><li>
{foreach $arr as $item first=$first last=$last}
{if !$first}
<li class='delim'>/</li>
<li>{$last}
{if $last}
<span>{$item.name}</span>
{else}
<a href='{$item.hidden_url}'>{$item.name}</a>
{/if}
</li>
{/if}
{/foreach}
</ul>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@
*/
namespace Fenom;
use Fenom\Error\CompileException;
use Fenom\Error\UnexpectedTokenException;
/**
@ -16,7 +17,7 @@ use Fenom\Error\UnexpectedTokenException;
* @package Fenom
*/
class Accessor {
public static $vars = array(
public static array $vars = array(
'get' => '$_GET',
'post' => '$_POST',
'session' => '$_SESSION',
@ -32,10 +33,11 @@ class Accessor {
* @param string $var variable expression on PHP ('App::get("storage")->user')
* @param Tokenizer $tokens
* @param Template $tpl
* @param $is_var
* @param bool $is_var
* @return string
* @throws CompileException
*/
public static function parserVar($var, Tokenizer $tokens, Template $tpl, &$is_var)
public static function parserVar(string $var, Tokenizer $tokens, Template $tpl, bool &$is_var): string
{
$is_var = true;
return $tpl->parseVariable($tokens, $var);
@ -47,7 +49,7 @@ class Accessor {
* @param Template $tpl
* @return string
*/
public static function parserCall($call, Tokenizer $tokens, Template $tpl)
public static function parserCall(string $call, Tokenizer $tokens, Template $tpl): string
{
return $call.$tpl->parseArgs($tokens);
}
@ -56,11 +58,13 @@ class Accessor {
* @param string $prop fenom's property name
* @param Tokenizer $tokens
* @param Template $tpl
* @param $is_var
* @param bool $is_var
* @return string
* @throws CompileException
*/
public static function parserProperty($prop, Tokenizer $tokens, Template $tpl, &$is_var)
public static function parserProperty(string $prop, Tokenizer $tokens, Template $tpl, bool &$is_var): string
{
$is_var = true;
return self::parserVar('$tpl->getStorage()->'.$prop, $tokens, $tpl, $is_var);
}
@ -70,7 +74,7 @@ class Accessor {
* @param Template $tpl
* @return string
*/
public static function parserMethod($method, Tokenizer $tokens, Template $tpl)
public static function parserMethod(string $method, Tokenizer $tokens, Template $tpl): string
{
return self::parserCall('$tpl->getStorage()->'.$method, $tokens, $tpl);
}
@ -80,13 +84,14 @@ class Accessor {
* @param Tokenizer $tokens
* @param Template $tpl
* @return string
* @throws CompileException
*/
public static function getVar(Tokenizer $tokens, Template $tpl)
public static function getVar(Tokenizer $tokens, Template $tpl): string
{
$name = $tokens->prev[Tokenizer::TEXT];
$name = $tokens->prevToken()[Tokenizer::TEXT];
if(isset(self::$vars[$name])) {
$var = $tpl->parseVariable($tokens, self::$vars[$name]);
return "(isset($var) ? $var : null)";
return "(($var) ?? null)";
} else {
throw new UnexpectedTokenException($tokens->back());
}
@ -97,7 +102,7 @@ class Accessor {
* @param Tokenizer $tokens
* @return string
*/
public static function tpl(Tokenizer $tokens)
public static function tpl(Tokenizer $tokens): string
{
$method = $tokens->skip('.')->need(T_STRING)->getAndNext();
if(method_exists('Fenom\Render', 'get'.$method)) {
@ -110,7 +115,7 @@ class Accessor {
/**
* @return string
*/
public static function version()
public static function version(): string
{
return 'Fenom::VERSION';
}
@ -119,9 +124,9 @@ class Accessor {
* @param Tokenizer $tokens
* @return string
*/
public static function constant(Tokenizer $tokens)
public static function constant(Tokenizer $tokens): string
{
$const = array($tokens->skip('.')->need(Tokenizer::MACRO_STRING)->getAndNext());
$const = [$tokens->skip('.')->need(Tokenizer::MACRO_STRING)->getAndNext()];
while($tokens->is('.')) {
$const[] = $tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext();
}
@ -129,7 +134,7 @@ class Accessor {
if($tokens->is(T_DOUBLE_COLON)) {
$const .= '::'.$tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext();
}
return '@constant('.var_export($const, true).')';
return '(defined('.var_export($const, true).') ? constant('.var_export($const, true).') : "")';
}
@ -138,9 +143,9 @@ class Accessor {
* @param Template $tpl
* @return string
*/
public static function php(Tokenizer $tokens, Template $tpl)
public static function call(Tokenizer $tokens, Template $tpl): string
{
$callable = array($tokens->skip('.')->need(Tokenizer::MACRO_STRING)->getAndNext());
$callable = [$tokens->skip('.')->need(Tokenizer::MACRO_STRING)->getAndNext()];
while($tokens->is('.')) {
$callable[] = $tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext();
}
@ -148,7 +153,7 @@ class Accessor {
if($tokens->is(T_DOUBLE_COLON)) {
$callable .= '::'.$tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext();
}
$call_filter = $tpl->getStorage()->call_filters;
$call_filter = $tpl->getStorage()->getCallFilters();
if($call_filter) {
foreach($call_filter as $filter) {
if(!fnmatch(addslashes($filter), $callable)) {
@ -174,7 +179,7 @@ class Accessor {
* @param Template $tpl
* @return string
*/
public static function fetch(Tokenizer $tokens, Template $tpl)
public static function fetch(Tokenizer $tokens, Template $tpl): string
{
$tokens->skip('(');
$name = $tpl->parsePlainArg($tokens, $static);
@ -184,12 +189,33 @@ class Accessor {
}
}
if($tokens->is(',')) {
$tokens->skip()->need('[');
$vars = $tpl->parseArray($tokens) . ' + $var';
$tokens->next();
if($tokens->is('[')){
$vars = $tpl->parseArray($tokens) . ' + $var';
} elseif($tokens->is(T_VARIABLE)){
$vars = $tpl->parseExpr($tokens) . ' + $var';
}
} else {
$vars = '$var';
}
$tokens->skip(')');
return '$tpl->getStorage()->fetch('.$name.', '.$vars.')';
}
}
/**
* Accessor {$.block.NAME}
* @param Tokenizer $tokens
* @param Template $tpl
* @return string
*/
public static function block(Tokenizer $tokens, Template $tpl): string
{
if($tokens->is('.')) {
$name = $tokens->next()->get(Tokenizer::MACRO_STRING);
$tokens->next();
return isset($tpl->blocks[$name]) ? 'true' : 'false';
} else {
return "array(".implode(",", array_keys($tpl->blocks)).")";
}
}
}

View File

@ -10,6 +10,7 @@
namespace Fenom;
use Doctrine\Instantiator\Exception\InvalidArgumentException;
use Fenom\Error\CompileException;
use Fenom\Error\InvalidUsageException;
use Fenom\Error\UnexpectedTokenException;
@ -23,13 +24,12 @@ class Compiler
/**
* Tag {include ...}
*
* @static
* @param Tokenizer $tokens
* @param Tag $tag
* @throws \LogicException
* @return string
*/
public static function tagInclude(Tokenizer $tokens, Tag $tag)
public static function tagInclude(Tokenizer $tokens, Tag $tag): string
{
$tpl = $tag->tpl;
$name = false;
@ -37,13 +37,24 @@ class Compiler
$p = $tpl->parseParams($tokens);
if ($name) {
if ($tpl->getStorage()->getOptions() & \Fenom::FORCE_INCLUDE) {
$inc = $tpl->getStorage()->compile($name, false);
$tpl->addDepend($inc);
$var = $tpl->tmpVar();
if ($p) {
return $var . ' = $var; $var = ' . self::toArray($p) . ' + $var; ?>' . $inc->getBody() . '<?php $var = ' . $var . '; unset(' . $var . ');';
} else {
return $var . ' = $var; ?>' . $inc->getBody() . '<?php $var = ' . $var . '; unset(' . $var . ');';
$_t = $tpl;
$recursion = false;
while($_t->parent) {
if($_t->parent->getName() == $name) { // recursion detected
$recursion = true;
}
$_t = $_t->parent;
}
if(!$recursion) {
$inc = $tpl->getStorage()->getRawTemplate($tpl);
$inc->load($name, true);
$tpl->addDepend($inc);
$var = $tpl->tmpVar();
if ($p) {
return $var . ' = $var; $var = ' . self::toArray($p) . ' + $var; ?>' . $inc->getBody() . '<?php $var = ' . $var . '; unset(' . $var . ');';
} else {
return $var . ' = $var; ?>' . $inc->getBody() . '<?php $var = ' . $var . '; unset(' . $var . ');';
}
}
} elseif (!$tpl->getStorage()->templateExists($name)) {
throw new \LogicException("Template $name not found");
@ -58,12 +69,13 @@ class Compiler
/**
* Tag {insert ...}
*
* @param Tokenizer $tokens
* @param Tag $tag
* @throws Error\InvalidUsageException
* @return string
* @throws Error\InvalidUsageException|CompileException
*/
public static function tagInsert(Tokenizer $tokens, Tag $tag)
public static function tagInsert(Tokenizer $tokens, Tag $tag): string
{
$tpl = $tag->tpl;
$tpl->parsePlainArg($tokens, $name);
@ -79,12 +91,11 @@ class Compiler
/**
* Open tag {if ...}
*
* @static
* @param Tokenizer $tokens
* @param Tag $scope
* @return string
*/
public static function ifOpen(Tokenizer $tokens, Tag $scope)
public static function ifOpen(Tokenizer $tokens, Tag $scope): string
{
$scope["else"] = false;
return 'if(' . $scope->tpl->parseExpr($tokens) . ') {';
@ -93,13 +104,12 @@ class Compiler
/**
* Tag {elseif ...}
*
* @static
* @param Tokenizer $tokens
* @param Tag $scope
* @throws InvalidUsageException
* @return string
*/
public static function tagElseIf(Tokenizer $tokens, Tag $scope)
public static function tagElseIf(Tokenizer $tokens, Tag $scope): string
{
if ($scope["else"]) {
throw new InvalidUsageException('Incorrect use of the tag {elseif}');
@ -114,7 +124,7 @@ class Compiler
* @param Tag $scope
* @return string
*/
public static function tagElse($tokens, Tag $scope)
public static function tagElse(Tokenizer $tokens, Tag $scope): string
{
$scope["else"] = true;
return '} else {';
@ -123,79 +133,62 @@ class Compiler
/**
* Open tag {foreach ...}
*
* @static
* @param Tokenizer $tokens
* @param Tag $scope
* @throws UnexpectedTokenException
* @throws InvalidUsageException
* @return string
* @throws InvalidUsageException*@throws CompileException
* @throws UnexpectedTokenException
* @throws CompileException
*/
public static function foreachOpen(Tokenizer $tokens, Tag $scope)
public static function foreachOpen(Tokenizer $tokens, Tag $scope): string
{
$p = array("index" => false, "first" => false, "last" => false);
$key = null;
$before = $body = array();
$prepend = "";
if ($tokens->is('[')) {
$scope["else"] = false;
$scope["key"] = null;
$scope["prepend"] = "";
$scope["before"] = array();
$scope["after"] = array();
$scope["body"] = array();
if ($tokens->is('[')) { // array
$count = 0;
$from = $scope->tpl->parseArray($tokens, $count);
$check = $count;
} else {
$from = $scope->tpl->parseExpr($tokens, $is_var);
$scope['from'] = $scope->tpl->parseArray($tokens, $count);
$scope['check'] = $count;
$scope["var"] = $scope->tpl->tmpVar();
$scope['prepend'] = $scope["var"].' = '.$scope['from'].';';
$scope['from'] = $scope["var"];
} else { // expression
$scope['from'] = $scope->tpl->parseExpr($tokens, $is_var);
if($is_var) {
$check = '!empty('.$from.') && (is_array('.$from.') || '.$from.' instanceof \Traversable)';
$scope['check'] = '!empty('.$scope['from'].') && (is_array('.$scope['from'].') || '.$scope['from'].' instanceof \Traversable)';
} else {
$scope["var"] = $scope->tpl->tmpVar();
$prepend = $scope["var"].' = '.$from.';';
$from = $scope["var"];
$check = 'is_array('.$from.') && count('.$from.') || ('.$from.' instanceof \Traversable)';
$scope['prepend'] = $scope["var"].' = '.$scope['from'].';';
$scope['from'] = $scope["var"];
$scope['check'] = 'is_array('.$scope['from'].') && count('.$scope['from'].') || ('.$scope['from'].' instanceof \Traversable)';
}
}
$tokens->get(T_AS);
$tokens->next();
$value = $scope->tpl->parseVariable($tokens);
if ($tokens->is(T_DOUBLE_ARROW)) {
if($tokens->is(T_AS)) {
$tokens->next();
$key = $value;
$value = $scope->tpl->parseVariable($tokens);
if ($tokens->is(T_DOUBLE_ARROW)) {
$tokens->next();
$scope["key"] = $value;
$scope["value"] = $scope->tpl->parseVariable($tokens);
} else {
$scope["value"] = $value;
}
} else {
$scope["value"] = '$_un';
}
$scope["after"] = array();
$scope["else"] = false;
while ($token = $tokens->key()) {
$param = $tokens->get(T_STRING);
if (!isset($p[$param])) {
throw new InvalidUsageException("Unknown parameter '$param' in {foreach}");
}
$var_name = self::foreachProp($scope, $param);
$tokens->getNext("=");
$tokens->next();
$p[$param] = $scope->tpl->parseVariable($tokens);
$scope['before'][] = $scope->tpl->parseVariable($tokens)." = &". $var_name;
}
if ($p["index"]) {
$before[] = $p["index"] . ' = 0';
$scope["after"][] = $p["index"] . '++';
}
if ($p["first"]) {
$before[] = $p["first"] . ' = true';
$scope["after"][] = $p["first"] . ' && (' . $p["first"] . ' = false )';
}
if ($p["last"]) {
$before[] = $p["last"] . ' = false';
$scope["uid"] = "v" . $scope->tpl->i++;
$before[] = '$' . $scope["uid"] . " = count($from)";
$body[] = 'if(!--$' . $scope["uid"] . ') ' . $p["last"] . ' = true';
}
$before = $before ? implode("; ", $before) . ";" : "";
$body = $body ? implode("; ", $body) . ";" : "";
$scope["after"] = $scope["after"] ? implode("; ", $scope["after"]) . ";" : "";
if ($key) {
return "$prepend if($check) {\n $before foreach($from as $key => $value) { $body";
} else {
return "$prepend if($check) {\n $before foreach($from as $value) { $body";
}
return '';
}
/**
@ -205,10 +198,44 @@ class Compiler
* @param Tag $scope
* @return string
*/
public static function foreachElse($tokens, Tag $scope)
public static function foreachElse(Tokenizer $tokens, Tag $scope): string
{
$scope["no-break"] = $scope["no-continue"] = $scope["else"] = true;
return " {$scope['after']} } } else {";
$after = $scope["after"] ? implode("; ", $scope["after"]) . ";" : "";
return " {$after} } } else {";
}
/**
* @param Tag $scope
* @param string $prop
* @return string
* @throws CompileException
*/
public static function foreachProp(Tag $scope, string $prop): string
{
if(empty($scope["props"][$prop])) {
$var_name = $scope["props"][$prop] = $scope->tpl->tmpVar()."_".$prop;
switch($prop) {
case "index":
$scope["before"][] = $var_name . ' = 0';
$scope["after"][] = $var_name . '++';
break;
case "first":
$scope["before"][] = $var_name . ' = true';
$scope["after"][] = $var_name . ' && (' . $var_name . ' = false )';
break;
case "last":
$scope["before"][] = $var_name . ' = false';
$scope["uid"] = $scope->tpl->tmpVar();
$scope["before"][] = $scope["uid"] . " = count({$scope["from"]})";
$scope["body"][] = 'if(!--' . $scope["uid"] . ') ' . $var_name . ' = true';
break;
default:
throw new CompileException("Unknown foreach property '$prop'");
}
}
return $scope["props"][$prop];
}
/**
@ -219,129 +246,31 @@ class Compiler
* @param Tag $scope
* @return string
*/
public static function foreachClose($tokens, Tag $scope)
public static function foreachClose(Tokenizer $tokens, Tag $scope): string
{
$before = $scope["before"] ? implode("; ", $scope["before"]) . ";" : "";
$head = $scope["body"] ? implode("; ", $scope["body"]) . ";" : "";
$body = $scope->getContent();
if ($scope["key"]) {
$code = "<?php {$scope["prepend"]} if({$scope["check"]}) {\n $before foreach({$scope["from"]} as {$scope["key"]} => {$scope["value"]}) { $head?>$body";
} else {
$code = "<?php {$scope["prepend"]} if({$scope["check"]}) {\n $before foreach({$scope["from"]} as {$scope["value"]}) { $head?>$body";
}
$scope->replaceContent($code);
if ($scope["else"]) {
return '}';
} else {
return " {$scope['after']} } }";
$after = $scope["after"] ? implode("; ", $scope["after"]) . ";" : "";
return " {$after} } }";
}
}
/**
* @static
* @param Tokenizer $tokens
* @param Tag $scope
* @throws Error\UnexpectedTokenException
* @throws Error\InvalidUsageException
* @return string
* @codeCoverageIgnore
*/
public static function forOpen(Tokenizer $tokens, Tag $scope)
{
trigger_error("Fenom: tag {for} deprecated, use {foreach 1..4 as \$value} (in {$scope->tpl->getName()}:{$scope->line})", E_USER_DEPRECATED);
$p = array(
"index" => false,
"first" => false,
"last" => false,
"step" => 1,
"to" => false,
// "max" => false,
// "min" => false
);
$scope["after"] = $before = $body = array();
$i = array('', '');
$c = "";
$var = $scope->tpl->parseTerm($tokens, $is_var);
if (!$is_var) {
throw new UnexpectedTokenException($tokens);
}
$tokens->get("=");
$tokens->next();
$val = $scope->tpl->parseExpr($tokens);
$p = $scope->tpl->parseParams($tokens, $p);
if (is_numeric($p["step"])) {
if ($p["step"] > 0) {
$condition = "$var <= {$p['to']}";
if ($p["last"]) {
$c = "($var + {$p['step']}) > {$p['to']}";
}
} elseif ($p["step"] < 0) {
$condition = "$var >= {$p['to']}";
if ($p["last"]) {
$c = "($var + {$p['step']}) < {$p['to']}";
}
} else {
throw new InvalidUsageException("Invalid step value");
}
} else {
$condition = "({$p['step']} > 0 && $var <= {$p['to']} || {$p['step']} < 0 && $var >= {$p['to']})";
if ($p["last"]) {
$c = "({$p['step']} > 0 && ($var + {$p['step']}) <= {$p['to']} || {$p['step']} < 0 && ($var + {$p['step']}) >= {$p['to']})";
}
}
if ($p["first"]) {
$before[] = $p["first"] . ' = true';
$scope["after"][] = $p["first"] . ' && (' . $p["first"] . ' = false )';
}
if ($p["last"]) {
$before[] = $p["last"] . ' = false';
$body[] = "if($c) {$p['last']} = true";
}
if ($p["index"]) {
$i[0] .= $p["index"] . ' = 0,';
$i[1] .= $p["index"] . '++,';
}
$scope["else"] = false;
$scope["else_cond"] = "$var==$val";
$before = $before ? implode("; ", $before) . ";" : "";
$body = $body ? implode("; ", $body) . ";" : "";
$scope["after"] = $scope["after"] ? implode("; ", $scope["after"]) . ";" : "";
return "$before for({$i[0]} $var=$val; $condition;{$i[1]} $var+={$p['step']}) { $body";
}
/**
* @static
* @param Tokenizer $tokens
* @param Tag $scope
* @return string
* @codeCoverageIgnore
*/
public static function forElse($tokens, Tag $scope)
{
$scope["no-break"] = $scope["no-continue"] = true;
$scope["else"] = true;
return " } if({$scope['else_cond']}) {";
}
/**
* @static
* @param Tokenizer $tokens
* @param Tag $scope
* @return string
* @codeCoverageIgnore
*/
public static function forClose($tokens, Tag $scope)
{
if ($scope["else"]) {
return '}';
} else {
return " {$scope['after']} }";
}
}
/**
* @static
* @param Tokenizer $tokens
* @param Tag $scope
* @return string
*/
public static function whileOpen(Tokenizer $tokens, Tag $scope)
public static function whileOpen(Tokenizer $tokens, Tag $scope): string
{
return 'while(' . $scope->tpl->parseExpr($tokens) . ') {';
}
@ -349,12 +278,11 @@ class Compiler
/**
* Open tag {switch}
*
* @static
* @param Tokenizer $tokens
* @param Tag $scope
* @return string
*/
public static function switchOpen(Tokenizer $tokens, Tag $scope)
public static function switchOpen(Tokenizer $tokens, Tag $scope): string
{
$expr = $scope->tpl->parseExpr($tokens);
$scope["case"] = array();
@ -370,7 +298,7 @@ class Compiler
* Resort cases for {switch}
* @param Tag $scope
*/
private static function _caseResort(Tag $scope)
private static function _caseResort(Tag $scope): void
{
$content = $scope->cutContent();
foreach ($scope["last"] as $case) {
@ -389,12 +317,11 @@ class Compiler
/**
* Tag {case ...}
*
* @static
* @param Tokenizer $tokens
* @param Tag $tag
* @return string
*/
public static function tagCase(Tokenizer $tokens, Tag $tag)
public static function tagCase(Tokenizer $tokens, Tag $tag): string
{
self::_caseResort($tag);
do {
@ -402,7 +329,7 @@ class Compiler
$tag["last"][] = false;
$tokens->next();
} else {
$tag["last"][] = $tag->tpl->parseScalar($tokens, false);
$tag["last"][] = $tag->tpl->parseScalar($tokens);
}
if ($tokens->is(',')) {
$tokens->next();
@ -417,12 +344,11 @@ class Compiler
/**
* Tag {default}
*
* @static
* @param Tokenizer $tokens
* @param Tag $scope
* @return string
*/
public static function tagDefault($tokens, Tag $scope)
public static function tagDefault(Tokenizer $tokens, Tag $scope): string
{
self::_caseResort($scope);
$scope["last"][] = false;
@ -432,12 +358,11 @@ class Compiler
/**
* Close tag {switch}
*
* @static
* @param Tokenizer $tokens
* @param Tag $scope
* @return string
*/
public static function switchClose($tokens, Tag $scope)
public static function switchClose(Tokenizer $tokens, Tag $scope): string
{
self::_caseResort($scope);
$expr = $scope["var"];
@ -456,13 +381,12 @@ class Compiler
/**
* Tag {continue}
*
* @static
* @param Tokenizer $tokens
* @param Tag $scope
* @throws InvalidUsageException
* @return string
* @throws InvalidUsageException
*/
public static function tagContinue($tokens, Tag $scope)
public static function tagContinue(Tokenizer $tokens, Tag $scope): string
{
if (empty($scope["no-continue"])) {
return 'continue;';
@ -474,13 +398,12 @@ class Compiler
/**
* Tag {break}
*
* @static
* @param Tokenizer $tokens
* @param Tag $scope
* @throws InvalidUsageException
* @return string
* @throws InvalidUsageException
*/
public static function tagBreak($tokens, Tag $scope)
public static function tagBreak(Tokenizer $tokens, Tag $scope): string
{
if (empty($scope["no-break"])) {
return 'break;';
@ -491,12 +414,13 @@ class Compiler
/**
* Dispatch {extends} tag
*
* @param Tokenizer $tokens
* @param Tag $tag
* @throws Error\InvalidUsageException
* @return string
*/
public static function tagExtends(Tokenizer $tokens, Tag $tag)
public static function tagExtends(Tokenizer $tokens, Tag $tag): void
{
$tpl = $tag->tpl;
if ($tpl->extends) {
@ -520,8 +444,9 @@ class Compiler
* Post compile action for {extends ...} tag
* @param Template $tpl
* @param string $body
* @throws CompileException
*/
public static function extendBody($tpl, &$body)
public static function extendBody(Template $tpl, string &$body): void
{
if ($tpl->dynamic_extends) {
if (!$tpl->ext_stack) {
@ -547,10 +472,9 @@ class Compiler
* Tag {use ...}
* @param Tokenizer $tokens
* @param Tag $tag
* @throws Error\InvalidUsageException
* @return string
* @throws Error\InvalidUsageException|CompileException
*/
public static function tagUse(Tokenizer $tokens, Tag $tag)
public static function tagUse(Tokenizer $tokens, Tag $tag): void
{
$tpl = $tag->tpl;
if ($tpl->getStackSize()) {
@ -569,13 +493,9 @@ class Compiler
* @param Tokenizer $tokens
* @param Tag $scope
* @throws \RuntimeException
* @return string
*/
public static function tagBlockOpen(Tokenizer $tokens, Tag $scope)
public static function tagBlockOpen(Tokenizer $tokens, Tag $scope): void
{
if ($scope->level > 0) {
$scope->tpl->_compatible = true;
}
$scope["cname"] = $scope->tpl->parsePlainArg($tokens, $name);
if (!$name) {
throw new \RuntimeException("Invalid block name");
@ -588,7 +508,7 @@ class Compiler
* @param Tokenizer $tokens
* @param Tag $scope
*/
public static function tagBlockClose($tokens, Tag $scope)
public static function tagBlockClose(Tokenizer $tokens, Tag $scope): void
{
$tpl = $scope->tpl;
$name = $scope["name"];
@ -622,7 +542,7 @@ class Compiler
* @param Tag $scope
* @return string
*/
public static function tagParent($tokens, Tag $scope)
public static function tagParent(Tokenizer $tokens, Tag $scope): string
{
$block_scope = $scope->tpl->getParentScope('block');
if (!$block_scope['use_parent']) {
@ -636,7 +556,7 @@ class Compiler
*
* @return string
*/
public static function stdClose()
public static function stdClose(): string
{
return '}';
}
@ -648,7 +568,7 @@ class Compiler
* @param Tag $tag
* @return string
*/
public static function stdFuncParser(Tokenizer $tokens, Tag $tag)
public static function stdFuncParser(Tokenizer $tokens, Tag $tag): string
{
if(is_string($tag->callback)) {
return $tag->out($tag->callback . "(" . self::toArray($tag->tpl->parseParams($tokens)) . ', $tpl, $var)');
@ -664,8 +584,9 @@ class Compiler
* @param Tokenizer $tokens
* @param Tag $tag
* @return string
* @throws \ReflectionException
*/
public static function smartFuncParser(Tokenizer $tokens, Tag $tag)
public static function smartFuncParser(Tokenizer $tokens, Tag $tag): string
{
if (strpos($tag->callback, "::") || is_array($tag->callback)) {
list($class, $method) = explode("::", $tag->callback, 2);
@ -694,7 +615,7 @@ class Compiler
* @param Tag $tag
* @return string
*/
public static function stdFuncOpen(Tokenizer $tokens, Tag $tag)
public static function stdFuncOpen(Tokenizer $tokens, Tag $tag): string
{
$tag["params"] = self::toArray($tag->tpl->parseParams($tokens));
$tag->setOption(\Fenom::AUTO_ESCAPE, false);
@ -708,7 +629,7 @@ class Compiler
* @param Tag $tag
* @return string
*/
public static function stdFuncClose($tokens, Tag $tag)
public static function stdFuncClose(Tokenizer $tokens, Tag $tag): string
{
$tag->restore(\Fenom::AUTO_ESCAPE);
if(is_string($tag->callback)) {
@ -721,10 +642,10 @@ class Compiler
/**
* Convert array of code to string array
* @param $params
* @param array $params
* @return string
*/
public static function toArray($params)
public static function toArray(array $params): string
{
$_code = array();
foreach ($params as $k => $v) {
@ -738,8 +659,9 @@ class Compiler
* @param Tokenizer $tokens
* @param Tag $scope
* @return string
* @throws CompileException
*/
public static function setOpen(Tokenizer $tokens, Tag $scope)
public static function setOpen(Tokenizer $tokens, Tag $scope): string
{
if($tokens->is(T_VARIABLE)) {
$var = $scope->tpl->parseVariable($tokens);
@ -784,29 +706,35 @@ class Compiler
* @param Tag $scope
* @return string
*/
public static function setClose($tokens, Tag $scope)
public static function setClose(Tokenizer $tokens, Tag $scope): string
{
return $scope["name"] . '=' . $scope["value"] . ';';
}
public static function tagDo(Tokenizer $tokens, Tag $scope): string
{
return $scope->tpl->parseExpr($tokens).';';
}
/**
* @param Tokenizer $tokens
* @param Tag $scope
* @return string
* @throws CompileException
*/
public static function filterOpen($tokens, Tag $scope)
public static function filterOpen(Tokenizer $tokens, Tag $scope): string
{
$scope["filter"] = $scope->tpl->parseModifier($tokens, "ob_get_clean()");
return "ob_start();";
}
/**
* @param $tokens
* @param Tokenizer $tokens
* @param Tag $scope
* @return string
*/
public static function filterClose($tokens, Tag $scope)
public static function filterClose(Tokenizer $tokens, Tag $scope): string
{
return "echo " . $scope["filter"] . ";";
}
@ -819,7 +747,7 @@ class Compiler
* @throws Error\InvalidUsageException
* @return string
*/
public static function tagCycle(Tokenizer $tokens, Tag $tag)
public static function tagCycle(Tokenizer $tokens, Tag $tag): string
{
$tpl = $tag->tpl;
if ($tokens->is("[")) {
@ -842,11 +770,11 @@ class Compiler
/**
* Runtime cycle callback
* @param mixed $vals
* @param $index
* @param array $vals
* @param int $index
* @return mixed
*/
public static function cycle($vals, $index)
public static function cycle(array $vals, int $index): mixed
{
return $vals[$index % count($vals)];
}
@ -856,11 +784,11 @@ class Compiler
*
* @param Tokenizer $tokens
* @param Tag $tag
* @throws Error\UnexpectedTokenException
* @throws Error\InvalidUsageException
* @return string
* @throws Error\InvalidUsageException|CompileException
* @throws Error\UnexpectedTokenException
*/
public static function tagImport(Tokenizer $tokens, Tag $tag)
public static function tagImport(Tokenizer $tokens, Tag $tag): string
{
$tpl = $tag->tpl;
$import = array();
@ -925,7 +853,7 @@ class Compiler
* @param Tag $scope
* @throws InvalidUsageException
*/
public static function macroOpen(Tokenizer $tokens, Tag $scope)
public static function macroOpen(Tokenizer $tokens, Tag $scope): void
{
$scope["name"] = $tokens->get(Tokenizer::MACRO_STRING);
$scope["recursive"] = false;
@ -935,7 +863,7 @@ class Compiler
return;
}
$tokens->next();
if($tokens->is('(') || !$tokens->isNext(')')){
if ($tokens->is('(') || !$tokens->isNext(')')) {
$tokens->next();
while ($tokens->is(Tokenizer::MACRO_STRING, T_VARIABLE)) {
$param = $tokens->current();
@ -970,7 +898,7 @@ class Compiler
* @param Tokenizer $tokens
* @param Tag $scope
*/
public static function macroClose($tokens, Tag $scope)
public static function macroClose(Tokenizer $tokens, Tag $scope): void
{
if ($scope["recursive"]) {
$scope["macro"]["recursive"] = true;
@ -986,7 +914,7 @@ class Compiler
* @param Tag $tag
* @return string
*/
public static function tagRaw(Tokenizer $tokens, Tag $tag)
public static function tagRaw(Tokenizer $tokens, Tag $tag): string
{
return 'echo ' . $tag->tpl->parseExpr($tokens);
}
@ -995,9 +923,9 @@ class Compiler
* @param Tokenizer $tokens
* @param Tag $tag
*/
public static function escapeOpen(Tokenizer $tokens, Tag $tag)
public static function escapeOpen(Tokenizer $tokens, Tag $tag): void
{
$expected = ($tokens->get(T_STRING) == "true" ? true : false);
$expected = $tokens->get(T_STRING) == "true";
$tokens->next();
$tag->setOption(\Fenom::AUTO_ESCAPE, $expected);
}
@ -1005,7 +933,7 @@ class Compiler
/**
* Do nothing
*/
public static function nope()
public static function nope(): void
{
}
@ -1013,30 +941,33 @@ class Compiler
* @param Tokenizer $tokens
* @param Tag $tag
*/
public static function stripOpen(Tokenizer $tokens, Tag $tag)
public static function stripOpen(Tokenizer $tokens, Tag $tag): void
{
$expected = ($tokens->get(T_STRING) == "true" ? true : false);
$expected = $tokens->get(T_STRING) == "true";
$tokens->next();
$tag->setOption(\Fenom::AUTO_STRIP, $expected);
}
/**
* Tag {ignore}
*
* @param Tokenizer $tokens
* @param Tag $tag
*/
public static function ignoreOpen($tokens, Tag $tag)
public static function ignoreOpen(Tokenizer $tokens, Tag $tag): void
{
$tag->tpl->ignore('ignore');
}
/**
* Tag {unset ...}
*
* @param Tokenizer $tokens
* @param Tag $tag
* @return string
* @throws CompileException
*/
public static function tagUnset(Tokenizer $tokens, Tag $tag)
public static function tagUnset(Tokenizer $tokens, Tag $tag): string
{
$unset = array();
while($tokens->valid()) {
@ -1044,4 +975,15 @@ class Compiler
}
return 'unset('.implode(", ", $unset).')';
}
public static function tagPaste(Tokenizer $tokens, Tag $tag): string
{
$name = str_replace(array('\'', '"'), '', $tokens->get(T_CONSTANT_ENCAPSED_STRING));
$tokens->next();
if(isset($tag->tpl->blocks[$name])) {
return "?>".substr($tag->tpl->blocks[$name]["block"], 1, -1)."<?php ";
} else {
return "";
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -20,20 +20,20 @@ interface ProviderInterface
* @param string $tpl
* @return bool
*/
public function templateExists($tpl);
public function templateExists(string $tpl): bool;
/**
* @param string $tpl
* @param int $time
* @param float $time seconds with micro
* @return string
*/
public function getSource($tpl, &$time);
public function getSource(string $tpl, float &$time): string;
/**
* @param string $tpl
* @return int
* @return float seconds with micro
*/
public function getLastModified($tpl);
public function getLastModified(string $tpl): float;
/**
* Verify templates (check mtime)
@ -41,11 +41,11 @@ interface ProviderInterface
* @param array $templates [template_name => modified, ...] By conversation, you may trust the template's name
* @return bool if true - all templates are valid else some templates are invalid
*/
public function verify(array $templates);
public function verify(array $templates): bool;
/**
* Get all names of template from provider
* @return array|\Iterator
* @return iterable
*/
public function getList();
public function getList(): iterable;
}

105
src/Fenom/RangeIterator.php Normal file
View File

@ -0,0 +1,105 @@
<?php
namespace Fenom;
use Countable;
use Iterator;
class RangeIterator implements Iterator, Countable
{
public int $current;
public int $index = 0;
public int $min;
public int $max;
public int $step;
public function __construct(int $min, int $max, int $step = 1)
{
$this->min = $min;
$this->max = $max;
$this->setStep($step);
}
/**
* @param int $step
* @return $this
*/
public function setStep(int $step): static
{
if($step > 0) {
$this->current = min($this->min, $this->max);
} elseif($step < 0) {
$this->current = max($this->min, $this->max);
} else {
$step = $this->max - $this->min;
$this->current = $this->min;
}
$this->step = $step;
return $this;
}
/**
* Return the current element
*/
public function current(): mixed
{
return $this->current;
}
/**
* Move forward to next element
*/
public function next(): void
{
$this->current += $this->step;
$this->index++;
}
/**
* Return the key of the current element
* @return mixed
*/
public function key(): mixed
{
return $this->index;
}
/**
* Checks if current position is valid
* @return bool
*/
public function valid(): bool
{
return $this->current >= $this->min && $this->current <= $this->max;
}
/**
* Rewind the Iterator to the first element
*/
public function rewind(): void
{
if($this->step > 0) {
$this->current = min($this->min, $this->max);
} else {
$this->current = max($this->min, $this->max);
}
$this->index = 0;
}
/**
* Count elements of an object
*/
public function count(): int
{
return intval(($this->max - $this->min + 1) / $this->step);
}
/**
* @return string
*/
public function __toString()
{
return "[".implode(", ", range($this->min, $this->max, $this->step))."]";
}
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -3,13 +3,17 @@ namespace Fenom;
use Fenom, Fenom\Provider as FS;
class TestCase extends \PHPUnit_Framework_TestCase
class CustomFenom extends Fenom {
public mixed $prop;
}
class TestCase extends \PHPUnit\Framework\TestCase
{
public $template_path = 'template';
/**
* @var Fenom
* @var CustomFenom
*/
public $fenom;
public CustomFenom $fenom;
public $values;
@ -48,15 +52,20 @@ class TestCase extends \PHPUnit_Framework_TestCase
);
}
public function setUp()
public function getCompilePath()
{
if (!file_exists(FENOM_RESOURCES . '/compile')) {
mkdir(FENOM_RESOURCES . '/compile', 0777, true);
return FENOM_RESOURCES . '/compile';
}
public function setUp(): void
{
if (!file_exists($this->getCompilePath())) {
mkdir($this->getCompilePath(), 0777, true);
} else {
FS::clean(FENOM_RESOURCES . '/compile/');
FS::clean($this->getCompilePath());
}
$this->fenom = Fenom::factory(FENOM_RESOURCES . '/' . $this->template_path, FENOM_RESOURCES . '/compile');
$this->fenom = CustomFenom::factory(FENOM_RESOURCES . '/' . $this->template_path, $this->getCompilePath());
$this->fenom->addProvider('persist', new Provider(FENOM_RESOURCES . '/provider'));
$this->fenom->addModifier('dots', __CLASS__ . '::dots');
$this->fenom->addModifier('concat', __CLASS__ . '::concat');
@ -84,7 +93,7 @@ class TestCase extends \PHPUnit_Framework_TestCase
public static function inlineFunction($params)
{
return isset($params["text"]) ? $params["text"] : "";
return $params["text"] ?? "";
}
public static function blockFunction($params, $text)
@ -92,7 +101,7 @@ class TestCase extends \PHPUnit_Framework_TestCase
return $text;
}
public static function setUpBeforeClass()
public static function setUpBeforeClass(): void
{
if (!file_exists(FENOM_RESOURCES . '/template')) {
mkdir(FENOM_RESOURCES . '/template', 0777, true);
@ -101,7 +110,7 @@ class TestCase extends \PHPUnit_Framework_TestCase
}
}
public function tpl($name, $code)
public function tpl($name, $code): float
{
$dir = dirname($name);
if ($dir != "." && !is_dir(FENOM_RESOURCES . '/template/' . $dir)) {
@ -111,6 +120,12 @@ class TestCase extends \PHPUnit_Framework_TestCase
return filemtime(FENOM_RESOURCES . '/template/' . $name);
}
public function tpls(array $list) {
foreach($list as $name => $tpl) {
$this->tpl($name, $tpl);
}
}
/**
* Compile and execute template
*
@ -157,7 +172,7 @@ class TestCase extends \PHPUnit_Framework_TestCase
$this->fenom->setOptions($options);
try {
$this->fenom->compileCode($code, "inline.tpl");
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->assertSame($exception, get_class($e), "Exception $code");
$this->assertStringStartsWith($message, $e->getMessage());
$this->fenom->setOptions($opt);
@ -169,7 +184,11 @@ class TestCase extends \PHPUnit_Framework_TestCase
public function assertRender($tpl, $result, array $vars = array(), $debug = false)
{
$template = $this->fenom->compileCode($tpl);
try {
$template = $this->fenom->compileCode($tpl);
} catch (\Throwable $e) {
throw new \RuntimeException("Template failed: $tpl",0 , $e);
}
if ($debug) {
print_r("\nDEBUG $tpl:\n" . $template->getBody());
}
@ -222,7 +241,7 @@ class TestCase extends \PHPUnit_Framework_TestCase
);
}
public function providerVariables()
public static function providerVariables()
{
return array(
array('$one', 1),
@ -289,7 +308,8 @@ class Helper
const CONSTANT = "helper.class.const";
public $word = 'helper';
public string $word = 'helper';
public Helper $self;
public function __construct($word)
{
@ -314,6 +334,13 @@ class Helper
public function getArray() {
return array(1,2,3);
}
/**
* @throws \Exception
*/
public static function throws() {
throw new \Exception("helper exception");
}
}
function helper_func($string, $pad = 10) {

View File

@ -1,7 +1,7 @@
<?php
require(__DIR__ . "/../src/Fenom.php");
Fenom::registerAutoload();
//Fenom::registerAutoload();
define('FENOM_RESOURCES', __DIR__ . "/resources");
@ -11,6 +11,3 @@ require_once __DIR__ . "/tools.php";
ini_set('date.timezone', 'Europe/Moscow');
if(PHP_VERSION_ID > 50400) {
function php_gte_54() {}
}

View File

@ -94,58 +94,67 @@ class AccessorTest extends TestCase
}
public static function providerPHP() {
public static function providerCall() {
return array(
array('$.php.strrev("string")', strrev("string")),
array('$.php.strrev("string")', strrev("string"), 'str*'),
array('$.php.strrev("string")', strrev("string"), 'strrev'),
array('$.php.get_current_user', get_current_user()),
array('$.php.Fenom.helper_func("string", 12)', helper_func("string", 12)),
array('$.php.Fenom.helper_func("string", 12)', helper_func("string", 12), 'Fenom\\*'),
array('$.php.Fenom.helper_func("string", 12)', helper_func("string", 12), 'Fenom\helper_func'),
array('$.php.Fenom.helper_func("string", 12)', helper_func("string", 12), '*helper_func'),
array('$.php.Fenom.helper_func("string", 12)', helper_func("string", 12), '*'),
array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string")),
array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\*'),
array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\TestCase*'),
array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\TestCase::*'),
array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\*::dots'),
array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\*::*'),
array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\TestCase::dots'),
array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), '*::dots'),
array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), '*'),
array('$.call.strrev("string")', strrev("string")),
array('$.call.strrev("string")', strrev("string"), 'str*'),
array('$.call.strrev("string")', strrev("string"), 'strrev'),
array('$.call.get_current_user', get_current_user()),
array('$.call.Fenom.helper_func("string", 12)', helper_func("string", 12)),
array('$.call.Fenom.helper_func("string", 12)', helper_func("string", 12), 'Fenom\\*'),
array('$.call.Fenom.helper_func("string", 12)', helper_func("string", 12), 'Fenom\helper_func'),
array('$.call.Fenom.helper_func("string", 12)', helper_func("string", 12), '*helper_func'),
array('$.call.Fenom.helper_func("string", 12)', helper_func("string", 12), '*'),
array('$.call.Fenom.TestCase::dots("string")', TestCase::dots("string")),
array('$.call.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\*'),
array('$.call.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\TestCase*'),
array('$.call.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\TestCase::*'),
array('$.call.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\*::dots'),
array('$.call.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\*::*'),
array('$.call.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\TestCase::dots'),
array('$.call.Fenom.TestCase::dots("string")', TestCase::dots("string"), '*::dots'),
array('$.call.Fenom.TestCase::dots("string")', TestCase::dots("string"), '*'),
);
}
/**
* @dataProvider providerPHP
* @dataProvider providerCall
* @group php
*/
public function testPHP($tpl, $result, $mask = null) {
public function testCall($tpl, $result, $mask = null) {
if($mask) {
$this->fenom->addCallFilter($mask);
}
$this->assertRender('{'.$tpl.'}', $result);
}
/**
* @group issue260
*/
public function testBug260() {
$t = $this->fenom->compileCode('{$.php.Fenom::factory()->addModifier("intval", "intval")}');
$this->assertInstanceOf(Template::class, $t);
}
public static function providerPHPInvalid() {
return array(
array('$.php.aaa("string")', 'Fenom\Error\CompileException', 'PHP method aaa does not exists'),
array('$.php.strrev("string")', 'Fenom\Error\SecurityException', 'Callback strrev is not available by settings', 'strrevZ'),
array('$.php.strrev("string")', 'Fenom\Error\SecurityException', 'Callback strrev is not available by settings', 'str*Z'),
array('$.php.strrev("string")', 'Fenom\Error\SecurityException', 'Callback strrev is not available by settings', '*Z'),
array('$.php.Fenom.aaa("string")', 'Fenom\Error\CompileException', 'PHP method Fenom.aaa does not exists'),
array('$.php.Fenom.helper_func("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.helper_func is not available by settings', 'Reflection\*'),
array('$.php.Fenom.helper_func("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.helper_func is not available by settings', 'Fenom\*Z'),
array('$.php.Fenom.helper_func("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.helper_func is not available by settings', 'Fenom\*::*'),
array('$.php.TestCase::aaa("string")', 'Fenom\Error\CompileException', 'PHP method TestCase::aaa does not exists'),
array('$.php.Fenom.TestCase::aaa("string")', 'Fenom\Error\CompileException', 'PHP method Fenom.TestCase::aaa does not exists'),
array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Reflection\*'),
array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Fenom\*Z'),
array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Fenom\*::get*'),
array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Fenom\TestCase::get*'),
array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Fenom\TestCase::*Z'),
array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', '*::*Z'),
array('$.call.aaa("string")', 'Fenom\Error\CompileException', 'PHP method aaa does not exists'),
array('$.call.strrev("string")', 'Fenom\Error\SecurityException', 'Callback strrev is not available by settings', 'strrevZ'),
array('$.call.strrev("string")', 'Fenom\Error\SecurityException', 'Callback strrev is not available by settings', 'str*Z'),
array('$.call.strrev("string")', 'Fenom\Error\SecurityException', 'Callback strrev is not available by settings', '*Z'),
array('$.call.Fenom.aaa("string")', 'Fenom\Error\CompileException', 'PHP method Fenom.aaa does not exists'),
array('$.call.Fenom.helper_func("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.helper_func is not available by settings', 'Reflection\*'),
array('$.call.Fenom.helper_func("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.helper_func is not available by settings', 'Fenom\*Z'),
array('$.call.Fenom.helper_func("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.helper_func is not available by settings', 'Fenom\*::*'),
array('$.call.TestCase::aaa("string")', 'Fenom\Error\CompileException', 'PHP method TestCase::aaa does not exists'),
array('$.call.Fenom.TestCase::aaa("string")', 'Fenom\Error\CompileException', 'PHP method Fenom.TestCase::aaa does not exists'),
array('$.call.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Reflection\*'),
array('$.call.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Fenom\*Z'),
array('$.call.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Fenom\*::get*'),
array('$.call.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Fenom\TestCase::get*'),
array('$.call.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Fenom\TestCase::*Z'),
array('$.call.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', '*::*Z'),
);
}
@ -228,15 +237,28 @@ class AccessorTest extends TestCase
$this->execError($tpl, $exception, $message);
}
public function getThree() {
public static function getThree(): int
{
return 3;
}
public static function getThreeArray(): array
{
return ["three" => 3];
}
public static function getThreeCb(): callable
{
return fn() => 3;
}
public static int $three = 3;
public static function providerSmartAccessor() {
return array(
array('acc', '$tpl->getStorage()->test->values', \Fenom::ACCESSOR_VAR, '{$.acc.three}', '3'),
array('acc', '$tpl->getStorage()->test->getThree', \Fenom::ACCESSOR_CALL, '{$.acc()}', '3'),
array('acc', 'three', \Fenom::ACCESSOR_PROPERTY, '{$.acc}', '3'),
array('acc', '\Fenom\AccessorTest::getThreeArray()', \Fenom::ACCESSOR_VAR, '{$.acc.three}', '3'),
array('acc', '\Fenom\AccessorTest::getThreeCb()', \Fenom::ACCESSOR_CALL, '{$.acc()}', '3'),
array('acc', 'prop', \Fenom::ACCESSOR_PROPERTY, '{$.acc}', 'something'),
array('acc', 'templateExists', \Fenom::ACCESSOR_METHOD, '{$.acc("persist:pipe.tpl")}', '1')
);
}
@ -251,9 +273,25 @@ class AccessorTest extends TestCase
* @param $result
*/
public function testSmartAccessor($name, $accessor, $type, $code, $result) {
$this->fenom->test = $this;
$this->fenom->three = 3;
$this->fenom->prop = "something";
$this->fenom->addAccessorSmart($name, $accessor, $type);
$this->assertRender($code, $result, $this->getVars());
}
/**
*
*/
public function testCallbackAccessor() {
$index = 1;
$test = $this;
$this->fenom->addAccessorCallback('index', function($name, $template, $vars) use (&$index, $test) {
$test->assertInstanceOf('Fenom\Render', $template);
$test->assertSame(1, $vars['one']);
$test->assertSame('index', $name);
return $index++;
});
$this->assertRender('{$.index}, {$.index}, {$.index}', '1, 2, 3', $this->getVars());
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ namespace Fenom;
use Fenom,
Fenom\Render;
class RenderTest extends \PHPUnit_Framework_TestCase
class RenderTest extends TestCase
{
/**
@ -12,7 +12,7 @@ class RenderTest extends \PHPUnit_Framework_TestCase
*/
public static $render;
public static function setUpBeforeClass()
public static function setUpBeforeClass(): void
{
self::$render = new Render(Fenom::factory("."), function ($tpl) {
echo "It is render's function " . $tpl["render"];
@ -44,12 +44,9 @@ class RenderTest extends \PHPUnit_Framework_TestCase
$this->assertSame("It is render's function fetch", self::$render->fetch(array("render" => "fetch")));
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage template error
*/
public function testFetchException()
{
$this->expectException(Fenom\Error\TemplateException::class);
$render = new Render(Fenom::factory("."), function () {
echo "error";
throw new \RuntimeException("template error");

View File

@ -1,34 +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('{set $.q.ddqd->d() = 333}')->getBody());
} catch (\Exception $e) {
print_r($e->getMessage() . "\n" . $e->getTraceAsString());
while ($e->getPrevious()) {
$e = $e->getPrevious();
print_r("\n\n" . $e->getMessage() . " in {$e->getFile()}:{$e->getLine()}\n" . $e->getTraceAsString());
}
}
exit;
}
}

View File

@ -1,36 +0,0 @@
<?php
namespace Fenom;
class ScopeTest extends TestCase
{
public function openTag($tokenizer, $scope)
{
$this->assertInstanceOf('Fenom\Tokenizer', $tokenizer);
$this->assertInstanceOf('Fenom\Scope', $scope);
$scope["value"] = true;
return "open-tag";
}
public function closeTag($tokenizer, $scope)
{
$this->assertInstanceOf('Fenom\Tokenizer', $tokenizer);
$this->assertInstanceOf('Fenom\Scope', $scope);
$this->assertTrue($scope["value"]);
return "close-tag";
}
public function testBlock()
{
/*$scope = new Scope($this->fenom, new Template($this->fenom), 1, array(
"open" => array($this, "openTag"),
"close" => array($this, "closeTag")
), 0);
$tokenizer = new Tokenizer("1+1");
$this->assertSame("open-tag /*#{$scope->id}#* /", $scope->open($tokenizer));
$this->assertSame("close-tag", $scope->close($tokenizer));
$content = " some ?> content\n\nwith /*#9999999#* / many\n\tlines";
$scope->tpl->_body = "start <?php ".$scope->open($tokenizer)." ?>".$content;
$this->assertSame($content, $scope->getContent());*/
}
}

View File

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

View File

@ -13,7 +13,7 @@ use Fenom\Template,
class TemplateTest extends TestCase
{
public function setUp()
public function setUp(): void
{
parent::setUp();
$this->tpl('welcome.tpl', '<b>Welcome, {$username} ({$email})</b>');
@ -111,6 +111,18 @@ class TemplateTest extends TestCase
);
}
public static function providerDo() {
$vars = array(
"c" => 4,
"world" => new Helper('world')
);
return array(
array('{do "nope"}', $vars, ""),
array('{do $c++} -> {$c}', $vars, "-> 5"),
array('{do $world->chunk(1)}', $vars, ""),
);
}
public static function providerVarsInvalid()
{
@ -256,48 +268,6 @@ class TemplateTest extends TestCase
);
}
public static function providerInclude()
{
$a = array(
"name" => "welcome",
"tpl" => "welcome.tpl",
"fragment" => "come",
"pr_fragment" => "Come",
"pr_name" => "Welcome",
"username" => "Master",
"email" => "dev@null.net"
);
$result = 'Include <b>Welcome, Master (dev@null.net)</b> template';
$result2 = 'Include <b>Welcome, Flame (dev@null.net)</b> template';
$result3 = 'Include <b>Welcome, Master (flame@dev.null)</b> template';
$result4 = 'Include <b>Welcome, Flame (flame@dev.null)</b> template';
return array(
array('Include {include "welcome.tpl"} template', $a, $result),
array('Include {include "welcome.tpl"} template', $a, $result, Fenom::FORCE_INCLUDE),
array('Include {include $tpl} template', $a, $result),
array('Include {include "$tpl"} template', $a, $result),
array('Include {include "{$tpl}"} template', $a, $result),
array('Include {include "$name.tpl"} template', $a, $result),
array('Include {include "{$name}.tpl"} template', $a, $result),
array('Include {include "{$pr_name|lower}.tpl"} template', $a, $result),
array('Include {include "wel{$fragment}.tpl"} template', $a, $result),
array('Include {include "wel{$pr_fragment|lower}.tpl"} template', $a, $result),
array('Include {include "welcome.tpl" username="Flame"} template', $a, $result2),
array('Include {include "welcome.tpl" username="Flame"} template', $a, $result2, Fenom::FORCE_INCLUDE),
array('Include {include "welcome.tpl" email="flame@dev.null"} template', $a, $result3),
array(
'Include {include "welcome.tpl" email="flame@dev.null"} template',
$a,
$result3,
Fenom::FORCE_INCLUDE
),
array(
'Include {include "welcome.tpl" username="Flame" email="flame@dev.null"} template',
$a,
$result4
),
);
}
public static function providerIncludeInvalid()
{
@ -696,11 +666,11 @@ class TemplateTest extends TestCase
'Fenom\Error\CompileException',
"Unexpected end of expression"
),
array(
'Foreach: {foreach $list} {$e}, {/foreach} end',
'Fenom\Error\CompileException',
"Unexpected end of expression"
),
// array(
// 'Foreach: {foreach $list} {$e}, {/foreach} end',
// 'Fenom\Error\CompileException',
// "Unexpected end of expression"
// ),
// array(
// 'Foreach: {foreach $list+1 as $e} {$e}, {/foreach} end',
// 'Fenom\Error\CompileException',
@ -754,7 +724,7 @@ class TemplateTest extends TestCase
array(
'Foreach: {foreach $list as $e unknown=1} {$e}, {/foreach} end',
'Fenom\Error\CompileException',
"Unknown parameter 'unknown'"
"Unknown foreach property 'unknown'"
),
array(
'Foreach: {foreach $list as $e index=$i+1} {$e}, {/foreach} end',
@ -818,38 +788,6 @@ class TemplateTest extends TestCase
);
}
public static function providerSwitch()
{
$code1 = 'Switch: {switch $a}
{case 1, "one"} one
{case 2, "two"} two
{case "string", default} str
{default} def
{/switch} end';
$code2 = 'Switch: {switch $a}
{case 1, "one"} one
{case 2, "two"} two
{case "string"} str
{/switch} end';
$code3 = 'Switch: {switch $a} invalid
{case 1, "one"} one
{/switch} end';
return array(
array($code1, array("a" => 1), 'Switch: one end'),
array($code1, array("a" => 'one'), 'Switch: one end'),
array($code1, array("a" => 2), 'Switch: two end'),
array($code1, array("a" => 'two'), 'Switch: two end'),
array($code1, array("a" => "string"), 'Switch: str end'),
array($code1, array("a" => "unk"), 'Switch: str def end'),
array($code2, array("a" => "unk"), 'Switch: end'),
array($code3, array("a" => 1), 'Switch: one end'),
array($code3, array("a" => 'one'), 'Switch: one end'),
);
}
public static function providerSwitchInvalid()
{
return array(
@ -1117,27 +1055,6 @@ class TemplateTest extends TestCase
);
}
/**
* @group sb
*/
public function _testSandbox()
{
try {
var_dump(
$this->fenom->compileCode(
'{Fenom\Helper::method()->page->title}'
)->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;
}
/**
* @dataProvider providerScalars
*/
@ -1154,6 +1071,15 @@ class TemplateTest extends TestCase
$this->exec($code, $vars, $result);
}
/**
* @dataProvider providerDo
*/
public function testDo($code, $vars, $result)
{
$this->exec($code, $vars, $result);
}
/**
* @dataProvider providerVarsInvalid
*/
@ -1195,12 +1121,66 @@ class TemplateTest extends TestCase
$this->execError($code, $exception, $message, $options);
}
public static function providerInclude()
{
$a = array(
"name" => "welcome",
"tpl" => "welcome.tpl",
"fragment" => "come",
"pr_fragment" => "Come",
"pr_name" => "Welcome",
"username" => "Master",
"email" => "dev@null.net"
);
$result = 'Include <b>Welcome, Master (dev@null.net)</b> template';
$result2 = 'Include <b>Welcome, Flame (dev@null.net)</b> template';
$result3 = 'Include <b>Welcome, Master (flame@dev.null)</b> template';
$result4 = 'Include <b>Welcome, Flame (flame@dev.null)</b> template';
$recursive_result = 'Include <b>Hello, Master (dev@null.net)</b> template';
$recursive_result2 = 'Include <b>Hello, Flame (dev@null.net)</b> template';
return array(
array('Include {include "welcome.tpl"} template', $a, $result),
array('Include {include "welcome.tpl"} template', $a, $result, Fenom::FORCE_INCLUDE),
array('Include {include "recursive.tpl"} template', $a, $recursive_result, Fenom::FORCE_INCLUDE),
array('Include {include $tpl} template', $a, $result),
array('Include {include "$tpl"} template', $a, $result),
array('Include {include "{$tpl}"} template', $a, $result),
array('Include {include "$name.tpl"} template', $a, $result),
array('Include {include "{$name}.tpl"} template', $a, $result),
array('Include {include "{$pr_name|lower}.tpl"} template', $a, $result),
array('Include {include "wel{$fragment}.tpl"} template', $a, $result),
array('Include {include "wel{$pr_fragment|lower}.tpl"} template', $a, $result),
array('Include {include "welcome.tpl" username="Flame"} template', $a, $result2),
array('Include {include "welcome.tpl" username="Flame"} template', $a, $result2, Fenom::FORCE_INCLUDE),
array('Include {include "recursive.tpl" username="Flame"} template', $a, $recursive_result2, Fenom::FORCE_INCLUDE),
array('Include {include "welcome.tpl" email="flame@dev.null"} template', $a, $result3),
array(
'Include {include "welcome.tpl" email="flame@dev.null"} template',
$a,
$result3,
Fenom::FORCE_INCLUDE
),
array(
'Include {include "welcome.tpl" username="Flame" email="flame@dev.null"} template',
$a,
$result4,
),
);
}
/**
* @group include
* @group dev
* @dataProvider providerInclude
*/
public function testInclude($code, $vars, $result, $options = 0)
{
$this->tpls(array(
'welcome.tpl' => '<b>Welcome, {$username} ({$email})</b>',
'recursive.tpl' => '<b>Hello, {$username} ({$email}){if false}{include "recursive.tpl"}{/if}</b>'
));
$this->exec($code, $vars, $result, $options);
}
@ -1222,8 +1202,8 @@ class TemplateTest extends TestCase
$this->values = $vars;
$this->tpl("insert.tpl", $code);
$tpl = $this->fenom->getTemplate('insert.tpl');
$this->assertSame($result, $tpl->fetch($vars));
$this->assertTrue($tpl->isValid());
$this->assertSame($result, $tpl->fetch($vars), $code);
$this->assertTrue($tpl->isValid(), $code);
}
/**
@ -1316,6 +1296,7 @@ class TemplateTest extends TestCase
return array(
array('Foreach: {foreach $list as $e} {$e}, {/foreach} end', $a, 'Foreach: one, two, three, end'),
array('Foreach: {foreach $list as $e} {$e},{break} break {/foreach} end', $a, 'Foreach: one, end'),
array('Foreach: {foreach $list} 1, {/foreach} end', $a, 'Foreach: 1, 1, 1, end'),
array(
'Foreach: {foreach $list as $e} {$e},{continue} continue {/foreach} end',
$a,
@ -1350,11 +1331,21 @@ class TemplateTest extends TestCase
$a,
'Foreach: 0: one, 1: two, 2: three, end'
),
array(
'Foreach: {foreach $list as $e} {$e@index}: {$e}, {/foreach} end',
$a,
'Foreach: 0: one, 1: two, 2: three, end'
),
array(
'Foreach: {foreach $list as $k => $e index=$i} {$i}: {$k} => {$e}, {/foreach} end',
$a,
'Foreach: 0: 1 => one, 1: 2 => two, 2: 3 => three, end'
),
array(
'Foreach: {foreach $list as $k => $e} {$e@index}: {$k} => {$e}, {/foreach} end',
$a,
'Foreach: 0: 1 => one, 1: 2 => two, 2: 3 => three, end'
),
array(
'Foreach: {foreach $empty as $k => $e index=$i} {$i}: {$k} => {$e}, {foreachelse} empty {/foreach} end',
$a,
@ -1365,11 +1356,21 @@ class TemplateTest extends TestCase
$a,
'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, end'
),
array(
'Foreach: {foreach $list as $k => $e} {if $e@first}first{/if} {$e@index}: {$k} => {$e}, {/foreach} end',
$a,
'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, end'
),
array(
'Foreach: {foreach $list as $k => $e last=$l first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {if $l}last{/if} {/foreach} end',
$a,
'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, last end'
),
array(
'Foreach: {foreach $list as $k => $e} {if $e@first}first{/if} {$e@index}: {$k} => {$e}, {if $e@last}last{/if} {/foreach} end',
$a,
'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, last end'
),
array(
'Foreach: {foreach $empty as $k => $e last=$l first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {if $l}last{/if} {foreachelse} empty {/foreach} end',
$a,
@ -1385,6 +1386,11 @@ class TemplateTest extends TestCase
$a,
'Foreach: 0 => 1, 1 => 2, 2 => 3, end'
),
array(
'Foreach: {foreach 1..3 as $k => $e last=$l} {$k} => {$e}, {if $l}last{/if} {/foreach} end',
$a,
'Foreach: 0 => 1, 1 => 2, 2 => 3, last end'
),
array(
'Foreach: {foreach $.get.items as $e} {$e}, {/foreach} end',
$a,
@ -1436,6 +1442,44 @@ class TemplateTest extends TestCase
$this->exec($code, $vars, $result);
}
public static function providerSwitch()
{
$code1 = 'Switch: {switch $a}
{case 1, "one"} one
{case 2, "two"} two
{case "string", default} str
{default} def
{/switch} end';
$code2 = 'Switch: {switch $a}
{case 1, "one"} one
{case 2, "two"} two
{case "string"} str
{/switch} end';
$code3 = 'Switch: {switch $a} invalid
{case 1, "one"} one
{/switch} end';
$code4 = 'Switch:{switch $a}{case 1}<b>one</b>{/switch}end';
return array(
array($code1, array("a" => 1), 'Switch: one end'),
array($code1, array("a" => 'one'), 'Switch: one end'),
array($code1, array("a" => 2), 'Switch: two end'),
array($code1, array("a" => 'two'), 'Switch: two end'),
array($code1, array("a" => "string"), 'Switch: str end'),
array($code1, array("a" => "unk"), 'Switch: str def end'),
array($code2, array("a" => "unk"), 'Switch: end'),
array($code3, array("a" => 1), 'Switch: one end'),
array($code3, array("a" => 'one'), 'Switch: one end'),
array($code4, array("a" => 1), 'Switch:<b>one</b>end'),
);
}
/**
* @group switch
* @dataProvider providerSwitch
@ -1566,9 +1610,11 @@ class TemplateTest extends TestCase
{
return array(
array('{set $a=1..3}', "1,2,3,"),
array('{set $a="a".."f"}', "a,b,c,d,e,f,"),
array('{set $a=1.."f"}', "1,0,"),
array('{set $a="a"..2}', "0,1,2,"),
// array('{set $a=0..0}', ""),
// array('{set $a=1..1}', ""),
// array('{set $a="a".."f"}', "a,b,c,d,e,f,"),
// array('{set $a=1.."f"}', "1,0,"),
// array('{set $a="a"..2}', "0,1,2,"),
array('{set $a=$one..$three}', "1,2,3,"),
array('{set $a=$one..3}', "1,2,3,"),
array('{set $a=1..$three}', "1,2,3,"),
@ -1576,10 +1622,21 @@ class TemplateTest extends TestCase
array('{set $a=$one..++$three}', "1,2,3,4,"),
array('{set $a=$one--..$three++}', "1,2,3,"),
array('{set $a=--$one..++$three}', "0,1,2,3,4,"),
array('{set $a="a"|up.."f"|up}', "A,B,C,D,E,F,"),
// array('{set $a="a"|up.."f"|up}', "A,B,C,D,E,F,"),
array('{set $a=$one|min:0..$three|max:4}', "0,1,2,3,4,"),
array('{set $a=$one|min:0..4}', "0,1,2,3,4,"),
array('{set $a=0..$three|max:4}', "0,1,2,3,4,"),
array('{set $a=0..$three|max:4}', "0,1,2,3,4,"),
array('{set $a=range(1,3)}', "1,2,3,"),
array('{set $a=range(1,3, 2)}', "1,3,"),
array('{set $a=range(1..3, 2)}', "1,3,"),
array('{set $a=range(1..3, 3)}', "1,"),
array('{set $a=range(1,3, -1)}', "3,2,1,"),
array('{set $a=range(1,3, -2)}', "3,1,"),
array('{set $a=range(1..3, -2)}', "3,1,"),
array('{set $a=range(1..3, -3)}', "3,"),
);
}

View File

@ -4,7 +4,7 @@ namespace Fenom;
use Fenom\Error\UnexpectedTokenException;
use Fenom\Tokenizer;
class TokenizerTest extends \PHPUnit_Framework_TestCase
class TokenizerTest extends TestCase
{
public function testGetName()
@ -45,7 +45,7 @@ class TokenizerTest extends \PHPUnit_Framework_TestCase
' ',
1
),
$tokens->curr
$tokens->currToken()
);
$this->assertSame("resolve", $tokens->getNext($tokens::MACRO_UNARY, T_STRING));
@ -80,6 +80,25 @@ class TokenizerTest extends \PHPUnit_Framework_TestCase
$this->assertSame('}', $tokens->end()->current());
}
public function testComplexTokens()
{
$text = "one\\two";
$tokens = new Tokenizer($text);
$this->assertSame("one", $tokens->current());
$this->assertSame("\\", $tokens->next()->current());
$this->assertSame("two", $tokens->next()->current());
$this->assertFalse($tokens->next()->valid());
$text = "\\one\\two";
$tokens = new Tokenizer($text);
$this->assertSame("\\", $tokens->current());
$this->assertSame("one", $tokens->next()->current());
$this->assertSame("\\", $tokens->next()->current());
$this->assertSame("two", $tokens->next()->current());
$this->assertFalse($tokens->next()->valid());
}
public function testSkip()
{
$text = "1 foo: bar ( 3 + double ) ";
@ -106,7 +125,6 @@ class TokenizerTest extends \PHPUnit_Framework_TestCase
$this->assertSame($tokens, $tokens->next());
$tokens->p = -1000;
$this->assertSame($tokens, $tokens->back());
$this->assertNull($tokens->undef);
}
public function testFixFloats() {

View File

@ -24,7 +24,7 @@ class FenomTest extends \Fenom\TestCase
$time = $this->tpl('temp.tpl', 'Template 1 a');
$fenom = new Fenom($provider = new \Fenom\Provider(FENOM_RESOURCES . '/template'));
$fenom->setCompileDir(FENOM_RESOURCES . '/compile');
$this->assertInstanceOf('Fenom\Template', $tpl = $fenom->getTemplate('temp.tpl'));
$this->assertInstanceOf('Fenom\Render', $tpl = $fenom->getTemplate('temp.tpl'));
$this->assertSame($provider, $tpl->getProvider());
$this->assertSame('temp.tpl', $tpl->getBaseName());
$this->assertSame('temp.tpl', $tpl->getName());
@ -40,7 +40,7 @@ class FenomTest extends \Fenom\TestCase
FENOM_RESOURCES . '/compile',
Fenom::AUTO_ESCAPE
);
$this->assertInstanceOf('Fenom\Template', $tpl = $fenom->getTemplate('temp.tpl'));
$this->assertInstanceOf('Fenom\Render', $tpl = $fenom->getTemplate('temp.tpl'));
$this->assertSame($provider, $tpl->getProvider());
$this->assertSame('temp.tpl', $tpl->getBaseName());
$this->assertSame('temp.tpl', $tpl->getName());
@ -48,25 +48,12 @@ class FenomTest extends \Fenom\TestCase
$fenom->clearAllCompiles();
}
/**
* @expectedException LogicException
* @expectedExceptionMessage Cache directory /invalid/path is not writable
*/
public function testFactoryInvalid()
{
$this->expectException(LogicException::class, "Cache directory /invalid/path is not writable");
Fenom::factory(FENOM_RESOURCES . '/template', '/invalid/path');
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage Source must be a valid path or provider object
*/
public function testFactoryInvalid2()
{
Fenom::factory(new StdClass);
}
public function testCompileFile()
{
$a = array(
@ -116,6 +103,19 @@ class FenomTest extends \Fenom\TestCase
$this->assertSame("Custom template (new)", $this->fenom->fetch('custom.tpl', array()));
}
/**
* @group dev
*/
public function testCompileIdAndName()
{
$this->fenom->setCompileId("iddqd.");
$this->tpl('custom.tpl', 'Custom template');
$this->assertSame("Custom template", $this->fenom->fetch('custom.tpl', array()));
$compile_name = $this->fenom->getCompileName('custom.tpl');
$this->assertFileExists($this->getCompilePath().'/'.$compile_name);
$this->assertStringStartsWith('iddqd.', $compile_name);
}
public function testSetModifier()
{
$this->fenom->addModifier("mymod", "myMod");
@ -308,7 +308,7 @@ class FenomTest extends \Fenom\TestCase
/**
* @requires function php_gte_54
* @requires PHP 5.4
* @group pipe
*/
public function testPipe()
@ -349,6 +349,18 @@ class FenomTest extends \Fenom\TestCase
TPL;
$this->assertRender($tpl, '<div class="item item-one"><a href="/item/1">number one</a></div>');
}
/**
* Bug #195
* @group strip-xml
*/
public function testStripXML() {
$this->fenom->setOptions(Fenom::AUTO_STRIP);
$tpl = <<<TPL
<?xml version="1.0"?>
TPL;
$this->assertRender($tpl, '<?xml version="1.0"?>');
}
}