├── sandbox ├── compiled │ └── .gitkeep ├── templates │ ├── bug247 │ │ ├── home.tpl │ │ └── base.tpl │ ├── blocks │ │ ├── body.tpl │ │ ├── root.tpl │ │ ├── main.tpl │ │ └── second.tpl │ ├── bug241 │ │ └── recursive.tpl │ ├── bug158 │ │ ├── main.tpl │ │ └── test.tpl │ ├── layout.tpl │ ├── bug215 │ │ ├── index.tpl │ │ └── favicon.tpl │ ├── footer.tpl │ ├── macros.tpl │ ├── header.tpl │ ├── bug249 │ │ └── bread.tpl │ └── greeting.tpl └── fenom.php ├── tests ├── resources │ ├── compile │ │ └── .gitkeep │ ├── template │ │ └── .gitkeep │ ├── provider │ │ ├── include.tpl │ │ ├── extends │ │ │ ├── auto │ │ │ │ ├── child.1.tpl │ │ │ │ ├── child.3.tpl │ │ │ │ ├── child.2.tpl │ │ │ │ ├── use.tpl │ │ │ │ ├── static │ │ │ │ │ └── child.1.tpl │ │ │ │ └── parent.tpl │ │ │ ├── dynamic │ │ │ │ ├── child.4.tpl │ │ │ │ ├── child.1.tpl │ │ │ │ ├── child.3.tpl │ │ │ │ ├── use.tpl │ │ │ │ ├── child.2.tpl │ │ │ │ └── parent.tpl │ │ │ └── static │ │ │ │ ├── child.1.tpl │ │ │ │ ├── child.3.tpl │ │ │ │ ├── use.tpl │ │ │ │ ├── auto │ │ │ │ └── child.3.tpl │ │ │ │ ├── nested │ │ │ │ ├── child.1.tpl │ │ │ │ └── parent.tpl │ │ │ │ ├── child.2.tpl │ │ │ │ └── parent.tpl │ │ ├── pipe.tpl │ │ ├── parent.tpl │ │ ├── use │ │ │ ├── blocks.tpl │ │ │ ├── child.tpl │ │ │ └── parent.tpl │ │ ├── blocks.tpl │ │ ├── macros.tpl │ │ └── child.tpl │ └── actions.php ├── autoload.php ├── tools.php └── cases │ └── Fenom │ ├── CustomProviderTest.php │ ├── CommentTest.php │ ├── RenderTest.php │ ├── TagsTest.php │ ├── FunctionsTest.php │ ├── ProviderTest.php │ ├── MacrosTest.php │ ├── ExtendsTest.php │ └── TokenizerTest.php ├── docs ├── en │ ├── callbacks.md │ ├── articles.md │ ├── mods │ │ ├── unescape.md │ │ ├── in.md │ │ ├── length.md │ │ ├── lower.md │ │ ├── replace.md │ │ ├── upper.md │ │ ├── strip.md │ │ ├── ematch.md │ │ ├── join.md │ │ ├── split.md │ │ ├── esplit.md │ │ ├── escape.md │ │ ├── date_format.md │ │ ├── match.md │ │ ├── truncate.md │ │ └── ereplace.md │ ├── tags │ │ ├── do.md │ │ ├── unset.md │ │ ├── cycle.md │ │ ├── autotrim.md │ │ ├── filter.md │ │ ├── autoescape.md │ │ ├── ignore.md │ │ ├── raw.md │ │ ├── if.md │ │ ├── include.md │ │ ├── switch.md │ │ ├── macro.md │ │ ├── extends.md │ │ ├── for.md │ │ ├── set.md │ │ └── foreach.md │ ├── ext │ │ ├── extensions.md │ │ ├── parsing.md │ │ ├── provider.md │ │ ├── mods.md │ │ ├── inheritance.md │ │ ├── tags.md │ │ └── extend.md │ ├── adapters.md │ ├── helpme.md │ ├── dev │ │ ├── readme.md │ │ ├── schema.md │ │ └── internal.md │ ├── start.md │ ├── benchmark.md │ ├── configuration.md │ ├── operators.md │ └── readme.md ├── ru │ ├── callbacks.md │ ├── articles.md │ ├── inheritance.md │ ├── tags │ │ ├── do.md │ │ ├── unset.md │ │ ├── autotrim.md │ │ ├── filter.md │ │ ├── autoescape.md │ │ ├── cycle.md │ │ ├── raw.md │ │ ├── ignore.md │ │ ├── if.md │ │ ├── extends.md │ │ ├── macro.md │ │ ├── for.md │ │ ├── switch.md │ │ ├── set.md │ │ ├── include.md │ │ └── foreach.md │ ├── mods │ │ ├── in.md │ │ ├── length.md │ │ ├── unescape.md │ │ ├── replace.md │ │ ├── lower.md │ │ ├── upper.md │ │ ├── join.md │ │ ├── ematch.md │ │ ├── split.md │ │ ├── esplit.md │ │ ├── strip.md │ │ ├── escape.md │ │ ├── match.md │ │ ├── ereplace.md │ │ ├── truncate.md │ │ └── date_format.md │ ├── ext │ │ ├── extensions.md │ │ └── tags.md │ ├── adapters.md │ ├── dev │ │ ├── readme.md │ │ ├── schema.md │ │ └── internal.md │ ├── benchmark.md │ ├── configuration.md │ ├── start.md │ └── readme.md └── readme.md ├── authors.md ├── .coveralls.yml ├── src └── Fenom │ ├── Error │ ├── TemplateException.php │ ├── CompileException.php │ ├── SecurityException.php │ ├── TokenizeException.php │ ├── InvalidUsageException.php │ └── UnexpectedTokenException.php │ ├── ProviderInterface.php │ ├── RangeIterator.php │ ├── Provider.php │ └── Render.php ├── .gitignore ├── .travis.yml ├── .github ├── ISSUE_TEMPLATE.md ├── workflows │ └── php.yml └── CONTRIBUTING.md ├── composer.json ├── phpunit.xml.dist ├── license.md └── README.md /sandbox/compiled/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/resources/compile/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/resources/template/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/en/callbacks.md: -------------------------------------------------------------------------------- 1 | Callbacks 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /docs/ru/callbacks.md: -------------------------------------------------------------------------------- 1 | Callbacks 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /tests/resources/provider/include.tpl: -------------------------------------------------------------------------------- 1 | include template -------------------------------------------------------------------------------- /sandbox/templates/bug247/home.tpl: -------------------------------------------------------------------------------- 1 | {extends 'bug247/base.tpl'} -------------------------------------------------------------------------------- /sandbox/templates/blocks/body.tpl: -------------------------------------------------------------------------------- 1 | {block 'body'} 2 | 3 | do extend 4 | {/block} -------------------------------------------------------------------------------- /tests/resources/provider/extends/auto/child.1.tpl: -------------------------------------------------------------------------------- 1 | {block 'body'}Child 1 {parent}{/block} -------------------------------------------------------------------------------- /tests/resources/provider/extends/auto/child.3.tpl: -------------------------------------------------------------------------------- 1 | {block 'body'}Child 3 content{/block} -------------------------------------------------------------------------------- /authors.md: -------------------------------------------------------------------------------- 1 | Project Founder and Developer: 2 | 3 | - Ivan Shalganov 4 | -------------------------------------------------------------------------------- /tests/resources/provider/pipe.tpl: -------------------------------------------------------------------------------- 1 | {foreach $items as $key => $value}{$key}:{$value}{/foreach} -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | 3 | coverage_clover: build/logs/clover.xml 4 | 5 | -------------------------------------------------------------------------------- /tests/resources/provider/extends/dynamic/child.4.tpl: -------------------------------------------------------------------------------- 1 | {extends "extends/dynamic/child.{$a = 3}.tpl"} -------------------------------------------------------------------------------- /sandbox/templates/blocks/root.tpl: -------------------------------------------------------------------------------- 1 | {* root.tpl *} 2 | {block 'header'} 3 | root header 4 | {/block} -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | Languages 2 | ========= 3 | 4 | * [English](./en/readme.md) 5 | * [Russian](./ru/readme.md) -------------------------------------------------------------------------------- /sandbox/templates/bug241/recursive.tpl: -------------------------------------------------------------------------------- 1 | {if $n < 10} 2 | {include 'bug241/recursive.tpl' n=$n - 1} 3 | {/if} -------------------------------------------------------------------------------- /tests/resources/provider/parent.tpl: -------------------------------------------------------------------------------- 1 | {block 'header'}header body{/block} 2 | {block 'bottom'}bottom body{/block} -------------------------------------------------------------------------------- /tests/resources/provider/use/blocks.tpl: -------------------------------------------------------------------------------- 1 | {block 'b1'} block 1 blocks {/block} 2 | {block 'b2'} block 2 blocks {/block} -------------------------------------------------------------------------------- /sandbox/templates/bug158/main.tpl: -------------------------------------------------------------------------------- 1 | {* Отображаемый шаблон *} 2 | {import [test] from "bug158/test.tpl" as test} 3 | {test.test} -------------------------------------------------------------------------------- /sandbox/templates/layout.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | {block 'body'} 4 | nothing to do there 5 | {/block} 6 | 7 | -------------------------------------------------------------------------------- /tests/resources/provider/extends/auto/child.2.tpl: -------------------------------------------------------------------------------- 1 | {use 'extends/auto/use.tpl'} 2 | {block 'header'}Child 2 header{/block} 3 | -------------------------------------------------------------------------------- /tests/resources/provider/extends/auto/use.tpl: -------------------------------------------------------------------------------- 1 | {block 'footer'}Footer from use{/block} 2 | {block 'header'}Header from use{/block} -------------------------------------------------------------------------------- /tests/resources/provider/extends/dynamic/child.1.tpl: -------------------------------------------------------------------------------- 1 | {extends 'extends/dynamic/parent.tpl'} 2 | {block 'body'}Child 1 {parent}{/block} -------------------------------------------------------------------------------- /tests/resources/provider/extends/dynamic/child.3.tpl: -------------------------------------------------------------------------------- 1 | {extends 'extends/dynamic/child.2.tpl'} 2 | {block 'body'}Child 3 content{/block} -------------------------------------------------------------------------------- /tests/resources/provider/extends/dynamic/use.tpl: -------------------------------------------------------------------------------- 1 | {block 'footer'}Footer from use{/block} 2 | {block 'header'}Header from use{/block} -------------------------------------------------------------------------------- /tests/resources/provider/extends/static/child.1.tpl: -------------------------------------------------------------------------------- 1 | {extends 'extends/static/parent.tpl'} 2 | {block 'body'}Child 1 {parent}{/block} -------------------------------------------------------------------------------- /tests/resources/provider/extends/static/child.3.tpl: -------------------------------------------------------------------------------- 1 | {extends 'extends/static/child.2.tpl'} 2 | {block 'body'}Child 3 content{/block} -------------------------------------------------------------------------------- /tests/resources/provider/extends/static/use.tpl: -------------------------------------------------------------------------------- 1 | {block 'footer'}Footer from use{/block} 2 | {block 'header'}Header from use{/block} -------------------------------------------------------------------------------- /tests/resources/provider/use/child.tpl: -------------------------------------------------------------------------------- 1 | {extends 'use/parent.tpl'} 2 | {use 'use/blocks.tpl'} 3 | {block 'b2'} block 2 child {/block} -------------------------------------------------------------------------------- /tests/resources/provider/extends/auto/static/child.1.tpl: -------------------------------------------------------------------------------- 1 | {extends 'extends/auto/parent.tpl'} 2 | {block 'body'}Child 1 {parent}{/block} -------------------------------------------------------------------------------- /tests/resources/provider/extends/static/auto/child.3.tpl: -------------------------------------------------------------------------------- 1 | {extends 'extends/auto/child.2.tpl'} 2 | {block 'body'}Child 3 content{/block} -------------------------------------------------------------------------------- /tests/resources/provider/use/parent.tpl: -------------------------------------------------------------------------------- 1 | 2 | {block 'b1'} block 1 parent {/block} 3 | {block 'b2'} block 2 parent {/block} 4 | -------------------------------------------------------------------------------- /docs/ru/articles.md: -------------------------------------------------------------------------------- 1 | Статьи 2 | ====== 3 | 4 | ## 2013 5 | 6 | [Fenom — yet another PHP template engine](http://habrahabr.ru/post/169525/) [RU] 7 | -------------------------------------------------------------------------------- /tests/resources/provider/blocks.tpl: -------------------------------------------------------------------------------- 1 | {block 'header'} 2 | header block 3 | {/block} 4 | 5 | {block 'bottom'} 6 | bottom block 7 | {/block} -------------------------------------------------------------------------------- /tests/resources/provider/extends/static/nested/child.1.tpl: -------------------------------------------------------------------------------- 1 | {extends 'extends/static/nested/parent.tpl'} 2 | {block 'header'}Child 1: {parent}{/block} -------------------------------------------------------------------------------- /docs/en/articles.md: -------------------------------------------------------------------------------- 1 | Articles 2 | ======== 3 | 4 | ## 2013 5 | 6 | [Fenom — yet another PHP template engine](http://habrahabr.ru/post/169525/) [RU] 7 | -------------------------------------------------------------------------------- /sandbox/templates/blocks/main.tpl: -------------------------------------------------------------------------------- 1 | {* main.tpl *} 2 | {extends 'blocks/root.tpl'} 3 | {block 'header'} 4 | {parent} 5 | main header 6 | {/block} -------------------------------------------------------------------------------- /sandbox/templates/blocks/second.tpl: -------------------------------------------------------------------------------- 1 | {* second.tpl *} 2 | {extends 'blocks/main.tpl'} 3 | {block 'header'} 4 | {parent} 5 | second header 6 | {/block} -------------------------------------------------------------------------------- /sandbox/templates/bug215/index.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {include "bug215/favicon.tpl"} 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/resources/provider/macros.tpl: -------------------------------------------------------------------------------- 1 | {macro add(a, b)} 2 | a + b = {$a + $b} 3 | {/macro} 4 | 5 | {macro rem(a, b)} 6 | a - b = {$a - $b} 7 | {/macro} -------------------------------------------------------------------------------- /docs/en/mods/unescape.md: -------------------------------------------------------------------------------- 1 | Modifier unescape 2 | ================= 3 | 4 | `Unescape` is used to decode entity, html, js and URI. See [escape](./escape.md) 5 | 6 | -------------------------------------------------------------------------------- /docs/ru/inheritance.md: -------------------------------------------------------------------------------- 1 | Наследование шаблонов 2 | ===================== 3 | 4 | Документации пока нет, [почитайте на хабре](http://habrahabr.ru/post/169525/) пока 5 | -------------------------------------------------------------------------------- /tests/resources/provider/extends/static/child.2.tpl: -------------------------------------------------------------------------------- 1 | {extends 'extends/static/child.1.tpl'} 2 | {use 'extends/static/use.tpl'} 3 | {block 'header'}Child 2 header{/block} -------------------------------------------------------------------------------- /docs/en/tags/do.md: -------------------------------------------------------------------------------- 1 | Tag {do} 2 | ======== 3 | 4 | Evaluates any expression and doesn't print anything 5 | 6 | ```smarty 7 | {do $count++} 8 | {do $object->method()} 9 | ``` -------------------------------------------------------------------------------- /tests/resources/provider/child.tpl: -------------------------------------------------------------------------------- 1 | {extends 'parent.tpl'} 2 | up 3 | {block 'header'}empty header{/block} 4 | middle 5 | {block 'bottom'}empty bottom{/block} 6 | down 7 | -------------------------------------------------------------------------------- /docs/ru/tags/do.md: -------------------------------------------------------------------------------- 1 | Тег {do} 2 | ======== 3 | 4 | Выполнить произвольноые выражение без вывода результата на экран 5 | 6 | ```smarty 7 | {do $count++} 8 | {do $object->method()} 9 | ``` -------------------------------------------------------------------------------- /sandbox/templates/footer.tpl: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sandbox/templates/bug158/test.tpl: -------------------------------------------------------------------------------- 1 | {* template:test.tpl *} 2 | {macro test($break = false)} 3 | Test macro recursive 4 | {if $break?} 5 | {macro.test break = true} 6 | {/if} 7 | {/macro} -------------------------------------------------------------------------------- /src/Fenom/Error/TemplateException.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/resources/provider/extends/static/nested/parent.tpl: -------------------------------------------------------------------------------- 1 | Before body 2 | {block 'body'} 3 | Before header 4 | {block 'header'}Content of the header{/block} 5 | Before footer 6 | {block 'footer'}Content of the footer{/block} 7 | {/block} -------------------------------------------------------------------------------- /docs/ru/mods/in.md: -------------------------------------------------------------------------------- 1 | Модификатор in 2 | =========== 3 | 4 | 5 | 6 | Модификатор является реализацией оператора содержания [in](../operators.md#Оператор-содержания). 7 | 8 | ```smarty 9 | {if $number|in:[1, 3, 42]} 10 | ... 11 | {/if} 12 | ``` 13 | -------------------------------------------------------------------------------- /docs/en/mods/in.md: -------------------------------------------------------------------------------- 1 | Modifier in 2 | =========== 3 | 4 | The modifier is implementation of [in](../operators.md#containment-operator) operator (performs containment test). 5 | 6 | ```smarty 7 | {if $number|in:[1, 3, 42]} 8 | ... 9 | {/if} 10 | ``` 11 | -------------------------------------------------------------------------------- /docs/ru/mods/length.md: -------------------------------------------------------------------------------- 1 | Модификатор length 2 | =============== 3 | 4 | Модификатор возвращает количество элементов массива, итератора или символов в строке (работает с UTF8). 5 | 6 | ```smarty 7 | {if $images|length > 5} 8 | to many images 9 | {/if} 10 | ``` -------------------------------------------------------------------------------- /docs/ru/tags/autotrim.md: -------------------------------------------------------------------------------- 1 | Тег {autotrim} 2 | ============== 3 | 4 | Задает индивидуальное значение параметра `auto_trim` на фрагмент шаблона: 5 | 6 | ```smarty 7 | {autotrim true} 8 | ... 9 | Text: {$text} 10 | ... 11 | {/autotrim} 12 | ``` 13 | -------------------------------------------------------------------------------- /docs/ru/mods/unescape.md: -------------------------------------------------------------------------------- 1 | Модификатор unescape 2 | ================= 3 | 4 | Модификато `unescape` является обратным действием модификатора `escape`. Так же имеет режимы `html`, `js` and `URI`. 5 | 6 | ```smarty 7 | {$text|unescape:$type = 'html'} 8 | ``` 9 | 10 | -------------------------------------------------------------------------------- /docs/ru/tags/filter.md: -------------------------------------------------------------------------------- 1 | Тег {filter} 2 | ============= 3 | 4 | Тег {filter} позволяет применить модификаторы на фрагмент шаблона 5 | 6 | ```smarty 7 | {filter|strip_tags|truncate:20} 8 | Remove all HTML tags and truncate {$text} to 20 symbols 9 | {/filter} 10 | ``` -------------------------------------------------------------------------------- /docs/en/mods/length.md: -------------------------------------------------------------------------------- 1 | Modifier length 2 | =============== 3 | 4 | The modifier returns the number of items of a sequence or mapping, or the length of a string (works with UTF8 without `mbstring`) 5 | 6 | ```smarty 7 | {if $images|length > 5} 8 | to many images 9 | {/if} 10 | ``` -------------------------------------------------------------------------------- /docs/en/tags/cycle.md: -------------------------------------------------------------------------------- 1 | Tag {cycle} 2 | =========== 3 | 4 | `{cycle}` is used to alternate a set of values. 5 | 6 | ```smarty 7 | {foreach 1..10} 8 |
9 | {/foreach} 10 | 11 | 12 | {foreach 1..10} 13 |
14 | {/foreach} 15 | ``` -------------------------------------------------------------------------------- /docs/en/ext/extensions.md: -------------------------------------------------------------------------------- 1 | Extensions 2 | ========== 3 | 4 | * [Extra pack](https://github.com/bzick/fenom-extra) of add-ons for Fenom template engine. 5 | * Tools for static files (css, js). 6 | * Global variables 7 | * Allow more hooks for extending 8 | * Add variable container 9 | * You can only use the necessary add-ons -------------------------------------------------------------------------------- /docs/ru/ext/extensions.md: -------------------------------------------------------------------------------- 1 | Extensions 2 | ========== 3 | 4 | * [Extra pack](https://github.com/bzick/fenom-extra) of add-ons for Fenom template engine. 5 | * Tools for static files (css, js). 6 | * Global variables 7 | * Allow more hooks for extending 8 | * Add variable container 9 | * You can only use the necessary add-ons -------------------------------------------------------------------------------- /docs/en/tags/autotrim.md: -------------------------------------------------------------------------------- 1 | Tag {autotrim} 2 | ============== 3 | 4 | Force enable or disable `auto_trim` option for block area: 5 | 6 | ```smarty 7 | {autotrim true} 8 | ... 9 | Text: {$text} {* value of the variable $text will be escaped *} 10 | ... 11 | {/autotrim} 12 | ``` 13 | 14 | Also see :trim, :rtrim and :ltrim tag options -------------------------------------------------------------------------------- /docs/en/tags/filter.md: -------------------------------------------------------------------------------- 1 | Tags {filter} 2 | ============= 3 | 4 | Apply modifier to template area. 5 | 6 | ```smarty 7 | {filter|strip_tags|truncate:20} 8 | Remove all HTML tags and truncate {$text} to 20 symbols 9 | {/filter} 10 | ``` 11 | 12 | **Note**: output buffering used. May be used a lot of memory if you output a lot of data. -------------------------------------------------------------------------------- /docs/en/ext/parsing.md: -------------------------------------------------------------------------------- 1 | Parsing templates [RU] 2 | ====================== 3 | 4 | ### Tokenizer 5 | 6 | Объект Tokenizer содержит список готовых к обработке токенов и необходимые для фильтрации токенов методы. Помимо основнях констант расширения Tokenizer у объекта есть макросы, объединения, определенных токенов. 7 | 8 | ### Parsers 9 | 10 | -------------------------------------------------------------------------------- /sandbox/templates/bug247/base.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {block 'content'}{/block} 10 | 11 | -------------------------------------------------------------------------------- /docs/en/adapters.md: -------------------------------------------------------------------------------- 1 | Adapters 2 | ======== 3 | 4 | * [Fenom + Yii](https://bitbucket.org/RSol/rfenomviewrender) 5 | * [Fenom + Kohana](https://github.com/2bj/kofenom) — Kofenom 6 | * Fenom + Symphony 7 | * Fenom + Symphony2 8 | * Fenom + Zend Framework 9 | * [Fenom + Slim Framework 3](https://github.com/runcmf/runbb-ext-renderer) — RunBB forum extension 10 | -------------------------------------------------------------------------------- /docs/en/tags/autoescape.md: -------------------------------------------------------------------------------- 1 | Tag {autoescape} 2 | ===================== 3 | 4 | Force enable or disable `auto_escape` option for block area: 5 | 6 | ```smarty 7 | {autoescape true} 8 | ... 9 | Text: {$text} {* value of the variable $text will be escaped *} 10 | ... 11 | {/autoescape} 12 | ``` 13 | 14 | Also see {raw} tag and :raw tag option 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | php: 6 | - 5.6 7 | - 7.0 8 | - 7.1 9 | - 7.2 10 | - 7.3 11 | - 7.4 12 | 13 | before_script: 14 | - composer global require satooshi/php-coveralls 15 | - composer update --quiet 16 | 17 | script: 18 | - vendor/bin/phpunit phpunit -c phpunit.xml.dist 19 | 20 | after_script: 21 | - coveralls 22 | -------------------------------------------------------------------------------- /docs/ru/mods/replace.md: -------------------------------------------------------------------------------- 1 | Модификатор replace 2 | ================ 3 | 4 | Заменяет все вхождения строки поиска на строку замены 5 | 6 | ``` 7 | {$string|replace:$search:$replace} 8 | ``` 9 | 10 | Этот модификатор возвращает строку, в котором все вхождения `$search` в `$subject` заменены на `$replace`. 11 | 12 | ```smarty 13 | {$fruits|replace:"pear":"orange"} 14 | ``` -------------------------------------------------------------------------------- /tests/autoload.php: -------------------------------------------------------------------------------- 1 | 10 | {/for} 11 | 12 | 13 | {for $i=1 to=6} 14 |
15 | {/for} 16 | ``` -------------------------------------------------------------------------------- /docs/en/mods/strip.md: -------------------------------------------------------------------------------- 1 | Modifier strip 2 | ============== 3 | 4 | This replaces all repeated spaces and tabs with a single space, or with the supplied string. 5 | 6 | ```smarty 7 | {" one two "|strip} => 'one two' 8 | ``` 9 | 10 | Optional boolean parameter tell to the modifier strip also newline 11 | 12 | ```smarty 13 | {" multi 14 | line 15 | text "|strip:true} => 'multi line text' 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/ru/mods/join.md: -------------------------------------------------------------------------------- 1 | Модификатор split 2 | ============== 3 | 4 | Объединяет элементы массива в строку. 5 | 6 | ``` 7 | {$array|join:$delimiter = ","} 8 | ``` 9 | 10 | Объединяет элементы массива с помощью строки `$delimiter`. 11 | 12 | ```smarty 13 | {var $fruits1 = ["banana", "apple", "pear"]} 14 | {$fruits1|join} выведет banana, apple, pear 15 | {$fruits1|join:" is not "} выведет banana is not apple is not pear 16 | ``` -------------------------------------------------------------------------------- /src/Fenom/Error/CompileException.php: -------------------------------------------------------------------------------- 1 | addProvider("db", $db_provider); 8 | ``` -------------------------------------------------------------------------------- /docs/en/mods/split.md: -------------------------------------------------------------------------------- 1 | Modifier split 2 | ============== 3 | 4 | Split a string by string 5 | 6 | ``` 7 | {$string|split:$delimiter = ","} 8 | ``` 9 | 10 | Returns an array of strings, each of which is a substring of `$string` formed by splitting it on boundaries formed by the string `$delimiter`. 11 | 12 | ```smarty 13 | {var $fruits1 = "banana,apple,pear"|split} 14 | $fruits1 is array ["banana", "apple", "pear"] 15 | 16 | {var $fruits2 = "banana,apple,pear"|split:',apple,'} 17 | $fruits2 is array ["banana", "pear"] 18 | ``` -------------------------------------------------------------------------------- /docs/en/ext/mods.md: -------------------------------------------------------------------------------- 1 | Модификаторы [RU] 2 | ============ 3 | 4 | 5 | ``` 6 | $fenom->addModifier(string $modifier, callable $callback); 7 | ``` 8 | 9 | * `$modifier` - название модификатора, которое будет использоваться в шаблоне 10 | * `$callback` - коллбек, который будет вызван для изменения данных 11 | 12 | For example: 13 | 14 | ```smarty 15 | {$variable|my_modifier:$param1:$param2} 16 | ``` 17 | 18 | ```php 19 | $fenom->addModifier('my_modifier', function ($variable, $param1, $param2) { 20 | // ... 21 | }); 22 | ``` 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | > Please use https://stackoverflow.com/questions/tagged/fenom for all your general questions or troubleshooting! 2 | 3 | > 4 | > *Warning*: Failing to provide necessary information may cause the issue to be closed without consideration 5 | 6 | ### I found this bug / would like to have this new functionality 7 | 8 | 9 | ### This is Fenom and PHP version and environment (server/fpm/cli etc) I am using 10 | 11 | 12 | ### This is a template code snippet I use 13 | 14 | ``` 15 | {var x=10} 16 | {Calc x=10 y=20} 17 | 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/en/dev/readme.md: -------------------------------------------------------------------------------- 1 | Develop 2 | ======= 3 | 4 | If you want to contribute to Fenom please feel free to create an issue or submit a pull request on Github. 5 | 6 | There are two branches: 7 | 8 | * `master` branch is for stable releases and hotfixes 9 | * `develop` branch is for development of features 10 | 11 | Each tag names by rule `major.minor.fix` (1.4.9, 1.1.2, etc.) and creates from master. Version changes by the rule of [semantic versioning](http://semver.org/). 12 | 13 | For questions: a.cobest@gmail.com (English, Russian languages) 14 | 15 | * the [scheme of work](schema.md) -------------------------------------------------------------------------------- /docs/en/mods/esplit.md: -------------------------------------------------------------------------------- 1 | Modifier esplit 2 | =============== 3 | 4 | Split string by a regular expression. 5 | [Read more](http://www.php.net/manual/en/reference.pcre.pattern.syntax.php) about regular expression. 6 | 7 | ``` 8 | {$string|esplit:$pattern = '/,\s*/'} 9 | ``` 10 | 11 | My default modifier split string by comma with spaces. 12 | 13 | ```smarty 14 | {var $fruits1 = "banana, apple, pear"|esplit} 15 | $fruits1 is array ["banana", "apple", "pear"] 16 | 17 | {var $fruits2 = "banana; apple; pear"|esplit:'/;\s/'} is ["banana", "apple", "pear"] 18 | $fruits2 is array ["banana", "apple", "pear"] too 19 | ``` -------------------------------------------------------------------------------- /docs/ru/mods/esplit.md: -------------------------------------------------------------------------------- 1 | Модификатор esplit 2 | =============== 3 | 4 | Разбивает строку по регулярному выражению. 5 | [Подробнее](http://www.php.net/manual/ru/reference.pcre.pattern.syntax.php) о регулярных выражениях. 6 | 7 | ``` 8 | {$string|esplit:$pattern = '/,\s*/'} 9 | ``` 10 | 11 | По умолчанию модификатор разбивает строку по запятой с возможнымиы проблеами 12 | 13 | ```smarty 14 | {var $fruits1 = "banana, apple, pear"|esplit} 15 | $fruits1 — массив ["banana", "apple", "pear"] 16 | 17 | {var $fruits2 = "banana; apple; pear"|esplit:'/;\s/'} is ["banana", "apple", "pear"] 18 | $fruits2 — массив ["banana", "apple", "pear"] 19 | ``` -------------------------------------------------------------------------------- /sandbox/templates/macros.tpl: -------------------------------------------------------------------------------- 1 | {macro pills($title, $items, $active)} 2 |
3 | 12 |

{$title}

13 |
14 | {/macro} -------------------------------------------------------------------------------- /docs/ru/tags/raw.md: -------------------------------------------------------------------------------- 1 | Тег {raw} 2 | ========= 3 | 4 | Тег `{raw }` позволяет вывести результат выражения без экранирования. 5 | 6 | 7 | ```smarty 8 | {autoescape true} 9 | ... 10 | {$var|up} {* escape *} 11 | {raw $var|up} {* unescape *} 12 | ... 13 | {"name is: {$name|low}"} {* escape *} 14 | {raw "name is: {$name|low}"} {* unescape *} 15 | ... 16 | {/autoescate} 17 | ``` 18 | 19 | Для функций используйте параметр тега `:raw`: 20 | 21 | ```smarty 22 | {autoescape true} 23 | ... 24 | {my_func page=5} {* escape *} 25 | {my_func:raw page=5} {* unescape *} 26 | ... 27 | {/autoescape} 28 | ``` -------------------------------------------------------------------------------- /docs/ru/mods/strip.md: -------------------------------------------------------------------------------- 1 | Модификатор strip 2 | ============== 3 | 4 | Заменяет все повторяющиеся пробелы, переводы строк и символы табуляции одним пробелом. 5 | 6 | This replaces all repeated spaces and tabs with a single space, or with the supplied string. 7 | 8 | ```smarty 9 | {" one two "|strip} 10 | ``` 11 | Результат обработки 12 | ``` 13 | one two 14 | ``` 15 | 16 | Опционально указывается флаг мультистрочности: `true` - тку же срезать переносы строк, `false` - срезать все кроме переносов строк. 17 | 18 | ```smarty 19 | {" multi 20 | line 21 | text "|strip:true} 22 | ``` 23 | 24 | Результат обработки 25 | ``` 26 | multi line text 27 | ``` -------------------------------------------------------------------------------- /docs/en/tags/ignore.md: -------------------------------------------------------------------------------- 1 | Tag {ignore} 2 | ============ 3 | 4 | {ignore} tags allow a block of data to be taken literally. 5 | This is typically used around Javascript or stylesheet blocks where {curly braces} would interfere with the template delimiter syntax. 6 | Anything within {ignore}{/ignore} tags is not interpreted, but displayed as-is. 7 | 8 | ```smarty 9 | {ignore} 10 | var data = {"time": obj.ts}; 11 | {/ignore} 12 | ``` 13 | 14 | {ignore} tags are normally not necessary, as Fenom ignores delimiters that are surrounded by whitespace. 15 | Be sure your javascript and CSS curly braces are surrounded by whitespace: 16 | 17 | ```smarty 18 | var data = { "time": obj.ts }; 19 | ``` -------------------------------------------------------------------------------- /docs/ru/mods/escape.md: -------------------------------------------------------------------------------- 1 | Модификатор escape 2 | =============== 3 | 4 | Используется для кодирования или экранирования спецсимволов по алгоритмам экранирования HTML, URL'ов и javascript. 5 | По умолчанию активирован режим экранирования HTML. 6 | 7 | ```smarty 8 | {$text|escape:$type = 'html':$charset = 'UTF8'} 9 | ``` 10 | 11 | Модификатор поддерживает несколько режимов работы 12 | 13 | * `html`: экранирует HTML сущности в строке. 14 | * `url`: экранирует строку для использования в URL. 15 | * `js`: экранирует строку для использования в JavaScript. 16 | 17 | Модификатор `e` является псевданимом модификатора от `escape`. 18 | 19 | Параметр `$charset` указывает кодировку для режима `html`. 20 | -------------------------------------------------------------------------------- /docs/en/mods/escape.md: -------------------------------------------------------------------------------- 1 | Modifier escape 2 | =============== 3 | 4 | The modifier escapes a string for safe insertion into the output. 5 | It supports different escaping strategies depending on the template context. 6 | By default, it uses the HTML escaping strategy: 7 | 8 | ```smarty 9 | {$post.title|escape:'html'} 10 | ``` 11 | 12 | The modifier supports the following escaping strategies: 13 | 14 | * html: escapes a string for the HTML body context. 15 | * url: escapes a string for the URI or parameter contexts. 16 | * js: escapes a string for the JavaScript context. 17 | 18 | For convenience, the `e` modifier is defined as an alias of `escape` modifier. 19 | 20 | Second parameter is charset. 21 | -------------------------------------------------------------------------------- /tests/tools.php: -------------------------------------------------------------------------------- 1 | getTraceAsString() . "\n"; 8 | exit(); 9 | } 10 | 11 | function dump() 12 | { 13 | foreach (func_get_args() as $arg) { 14 | fwrite(STDERR, "DUMP: " . call_user_func("print_r", $arg, true) . "\n"); 15 | 16 | } 17 | } 18 | 19 | function dumpt() 20 | { 21 | foreach (func_get_args() as $arg) { 22 | fwrite(STDERR, "DUMP: " . call_user_func("print_r", $arg, true) . "\n"); 23 | 24 | } 25 | $e = new Exception(); 26 | echo "-------\nDump trace: \n" . $e->getTraceAsString() . "\n"; 27 | } -------------------------------------------------------------------------------- /tests/cases/Fenom/CustomProviderTest.php: -------------------------------------------------------------------------------- 1 | fenom->addProvider("my", new Provider(FENOM_RESOURCES . '/provider')); 13 | } 14 | 15 | public function testCustom() 16 | { 17 | $this->assertTrue($this->fenom->templateExists('my:include.tpl')); 18 | $this->assertFalse($this->fenom->templateExists('my:include-none.tpl')); 19 | $this->assertRender("start: {include 'my:include.tpl'}", 'start: include template'); 20 | $this->assertTrue($this->fenom->getTemplate('my:include.tpl')->isValid()); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /sandbox/templates/header.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {$title} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
-------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fenom/fenom", 3 | "type": "library", 4 | "description": "Fenom - excellent template engine for PHP", 5 | "keywords": ["fenom", "template", "templating", "templater"], 6 | "license": "BSD-3-Clause", 7 | "authors": [ 8 | { 9 | "name": "Ivan Shalganov", 10 | "email": "a.cobest@gmail.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=8.0.0", 15 | "ext-tokenizer": "*" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit": "9.*" 19 | }, 20 | "autoload": { 21 | "psr-4": { "Fenom\\": "src/Fenom" }, 22 | "classmap": [ "src/Fenom.php" ] 23 | }, 24 | "autoload-dev": { 25 | "classmap": [ "tests/cases" ] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docs/en/tags/raw.md: -------------------------------------------------------------------------------- 1 | Tag {raw} 2 | ========= 3 | 4 | Tag `{raw }` allow outputs render results without escaping. 5 | This tag rewrite global option `auto_escape` for specified code. 6 | 7 | ```smarty 8 | {autoescape true} 9 | ... 10 | {$var|up} {* escape *} 11 | {raw $var|up} {* unescape *} 12 | ... 13 | {"name is: {$name|low}"} {* escape *} 14 | {raw "name is: {$name|low}"} {* unescape *} 15 | ... 16 | {/autoescate} 17 | ``` 18 | 19 | For functions use tag with prefix `raw:`: 20 | 21 | ```smarty 22 | {autoescape true} 23 | ... 24 | {my_func page=5} {* escape *} 25 | {my_func:raw page=5} {* unescape *} 26 | ... 27 | {/autoescate} 28 | ``` 29 | 30 | Tag can not be applied to compilers as `foreach`, `if` and other. -------------------------------------------------------------------------------- /docs/en/mods/date_format.md: -------------------------------------------------------------------------------- 1 | Modifier date_format 2 | ==================== 3 | 4 | This formats a date and time into the given [strftime()](http://docs.php.net/strftime) format. 5 | Dates can be passed to Fenom as unix timestamps, DateTime objects or any string made up of month day year, parsable by [strftime()](http://docs.php.net/strftime). 6 | By default format is: `%b %e, %Y`. 7 | 8 | ```smarty 9 | {var $ts = time()} 10 | 11 | {$ts|date_format:"%Y/%m/%d %H:%M:%S"} outputs 2013/02/08 21:01:43 12 | {$ts|date_format:"-1 day"} outputs 2013/02/07 21:01:43 13 | 14 | {var $date = "2008-12-08"} 15 | 16 | {$ts|date_format:"%Y/%m/%d %H:%M:%S"} outputs 2008/12/08 00:00:00 17 | ``` 18 | 19 | [Allowed quantificators](http://docs.php.net/strftime#refsect1-function.strftime-parameters) in **date_format** 20 | -------------------------------------------------------------------------------- /docs/ru/dev/readme.md: -------------------------------------------------------------------------------- 1 | Develop 2 | ======= 3 | 4 | Если у Вас есть что обсудить или предложить создайте issue на Github или сделайте запрос на сливание. 5 | 6 | По вопросам можете слать письмо на email: a.cobest@gmail.com (Русский и английские языки) 7 | 8 | 9 | ## Соглашение по наименованию версий 10 | 11 | Версии именуются согласно [Semantic Versioning 2.0.0](http://semver.org/). 12 | 13 | ## Соглашение по GIT 14 | 15 | Ветка `master` содержит стабильную последнюю версию проекта. 16 | В ветку `master` может сливаться новая версия проекта из `develop` или исправления. 17 | Ветка `develop`, для разработки, содержит не стабильную версию проекта. Принимает новшества, изменения и исправления. 18 | 19 | 20 | ## Принцип работы 21 | 22 | Разобраться в принципе работы поможет [эта схема](schema.md). 23 | -------------------------------------------------------------------------------- /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: PHP Composer 2 | 3 | on: push 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Validate composer.json and composer.lock 17 | run: composer validate --strict 18 | 19 | - name: Cache Composer packages 20 | id: composer-cache 21 | uses: actions/cache@v3 22 | with: 23 | path: vendor 24 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 25 | restore-keys: | 26 | ${{ runner.os }}-php- 27 | - name: Install dependencies 28 | run: composer install --prefer-dist --no-progress 29 | 30 | - name: Run tests 31 | run: vendor/bin/phpunit -------------------------------------------------------------------------------- /sandbox/fenom.php: -------------------------------------------------------------------------------- 1 | setOptions(Fenom::AUTO_RELOAD | Fenom::FORCE_VERIFY | Fenom::FORCE_INCLUDE); 10 | //var_dump($fenom->compile("nested.tpl", [])->getTemplateCode()); 11 | //exit; 12 | //var_dump($fenom->compile('bug241/recursive.tpl', false)->getBody()); 13 | //var_dump($fenom->compile('bug249/bread.tpl', false)->getBody()); 14 | //var_dump($fenom->compile("bug158/main.tpl", [])->getTemplateCode()); 15 | //var_dump($fenom->display("bug158/main.tpl", [])); 16 | // $fenom->getTemplate("problem.tpl"); 17 | 18 | var_dump($fenom->compileCode('{$.php.Fenom::factory()->addDay()}')->getBody()); 19 | -------------------------------------------------------------------------------- /docs/en/tags/if.md: -------------------------------------------------------------------------------- 1 | Tag {if} 2 | ======== 3 | 4 | Tag {if} have much the same flexibility as PHP [if](http://docs.php.net/if) statements, 5 | with a few added features for the template engine. 6 | All operators, allowed functions and variables are recognized in conditions. 7 | 8 | ```smarty 9 | {if } 10 | {* ...code... *} 11 | {elseif } 12 | {* ...code... *} 13 | {else} 14 | {* ...code... *} 15 | {/if} 16 | ``` 17 | 18 | ### {if} 19 | 20 | ```smarty 21 | {if } 22 | {*...some code...*} 23 | {/if} 24 | ``` 25 | 26 | ### {elseif} 27 | 28 | ```smarty 29 | {if } 30 | {*...some code...*} 31 | {elseif } 32 | {*...some code...*} 33 | {/if} 34 | ``` 35 | 36 | ### {else} 37 | 38 | ```smarty 39 | {if } 40 | {*...some code...*} 41 | {else} 42 | {*...some code...*} 43 | {/if} 44 | ``` -------------------------------------------------------------------------------- /docs/en/mods/match.md: -------------------------------------------------------------------------------- 1 | Modifier match 2 | ============== 3 | 4 | Match string against a pattern. 5 | The average user may be used to shell patterns or at least in their simplest form to `?` and `*` wildcards so using `match` 6 | instead of `ematch` for frontend search expression input may be way more convenient for non-programming users. 7 | 8 | ``` 9 | {$string|match:$pattern} 10 | ``` 11 | 12 | Special pattern symbols: 13 | 14 | * `?` — match one or zero unknown characters. `?at` matches `Cat`, `cat`, `Bat` or `bat` but not `at`. 15 | * `*` — match any number of unknown characters. `Law*` matches `Law`, `Laws`, or `Lawyer`. 16 | * `[characters]` — Match a character as part of a group of characters. `[CB]at` matches `Cat` or `Bat` but not `cat`, `rat` or `bat`. 17 | * `\` - Escape character. `Law\*` will only match `Law*` 18 | 19 | 20 | ```smarty 21 | {if $color|match:"*gr[ae]y"} 22 | some form of gray ... 23 | {/if} 24 | ``` -------------------------------------------------------------------------------- /docs/en/mods/truncate.md: -------------------------------------------------------------------------------- 1 | Modifier truncate 2 | ================= 3 | 4 | Modifier truncates a variable to a character length. 5 | 6 | ```smarty 7 | {$long_string|truncate:$length:$etc:$by_words:$middle} 8 | ``` 9 | 10 | * `$length`, required. Parameter determines how many characters to truncate to. 11 | * `$etc`, by default `...`. This is a text string that replaces the truncated text. 12 | * `$by_word`, by default **FALSE**. This determines whether or not to truncate at a word boundary with TRUE, or at the exact character with FALSE. 13 | * `$middle`, by default **FALSE**. This determines whether the truncation happens at the end of the string with FALSE, or in the middle of the string with TRUE. 14 | 15 | ```smarty 16 | {var $str = "very very long string"} 17 | 18 | {$str|truncate:10:" read more..."} output: very very read more... 19 | {$str|truncate:5:" ... ":true:true} output: very ... string 20 | ``` 21 | 22 | Modifier do not use `mbstring` when works with UTF8. -------------------------------------------------------------------------------- /docs/en/mods/ereplace.md: -------------------------------------------------------------------------------- 1 | Modifier ereplace 2 | ================= 3 | 4 | Perform a regular expression search and replace. 5 | [Read more](http://www.php.net/manual/en/reference.pcre.pattern.syntax.php) about regular expression. 6 | 7 | ``` 8 | {$string|replace:$pattern:$replacement} 9 | ``` 10 | 11 | Searches `$string` for matches to `$pattern` and replaces them with `$replacement`. 12 | 13 | `$replacement` may contain references of the form `\n`, `$n` or `${n}`, with the latter form being the preferred one. 14 | Every such reference will be replaced by the text captured by the n'th parenthesized pattern. n can be from 0 to 99, 15 | and `\0` or `$0` refers to the text matched by the whole pattern. 16 | Opening parentheses are counted from left to right (starting from 1) to obtain the number of the capturing subpattern. 17 | To use backslash in replacement, it must be doubled. 18 | 19 | ```smarty 20 | {var $string = 'April 15, 2014'} 21 | 22 | {$string|ereplace:'/(\w+) (\d+), (\d+)/i':'${1}1, $3'} {* April1, 2014 *} 23 | ``` -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ./tests/ 22 | 23 | 24 | 25 | 26 | 27 | benchmark 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /docs/ru/tags/ignore.md: -------------------------------------------------------------------------------- 1 | Тег {ignore} 2 | ============ 3 | 4 | Теги `{ignore}{/ignore}` позволяют игнорировать заключенные в них другие теги. 5 | Весь текст внутри тегов `{ignore}{/ignore}` не интерпретируется, а выводится "как есть". 6 | Обычно они используются вместе с javascript или таблицами стилей, в которых фигурные скобки конфликтуют с синтаксисом разделителей. 7 | 8 | ```smarty 9 | {ignore} 10 | var data = {"time": obj.ts}; 11 | {/ignore} 12 | ``` 13 | 14 | Использование тегов {ignore} не всегда обязательное так как Fenom игнорирует фигурные скобки `{` сразу после которых идут пробельные символы. 15 | Убедитесь что открывающие фигурные скобки в JavaScript и CSS имеют хотя бы по пробелу справа: 16 | 17 | ```smarty 18 | var data = { "time": obj.ts }; 19 | ``` 20 | 21 | Так же для игнорирования синтаксиса Fenom можно использовать опцию `:ignore` для любого блочного тега. 22 | ```smarty 23 | {if:ignore $cdn.yandex} 24 | var item = {cdn: "//yandex.st/"}; 25 | {/if} 26 | {script:ignore} ... {/script} 27 | {foreach:ignore ...} ... {/foreach} 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/en/tags/include.md: -------------------------------------------------------------------------------- 1 | Tag {include} 2 | ============= 3 | 4 | `{include}` tags are used for including other templates in the current template. Any variables available in the current template are also available within the included template. 5 | 6 | ```smarty 7 | {include "about.tpl"} 8 | ``` 9 | 10 | If you need to set yours variables for template list them in attributes. 11 | 12 | ```smarty 13 | {include "about.tpl" page=$item limit=50} 14 | ``` 15 | 16 | All variables changed in child template has no affect to variables in parent template. 17 | 18 | ### {insert} 19 | 20 | The tag insert template code instead self. 21 | 22 | * No dynamic name allowed. 23 | * No variables as attribute allowed. 24 | * Increase performance because insert code as is in compilation time. 25 | 26 | For example, main.tpl: 27 | 28 | ```smarty 29 | a: {$a} 30 | {insert 'b.tpl'} 31 | c: {$c} 32 | ``` 33 | 34 | b.tpl: 35 | 36 | ``` 37 | b: {$b} 38 | ``` 39 | 40 | Code of `b.tpl` will be inserted into `main.tpl` as is: 41 | 42 | ```smarty 43 | a: {$a} 44 | b: {$b} 45 | c: {$c} 46 | ``` 47 | -------------------------------------------------------------------------------- /tests/cases/Fenom/CommentTest.php: -------------------------------------------------------------------------------- 1 | assertRender("before {* $tpl_val *} after", "before after"); 15 | $this->assertRender("before {* {{$tpl_val}} {{$tpl_val}} *} after", "before after"); 16 | $this->assertRender("before {*{{$tpl_val}}*} after", "before after"); 17 | } 18 | 19 | public function testError() 20 | { 21 | $this->execError('{* ', 'Fenom\Error\CompileException', "Unclosed comment block in line"); 22 | } 23 | 24 | /** 25 | * @dataProvider providerScalars 26 | */ 27 | public function testMultiLine($tpl_val) 28 | { 29 | $this->assertRender( 30 | "before-1\nbefore-2 {* before-3\nbefore-4 $tpl_val after-1\nafter-2 *} after-3\nafter-4{* dummy *}\nafter-5", 31 | "before-1\nbefore-2 after-3\nafter-4\nafter-5" 32 | ); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/Fenom/Error/UnexpectedTokenException.php: -------------------------------------------------------------------------------- 1 | currToken()) { 28 | $this->message = "Unexpected end of " . ($where ? : "expression") . "$expect"; 29 | } else { 30 | $this->message = "Unexpected token '" . $tokens->current() . "' in " . ($where ? : "expression") . "$expect"; 31 | } 32 | } 33 | } 34 | 35 | ; -------------------------------------------------------------------------------- /docs/ru/mods/match.md: -------------------------------------------------------------------------------- 1 | Модификатор match 2 | ================= 3 | 4 | Проверяет совпадение строки с паттерном. 5 | Среднестатистический пользователь знаком с подстановками оболочки, как минимум с самыми простыми из них - `?` и `*`, 6 | так что использование `match` вместо `ematch` для поиска в пользовательской части сайта может быть намного удобнее для пользователей, 7 | не являющихся программистами. 8 | 9 | 10 | ``` 11 | {$string|match:$pattern} 12 | ``` 13 | 14 | Специальные символы: 15 | 16 | * `?` — соответствие одному или нулю любых символов. `?at` соответствует `Cat`, `cat`, `Bat` или `bat`. 17 | * `*` — соответствие любому количеству символов. `Law*` соответствует `Law`, `Laws`, или `Lawyer`. 18 | * `[characters]` — соответствие символа группе символов. `[CB]at` соответствует `Cat` или `Bat`, но не `cat`, `rat` или `bat`. 19 | * `\` - экрнирующийсимвол. `Law\*` будет соответвовать только `Law*` 20 | 21 | 22 | ```smarty 23 | {if $color|match:"*gr[ae]y"} 24 | какой-то оттенок серого 25 | {/if} 26 | ``` 27 | 28 | **Замечание:** 29 | максимальная длинна проверяемой строки не должна превышать 4096 символов. 30 | -------------------------------------------------------------------------------- /docs/ru/mods/ereplace.md: -------------------------------------------------------------------------------- 1 | Модификатор ereplace 2 | ================= 3 | 4 | Выполняет поиск и замену по регулярному выражению. 5 | [Подробнее](http://www.php.net/manual/ru/reference.pcre.pattern.syntax.php) о регулярных выражениях. 6 | 7 | ``` 8 | {$string|ereplace:$pattern:$replacement} 9 | ``` 10 | 11 | Выполняет поиск совпадений в строке `$subject` с шаблоном pattern и заменяет их на replacement. 12 | 13 | `$replacement` может содержать ссылки вида `\n`, `$n` или `${n}`, причем последний вариант предпочтительней. 14 | Каждая такая ссылка будет заменена на подстроку, соответствующую n-ой подмаске. n может принимать значения от 0 до 99, 15 | причем ссылка `\0` (либо $0) соответствует вхождению всего шаблона. 16 | Подмаски нумеруются слева направо, начиная с единицы. Для использования обратного слэша, его необходимо продублировать. 17 | 18 | 19 | ```smarty 20 | {var $string = 'April 15, 2014'} 21 | 22 | {$string|ereplace:'/(\w+) (\d+), (\d+)/i':'${1}1, $3'} {* April1, 2014 *} 23 | ``` 24 | 25 | **Замечание:** воизбежание скрытых ошибок при выполнении сущностей регулярные выражения стоит помещать в одинарные кавычки. 26 | -------------------------------------------------------------------------------- /tests/resources/actions.php: -------------------------------------------------------------------------------- 1 | tpl->parseParams($tokenizer); 21 | return 'echo "PHP_VERSION: ".PHP_VERSION." (for ".' . $p["name"] . '.")";'; 22 | } 23 | 24 | function myBlockCompilerOpen(Fenom\Tokenizer $tokenizer, Fenom\Tag $scope) 25 | { 26 | $p = $scope->tpl->parseParams($tokenizer); 27 | return 'echo "PHP_VERSION: ".PHP_VERSION." (for ".' . $p["name"] . '.")";'; 28 | } 29 | 30 | function myBlockCompilerClose(Fenom\Tokenizer $tokenizer, Fenom\Tag $scope) 31 | { 32 | return 'echo "End of compiler";'; 33 | } 34 | 35 | function myBlockCompilerTag(Fenom\Tokenizer $tokenizer, Fenom\Tag $scope) 36 | { 37 | $p = $scope->tpl->parseParams($tokenizer); 38 | return 'echo "Tag ".' . $p["name"] . '." of compiler";'; 39 | } -------------------------------------------------------------------------------- /docs/en/tags/switch.md: -------------------------------------------------------------------------------- 1 | Tag {switch} 2 | ============ 3 | 4 | The `{switch}` tag is similar to a series of `{if}` statements on the same expression. 5 | In many occasions, you may want to compare the same variable (or expression) with many different values, 6 | and execute a different piece of code depending on which value it equals to. This is exactly what the `{switch}` tag is for. 7 | 8 | Tag `{switch}` accepts any expression. But `{case}` accepts only static scalar values or constants. 9 | 10 | ```smarty 11 | {switch } 12 | {case } 13 | ... 14 | {case , , ...} 15 | ... 16 | {case } 17 | ... 18 | {case default, } 19 | ... 20 | {/switch} 21 | ``` 22 | 23 | For example, 24 | 25 | ```smarty 26 | {switch $type} 27 | {case 'new'} 28 | It is new item 29 | {case 'current', 'new'} 30 | It is new or current item 31 | {case 'current'} 32 | It is current item 33 | {case 'new', 'newer'} 34 | It is new item, again 35 | {case default} 36 | I don't know the type {$type} 37 | {/switch} 38 | ``` 39 | 40 | if `$type = 'new'` then template output 41 | 42 | ``` 43 | It is new item 44 | It is new or current item 45 | It is new item, again 46 | ``` -------------------------------------------------------------------------------- /docs/en/tags/macro.md: -------------------------------------------------------------------------------- 1 | Tag {macro} 2 | =========== 3 | 4 | Macros are comparable with functions in regular programming languages. 5 | They are useful to put often used HTML idioms into reusable elements to not repeat yourself. 6 | 7 | ### {macro} 8 | 9 | Macros can be defined in any template using tag `{macro}`. 10 | 11 | ```smarty 12 | {macro plus($x, $y, $z=0)} 13 | x + y + z = {$x + $y + $z} 14 | {/macro} 15 | ``` 16 | 17 | ```smarty 18 | {macro.plus x=$num y=100} 19 | ``` 20 | 21 | ```smarty 22 | {macro plus($x, $y, $z=0)} 23 | ... 24 | {macro.plus x=2 y=$y} 25 | ... 26 | {/macro} 27 | ``` 28 | 29 | ### {import} 30 | 31 | Macros can be defined in any template, and need to be "imported" before being used. 32 | The above import call imports the "math.tpl" file (which can contain only macros, or a template and some macros), 33 | and import the functions as items of the `macro` namespace. 34 | 35 | ```smarty 36 | {import 'math.tpl'} 37 | 38 | {macro.plus x=1 y=3} 39 | ``` 40 | 41 | Use another namespace instead of `macro` 42 | 43 | ```smarty 44 | {import 'math.tpl' as math} 45 | ... 46 | {math.plus x=5 y=100} 47 | ``` 48 | 49 | ```smarty 50 | {import [plus, minus, exp] from 'math.tpl' as math} 51 | ``` -------------------------------------------------------------------------------- /sandbox/templates/bug249/bread.tpl: -------------------------------------------------------------------------------- 1 | {*
*} 2 | {**} 14 | {*
*} 15 | 16 | === {(1..3)|length} === 17 | 18 | {foreach 1..3 as $c index=$i first=$first last=$last} 19 | {$i}: {$last} 20 | {/foreach} 21 | 22 |
23 |
    24 |
  • Главная
  • 25 | {foreach $arr as $item first=$first last=$last} 26 | {if !$first} 27 |
  • /
  • 28 |
  • {$last} 29 | {if $last} 30 | {$item.name} 31 | {else} 32 | {$item.name} 33 | {/if} 34 |
  • 35 | {/if} 36 | {/foreach} 37 |
38 |
-------------------------------------------------------------------------------- /docs/ru/tags/if.md: -------------------------------------------------------------------------------- 1 | Тег {if} 2 | ======== 3 | 4 | Реализация оператора [if](http://docs.php.net/if) из PHP 5 | 6 | ```smarty 7 | {if } 8 | {* ...code... *} 9 | {elseif } 10 | {* ...code... *} 11 | {else} 12 | {* ...code... *} 13 | {/if} 14 | ``` 15 | 16 | ### {if} 17 | 18 | ```smarty 19 | {if } 20 | {*...some code...*} 21 | {/if} 22 | ``` 23 | 24 | Код, расположенный в теге `{if}` будет выполнен/выведен если выражение ** возвращает значение приводимое к **TRUE** 25 | 26 | ### {elseif} 27 | 28 | ```smarty 29 | {if } 30 | {*...some code...*} 31 | {elseif } 32 | {*...some code...*} 33 | {/if} 34 | ``` 35 | 36 | Код, расположенный после тега `{elseif}` будет выполнен/выведен, если выражение вернуло значение приводимое к **FALSE**, а - приводимое к **TRUE** 37 | 38 | ### {else} 39 | 40 | ```smarty 41 | {if } 42 | {*...some code...*} 43 | {else} 44 | {*...some code...*} 45 | {/if} 46 | ``` 47 | 48 | Код, расположенный после тега `{else}` будет выполнен/выведен, если выражение вернуло значение приводимое к **FALSE** 49 | В тестируемых выражениях могут быть использованы логические операторы, что позволяет обрабатывать сочетания нескольких условий. -------------------------------------------------------------------------------- /docs/en/ext/inheritance.md: -------------------------------------------------------------------------------- 1 | Inheritance algorithm 2 | ===================== 3 | 4 | Variant #1. Sunny. 5 | 6 | | level.2.tpl | b1 | add new block | `$tpl->block['b1'] = $content;` 7 | | level.2.tpl | b1 | rewrite block | `$tpl->block['b1'] = $content;` 8 | | level.1.tpl | b1 | skip because block exists | `if(!isset($tpl->block['b1'])) $tpl->block['b1'] = $content;` 9 | | use.tpl | b1 | skip because block exists | `if(!isset($tpl->block['b1'])) $tpl->block['b1'] = $content;` 10 | | use.tpl | b2 | add new block | `$tpl->block['b2'] = $content;` 11 | | level.1.tpl | b2 | rewrite block | `$tpl->block['b2'] = $content;` 12 | | parent.tpl | b1 | get block from stack 13 | | parent.tpl | b2 | get block from stack 14 | | parent.tpl | b3 | get own block 15 | ------Result-------- 16 | | level.2.tpl | b1 | 17 | | level.1.tpl | b2 | 18 | 19 | Variant #2. Сloudy. 20 | 21 | | level.2.tpl | b1 | add new block 22 | | level.1.tpl | b1 | skip because block exists 23 | | use.tpl | b1 | skip because block exists 24 | | use.tpl | b2 | add new block 25 | | level.1.tpl | b2 | rewrite block 26 | | $parent | b1 | dynamic extend 27 | ------Result-------- 28 | | level.2.tpl | b1 | 29 | | level.1.tpl | b2 | 30 | 31 | Variant #3. Rain. 32 | 33 | Variant #4. Tornado. 34 | -------------------------------------------------------------------------------- /docs/ru/mods/truncate.md: -------------------------------------------------------------------------------- 1 | Модификатор truncate 2 | ================= 3 | 4 | Обрезает переменную до определенной длинны, по умолчанию - 80 символов. 5 | В качестве необязательного второго параметра, вы можете передать строку текста, которая будет отображатся в конце обрезанной переменной. Символы этой строки не включаются в общую длинну обрезаемой строки. По умолчанию, truncate попытается обрезать строку в промежутке между словами. Если вы хотите обрезать строку строго на указаной длинне, передайте в третий необязательный параметр значение true. 6 | 7 | ```smarty 8 | {$long_string|truncate:$length:$etc:$by_words:$middle} 9 | ``` 10 | 11 | * `$length`, обязателен. Определяет максимальную длинну обрезаемой строки. 12 | * `$etc`, по умолчанию `...`. Текстовая строка, которая заменяет обрезанный текст. Её длинна НЕ включена в максимальную длинну обрезаемой строки. 13 | * `$by_word`, по умолчанию **FALSE**. Определяет, обрезать ли строку в промежутке между словами (true) или строго на указаной длинне (false). 14 | * `$middle`, по умолчанию **FALSE**. Определяет, нужно ли обрезать строку в конце (false) или в середине строки (true). 15 | 16 | ```smarty 17 | {var $str = "very very long string"} 18 | 19 | {$str|truncate:10:" read more..."} output: very very read more... 20 | {$str|truncate:5:" ... ":true:true} output: very ... string 21 | ``` 22 | 23 | Модификатор работает с unicode без дополнительных расширений. -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Issue tracker 5 | ------------- 6 | 7 | The Issue tracker serves mainly as a place to report bugs and request new features. 8 | Please do not abuse it as a general questions or troubleshooting location. 9 | 10 | For these questions you can always use the 11 | [fenom tag](https://stackoverflow.com/questions/tagged/fenom) at [Stack Overflow](https://stackoverflow.com/). 12 | 13 | * Please provide a small example in php/html that reproduces your situation 14 | * Please report one feature or one bug per issue 15 | * Failing to provide necessary information or not using the issue template may cause the issue to be closed without consideration. 16 | 17 | Pull requests 18 | ------------- 19 | 20 | Pull requests should be always based on the default [development](https://github.com/fenom-template/fenom/tree/master) 21 | branch except for backports to older versions. 22 | 23 | Some guidelines: 24 | 25 | * Use an aptly named feature branch for the Pull request. 26 | 27 | * Only files and lines affecting the scope of the Pull request must be affected. 28 | 29 | * Make small, *atomic* commits that keep the smallest possible related code changes together. 30 | 31 | * Code should be accompanied by a unit test testing expected behaviour. 32 | 33 | When updating a PR, do not create a new one, just `git push --force` to your former feature branch, the PR will 34 | update itself. 35 | -------------------------------------------------------------------------------- /docs/ru/tags/extends.md: -------------------------------------------------------------------------------- 1 | Тег `{extends}` 2 | ============= 3 | 4 | Тег `{extends}` реализует [наследование](../inheritance.md) шаблонов, иерархия, обратная {include}. То есть шаблон сам выбирает своего родителя. 5 | 6 | ### `{extends}` 7 | 8 | Родительский шаблон можно задать единожды и до объявления какого-либо блока. 9 | 10 | ```smarty 11 | {extends 'parent.tpl'} 12 | ``` 13 | 14 | **Замечание:** 15 | Имя родительского шаблона может быть задан динамически, но в этом случае производительность шаблона значительно снижается. 16 | ```smarty 17 | {extends $parent_tpl} 18 | ``` 19 | 20 | ### `{block}` 21 | 22 | Блок указывает фрагмент шаблона, который будет передан родителю. Имя блока должно быть задано явно: 23 | 24 | ```smarty 25 | {block 'bk1'}content 1{/block} 26 | ... 27 | {block 'bk2'}content 2{/block} 28 | ``` 29 | 30 | 31 | ### `{use}` 32 | 33 | Что бы импортировать блоки из другого шаблона используйте тег {use}: 34 | 35 | ```smarty 36 | {use 'blocks.tpl'} 37 | ``` 38 | 39 | ### `{parent}` 40 | 41 | ```smarty 42 | {block 'block1'} 43 | content ... 44 | {parent} 45 | content ... 46 | {/block} 47 | ``` 48 | 49 | ### `{paste}` 50 | 51 | Вставка кода блока в любое место через тег `{paste}` 52 | 53 | ```smarty 54 | {block 'b1'} 55 | ... 56 | {/block} 57 | 58 | {paste 'b1'} 59 | ``` 60 | 61 | ### `{$.block}` 62 | 63 | Проверка наличия блока через глобальную переменную `$.block` 64 | 65 | ```smarty 66 | {if $.block.header} 67 | ... 68 | {/if} 69 | ``` 70 | -------------------------------------------------------------------------------- /src/Fenom/ProviderInterface.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface ProviderInterface 18 | { 19 | /** 20 | * @param string $tpl 21 | * @return bool 22 | */ 23 | public function templateExists(string $tpl): bool; 24 | 25 | /** 26 | * @param string $tpl 27 | * @param float $time seconds with micro 28 | * @return string 29 | */ 30 | public function getSource(string $tpl, float &$time): string; 31 | 32 | /** 33 | * @param string $tpl 34 | * @return float seconds with micro 35 | */ 36 | public function getLastModified(string $tpl): float; 37 | 38 | /** 39 | * Verify templates (check mtime) 40 | * 41 | * @param array $templates [template_name => modified, ...] By conversation, you may trust the template's name 42 | * @return bool if true - all templates are valid else some templates are invalid 43 | */ 44 | public function verify(array $templates): bool; 45 | 46 | /** 47 | * Get all names of template from provider 48 | * @return iterable 49 | */ 50 | public function getList(): iterable; 51 | } 52 | -------------------------------------------------------------------------------- /docs/en/tags/extends.md: -------------------------------------------------------------------------------- 1 | Tag {extends} 2 | ============= 3 | 4 | `{extends}` tags are used in child templates in template inheritance for extending parent templates. 5 | The `{extends}` tag must be on before any block. 6 | 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. 7 | 8 | ### {extends} 9 | 10 | ```smarty 11 | {extends 'parent.tpl'} 12 | ``` 13 | 14 | ### {block} 15 | 16 | ```smarty 17 | {block 'bk2'}content 2{/block} 18 | ``` 19 | 20 | ### {use} 21 | 22 | Import the blocks defined in another file. Specifying blocks in this template will override those from the other file. 23 | 24 | ```smarty 25 | {use 'blocks.tpl'} merge blocks from blocks.tpl template 26 | 27 | {block 'alpha'} rewrite block alpha from blocks.tpl template, if it exists 28 | ... 29 | {/block} 30 | ``` 31 | 32 | ### {parent} 33 | 34 | Uses the code from the block as defined in the parent. 35 | 36 | ```smarty 37 | {extends 'parent.tpl'} 38 | 39 | {block 'header'} 40 | content ... 41 | {parent} pase code from block 'header' from parent.tpl 42 | content ... 43 | {/block} 44 | ``` 45 | 46 | ### {paste} 47 | 48 | Pastes the code of any block 49 | 50 | ```smarty 51 | {block 'b1'} 52 | ... 53 | {/block} 54 | 55 | {block 'b2'} 56 | ... 57 | {paste 'b1'} paste code from b1 58 | {/block} 59 | 60 | ``` 61 | 62 | ### {$.block} 63 | 64 | Checks if clock exists 65 | 66 | ```smarty 67 | {if $.block.header} 68 | block header exists 69 | {/if} 70 | ``` 71 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 by Ivan Shalganov. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /docs/ru/tags/macro.md: -------------------------------------------------------------------------------- 1 | Тег {macro} 2 | =========== 3 | 4 | Макросы - фрагмент шаблона который можно повторить сколь угодно раз и в каком угодно месте. 5 | Макросы не имеют общего пространства имен с шаблоном и могут оперировать только переданными переменными. 6 | 7 | ### {macro} 8 | 9 | Обявление макроса происходит при помощи блочного тега `{macro}` 10 | 11 | ```smarty 12 | {macro plus($x, $y, $z=0)} 13 | x + y + z = {$x + $y + $z} 14 | {/macro} 15 | ``` 16 | 17 | Вызов макроса происходит при помощи строкового тега `{macro}`. Аргументы передаются стандартно, как атрибуты в HTML тегах 18 | 19 | ```smarty 20 | {macro.plus x=$num y=100} 21 | ``` 22 | 23 | Во время рекурсивного вызова используйте суффикс macro что бы обратиться к текущему макросу: 24 | 25 | ```smarty 26 | {macro plus($x, $y, $z=0)} 27 | ... 28 | {macro.plus x=2 y=$y} 29 | ... 30 | {/macro} 31 | ``` 32 | 33 | ### {import} 34 | 35 | Для использования маросов в другом шаблоне необходимо их импортировать при помощи тега `{import}` 36 | 37 | ```smarty 38 | {import 'math.tpl'} 39 | ``` 40 | 41 | При импорте можно указать другое пространство имен что бы можно было использовать одноименные макросы из разных шаблонов 42 | 43 | ```smarty 44 | {import 'math.tpl' as math} 45 | ... 46 | {math.plus x=5 y=100} 47 | ``` 48 | 49 | Пространство имен макросов может совпадать с названием какого-либо тега, в данном случае ничего плохого не произойдет: будет вызван макрос, а тег не исчезнит 50 | При необходимости можно импортировать только необходимые макросы, явно указав в теге `{import}` 51 | 52 | ```smarty 53 | {import [plus, minus, exp] from 'math.tpl' as math} 54 | ``` 55 | -------------------------------------------------------------------------------- /sandbox/templates/greeting.tpl: -------------------------------------------------------------------------------- 1 | {include "header.tpl" title=""} 2 | 3 | {import 'macros.tpl' as menu} 4 | 5 | {menu.pills active='sandbox' items=$items} 6 | 7 |
8 |

Jumbotron heading

9 |

Cras justo odio, dapibus ac facilisis in, egestas eget quam. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

10 |

Sign up today

11 |
12 | 13 |
14 | {set $text} 15 |
16 |

Subheading

17 |

Donec id elit non mi porta gravida at eget metus. Maecenas faucibus mollis interdum.

18 | 19 |

Subheading

20 |

Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum.

21 | 22 |

Subheading

23 |

Maecenas sed diam eget risus varius blandit sit amet non magna.

24 |
25 | {/set} 26 | 27 | {$text} 28 | 29 |
30 |

Subheading

31 |

Donec id elit non mi porta gravida at eget metus. Maecenas faucibus mollis interdum.

32 | 33 |

Subheading

34 |

Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum.

35 | 36 |

Subheading

37 |

Maecenas sed diam eget risus varius blandit sit amet non magna.

38 |
39 |
40 | {include "footer.tpl"} -------------------------------------------------------------------------------- /tests/cases/Fenom/RenderTest.php: -------------------------------------------------------------------------------- 1 | "render.tpl" 21 | )); 22 | } 23 | 24 | public function testCreate() 25 | { 26 | $r = new Render(Fenom::factory("."), function () { 27 | echo "Test render"; 28 | }, array( 29 | "name" => "test.render.tpl" 30 | )); 31 | $this->assertSame("Test render", $r->fetch(array())); 32 | } 33 | 34 | public function testDisplay() 35 | { 36 | ob_start(); 37 | self::$render->display(array("render" => "display")); 38 | $out = ob_get_clean(); 39 | $this->assertSame("It is render's function display", $out); 40 | } 41 | 42 | public function testFetch() 43 | { 44 | $this->assertSame("It is render's function fetch", self::$render->fetch(array("render" => "fetch"))); 45 | } 46 | 47 | public function testFetchException() 48 | { 49 | $this->expectException(Fenom\Error\TemplateException::class); 50 | $render = new Render(Fenom::factory("."), function () { 51 | echo "error"; 52 | throw new \RuntimeException("template error"); 53 | }, array( 54 | "name" => "render.tpl" 55 | )); 56 | $render->fetch(array()); 57 | } 58 | 59 | } 60 | 61 | -------------------------------------------------------------------------------- /docs/en/tags/for.md: -------------------------------------------------------------------------------- 1 | Tag {for} 2 | ========= 3 | 4 | ```smarty 5 | {for $counter= to= [step=] [index=$index] [first=$first] [last=$last]} 6 | {* ...code... *} 7 | {break} 8 | {* ...code... *} 9 | {continue} 10 | {* ...code... *} 11 | {forelse} 12 | {* ...code... *} 13 | {/for} 14 | ``` 15 | 16 | ### {for} 17 | 18 | Переменная `$counter` принимает значение и увеличивает своё значение на на каждой итерации цикла пока не достигнет или не станет больше . 19 | является необязательным аргументом. Если не указан, считается равным единице. 20 | `$index` имеет значение номера текущей итерации. Первая итерация имеет номер 0. 21 | `$first` равно **TRUE**, если итерация первая. 22 | `$last` равно **TRUE**, если итерация последняя. 23 | 24 | Поля ``, ``, `` могут быть числами, или переменными, значение которых приводится к числовому. 25 | Значением параметров _index_, _first_, _last_ может быть только переменная (допускаются вложенности на подобии `$a.b.c`, но массив `$a.b` должен быть объявлен). 26 | 27 | 28 | ### {break} 29 | 30 | Тег `{break}` используется для выхода из цикла до достижения последней итерации. Если в цикле встречается тег {break}, цикл завершает свою работу, и далее выполняется код, следующий сразу за блоком цикла 31 | 32 | 33 | ### {continue} 34 | 35 | Тег `{continue}` используется для прерывания текущей итерации. Если в цикле встречается тег {continue}, часть цикла, следующая после тега, не выполняется, и начинается следующая итерация. Если текущая итерация была последней, цикл завершается. 36 | 37 | 38 | ### {forelse} 39 | 40 | Тег `{forelse}` ограничивает код, который должен быть выполнен, если сочетание полей , и не обеспечивают ни одной итерации. 41 | -------------------------------------------------------------------------------- /docs/ru/tags/for.md: -------------------------------------------------------------------------------- 1 | Тег {for} 2 | ========= 3 | 4 | ```smarty 5 | {for $counter= to= [step=] [index=$index] [first=$first] [last=$last]} 6 | {* ...code... *} 7 | {break} 8 | {* ...code... *} 9 | {continue} 10 | {* ...code... *} 11 | {forelse} 12 | {* ...code... *} 13 | {/for} 14 | ``` 15 | 16 | ### {for} 17 | 18 | Переменная `$counter` принимает значение и увеличивает своё значение на на каждой итерации цикла пока не достигнет или не станет больше . 19 | является необязательным аргументом. Если не указан, считается равным единице. 20 | `$index` имеет значение номера текущей итерации. Первая итерация имеет номер 0. 21 | `$first` равно **TRUE**, если итерация первая. 22 | `$last` равно **TRUE**, если итерация последняя. 23 | 24 | Поля ``, ``, `` могут быть числами, или переменными, значение которых приводится к числовому. 25 | Значением параметров _index_, _first_, _last_ может быть только переменная (допускаются вложенности на подобии `$a.b.c`, но массив `$a.b` должен быть объявлен). 26 | 27 | 28 | ### {break} 29 | 30 | Тег `{break}` используется для выхода из цикла до достижения последней итерации. Если в цикле встречается тег {break}, цикл завершает свою работу, и далее выполняется код, следующий сразу за блоком цикла 31 | 32 | 33 | ### {continue} 34 | 35 | Тег `{continue}` используется для прерывания текущей итерации. Если в цикле встречается тег {continue}, часть цикла, следующая после тега, не выполняется, и начинается следующая итерация. Если текущая итерация была последней, цикл завершается. 36 | 37 | 38 | ### {forelse} 39 | 40 | Тег `{forelse}` ограничивает код, который должен быть выполнен, если сочетание полей , и не обеспечивают ни одной итерации. 41 | -------------------------------------------------------------------------------- /docs/en/tags/set.md: -------------------------------------------------------------------------------- 1 | Tag {set} 2 | ========= 3 | 4 | The tag {set} is used for assigning template variables during the execution of a template. 5 | 6 | ```smarty 7 | {set $var=EXPR} 8 | ``` 9 | 10 | ```smarty 11 | {set $var} 12 | ... any content ... 13 | {/set} 14 | ``` 15 | 16 | ```smarty 17 | {set $var|modifiers} 18 | ... any content ... 19 | {/set} 20 | ``` 21 | 22 | Variable names follow the same rules as other labels in PHP. 23 | A valid variable name starts with a letter or underscore, followed by any number of letters, numbers, or underscores. 24 | 25 | ```smarty 26 | {set $v = 5} 27 | {set $v = "value"} 28 | 29 | {set $v = $x+$y} 30 | {set $v = 4} 31 | {set $v = $z++ + 1} 32 | {set $v = --$z} 33 | {set $v = $y/$x} 34 | {set $v = $y-$x} 35 | {set $v = $y*$x-2} 36 | {set $v = ($y^$x)+7} 37 | ``` 38 | 39 | Works this array too 40 | 41 | ```smarty 42 | {set $v = [1,2,3]} 43 | {set $v = []} 44 | {set $v = ["one"|upper => 1, 4 => $x, "three" => 3]} 45 | {set $v = ["key1" => $y*$x-2, "key2" => ["z" => $z]]} 46 | ``` 47 | 48 | Getting function result into variable 49 | 50 | ```smarty 51 | {set $v = count([1,2,3])+7} 52 | ``` 53 | 54 | Fetch the output of the template into variable 55 | 56 | ```smarty 57 | {set $v} 58 | Some long {$text|trim} 59 | {/set} 60 | 61 | {set $v|escape} {* apply modifier to variable*} 62 | Some long {$text|trim} 63 | {/set} 64 | ``` 65 | 66 | ### {add} 67 | 68 | The tag {add} the same tag as {set} except that sets the value of the variable if it does not exist. 69 | 70 | ```smarty 71 | {add $var = 'value'} 72 | ``` 73 | 74 | instead of 75 | 76 | ```smarty 77 | {if $var is not set} 78 | {set $var = 'value'} 79 | {/if} 80 | ``` 81 | 82 | ### {var} 83 | 84 | Old name of tag {set}. Currently tag {var} the same tag as {set}. -------------------------------------------------------------------------------- /docs/ru/tags/switch.md: -------------------------------------------------------------------------------- 1 | Тег {switch} 2 | ============ 3 | 4 | Тег `{switch}` подобен серии операторов `{if}` с одинаковым условием. 5 | Во многих случаях вам может понадобиться сравнивать одну и ту же переменную (или выражение) с множеством различных значений, 6 | и выполнять различные участки кода в зависимости от того, какое значение принимает эта переменная (или выражение). 7 | Это именно тот случай, для которого удобен тег `{switch}`. 8 | 9 | Тег `{switch}` в качестве аргумента принимает любое выражение. 10 | Каждый возможный случай описывается тегом `{case value}` значения `value` которых могут быть только явно заданные скалярные значения. 11 | Случаи могут повторятся, в этом случае блоки для которых повторялся случай будут последовательно выполнены по направлению сверху вниз. 12 | Случай `default` подразумевает обработку если ни один случай не произошел. 13 | 14 | ```smarty 15 | {switch } 16 | {case value1} 17 | ... 18 | {case value2, value3, ...} 19 | ... 20 | {case value3} 21 | ... 22 | {case default, } 23 | ... 24 | {/switch} 25 | ``` 26 | 27 | Рассмотрим пример: 28 | 29 | ```smarty 30 | {switch $color} 31 | {case 'red', 'scarlet'} 32 | Оттенок красного цвета 33 | {case 'green', 'harlequin'} 34 | Оттенок зеленого цвета 35 | {case 'black', 'grey', 'gray'} 36 | Черный цвет 37 | {case 'white', 'grey', 'gray'} 38 | Белый цвет 39 | {case default, 'unknown'} 40 | Неизвестный цвет 41 | {/switch} 42 | ``` 43 | 44 | если задать `$color = 'red'` результатом будет: 45 | 46 | ``` 47 | Оттенок красного цвета 48 | ``` 49 | 50 | для случая `$color = 'grey'` будут вызваны два бока: 51 | 52 | ``` 53 | Черный цвет 54 | Белый цвет 55 | ``` 56 | 57 | случаи `$color = 'yellow'` и `$color = 'unknown'` будут обработаны последним блоком: 58 | 59 | ``` 60 | Неизвестный цвет 61 | ``` -------------------------------------------------------------------------------- /tests/cases/Fenom/TagsTest.php: -------------------------------------------------------------------------------- 1 | assertRender('{for $i=0 to=3}{$i},{/for}', "0,1,2,3,"); 14 | // } 15 | 16 | /** 17 | * @dataProvider providerScalars 18 | */ 19 | public function testVar($tpl_val, $val) 20 | { 21 | $this->assertRender("{set \$a=$tpl_val}\nVar: {\$a}", "Var: " . $val); 22 | } 23 | 24 | /** 25 | * @dataProvider providerScalars 26 | */ 27 | public function testVarBlock($tpl_val, $val) 28 | { 29 | $this->assertRender("{set \$a}before {{$tpl_val}} after{/set}\nVar: {\$a}", "Var: before " . $val . " after"); 30 | } 31 | 32 | /** 33 | * @dataProvider providerScalars 34 | */ 35 | public function testVarBlockModified($tpl_val, $val) 36 | { 37 | $this->assertRender( 38 | "{set \$a|low|dots}before {{$tpl_val}} after{/set}\nVar: {\$a}", 39 | "Var: " . strtolower("before " . $val . " after") . "..." 40 | ); 41 | } 42 | 43 | public function testCycle() 44 | { 45 | $this->assertRender('{foreach 0..4 as $i}{cycle ["one", "two"]}, {/foreach}', "one, two, one, two, one, "); 46 | } 47 | 48 | /** 49 | * 50 | */ 51 | public function testCycleIndex() 52 | { 53 | $this->assertRender( 54 | '{set $a=["one", "two"]}{foreach 1..5 as $i}{cycle $a index=$i}, {/foreach}', 55 | "two, one, two, one, two, " 56 | ); 57 | } 58 | 59 | /** 60 | * @dataProvider providerScalars 61 | */ 62 | public function testFilter($tpl_val, $val) 63 | { 64 | $this->assertRender("{filter|up} before {{$tpl_val}} after {/filter}", strtoupper(" before {$val} after ")); 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /docs/ru/tags/set.md: -------------------------------------------------------------------------------- 1 | Тег {set} 2 | ========= 3 | 4 | ### {set} 5 | 6 | Тег {set} используется для установки значения переменной в процессе выполнения шаблона. 7 | 8 | ```smarty 9 | {set $var=(expr)} 10 | ``` 11 | 12 | ```smarty 13 | {set $var} 14 | ... any content ... 15 | {/set} 16 | ``` 17 | 18 | ```smarty 19 | {set $var|modifiers} 20 | ... any content ... 21 | {/set} 22 | ``` 23 | 24 | Имя переменной `$var` должно соответствовать [принятому правилу именования переменных](../syntax.md#Переменные). 25 | Выражение `expr` может быть любое сочетание [переменных](../syntax.md#Переменные), [скалярных значений](../syntax.md#Скалярные+значения) и массивов. 26 | 27 | ```smarty 28 | {set $v = 5} 29 | {set $v = "value"} 30 | {set $v = $x+$y} 31 | {set $v = 4} 32 | {set $v = $z++ + 1} 33 | {set $v = --$z} 34 | {set $v = $y/$x} 35 | {set $v = $y-$x} 36 | {set $v = $y*$x-2} 37 | {set $v = ($y^$x)+7} 38 | 39 | {set $v = [1,2,3]} 40 | {set $v = []} 41 | {set $v = ["one"|upper => 1, 4 => $x, "three" => 3]} 42 | {set $v = ["key1" => $y*$x-2, "key2" => ["z" => $z]]} 43 | 44 | {set $v = count([1,2,3])+7} 45 | ``` 46 | 47 | В качестве значения переменной можно задать результат отрисовки фрагмента шаблона: 48 | 49 | ```smarty 50 | {set $v} 51 | Some long {$text|trim} 52 | {/set} 53 | ``` 54 | 55 | Такой вариант создания позволяет применить модификаторы к данным перед тем как они будут сохранены в переменную: 56 | 57 | ```smarty 58 | {set $v|escape} {* применение модификатора к значению *} 59 | Some long {$text|trim} 60 | {/set} 61 | ``` 62 | 63 | ### {add} 64 | 65 | Тег {add} делает тоже самое что и тег {set} за исключением того что сначала проверяет наличие переменной и если переменной нет — задет новое значение. 66 | 67 | ```smarty 68 | {add $var = 'value'} 69 | ``` 70 | Работу тега можно описать следующим образом: 71 | ```smarty 72 | {if $var is not set} 73 | {set $var = 'value'} 74 | {/if} 75 | ``` 76 | 77 | ### {var} 78 | 79 | Тег {var} старое название тега {set}, сейчас это одно и тоже. 80 | 81 | -------------------------------------------------------------------------------- /docs/ru/tags/include.md: -------------------------------------------------------------------------------- 1 | Тег {include} 2 | ============= 3 | 4 | Тэги `{include}` используются для включения других шаблонов в текущий. Любые переменные, доступные в текущем шаблоне, доступны и во включаемом. 5 | 6 | ```smarty 7 | {include "about.tpl"} 8 | ``` 9 | 10 | Вы также можете передать переменные в подключаемый шаблон в виде атрибутов. 11 | Любая переменная, переданная в подключаемый шаблон, доступны только в области видимости подключаемого файла. 12 | Переданные переменные имеют преимущество перед существующими переменными с аналогичными именами. 13 | 14 | ```smarty 15 | {include "pagination.tpl" count=$total_pages current=$.get.page} 16 | ``` 17 | 18 | Все значения присвоенных переменных восстанавливаются после того, как подключаемый шаблон отработал. 19 | Это значит, что вы можете использовать все переменные из подключающего шаблона в подключаемом, но изменения переменных внутри подключаемого шаблона не будут видны внутри подключающего шаблона после команды {include}. 20 | 21 | Если требуется сохранить результат отрисовки шаблона в переменную то используйте `$.fetch($templates, $values)`: 22 | 23 | ```smarty 24 | {set $data = $.fetch('user.tpl', ["name" => $data.name, "email" => $data.email])} 25 | ``` 26 | 27 | ### {insert} 28 | 29 | В отличии от `{include}` тег `{insert}` не вызывает дочерний шаблон во время отрисовки, а вставляет код дочернего шаблона в родительский на момент компиляции. 30 | Это позволяет сэкономить ресурсы на проверке и чтении шаблона. Однако такой формат подключения шаблона имеет ограничения. 31 | Имя шаблона должно быть задано явно, без использования переменных и выражений: 32 | 33 | ```smarty 34 | {insert 'pagination.tpl'} {* отработает *} 35 | {insert $pagination} {* вызовет ошибку *} 36 | ``` 37 | 38 | Рассмотрим тега `{insert}` на примере. Допустим шаблон `main.tpl` имеет следующий код: 39 | 40 | ```smarty 41 | a: {$a} 42 | {insert 'b.tpl'} 43 | c: {$c} 44 | ``` 45 | 46 | `b.tpl`: 47 | 48 | ``` 49 | b: {$b} 50 | ``` 51 | 52 | Компилятор увидит шаблон `main.tpl` таким: 53 | 54 | ```smarty 55 | a: {$a} 56 | b: {$b} 57 | c: {$c} 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/en/start.md: -------------------------------------------------------------------------------- 1 | Basic usage 2 | =========== 3 | 4 | ## Install Fenom 5 | 6 | ### Composer 7 | 8 | Add package Fenom in your require-list in `composer.json`: 9 | ```json 10 | { 11 | "require": { 12 | "fenom/fenom": "2.*" 13 | } 14 | } 15 | ``` 16 | and update project's dependencies: `composer update`. 17 | 18 | ### Custom loader 19 | 20 | Clone Fenom to any directory: `git clone https://github.com/bzick/fenom.git`. Recommended use latest tag. 21 | Fenom use [psr-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md#autoloading-standard) autoloading standard. Therefore you can 22 | * use `psr-0` format in your project loader for loading Fenom's classes 23 | * or register Fenom's autoloader: `Fenom::registerAutoload();` for loading itself. 24 | 25 | Also you can use this autoloader for loading any library with `psr-0` file naming: 26 | ```php 27 | Fenom::registerAutoload(PROJECT_DIR."/src"); 28 | ``` 29 | 30 | ## Setup Fenom 31 | 32 | Create an object via factory method 33 | ```php 34 | $fenom = Fenom::factory('/path/to/templates', '/path/to/compiled/template', $options); 35 | ``` 36 | 37 | Create an object via `new` operator 38 | ```php 39 | $fenom = new Fenom(new Provider('/path/to/templates')); 40 | $fenom->setCompileDir('/path/to/template/cache'); 41 | $fenom->setOptions($options); 42 | ``` 43 | 44 | * `/path/to/templates` — directory, where stores your templates. 45 | * `/path/to/template/cache` — directory, where stores compiled templates in PHP files. 46 | * `$options` - bit-mask or array of [Fenom settings](./configuration.md#template-settings). 47 | 48 | ### Use Fenom 49 | 50 | Output template 51 | ```php 52 | $fenom->display("template/name.tpl", $vars); 53 | ``` 54 | 55 | Get the result of rendering the template 56 | ```php 57 | $result = $fenom->fetch("template/name.tpl", $vars); 58 | ``` 59 | 60 | Create the pipeline of rendering into callback 61 | ```php 62 | $fenom->pipe( 63 | "template/sitemap.tpl", 64 | $vars, 65 | $callback = [new SplFileObject("/tmp/sitemap.xml", "w"), "fwrite"], // pipe to file /tmp/sitemap.xml 66 | $chunk_size = 1e6 // chunk size for callback 67 | ); 68 | ``` 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MAINTAINERS WANTED 2 | === 3 | Create issue if you want to be a maintainer of Fenom 4 | 5 | Fenom - Template Engine for PHP 6 | =============================== 7 | 8 | **Fenóm** - lightweight and fast template engine for PHP. 9 | 10 | * **Subject:** Template engine 11 | * **Syntax:** Smarty-like 12 | * **Documentation:** **[English](./docs/en/readme.md)**, **[Russian](./docs/ru/readme.md)** 13 | * **PHP version:** 8.0+ 14 | * **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) 15 | * **Version:** [![Latest Stable Version](https://poser.pugx.org/fenom/fenom/v/stable.png)](https://packagist.org/packages/fenom/fenom) 16 | * **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) 17 | * **Composer:** `composer require fenom/fenom` 18 | * **Discussion:** [Fenom Forum](https://groups.google.com/forum/#!forum/php-ion) 19 | * **Versioning:** [semver2](http://semver.org/) 20 | * **Performance:** see [benchmark](./docs/en/benchmark.md) 21 | 22 | *** 23 | 24 | ## Quick Start 25 | 26 | ### Install 27 | 28 | If you use composer in your project then you can to install Fenom as package. 29 | 30 | ### Setup 31 | 32 | There is two-way to create Fenom instance: 33 | 34 | * Long way: use operator `new` 35 | * Shot way: use static factory-method 36 | 37 | **Long way.** Create you own template provider or default provider `Fenom\Provider` (that is provider read [there](./)). 38 | Using provider instance create Fenom instance: 39 | 40 | ```php 41 | $fenom = new Fenom(new Fenom\Provider($template_dir)); 42 | ``` 43 | 44 | After that, set compile directory: 45 | 46 | ```php 47 | $fenom->setCompileDir($template_cache_dir); 48 | ``` 49 | 50 | This directory will be used for storing compiled templates, therefore it should be writable for Fenom. 51 | Now Fenom is ready to work and now you can to configure it: 52 | 53 | ```php 54 | $fenom->setOptions($options); 55 | ``` 56 | 57 | **Short way.** Creating an object via factory method with arguments from long way. 58 | 59 | ```php 60 | $fenom = Fenom::factory($template_dir, $template_cache_dir, $options); 61 | ``` 62 | 63 | Now Fenom is ready to work. 64 | 65 | ### Usage 66 | 67 | ### Example 68 | -------------------------------------------------------------------------------- /docs/en/tags/foreach.md: -------------------------------------------------------------------------------- 1 | Tag {foreach} 2 | ============= 3 | 4 | The tag `{foreach}` construct provides an easy way to iterate over arrays and ranges. 5 | 6 | ```smarty 7 | {foreach $list as [$key =>] $value [index=$index] [first=$first] [last=$last]} 8 | {* ...code... *} 9 | {break} 10 | {* ...code... *} 11 | {continue} 12 | {* ...code... *} 13 | {foreachelse} 14 | {* ...code... *} 15 | {/foreach} 16 | ``` 17 | 18 | ### {foreach} 19 | 20 | On each iteration, the value of the current element is assigned to `$value` and the internal array pointer is 21 | advanced by one (so on the next iteration, you'll be looking at the next element). 22 | 23 | ```smarty 24 | {foreach $list as $value} 25 |
{$value}
26 | {/foreach} 27 | ``` 28 | 29 | The next form will additionally assign the current element's key to the `$key` variable on each iteration. 30 | 31 | ```smarty 32 | {foreach $list as $key => $value} 33 |
{$key}: {$value}
34 | {/foreach} 35 | ``` 36 | 37 | Gets the current array index, starting with zero. 38 | 39 | ```smarty 40 | {foreach $list as $value} 41 |
№{$value@index}: {$value}
42 | {/foreach} 43 | 44 | or 45 | 46 | {foreach $list as $value index=$index} 47 |
№{$index}: {$value}
48 | {/foreach} 49 | ``` 50 | 51 | Detect first iteration: 52 | 53 | ```smarty 54 | {foreach $list as $value} 55 |
{if $value@first} first item {/if} {$value}
56 | {/foreach} 57 | 58 | or 59 | 60 | {foreach $list as $value first=$first} 61 |
{if $first} first item {/if} {$value}
62 | {/foreach} 63 | ``` 64 | 65 | `$first` is `TRUE` if the current `{foreach}` iteration is first iteration. 66 | 67 | Detect last iteration: 68 | 69 | ```smarty 70 | {foreach $list as $value} 71 |
{if $value@last} last item {/if} {$value}
72 | {/foreach} 73 | 74 | or 75 | 76 | {foreach $list as $value last=$last} 77 |
{if $last} last item {/if} {$value}
78 | {/foreach} 79 | ``` 80 | 81 | `$last` is set to `TRUE` if the current `{foreach}` iteration is last iteration. 82 | 83 | ### {break} 84 | 85 | Tag `{break}` aborts the iteration. 86 | 87 | ### {continue} 88 | 89 | Tag `{continue}` leaves the current iteration and begins with the next iteration. 90 | 91 | ### {foreachelse} 92 | 93 | `{foreachelse}` is executed when there are no values in the array variable. 94 | 95 | ```smarty 96 | {set $list = []} 97 | {foreach $list as $value} 98 |
{if $last} last item {/if} {$value}
99 | {foreachelse} 100 |
empty
101 | {/foreach} 102 | ``` 103 | 104 | `{foreachelse}` does not support tags `{break}` and `{continue}`. -------------------------------------------------------------------------------- /src/Fenom/RangeIterator.php: -------------------------------------------------------------------------------- 1 | min = $min; 20 | $this->max = $max; 21 | $this->setStep($step); 22 | } 23 | 24 | /** 25 | * @param int $step 26 | * @return $this 27 | */ 28 | public function setStep(int $step): static 29 | { 30 | if($step > 0) { 31 | $this->current = min($this->min, $this->max); 32 | } elseif($step < 0) { 33 | $this->current = max($this->min, $this->max); 34 | } else { 35 | $step = $this->max - $this->min; 36 | $this->current = $this->min; 37 | } 38 | $this->step = $step; 39 | return $this; 40 | } 41 | 42 | /** 43 | * Return the current element 44 | */ 45 | public function current(): mixed 46 | { 47 | return $this->current; 48 | } 49 | 50 | /** 51 | * Move forward to next element 52 | */ 53 | public function next(): void 54 | { 55 | $this->current += $this->step; 56 | $this->index++; 57 | } 58 | 59 | /** 60 | * Return the key of the current element 61 | * @return mixed 62 | */ 63 | public function key(): mixed 64 | { 65 | return $this->index; 66 | } 67 | 68 | /** 69 | * Checks if current position is valid 70 | * @return bool 71 | */ 72 | public function valid(): bool 73 | { 74 | return $this->current >= $this->min && $this->current <= $this->max; 75 | } 76 | 77 | /** 78 | * Rewind the Iterator to the first element 79 | */ 80 | public function rewind(): void 81 | { 82 | if($this->step > 0) { 83 | $this->current = min($this->min, $this->max); 84 | } else { 85 | $this->current = max($this->min, $this->max); 86 | } 87 | $this->index = 0; 88 | } 89 | 90 | /** 91 | * Count elements of an object 92 | */ 93 | public function count(): int 94 | { 95 | return intval(($this->max - $this->min + 1) / $this->step); 96 | } 97 | 98 | /** 99 | * @return string 100 | */ 101 | public function __toString() 102 | { 103 | return "[".implode(", ", range($this->min, $this->max, $this->step))."]"; 104 | } 105 | } -------------------------------------------------------------------------------- /tests/cases/Fenom/FunctionsTest.php: -------------------------------------------------------------------------------- 1 | fenom->addFunctionSmart('sum', __CLASS__ . '::functionSum'); 28 | $this->fenom->addFunctionSmart('pow', __CLASS__ . '::functionPow'); 29 | $this->fenom->addFunctionSmart('inc', __CLASS__ . '::functionInc'); 30 | 31 | $this->tpl('function_params_scalar.tpl', '{pow a=2 n=3}'); 32 | $this->tpl('function_params_dynamic.tpl', '{pow a=$a n=$n}'); 33 | $this->tpl('function_default_param_scalar.tpl', '{pow a=2}'); 34 | $this->tpl('function_default_param_empty_array.tpl', '{sum}'); 35 | $this->tpl('function_default_param_const.tpl', '{inc a=1}'); 36 | $this->tpl('function_array_param.tpl', '{sum of=[1, 2, 3, 4, 5]}'); 37 | $this->tpl('function_array_param_pos.tpl', '{sum [1, 2, 3, 4, 5]}'); 38 | } 39 | 40 | /** 41 | * @group sb 42 | */ 43 | public function testFunctionWithParams() 44 | { 45 | $output = $this->fenom->fetch('function_params_scalar.tpl'); 46 | $this->assertEquals('8', $output); 47 | } 48 | 49 | public function testFunctionWithDynamicParams() 50 | { 51 | $output = $this->fenom->fetch('function_params_dynamic.tpl', array('a' => 3, 'n' => 4)); 52 | $this->assertEquals('81', $output); 53 | } 54 | 55 | public function testFunctionWithDefaultParamScalar() 56 | { 57 | $output = $this->fenom->fetch('function_default_param_scalar.tpl'); 58 | $this->assertEquals('4', $output); 59 | } 60 | 61 | public function testFunctionWithDefaultParamArray() 62 | { 63 | $output = $this->fenom->fetch('function_default_param_empty_array.tpl'); 64 | $this->assertEquals('0', $output); 65 | } 66 | 67 | public function testFunctionWithDefaultParamConst() 68 | { 69 | $output = $this->fenom->fetch('function_default_param_const.tpl'); 70 | $this->assertEquals('2', $output); 71 | } 72 | 73 | public function testFunctionWithArrayNamedParam() 74 | { 75 | $output = $this->fenom->fetch('function_array_param.tpl'); 76 | $this->assertEquals('15', $output); 77 | } 78 | 79 | public function testFunctionWithArrayPositionalParam() 80 | { 81 | $output = $this->fenom->fetch('function_array_param_pos.tpl'); 82 | $this->assertEquals('15', $output); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /docs/en/benchmark.md: -------------------------------------------------------------------------------- 1 | Benchmark 2 | ========= 3 | 4 | To start benchmark use script `benchmark/run.php -h`. 5 | 6 | ### Smarty3 vs Twig vs Fenom 7 | 8 | Smarty3 vs Twig vs Fenom 9 | 10 | Generate templates... Done 11 | 12 | Testing a lot output... 13 | smarty3: !compiled and !loaded 3.9101 sec, 15.1 MiB 14 | smarty3: compiled and !loaded 0.0235 sec, 9.3 MiB 15 | smarty3: compiled and loaded 0.0015 sec, 9.3 MiB 16 | 17 | twig: !compiled and !loaded 1.8725 sec, 68.9 MiB 18 | twig: compiled and !loaded 0.0337 sec, 17.0 MiB 19 | twig: compiled and loaded 0.0013 sec, 17.0 MiB 20 | 21 | fenom: !compiled and !loaded 0.3157 sec, 8.9 MiB 22 | fenom: compiled and !loaded 0.0159 sec, 6.6 MiB 23 | fenom: compiled and loaded 0.0012 sec, 6.6 MiB 24 | 25 | 26 | Testing 'foreach' of big array... 27 | smarty3: !compiled and !loaded 0.0355 sec, 5.8 MiB 28 | smarty3: compiled and !loaded 0.0032 sec, 3.1 MiB 29 | smarty3: compiled and loaded 0.0024 sec, 3.1 MiB 30 | 31 | twig: !compiled and !loaded 0.0799 sec, 4.7 MiB 32 | twig: compiled and !loaded 0.0065 sec, 3.2 MiB 33 | twig: compiled and loaded 0.0054 sec, 3.5 MiB 34 | 35 | fenom: !compiled and !loaded 0.0459 sec, 3.1 MiB 36 | fenom: compiled and !loaded 0.0024 sec, 2.5 MiB 37 | fenom: compiled and loaded 0.0017 sec, 2.5 MiB 38 | 39 | 40 | Testing deep 'inheritance'... 41 | smarty3: !compiled and !loaded 0.3984 sec, 10.2 MiB 42 | smarty3: compiled and !loaded 0.0009 sec, 3.1 MiB 43 | smarty3: compiled and loaded 0.0001 sec, 3.1 MiB 44 | 45 | twig: !compiled and !loaded 0.2897 sec, 11.2 MiB 46 | twig: compiled and !loaded 0.0197 sec, 6.5 MiB 47 | twig: compiled and loaded 0.0019 sec, 6.5 MiB 48 | 49 | fenom: !compiled and !loaded 0.0546 sec, 3.2 MiB 50 | fenom: compiled and !loaded 0.0005 sec, 2.5 MiB 51 | fenom: compiled and loaded 0.0000 sec, 2.5 MiB 52 | 53 | * **!compiled and !loaded** - template engine object created but parsers not initialized and templates not compiled 54 | * **compiled and !loaded** - template engine object created, template compiled but not loaded 55 | * **compiled and loaded** - template engine object created, template compiled and loaded 56 | 57 | ### Stats 58 | 59 | | Template Engine | Files | Classes | Lines | 60 | | --------------- | ------:| --------:| ------:| 61 | | Smarty3 (3.1.13)| 320 | 190 | 55095 | 62 | | Twig (1.13.0) | 162 | 131 | 13908 | 63 | | Fenom (1.0.1) | 9 | 16 | 3899 | 64 | -------------------------------------------------------------------------------- /docs/ru/benchmark.md: -------------------------------------------------------------------------------- 1 | Benchmark 2 | ========= 3 | 4 | To start benchmark use script `benchmark/run.php -h`. 5 | 6 | ### Smarty3 vs Twig vs Fenom 7 | 8 | Smarty3 vs Twig vs Fenom 9 | 10 | Generate templates... Done 11 | 12 | Testing a lot output... 13 | smarty3: !compiled and !loaded 3.9101 sec, 15.1 MiB 14 | smarty3: compiled and !loaded 0.0235 sec, 9.3 MiB 15 | smarty3: compiled and loaded 0.0015 sec, 9.3 MiB 16 | 17 | twig: !compiled and !loaded 1.8725 sec, 68.9 MiB 18 | twig: compiled and !loaded 0.0337 sec, 17.0 MiB 19 | twig: compiled and loaded 0.0013 sec, 17.0 MiB 20 | 21 | fenom: !compiled and !loaded 0.3157 sec, 8.9 MiB 22 | fenom: compiled and !loaded 0.0159 sec, 6.6 MiB 23 | fenom: compiled and loaded 0.0012 sec, 6.6 MiB 24 | 25 | 26 | Testing 'foreach' of big array... 27 | smarty3: !compiled and !loaded 0.0355 sec, 5.8 MiB 28 | smarty3: compiled and !loaded 0.0032 sec, 3.1 MiB 29 | smarty3: compiled and loaded 0.0024 sec, 3.1 MiB 30 | 31 | twig: !compiled and !loaded 0.0799 sec, 4.7 MiB 32 | twig: compiled and !loaded 0.0065 sec, 3.2 MiB 33 | twig: compiled and loaded 0.0054 sec, 3.5 MiB 34 | 35 | fenom: !compiled and !loaded 0.0459 sec, 3.1 MiB 36 | fenom: compiled and !loaded 0.0024 sec, 2.5 MiB 37 | fenom: compiled and loaded 0.0017 sec, 2.5 MiB 38 | 39 | 40 | Testing deep 'inheritance'... 41 | smarty3: !compiled and !loaded 0.3984 sec, 10.2 MiB 42 | smarty3: compiled and !loaded 0.0009 sec, 3.1 MiB 43 | smarty3: compiled and loaded 0.0001 sec, 3.1 MiB 44 | 45 | twig: !compiled and !loaded 0.2897 sec, 11.2 MiB 46 | twig: compiled and !loaded 0.0197 sec, 6.5 MiB 47 | twig: compiled and loaded 0.0019 sec, 6.5 MiB 48 | 49 | fenom: !compiled and !loaded 0.0546 sec, 3.2 MiB 50 | fenom: compiled and !loaded 0.0005 sec, 2.5 MiB 51 | fenom: compiled and loaded 0.0000 sec, 2.5 MiB 52 | 53 | * **!compiled and !loaded** - template engine object created but parsers not initialized and templates not compiled 54 | * **compiled and !loaded** - template engine object created, template compiled but not loaded 55 | * **compiled and loaded** - template engine object created, template compiled and loaded 56 | 57 | ### Stats 58 | 59 | | Template Engine | Files | Classes | Lines | 60 | | --------------- | ------:| --------:| ------:| 61 | | Smarty3 (3.1.13)| 320 | 190 | 55095 | 62 | | Twig (1.13.0) | 162 | 131 | 13908 | 63 | | Fenom (1.0.1) | 9 | 16 | 3899 | 64 | -------------------------------------------------------------------------------- /docs/ru/configuration.md: -------------------------------------------------------------------------------- 1 | Настройка 2 | ========= 3 | 4 | ## Configure 5 | 6 | ### Кеш шаблонов 7 | 8 | ```php 9 | $fenom->setCompileDir($dir); 10 | ``` 11 | 12 | Задает имя каталога, в котором хранятся компилированные шаблоны. По умолчанию это `/tmp`. Каталог дожен быть доступен на запись. 13 | 14 | ### Параметры обработчика 15 | 16 | Установка параметров через фабрику 17 | ```php 18 | $fenom = Fenom::factory($tpl_dir, $compile_dir, $options); 19 | ``` 20 | 21 | Установка параметров через метод 22 | ```php 23 | $fenom->setOptions($options); 24 | ``` 25 | В обоих случаях аргумет `$options` может быть массивом или битовой маской. 26 | В массиве ключем должно быть название параметра, а значением — булевый флаг `true` (активировать) или `false` (деактивировать). 27 | Битовая маска должна состоять из значений констант, согласно следующей таблице 28 | 29 | | Название параметра | Константа | Описание | Эффект | 30 | | ---------------------- | ------------------------- | ------------ | ------- | 31 | | *disable_methods* | `Fenom::DENY_METHODS` | отключает возможность вызова методов в шаблоне | | 32 | | *disable_native_funcs* | `Fenom::DENY_NATIVE_FUNCS`| отключает возможность использования функций PHP, за исключением разрешенных | | 33 | | *auto_reload* | `Fenom::AUTO_RELOAD` | автоматически пересобирать кеш шаблона если шаблон изменился | понижает производительность | 34 | | *force_compile* | `Fenom::FORCE_COMPILE` | каждый раз пересобирать кеш шаблонов (рекомендуется только для отладки)| очень сильно понижает производительность | 35 | | *disable_cache* | `Fenom::DISABLE_CACHE` | не кешировать компилированный шаблон | эпично понижает производительность | 36 | | *force_include* | `Fenom::FORCE_INCLUDE` | стараться по возможности вставить код дочернего шаблона в родительский при подключении шаблона | повышает производительность, увеличивает размер файлов в кеше, уменьшает количество файлов в кеше | 37 | | *auto_escape* | `Fenom::AUTO_ESCAPE` | автоматически экранировать HTML сущности при выводе переменных в шаблон | понижает производительность | 38 | | *force_verify* | `Fenom::FORCE_VERIFY` | автоматически проверять существование переменной перед использованием в шаблоне | понижает производительность | 39 | | *disable_call* | `Fenom::DENY_CALL` | отключает возможность вызова статических методов и функций в шаблоне | | 40 | | *disable_php_calls* | `Fenom::DENY_PHP_CALLS` | устаревшее название disable_call | | 41 | | *disable_statics* | `Fenom::DENY_STATICS` | устаревшее название disable_call | | 42 | | *strip* | `Fenom::AUTO_STRIP` | удаляет лишиние пробелы в шаблоне | уменьшает размер кеша | 43 | 44 | ```php 45 | $fenom->setOptions(array( 46 | "force_compile" => true, 47 | "force_include" => true 48 | )); 49 | // тоже самое что и 50 | $fenom->setOptions(Fenom::AUTO_RELOAD | Fenom::FORCE_INCLUDE); 51 | ``` 52 | 53 | ```php 54 | $fenom->addCallFilter('View\Widget\*::get*') 55 | ``` 56 | 57 | **Замечание** 58 | По умолчанию все параметры деактивированы. 59 | -------------------------------------------------------------------------------- /docs/en/configuration.md: -------------------------------------------------------------------------------- 1 | Setup 2 | ===== 3 | 4 | ## Configure 5 | 6 | ### Template cache 7 | 8 | ```php 9 | $fenom->setCompileDir($dir); 10 | ``` 11 | 12 | This method set the name of the directory where template caches are stored. By default this is `/tmp`. This directory must be writeable. 13 | 14 | ### Template settings 15 | 16 | ```php 17 | // set options using factory 18 | $fenom = Fenom::factory($tpl_dir, $compile_dir, $options); 19 | // or inline using method setOptions 20 | $fenom->setOptions($options); 21 | ``` 22 | 23 | Options may by associative array like `'option_name' => true` or bitwise mask. 24 | 25 | | Option name | Constant | Description | Affect | 26 | | ---------------------- | ------------------------- | ------------ | ------- | 27 | | *disable_methods* | `Fenom::DENY_METHODS` | disable calling methods of objects in templates. | | 28 | | *disable_native_funcs* | `Fenom::DENY_NATIVE_FUNCS`| disable calling native function in templates, except allowed. | | 29 | | *auto_reload* | `Fenom::AUTO_RELOAD` | reload template if source will be changed | decreases performance | 30 | | *force_compile* | `Fenom::FORCE_COMPILE` | recompile template every time when the template renders | very decreases performance | 31 | | *disable_cache* | `Fenom::DISABLE_CACHE` | disable compile cache | greatly decreases performance | 32 | | *force_include* | `Fenom::FORCE_INCLUDE` | paste template body instead of include-tag | increases performance, increases cache size | 33 | | *auto_escape* | `Fenom::AUTO_ESCAPE` | html-escape each variables outputs | decreases performance | 34 | | *force_verify* | `Fenom::FORCE_VERIFY` | check existence every used variable | decreases performance | 35 | 36 | | *disable_statics* | `Fenom::DENY_STATICS` | disable calling static methods in templates. | | 37 | | *strip* | `Fenom::AUTO_STRIP` | strip all whitespaces in templates. | decrease cache size | 38 | 39 | ```php 40 | $fenom->setOptions(array( 41 | "force_compile" => true, 42 | "force_include" => true 43 | )); 44 | // same 45 | $fenom->setOptions(Fenom::AUTO_RELOAD | Fenom::FORCE_INCLUDE); 46 | ``` 47 | 48 | **Note** 49 | By default all options disabled 50 | 51 | ## Extends 52 | 53 | ### Template providers 54 | 55 | Бывает так что шаблны не хранятся на файловой сиситеме, а хранятся в некотором хранилище, например, в базе данных MySQL. 56 | В этом случае шаблонизатору нужно описать как забирать шаблоны из хранилища, как проверять дату изменения шаблона и где хранить кеш шаблонов (опционально). 57 | Эту задачу берут на себя Providers, это объекты реальзующие интерфейс `Fenom\ProviderInterface`. 58 | 59 | ### Callbacks and filters 60 | 61 | #### Before compile callback 62 | 63 | ```php 64 | $fenom->addPreFilter(function () { /* ... */ }); 65 | ``` 66 | 67 | #### Tag filter callback 68 | 69 | #### Filter callback 70 | 71 | #### After compile callback 72 | 73 | 74 | -------------------------------------------------------------------------------- /docs/ru/mods/date_format.md: -------------------------------------------------------------------------------- 1 | Модификатор date_format 2 | ==================== 3 | 4 | Форматирует дату согласно указанному формату [strftime()](http://docs.php.net/ru/strftime). 5 | Даты могут быть переданы в виде временных меток unix, временных меток mysql или в виде любой строки, содержащей день, 6 | месяц и год, которую может обработать функция [strftime()](http://docs.php.net/ru/strftime). 7 | 8 | ```smarty 9 | {$date|date_format:$format = `%b %e, %Y`} 10 | ``` 11 | 12 | Формат по умолчанию: `%b %e, %Y`. 13 | 14 | ```smarty 15 | {var $ts = time()} 16 | 17 | {$ts|date_format:"%Y/%m/%d %H:%M:%S"} выведет 2013/02/08 21:01:43 18 | {$ts|date_format:"-1 day"} выведет вчерашний день, например 2013/02/07 21:01:43 19 | 20 | {var $date = "2008-12-08"} 21 | 22 | {$ts|date_format:"%Y/%m/%d %H:%M:%S"} выведет 2008/12/08 00:00:00 23 | ``` 24 | 25 | [Конверсионные указатели](http://docs.php.net/ru/strftime#refsect1-function.strftime-parameters) в модификаторе **date_format**: 26 | 27 | * %a - сокращенное название дня недели, в зависимости от текущей локали 28 | * %A - полное название дня недели, в зависимости от текущей локали 29 | * %b - сокращенное название месяца, в зависимости от текущей локали 30 | * %B - полное название месяца, в зависимости от текущей локали 31 | * %c - формат даты и времени по умолчанию для текущей локали 32 | * %C - номер века (год, деленный на 100, представленный в виде целого в промежутке от 00 до 99) 33 | * %d - день месяца в десятичном формате (от 01 до 31) 34 | * %D - синоним %m/%d/%y 35 | * %e - день месяца в десятичном формате без ведущего нуля (от 1 до 31) 36 | * %g - Week-based year within century [00,99] 37 | * %G - Week-based year, including the century [0000,9999] 38 | * %h - синоним %b 39 | * %H - часы по 24-часовым часам (от 00 до 23) 40 | * %I - часы по 12-часовым часам (от 01 до 12) 41 | * %j - день года (от 001 до 366) 42 | * %k - часы по 24-часовым часам без ведущего нуля (от 0 до 23) 43 | * %l - часы по 12-часовым часам без ведущего нуля (от 1 до 12) 44 | * %m - номер месяца (от 01 до 12) 45 | * %M - минуты 46 | * %n - символ новой строки 47 | * %p - `am' или `pm', в зависимости от заданного формата времени и текущей локали. 48 | * %r - time in a.m. and p.m. notation 49 | * %R - time in 24 hour notation 50 | * %S - секунды 51 | * %t - символ табуляции 52 | * %T - время в формате %H:%M:%S 53 | * %u - номер дня недели [1,7], где 1-ый день - понедельник 54 | * %U - номер недели в году, считая первое воскресенья года первым днем первой недели 55 | * %V - номер недели в году (по ISO 8601:1988) в диапазоне от 01 до 53, где первая неделя та, у которой хотя бы 4 дня находятся в данном году. Понедельник считается первым днем недели. 56 | * %w - номер дня недели, где 0 - воскресенье 57 | * %W - номер недели в году, считаю первый понедельник первым днем первой недели. 58 | * %x - предпочтительное представление даты для текущих настроек locale без времени 59 | * %X - предпочтительное представление времени для текущих настроек locale без даты 60 | * %y - год в виде десятичного числа без века (от 00 до 99) 61 | * %Y - год в виде десятичного числа включая век 62 | * %Z - часовой пояс или имя или сокращение 63 | * %% - буквальный символ `%' 64 | -------------------------------------------------------------------------------- /docs/ru/tags/foreach.md: -------------------------------------------------------------------------------- 1 | Тег {foreach} 2 | ============= 3 | 4 | Тег `foreach` предоставляет простой способ перебора массивов. 5 | `Foreach` работает только с массивами, объектами и интервалами. 6 | 7 | ```smarty 8 | {foreach $list [as [$key =>] $value] [index=$index] [first=$first] [last=$last]} 9 | {* ...code... *} 10 | {break} 11 | {* ...code... *} 12 | {continue} 13 | {* ...code... *} 14 | {foreachelse} 15 | {* ...code... *} 16 | {/foreach} 17 | ``` 18 | 19 | ### {foreach} 20 | 21 | Перебор значений массива $list: 22 | 23 | ```smarty 24 | {foreach $list as $value} 25 |
{$value}
26 | {/foreach} 27 | 28 | {foreach 1..7 as $value} {* так же хорошо работает и с интервалами *} 29 |
№{$value}
30 | {/foreach} 31 | ``` 32 | 33 | Перебор ключей и значений массива $list: 34 | 35 | ```smarty 36 | {foreach $list as $key => $value} 37 |
{$key}: {$value}
38 | {/foreach} 39 | ``` 40 | 41 | 42 | Получение номера (индекса) итерации, начиная с 0 43 | 44 | ```smarty 45 | {foreach $list as $value} 46 |
№{$value@index}: {$value}
47 | {/foreach} 48 | 49 | или 50 | 51 | {foreach $list as $value index=$index} 52 |
№{$index}: {$value}
53 | {/foreach} 54 | ``` 55 | 56 | Определение первой итерации: 57 | 58 | ```smarty 59 | {foreach $list as $value} 60 |
{if $value@first} first item {/if} {$value}
61 | {/foreach} 62 | 63 | или 64 | 65 | {foreach $list as $value first=$first} 66 |
{if $first} first item {/if} {$value}
67 | {/foreach} 68 | ``` 69 | 70 | Переменная `$value@first` будет иметь значение **TRUE**, если текущая итерация является первой. 71 | Определение последней интерации: 72 | 73 | ```smarty 74 | {foreach $list as $value} 75 |
{if $value@last} last item {/if} {$value}
76 | {/foreach} 77 | 78 | или 79 | 80 | {foreach $list as $value last=$last} 81 |
{if $last} last item {/if} {$value}
82 | {/foreach} 83 | ``` 84 | 85 | Переменная `$value:last` будет иметь значение **TRUE**, если текущая итерация является последней. 86 | 87 | **Замечание:** 88 | Использование `last` требует от `$list` быть **countable**. 89 | 90 | ### {break} 91 | 92 | Тег `{break}` используется для выхода из цикла до достижения последней итерации. 93 | Если в цикле встречается тег `{break}`, цикл завершает свою работу, и далее, выполняется код, следующий сразу за блоком цикла 94 | 95 | ### {continue} 96 | 97 | Тег `{continue}` используется для прерывания текущей итерации. 98 | Если в цикле встречается тег `{continue}`, часть цикла, следующая после тега, не выполняется, и начинается следующая итерация. 99 | Если текущая итерация была последней, цикл завершается. 100 | 101 | ### {foreachelse} 102 | 103 | Тег {foreachelse} ограничивает код, который должен быть выполнен, если итерируемый объект пуст. 104 | 105 | ```smarty 106 | {var $list = []} 107 | {foreach $list as $value} 108 |
{if $last} last item {/if} {$value}
109 | {foreachelse} 110 |
empty
111 | {/foreach} 112 | ``` 113 | 114 | В блоке `{foreachelse}...{/foreach}` использование `{break}`, `{continue}` выбросит исключение `Fenom\CompileException` при компиляции 115 | -------------------------------------------------------------------------------- /docs/en/ext/tags.md: -------------------------------------------------------------------------------- 1 | Tags [RU] 2 | ========= 3 | 4 | В шаблонизаторе принято различать два типа тегов: _компиляторы_ и _функции_. 5 | Компиляторы вызываются во время преобразования кода шаблона в PHP код и возвращяю PHP код который будет вставлен вместо тега. 6 | А функции вызываются непременно в момент выполнения шаблона и возвращают непосредственно данные которые будут отображены. 7 | Среди тегов как и в HTML есть строчные и блоковые теги. 8 | 9 | ## Inline function 10 | 11 | Примитивное добавление функции можно осуществить следующим образом: 12 | 13 | ```php 14 | $fenom->addFunction(string $function_name, callable $callback[, callable $parser]); 15 | ``` 16 | 17 | В данном случае запускается стандартный парсер, который автоматически разберет аргументы тега, которые должны быть в формате HTML аттрибутов и отдаст их в функцию ассоциативным массивом: 18 | ```php 19 | $fenom->addFunction("some_function", function (array $params) { /* ... */ }); 20 | ``` 21 | При необходимости можно переопределить парсер на произвольный: 22 | ```php 23 | $fenom->addFunction("some_function", $some_function, function (Fenom\Tokenizer $tokenizer, Fenom\Template $template) { /* parse tag */}); 24 | ``` 25 | Существует более простой способ добавления произвольной функции: 26 | 27 | ```php 28 | $fenom->addFunctionSmarty(string $function_name, callable $callback); 29 | ``` 30 | 31 | В данном случае парсер сканирует список аргументов коллбека и попробует сопоставить с аргументами тега. 32 | 33 | ```php 34 | // ... class XYCalcs .. 35 | public static function calc($x, $y = 5) { /* ... */} 36 | // ... 37 | $fenom->addFunctionSmart('calc', 'XYCalcs::calc'); 38 | ``` 39 | then 40 | ```smarty 41 | {calc x=$top y=50} or {calc y=50 x=$top} is XYCalcs::calc($top, 50) 42 | {calc x=$top} or {calc $top} is XYCalcs::calc($top) 43 | ``` 44 | Таким образом вы успешно можете добавлять Ваши функции или методы. 45 | 46 | ## Block function 47 | 48 | Добавление блоковой функции аналогичен добавлению строковой за исключением того что есть возможность указать парсер для закрывающего тега. 49 | 50 | ```php 51 | $fenom->addBlockFunction(string $function_name, callable $callback[, callable $parser_open[, callable $parser_close]]); 52 | ``` 53 | 54 | Сам коллбек принимает первым аргументом контент между открывающим и закрывающим тегом, а вторым аргументом - ассоциативный массив из аргуметов тега: 55 | ```php 56 | $fenom->addBlockFunction('some_block_function', function ($content, array $params) { /* ... */}); 57 | ``` 58 | 59 | ## Inline compiler 60 | 61 | Добавление строчного компилятора осуществляеться очень просто: 62 | 63 | ```php 64 | $fenom->addCompiler(string $compiler, callable $parser); 65 | ``` 66 | 67 | Парсер должен принимать `Fenom\Tokenizer $tokenizer`, `Fenom\Template $template` и возвращать PHP код. 68 | Компилятор так же можно импортировать из класса автоматически 69 | 70 | ```php 71 | $fenom->addCompilerSmart(string $compiler, $storage); 72 | ``` 73 | 74 | `$storage` может быть как классом так и объектом. В данном случае шаблонизатор будет искать метод `tag{$compiler}`, который будет взят в качестве парсера тега. 75 | 76 | ## Block compiler 77 | 78 | Добавление блочного компилятора осуществяется двумя способами. Первый 79 | 80 | ```php 81 | $fenom->addBlockCompiler(string $compiler, array $parsers, array $tags); 82 | ``` 83 | 84 | где `$parser` ассоциативный массив `["open" => parser, "close" => parser]`, сождержащий парсер на открывающий и на закрывающий тег, а `$tags` содержит список внутренних тегов в формате `["tag_name"] => parser`, которые могут быть использованы только с этим компилятором. 85 | Второй способ добавления парсера через импортирование из класса или объекта методов: 86 | 87 | ```php 88 | $fenom->addBlockCompilerSmart(string $compiler, $storage, array $tags, array $floats); 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /docs/ru/ext/tags.md: -------------------------------------------------------------------------------- 1 | Tags [RU] 2 | ========= 3 | 4 | В шаблонизаторе принято различать два типа тегов: _компиляторы_ и _функции_. 5 | Компиляторы вызываются во время преобразования кода шаблона в PHP код и возвращяю PHP код который будет вставлен вместо тега. 6 | А функции вызываются непременно в момент выполнения шаблона и возвращают непосредственно данные которые будут отображены. 7 | Среди тегов как и в HTML есть строчные и блоковые теги. 8 | 9 | ## Inline function 10 | 11 | Примитивное добавление функции можно осуществить следующим образом: 12 | 13 | ```php 14 | $fenom->addFunction(string $function_name, callable $callback[, callable $parser]); 15 | ``` 16 | 17 | В данном случае запускается стандартный парсер, который автоматически разберет аргументы тега, которые должны быть в формате HTML аттрибутов и отдаст их в функцию ассоциативным массивом: 18 | ```php 19 | $fenom->addFunction("some_function", function (array $params) { /* ... */ }); 20 | ``` 21 | При необходимости можно переопределить парсер на произвольный: 22 | ```php 23 | $fenom->addFunction("some_function", $some_function, function (Fenom\Tokenizer $tokenizer, Fenom\Template $template) { /* parse tag */}); 24 | ``` 25 | Существует более простой способ добавления произвольной функции: 26 | 27 | ```php 28 | $fenom->addFunctionSmarty(string $function_name, callable $callback); 29 | ``` 30 | 31 | В данном случае парсер сканирует список аргументов коллбека и попробует сопоставить с аргументами тега. 32 | 33 | ```php 34 | // ... class XYCalcs .. 35 | public static function calc($x, $y = 5) { /* ... */} 36 | // ... 37 | $fenom->addFunctionSmart('calc', 'XYCalcs::calc'); 38 | ``` 39 | then 40 | ```smarty 41 | {calc x=$top y=50} or {calc y=50 x=$top} is XYCalcs::calc($top, 50) 42 | {calc x=$top} or {calc $top} is XYCalcs::calc($top) 43 | ``` 44 | Таким образом вы успешно можете добавлять Ваши функции или методы. 45 | 46 | ## Block function 47 | 48 | Добавление блоковой функции аналогичен добавлению строковой за исключением того что есть возможность указать парсер для закрывающего тега. 49 | 50 | ```php 51 | $fenom->addBlockFunction(string $function_name, callable $callback[, callable $parser_open[, callable $parser_close]]); 52 | ``` 53 | 54 | Сам коллбек принимает первым аргументом контент между открывающим и закрывающим тегом, а вторым аргументом - ассоциативный массив из аргуметов тега: 55 | ```php 56 | $fenom->addBlockFunction('some_block_function', function ($content, array $params) { /* ... */}); 57 | ``` 58 | 59 | ## Inline compiler 60 | 61 | Добавление строчного компилятора осуществляеться очень просто: 62 | 63 | ```php 64 | $fenom->addCompiler(string $compiler, callable $parser); 65 | ``` 66 | 67 | Парсер должен принимать `Fenom\Tokenizer $tokenizer`, `Fenom\Template $template` и возвращать PHP код. 68 | Компилятор так же можно импортировать из класса автоматически 69 | 70 | ```php 71 | $fenom->addCompilerSmart(string $compiler, $storage); 72 | ``` 73 | 74 | `$storage` может быть как классом так и объектом. В данном случае шаблонизатор будет искать метод `tag{$compiler}`, который будет взят в качестве парсера тега. 75 | 76 | ## Block compiler 77 | 78 | Добавление блочного компилятора осуществяется двумя способами. Первый 79 | 80 | ```php 81 | $fenom->addBlockCompiler(string $compiler, array $parsers, array $tags); 82 | ``` 83 | 84 | где `$parser` ассоциативный массив `["open" => parser, "close" => parser]`, сождержащий парсер на открывающий и на закрывающий тег, а `$tags` содержит список внутренних тегов в формате `["tag_name"] => parser`, которые могут быть использованы только с этим компилятором. 85 | Второй способ добавления парсера через импортирование из класса или объекта методов: 86 | 87 | ```php 88 | $fenom->addBlockCompilerSmart(string $compiler, $storage, array $tags, array $floats); 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /docs/ru/start.md: -------------------------------------------------------------------------------- 1 | Быстрый старт 2 | ============= 3 | 4 | ## Установка Fenom 5 | 6 | ### Composer 7 | 8 | Fenom зарегистрирован на [packagist.org](https://packagist.org/) как пакет [fenom/fenom](https://packagist.org/packages/fenom/fenom). 9 | Что бы установить Fenom через composer пропишите в `composer.json` списке пакетов: 10 | ```json 11 | { 12 | "require": { 13 | "fenom/fenom": "2.*" 14 | } 15 | } 16 | ``` 17 | и обновите зависимости: `composer update`. 18 | 19 | ### Произвольная подгрузка 20 | 21 | Клонируйте Fenom в любую директорию Вашего проекта: `git clone https://github.com/bzick/fenom.git`. Рекомендуется использовать последнюю версию. 22 | Для загрузки классов Fenom использует [psr-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md#autoloading-standard) стандарт. 23 | Таким образом вы можете: 24 | * использовать Ваш автозагрузчик, который понимает `psr-0` формат для загрузки классов Fenom из директории `src/` с пространством имен `Fenom`. 25 | * или использовать встроенный автозагрузчик Fenom: `Fenom::registerAutoload();` для загрузки самого себя. 26 | 27 | Так же вы можете использовать встроенный в Fenom автозагрузчик для загрузки других классов в `psr-0` формате: 28 | ```php 29 | Fenom::registerAutoload(PROJECT_DIR."/classes"); 30 | ``` 31 | 32 | ## Настройка Fenom 33 | 34 | Есть два варианта инициировать объект шаблонизатора: через `new` оператор и фабрику. 35 | Пример создания Fenom через фабрику: 36 | ```php 37 | $fenom = Fenom::factory('/path/to/templates', '/path/to/compiled/template', $options); 38 | ``` 39 | Пример создания Fenom через оператор `new`: 40 | ```php 41 | $fenom = new Fenom(new Fenom\Provider('/path/to/templates')); 42 | $fenom->setCompileDir('/path/to/template/cache'); 43 | $fenom->setOptions($options); 44 | ``` 45 | 46 | * `/path/to/templates` — директория в которой хранятся шаблоны. 47 | * `/path/to/template/cache` — директория в которую Fenom будет сохранять PHP-кеш шаблонов 48 | * `$options` - битовая маска или массив [параметров](./configuration.md). 49 | 50 | ### Использование 51 | 52 | Что бы отобразить шаблон на экран используйте метод `display`: 53 | 54 | ```php 55 | // $fenom->display(string $template, array $variables) : void 56 | 57 | $fenom->display("template/name.tpl", $vars); 58 | ``` 59 | 60 | Метод найдет шаблон `template/name.tpl` отрисует его в `stdout`, подставляя переменные из массива `$vars`. 61 | 62 | Метод `fetch` возвращает вывод шаблона вместо его отображения на экран. 63 | ```php 64 | // $fenom->fetch(string $template, array $variables) : string 65 | 66 | $result = $fenom->fetch("template/name.tpl", $vars); 67 | ``` 68 | 69 | Для вывода большого количества данных можно использовать поток 70 | 71 | ```php 72 | // $fenom->pipe(string $template, array $variables, callable $callback, int $chunk_size) : void 73 | 74 | $fenom->pipe( 75 | "template/sitemap.tpl", 76 | $vars, 77 | $callback = [new SplFileObject("compress.zlib:///tmp/sitemap.xml.gz", "w"), "fwrite"], // поток с архивацией в файл /tmp/sitemap.xml.gz 78 | 1e6 // размер куска данных в байтах 79 | ); 80 | ``` 81 | 82 | Поток позволяет обрабатывать большой результат по кускам, размер куска указывается в байтах аргументом `$chunk_size`. 83 | Каждый кусок передается в `$callback` для обработки или вывода. 84 | 85 | 111 | -------------------------------------------------------------------------------- /docs/en/operators.md: -------------------------------------------------------------------------------- 1 | Operators 2 | ========= 3 | 4 | ### Arithmetic operators 5 | 6 | * `$a + $b` - addition 7 | * `$a - $b` - subtraction 8 | * `$a * $b` - multiplication 9 | * `$a / $b` - division 10 | * `$a % $b` - modulus 11 | 12 | ```smarty 13 | {$a + $b * $c/$d - $e*5 + 1e3} 14 | ``` 15 | 16 | ### Logical operators 17 | 18 | * `$a || $b` - or 19 | * `$a && $b` - and 20 | * `!$a` - not, unary operator 21 | * `$a and $b` - and 22 | * `$a or $b` - or 23 | * `$a xor $b` - xor 24 | 25 | ```smarty 26 | {if $b && $c} ... {/if} 27 | ``` 28 | 29 | ### Comparison operators 30 | 31 | * `$a < $b` - less than 32 | * `$a > $b` - greater than 33 | * `$a <= $b` - less than or equal to 34 | * `$a >= $b` - greater than or equal to 35 | * `$a == $b` - equal 36 | * `$a === $b` - identical 37 | * `$a !== $b` - not identical 38 | * `$a != $b` - not equal 39 | * `$a <> $b` - not equal 40 | 41 | ```smarty 42 | {if $b >= 5} ... {/if} 43 | ``` 44 | 45 | ### Bitwise operators 46 | 47 | * `$a | $b` - or 48 | * `$a & $b` - and 49 | * `$a ^ $b` - xor 50 | * `~$a` - not, unary operator 51 | * `$a << $b` - shift left 52 | * `$a >> $b` - shift right 53 | 54 | ```smarty 55 | {if $a & 1} {var $b = 4 | $flags} {/if} 56 | ``` 57 | 58 | ### Assignment operators 59 | 60 | * `$a = $b` - assignment 61 | * `$a += $b` - assignment with addition 62 | * `$a -= $b` - assignment with subtraction 63 | * `$a *= $b` - assignment with multiplication 64 | * `$a /= $b` - assignment with division 65 | * `$a %= $b` - assignment with modulus 66 | * `$a &= $b` - assignment with bitwise And 67 | * `$a |= $b` - assignment with bitwise or 68 | * `$a ^= $b` - assignment with bitwise xor 69 | * `$a <<= $b` - assignment with left shift 70 | * `$a >>= $b` - assignment with right shift 71 | 72 | 73 | ```smarty 74 | {var $b |= $flags} 75 | ``` 76 | 77 | ### Incrementing/Decrementing operators 78 | 79 | * `++$a` - increment the variable and use it 80 | * `$a++` - use the variable and increment it 81 | * `--$a` - decrement the variable and use it 82 | * `$a--` - use the variable and decrement it 83 | 84 | ### String operators 85 | 86 | * `$a ~ $b` - return concatenation of variables `$a` and `$b` 87 | * `$a ~~ $b` - return concatenation of variables `$a` and `$b` separated by a space 88 | * `$a ~= $b` - assignment with concatenation 89 | 90 | ### Ternary operators 91 | 92 | * `$a ? $b : $c` - returns `$b` if `$a` is not empty, and `$c` otherwise 93 | * `$a ! $b : $c` - returns `$b` if `$a` is set, and `$c` otherwise 94 | * `$a ?: $c` - returns `$a` if `$a` is not empty, and `$c` otherwise 95 | * `$a !: $c` - returns `$a` if `$a` is set, and `$c` otherwise 96 | 97 | ```smarty 98 | {var $a = true} 99 | {$a ? 5 : 10} {* outputs 5 *} 100 | {var $a = false} 101 | {$a ? 5 : 10} {* outputs 10 *} 102 | ``` 103 | 104 | ### Check operators 105 | 106 | * `$a?` - returns `TRUE` if `$a` is not empty 107 | * `$a!` - returns `TRUE` if `$a` is set 108 | 109 | ```smarty 110 | {if $a?} {* instead of {if !empty($a)} *} 111 | {if $a!} {* instead of {if isset($a)} *} 112 | {$a?:"some text"} {* instead of {if empty($a) ? "some text" : $a} *} 113 | {$a!:"some text"} {* instead of {if isset($a) ? $a : "some text"} *} 114 | ``` 115 | 116 | ### Test operator 117 | 118 | Tests can be negated by using the `is not` operator. 119 | 120 | * `$a is $b` - $a identical $b 121 | * `$a is integer` - test variable type. Type may be int/integer, bool/boolean, float/double/decimal, array, object, scalar, string, callback/callable, number/numeric. 122 | * `$a is iterable` - test variable for iteration. 123 | * `$a is template` - variable `$a` contain existing template name. 124 | * `$a is empty` - checks if a variable is empty. 125 | * `$a is set` - checks if a variable is set. 126 | * `$a is even` - variable `$a` is even. 127 | * `$a is odd` - variable `$a` is odd. 128 | * `$a is MyClass` or `$a is \MyClass` - variable `$a` instance of `MyClass` class 129 | 130 | ### Containment operator 131 | 132 | Tests can be negated by using the `not in` operator. 133 | 134 | * `$a in $b` - variable `$a` contains in `$b`, $b may be string, plain or assoc array. 135 | * `$a in list $b` - variable `$a` contains in array `$b` as value 136 | * `$a in keys $b` - array `$b` contain key `$a` 137 | * `$a in string $b` - variable `$a` contains in string `$b` as substring 138 | 139 | ```smarty 140 | {'df' in 'abcdefg'} 141 | {5 in [1, 5, 25, 125]} 142 | {99 in keys [1, 5, 25, 99 => 125]} 143 | ``` -------------------------------------------------------------------------------- /tests/cases/Fenom/ProviderTest.php: -------------------------------------------------------------------------------- 1 | tpl("template1.tpl", 'Template 1 {$a}'); 18 | $this->tpl("template2.tpl", 'Template 2 {$a}'); 19 | $this->tpl("sub/template3.tpl", 'Template 3 {$a}'); 20 | $this->provider = new Provider(FENOM_RESOURCES . '/template'); 21 | clearstatcache(); 22 | } 23 | 24 | public function testIsTemplateExists() 25 | { 26 | clearstatcache(); 27 | $this->assertTrue($this->provider->templateExists("template1.tpl")); 28 | $this->assertFalse($this->provider->templateExists("unexists.tpl")); 29 | } 30 | 31 | public function testGetSource() 32 | { 33 | $time = 0.0; 34 | clearstatcache(); 35 | $src = $this->provider->getSource("template1.tpl", $time); 36 | clearstatcache(); 37 | $this->assertEquals(file_get_contents(FENOM_RESOURCES . '/template/template1.tpl'), $src); 38 | $this->assertEquals(filemtime(FENOM_RESOURCES . '/template/template1.tpl'), $time); 39 | } 40 | 41 | public function testGetSourceInvalid() 42 | { 43 | $this->expectException(\RuntimeException::class); 44 | $this->provider->getSource("unexists.tpl", $time); 45 | } 46 | 47 | public function testGetLastModified() 48 | { 49 | $time = $this->provider->getLastModified("template1.tpl"); 50 | clearstatcache(); 51 | $this->assertEquals(filemtime(FENOM_RESOURCES . '/template/template1.tpl'), $time); 52 | } 53 | 54 | public function testGetLastModifiedInvalid() 55 | { 56 | $this->expectException(\RuntimeException::class); 57 | $this->provider->getLastModified("unexists.tpl"); 58 | } 59 | 60 | public function testVerify() 61 | { 62 | $templates = array( 63 | "template1.tpl" => filemtime(FENOM_RESOURCES . '/template/template1.tpl'), 64 | "template2.tpl" => filemtime(FENOM_RESOURCES . '/template/template2.tpl') 65 | ); 66 | clearstatcache(); 67 | $this->assertTrue($this->provider->verify($templates)); 68 | clearstatcache(); 69 | $templates = array( 70 | "template2.tpl" => filemtime(FENOM_RESOURCES . '/template/template2.tpl'), 71 | "template1.tpl" => filemtime(FENOM_RESOURCES . '/template/template1.tpl') 72 | ); 73 | clearstatcache(); 74 | $this->assertTrue($this->provider->verify($templates)); 75 | } 76 | 77 | public function testVerifyInvalid() 78 | { 79 | $templates = array( 80 | "template1.tpl" => filemtime(FENOM_RESOURCES . '/template/template1.tpl'), 81 | "template2.tpl" => filemtime(FENOM_RESOURCES . '/template/template2.tpl') + 1 82 | ); 83 | clearstatcache(); 84 | $this->assertFalse($this->provider->verify($templates)); 85 | clearstatcache(); 86 | $templates = array( 87 | "template1.tpl" => filemtime(FENOM_RESOURCES . '/template/template1.tpl'), 88 | "unexists.tpl" => 1234567890 89 | ); 90 | $this->assertFalse($this->provider->verify($templates)); 91 | } 92 | 93 | public function testGetAll() 94 | { 95 | $list = $this->provider->getList(); 96 | sort($list); 97 | $this->assertSame( 98 | array( 99 | "sub/template3.tpl", 100 | "template1.tpl", 101 | "template2.tpl" 102 | ), 103 | $list 104 | ); 105 | } 106 | 107 | public function testRm() 108 | { 109 | $this->assertTrue(is_dir(FENOM_RESOURCES . '/template/sub')); 110 | Provider::rm(FENOM_RESOURCES . '/template/sub'); 111 | $this->assertFalse(is_dir(FENOM_RESOURCES . '/template/sub')); 112 | $this->assertTrue(is_file(FENOM_RESOURCES . '/template/template1.tpl')); 113 | Provider::rm(FENOM_RESOURCES . '/template/template1.tpl'); 114 | $this->assertFalse(is_file(FENOM_RESOURCES . '/template/template1.tpl')); 115 | $this->assertTrue(is_file(FENOM_RESOURCES . '/template/template2.tpl')); 116 | Provider::clean(FENOM_RESOURCES . '/template/'); 117 | $this->assertFalse(is_file(FENOM_RESOURCES . '/template/template2.tpl')); 118 | } 119 | } 120 | 121 | -------------------------------------------------------------------------------- /tests/cases/Fenom/MacrosTest.php: -------------------------------------------------------------------------------- 1 | tpl("math.tpl", 11 | '{macro plus(x, y)} 12 | x + y = {$x + $y} 13 | {/macro} 14 | 15 | {macro minus(x, y, z=0)} 16 | x - y - z = {$x - $y - $z} 17 | {/macro} 18 | 19 | {macro multi(x, y)} 20 | x * y = {$x * $y} 21 | {/macro} 22 | 23 | {macro e()} 24 | 2.71827 25 | {/macro} 26 | 27 | {macro pi} 28 | 3.14159 29 | {/macro} 30 | 31 | Math: {macro.plus x=2 y=3}, {macro.minus x=10 y=4} 32 | ' 33 | ); 34 | 35 | $this->tpl( 36 | "import.tpl", 37 | ' 38 | {import "math.tpl"} 39 | {import "math.tpl" as math} 40 | 41 | Imp: {macro.plus x=1 y=2}, {math.minus x=6 y=2 z=1} 42 | ' 43 | ); 44 | 45 | $this->tpl( 46 | "import_custom.tpl", 47 | ' 48 | {macro minus($x, $y)} 49 | new minus macros 50 | {/macro} 51 | {import [plus, minus] from "math.tpl" as math} 52 | 53 | a: {math.plus x=1 y=2}, {math.minus x=6 y=2 z=1}, {macro.minus x=5 y=3}. 54 | ' 55 | ); 56 | 57 | $this->tpl( 58 | "import_miss.tpl", 59 | ' 60 | {import [minus] from "math.tpl" as math} 61 | 62 | a: {macro.plus x=5 y=3}. 63 | ' 64 | ); 65 | 66 | $this->tpl( 67 | "macro_recursive.tpl", 68 | '{macro factorial(num)} 69 | {if $num} 70 | {$num} {macro.factorial num=$num-1} {$num} 71 | {/if} 72 | {/macro} 73 | 74 | {macro.factorial num=10}' 75 | ); 76 | 77 | $this->tpl( 78 | "macro_recursive_import.tpl", 79 | ' 80 | {import [factorial] from "macro_recursive.tpl" as math} 81 | 82 | {math.factorial num=10}' 83 | ); 84 | } 85 | 86 | /** 87 | * @throws \Exception 88 | * @group macros 89 | */ 90 | public function testMacros() 91 | { 92 | $tpl = $this->fenom->compile('math.tpl'); 93 | 94 | $this->assertStringStartsWith('x + y = ', trim($tpl->macros["plus"]["body"])); 95 | $this->assertSame('Math: x + y = 5 , x - y - z = 6', Modifier::strip($tpl->fetch(array()), true)); 96 | } 97 | 98 | public function testImport() 99 | { 100 | $tpl = $this->fenom->compile('import.tpl'); 101 | 102 | $this->assertSame('Imp: x + y = 3 , x - y - z = 3', Modifier::strip($tpl->fetch(array()), true)); 103 | } 104 | 105 | /** 106 | * @group importCustom 107 | */ 108 | public function testImportCustom() 109 | { 110 | $tpl = $this->fenom->compile('import_custom.tpl'); 111 | 112 | $this->assertSame( 113 | 'a: x + y = 3 , x - y - z = 3 , new minus macros .', 114 | Modifier::strip($tpl->fetch(array()), true) 115 | ); 116 | } 117 | 118 | public function testImportMiss() 119 | { 120 | $this->expectException(exception: \Fenom\Error\CompileException::class); 121 | $this->expectExceptionMessage("Undefined macro 'plus'"); 122 | $tpl = $this->fenom->compile('import_miss.tpl'); 123 | 124 | $this->assertSame( 125 | 'a: x + y = 3 , x - y - z = 3 , new minus macros .', 126 | Modifier::strip($tpl->fetch(array()), true) 127 | ); 128 | } 129 | 130 | /** 131 | * @group macro-recursive 132 | */ 133 | public function testRecursive() 134 | { 135 | $this->fenom->compile('macro_recursive.tpl'); 136 | $this->fenom->flush(); 137 | $tpl = $this->fenom->getTemplate('macro_recursive.tpl'); 138 | $this->assertSame("10 9 8 7 6 5 4 3 2 1 1 2 3 4 5 6 7 8 9 10", Modifier::strip($tpl->fetch(array()), true)); 139 | } 140 | 141 | 142 | public function testImportRecursive() 143 | { 144 | $this->fenom->compile('macro_recursive_import.tpl'); 145 | $this->fenom->flush(); 146 | $tpl = $this->fenom->getTemplate('macro_recursive.tpl'); 147 | $this->assertSame("10 9 8 7 6 5 4 3 2 1 1 2 3 4 5 6 7 8 9 10", Modifier::strip($tpl->fetch(array()), true)); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /docs/en/readme.md: -------------------------------------------------------------------------------- 1 | Documentation 2 | ============= 3 | 4 | ### Fenom 5 | 6 | * [Quick start](./start.md) 7 | * [Usage](./start.md#install-fenom) 8 | * [Framework adapters](./adapters.md) 9 | * [For developers](./dev/readme.md) 10 | * [Configuration](./configuration.md) 11 | * [Syntax](./syntax.md) 12 | * [Variables](./syntax.md#Variables) 13 | * [Values](./syntax.md#Values) 14 | * [Arrays](./syntax.md#Arrays) 15 | * [Operators](./operators.md) 16 | * [Modificators](./syntax.md#Modificators) 17 | * [Tags](./syntax.md#Tags) 18 | * [Tag configuration](./syntax.md#Tag-configuration) 19 | 20 | *** 21 | 22 | ### Tags 23 | 24 | [Usage](./syntax.md#tags) 25 | 26 | * [set](./tags/set.md), [add](./tags/set.md#add) and `var` — define variables 27 | * [if](./tags/if.md), [elseif](./tags/if.md#elseif) and [else](./tags/if.md#else) — conditional statement 28 | * [foreach](./tags/foreach.md), [foreaelse](./tags/foreach.md#foreaelse), 29 | [break](./tags/foreach.md#break) and [continue](./tags/foreach.md#continue) — traversing items in an array or object 30 | * [switch](./tags/switch.md), [case](./tags/switch.md#case) — complex condition statement 31 | * [cycle](./tags/cycle.md) — cycles on an array of values 32 | * [include](./tags/include.md), [insert](./tags/include.md#insert) — includes and evaluates the specified template 33 | * [extends](./tags/extends.md), [use](./tags/extends.md#use), 34 | [block](./tags/extends.md#block), [parent](./tags/extends.md#parent) and [paste](./tags/extends.md#paste) — template inheritance 35 | * [filter](./tags/filter.md) — apply modifier on a block of template data 36 | * [ignore](./tags/ignore.md) — ignore Fenom syntax 37 | * [macro](./tags/macro.md) and [import](./tags/macro.md#import) — template functions 38 | * [autoescape](./tags/autoescape.md) — escape template fragment 39 | * [raw](./tags/raw.md) — unescape template fragment 40 | * [unset](./tags/unset.md) — unset a given variables 41 | * or [add](./ext/extend.md#add-tags) yours 42 | 43 | Deprecated tags 44 | 45 | * [for](./tags/for.md), `forelse`, `break` and `continue` — loop statement 46 | 47 | *** 48 | 49 | ### Modifiers 50 | 51 | [Usage](./syntax.md#modifiers) 52 | 53 | * [upper](./mods/upper.md) aka `up` — convert to uppercase a string 54 | * [lower](./mods/lower.md) aka `low` — convert to lowercase a string 55 | * [date_format](./mods/date_format.md) - format date, timestamp via strftime() function 56 | * [date](./mods/date.md) - format date, timestamp via date() function 57 | * [truncate](./mods/truncate.md) — truncate thee string to specified length 58 | * [escape](./mods/escape.md) aka `e` — escape the string 59 | * [unescape](./mods/unescape.md) — unescape the string 60 | * [strip](./mods/strip.md) — remove extra whitespaces 61 | * [length](./mods/length.md) — calculate length of string, array, object 62 | * [in](./mods/in.md) — find value in string or array 63 | * [match](./mods/match.md) — match string against a pattern. 64 | * [ematch](./mods/ematch.md) — perform a regular expression match. 65 | * [replace](./mods/replace.md) — replace all occurrences of the search string with the replacement string. 66 | * [ereplace](./mods/ereplace.md) — perform a regular expression search and replace. 67 | * [split](./mods/split.md) — split a string by string. 68 | * [esplit](./mods/esplit.md) — split string by a regular expression. 69 | * [join](./mods/join.md) — join array elements with a string. 70 | * allowed functions: `json_encode`, `json_decode`, `count`, `is_string`, `is_array`, `is_numeric`, `is_int`, `is_object`, 71 | `strtotime`, `gettype`, `is_double`, `ip2long`, `long2ip`, `strip_tags`, `nl2br` 72 | * or [add](./ext/extend.md#add-modifiers) yours 73 | 74 | *** 75 | 76 | ### Operators 77 | 78 | * [Arithmetic operators](./operators.md#arithmetic-operators) — `+`, `-`, `*`, `/`, `%` 79 | * [Logical operators](./operators.md#logical-operators) — `||`, `&&`, `!$var`, `and`, `or`, `xor` 80 | * [Comparison operators](./operators.md#comparison-operators) — `>`, `>=`, `<`, `<=`, `==`, `!=`, `!==`, `<>` 81 | * [Bitwise operators](./operators.md#bitwise-operators) — `|`, `&`, `^`, `~$var`, `>>`, `<<` 82 | * [Assignment operators](./operators.md#assignment-operators) — `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `>>=`, `<<=` 83 | * [String concatenation operators](./operators.md#string-operators) — `$str1 ~ $str2`, `$str1 ~~ $str2`, `$str1 ~= $str2` 84 | * [Ternary operators](./operators.md#ternary-operators) — `$a ? $b : $c`, `$a ! $b : $c`, `$a ?: $c`, `$a !: $c` 85 | * [Check operators](./operators.md#check-operators) — `$var?`, `$var!` 86 | * [Test operator](./operators.md#test-operator) — `is`, `is not` 87 | * [Containment operator](./operators.md#containment-operator) — `in`, `not in` 88 | 89 | *** 90 | 91 | ### Extends 92 | 93 | * [Extend Fenom](./ext/extend.md) 94 | * [Add-ons](./ext/extensions.md) 95 | -------------------------------------------------------------------------------- /tests/cases/Fenom/ExtendsTest.php: -------------------------------------------------------------------------------- 1 | fenom->getTemplate('extends/dynamic/child.3.tpl')->getBody()); 15 | } catch (\Exception $e) { 16 | echo "$e"; 17 | } 18 | exit; 19 | } 20 | 21 | public static function providerExtendsInvalid() 22 | { 23 | return array( 24 | array( 25 | '{extends "extends/dynamic/child.3.tpl"} {extends "extends/dynamic/child.3.tpl"}', 26 | 'Fenom\Error\CompileException', 27 | "Only one {extends} allowed" 28 | ), 29 | array( 30 | '{if true}{extends "extends/dynamic/child.3.tpl"}{/if}', 31 | 'Fenom\Error\CompileException', 32 | "Tag {extends} can not be nested" 33 | ), 34 | array( 35 | '{if true}{use "extends/dynamic/use.tpl"}{/if}', 36 | 'Fenom\Error\CompileException', 37 | "Tag {use} can not be nested" 38 | ), 39 | array('{use $use_this}', 'Fenom\Error\CompileException', "Invalid template name for tag {use}"), 40 | array('{block $use_this}{/block}', 'Fenom\Error\CompileException', "Invalid block name"), 41 | ); 42 | } 43 | 44 | public function testAutoExtendsManual() 45 | { 46 | $child = $this->fenom->getRawTemplate()->load('extends/auto/child.1.tpl', false); 47 | $child->extend('extends/auto/parent.tpl'); 48 | $child->compile(); 49 | $result = "Before header 50 | Content of the header 51 | Before body 52 | Child 1 Body 53 | Before footer 54 | Content of the footer"; 55 | $this->assertSame($result, $child->fetch(array())); 56 | } 57 | 58 | /** 59 | * @group testAutoExtends 60 | */ 61 | public function testAutoExtends() 62 | { 63 | $result = "Before header 64 | Child 2 header 65 | Before body 66 | Child 3 content 67 | Before footer 68 | Footer from use"; 69 | $this->assertSame( 70 | $result, 71 | $this->fenom->fetch( 72 | array( 73 | 'extends/auto/child.3.tpl', 74 | 'extends/auto/child.2.tpl', 75 | 'extends/auto/child.1.tpl', 76 | 'extends/auto/parent.tpl', 77 | ), 78 | array() 79 | ) 80 | ); 81 | } 82 | 83 | public function testStaticExtendLevel1() 84 | { 85 | $result = "Before header 86 | Content of the header 87 | Before body 88 | Child 1 Body 89 | Before footer 90 | Content of the footer"; 91 | $this->assertSame($result, $this->fenom->fetch('extends/static/child.1.tpl', array())); 92 | } 93 | 94 | public function testStaticExtendLevel3() 95 | { 96 | $result = "Before header 97 | Child 2 header 98 | Before body 99 | Child 3 content 100 | Before footer 101 | Footer from use"; 102 | $this->assertSame($result, $this->fenom->fetch('extends/static/child.3.tpl', array())); 103 | } 104 | 105 | public function testAutoAndStaticExtend() 106 | { 107 | $result = "Before header 108 | Child 2 header 109 | Before body 110 | Child 3 content 111 | Before footer 112 | Footer from use"; 113 | $this->assertSame( 114 | $result, 115 | $this->fenom->fetch( 116 | array( 117 | 'extends/auto/child.3.tpl', 118 | 'extends/auto/child.2.tpl', 119 | 'extends/auto/static/child.1.tpl' 120 | ), 121 | array() 122 | ) 123 | ); 124 | } 125 | 126 | public function testStaticExtendNested() 127 | { 128 | $result = "Before body 129 | 130 | Before header 131 | Child 1: Content of the header 132 | Before footer 133 | Content of the footer 134 | "; 135 | $this->assertSame($result, $this->fenom->fetch('extends/static/nested/child.1.tpl', array())); 136 | } 137 | 138 | public function testDynamicExtendLevel2() 139 | { 140 | $result = "Before header 141 | Child 2 header 142 | Before body 143 | Child 1 Body 144 | Before footer 145 | Footer from use"; 146 | $this->assertSame($result, $this->fenom->fetch('extends/dynamic/child.2.tpl', array())); 147 | } 148 | 149 | public function testDynamicExtendLevel3() 150 | { 151 | $result = "Before header 152 | Child 2 header 153 | Before body 154 | Child 3 content 155 | Before footer 156 | Footer from use"; 157 | $this->assertSame($result, $this->fenom->fetch('extends/dynamic/child.3.tpl', array())); 158 | } 159 | 160 | public function testDynamicExtendLevel4() 161 | { 162 | $result = "Before header 163 | Child 2 header 164 | Before body 165 | Child 3 content 166 | Before footer 167 | Footer from use"; 168 | $this->assertSame($result, $this->fenom->fetch('extends/dynamic/child.4.tpl', array())); 169 | } 170 | 171 | /** 172 | * @group static-invalid 173 | * @dataProvider providerExtendsInvalid 174 | */ 175 | public function testExtendsInvalid($code, $exception, $message, $options = 0) 176 | { 177 | $this->execError($code, $exception, $message, $options); 178 | } 179 | } 180 | 181 | -------------------------------------------------------------------------------- /docs/ru/readme.md: -------------------------------------------------------------------------------- 1 | Документация 2 | ============= 3 | 4 | 5 | 6 | **Внимание! Документация в режиме беты, тексты могут содержать опечатки** 7 | 8 | ### Fenom 9 | 10 | * [Быстрый старт](./start.md) 11 | * [Адаптеры для фрейморков](./adapters.md) 12 | * [Разработка Fenom](./dev/readme.md) 13 | * [Настройки](./configuration.md) 14 | * [Синтаксис](./syntax.md) 15 | * [Переменные](./syntax.md#Переменные) 16 | * [Значения](./syntax.md#Скалярные-значения) 17 | * [Массивы](./syntax.md#Массивы) 18 | * [Операторы](./operators.md) 19 | * [Модификаторы](./syntax.md#Модификаторы) 20 | * [Теги](./syntax.md#Теги) 21 | * [Параметры тегов](./syntax.md#Параметры-тегов) 22 | 23 | *** 24 | 25 | ### Теги 26 | 27 | [Использование](./syntax.md#Теги) тегов. 28 | 29 | * [set](./tags/set.md), [add](./tags/set.md#add) и [var](./tags/set.md#var) — определение значения переменной 30 | * [if](./tags/if.md), [elseif](./tags/if.md#elseif) и [else](./tags/if.md#else) — условный оператор 31 | * [foreach](./tags/foreach.md), [foreachelse](./tags/foreach.md#foreachelse), 32 | [break](./tags/foreach.md#break) и [continue](./tags/foreach.md#continue) — перебор элементов массива или объекта 33 | * [switch](./tags/switch.md) и [case](./tags/switch.md#case) — групповой условный оператор 34 | * [cycle](./tags/cycle.md) — циклицеский перебор массива значений 35 | * [include](./tags/include.md), [insert](./tags/include.md#insert) — вставляет и исполняет указанный шаблон 36 | * [extends](./tags/extends.md), [use](./tags/extends.md#use), 37 | [block](./tags/extends.md#block), [parent](./tags/extends.md#parent) и 38 | [paste](./tags/extends.md#paste) — [наследование](./inheritance.md) шаблонов 39 | * [filter](./tags/filter.md) — применение модификаторов к фрагменту шаблона 40 | * [ignore](./tags/ignore.md) — игнорирование тегов Fenom 41 | * [macro](./tags/macro.md) и [import](./tags/macro.md#macro) — пользовательские функции шаблонов 42 | * [autoescape](./tags/autoescape.md) — экранирует фрагмент шаблона 43 | * [raw](./tags/raw.md) — отключает экранирование фрагмента шаблона 44 | * [unset](./tags/unset.md) — удаляет переменные 45 | * или [добавьте](./ext/extend.md#Добавление-тегов) свои 46 | 47 | Устаревшие теги 48 | 49 | * [for](./tags/for.md), `forelse`, `break` and `continue` — цикл 50 | 51 | *** 52 | 53 | ### Модификаторы 54 | 55 | [Использование](./syntax.md#modifiers) модификаторов. 56 | 57 | * [upper](./mods/upper.md) aka `up` — конвертирование строки в верхний регистр 58 | * [lower](./mods/lower.md) aka `low` — конвертирование строки в нижний регистр 59 | * [date_format](./mods/date_format.md) - форматирует дату, штамп времени через strftime() функцию 60 | * [date](./mods/date.md) - форматирует дату, штамп времени через date() функцию 61 | * [truncate](./mods/truncate.md) — обрезает текст до указанной длины 62 | * [escape](./mods/escape.md) aka `e` — экранирует строку 63 | * [unescape](./mods/unescape.md) — убирает экранирование строки 64 | * [strip](./mods/strip.md) — удаляет лишние пробелы 65 | * [length](./mods/length.md) — подсчитывает длину строки, массива, объекта 66 | * [in](./mods/in.md) — проверяет наличие значения в массиве 67 | * [match](./mods/match.md) — проверяет соответствие паттерну 68 | * [ematch](./mods/ematch.md) — проверяет соответствие регулярному выражению 69 | * [replace](./mods/replace.md) — заменяет все вхождения подстроки на строку замену 70 | * [ereplace](./mods/ereplace.md) — заменяет все соответсвия регулярному выражению на строку замену. 71 | * [split](./mods/split.md) — разбивает строку по подстроке 72 | * [esplit](./mods/esplit.md) — разбивает строку по регулярному выражению 73 | * [join](./mods/join.md) — объединяет массив в строку 74 | * так же разрешены функции: `json_encode`, `json_decode`, `count`, `is_string`, `is_array`, `is_numeric`, `is_int`, `is_object`, 75 | `strtotime`, `gettype`, `is_double`, `ip2long`, `long2ip`, `strip_tags`, `nl2br` 76 | * или [добавьте](./ext/extend.md#Добавление-модификаторов) свои 77 | 78 | *** 79 | 80 | ### Операторы 81 | 82 | * [Арифметические операторы](./operators.md#Арифметические-операторы) — `+`, `-`, `*`, `/`, `%` 83 | * [Логические операторы](./operators.md#Логические-операторы) — `||`, `&&`, `!$var`, `and`, `or`, `xor` 84 | * [Операторы сравнения](./operators.md#Операторы-сравнения) — `>`, `>=`, `<`, `<=`, `==`, `!=`, `!==`, `<>` 85 | * [Битовые операторы](./operators.md#Битовые-операторы) — `|`, `&`, `^`, `~$var`, `>>`, `<<` 86 | * [Операторы присвоения](./operators.md#Операторы-присвоения) — `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `>>=`, `<<=` 87 | * [Строковые операторы](./operators.md#Строковые-операторы) — `$str1 ~ $str2`, `$str1 ~~ $str2`, `$str1 ~= $str2` 88 | * [Тернарные операторы](./operators.md#Тернарные-операторы) — `$a ? $b : $c`, `$a ! $b : $c`, `$a ?: $c`, `$a !: $c` 89 | * [Проверяющие операторы](./operators.md#Проверяющие-операторы) — `$var?`, `$var!` 90 | * [Оператор тестирования](./operators.md#Оператор-тестирования) — `is`, `is not` 91 | * [Оператор содержания](./operators.md#Оператор-содержания) — `in`, `not in` 92 | 93 | *** 94 | 95 | ### Расширение 96 | 97 | * [Источники шаблонов](./ext/extend.md#Источники-шаблонов) 98 | * [Добавление модификаторов](./ext/extend.md#Добавление-модификаторов) 99 | * [Добавление тегов](./ext/extend.md#Добавление-тегов) 100 | * [Расширение тестового оператора](./ext/extend.md#Расширение-тестового-оператора) 101 | * [Расширение глобальной переменной](./ext/extend.md#Расширение-глобальной-переменной) 102 | * [Расширение Fenom](./ext/extend.md) 103 | * [Add-ons](./ext/extensions.md) 104 | -------------------------------------------------------------------------------- /tests/cases/Fenom/TokenizerTest.php: -------------------------------------------------------------------------------- 1 | assertSame('T_DOUBLE_COLON', Tokenizer::getName(T_DOUBLE_COLON)); 13 | $this->assertSame('++', Tokenizer::getName('++')); 14 | $this->assertSame('T_STRING', Tokenizer::getName(array(T_STRING, 'all', "", 1, "T_STRING"))); 15 | $this->assertNull(Tokenizer::getName(false)); 16 | } 17 | 18 | public function testTokens() 19 | { 20 | $code = 'hello, please resolve this example: sin($x)+tan($x*$t) = {U|[0,1]}'; 21 | $tokens = new Tokenizer($code); 22 | $this->assertSame(27, $tokens->count()); 23 | $this->assertSame($tokens, $tokens->back()); 24 | $this->assertSame(T_STRING, $tokens->key()); 25 | $this->assertSame("hello", $tokens->current()); 26 | $this->assertSame(1, $tokens->getLine()); 27 | 28 | 29 | $this->assertTrue($tokens->isNext(",")); 30 | 31 | $this->assertFalse($tokens->isNext("=")); 32 | $this->assertFalse($tokens->isNext(T_STRING)); 33 | $this->assertFalse($tokens->isNext($tokens::MACRO_UNARY)); 34 | 35 | $this->assertFalse($tokens->isNext("=", T_STRING, $tokens::MACRO_UNARY)); 36 | $this->assertTrue($tokens->isNext("=", T_STRING, $tokens::MACRO_UNARY, ",")); 37 | 38 | $this->assertSame(",", $tokens->getNext()); 39 | $this->assertSame(",", $tokens->key()); 40 | $this->assertSame("please", $tokens->getNext(T_STRING)); 41 | $this->assertSame( 42 | array( 43 | T_STRING, 44 | 'please', 45 | ' ', 46 | 1 47 | ), 48 | $tokens->currToken() 49 | ); 50 | $this->assertSame("resolve", $tokens->getNext($tokens::MACRO_UNARY, T_STRING)); 51 | 52 | $tokens->next(); 53 | $tokens->next(); 54 | $tokens->next(); 55 | 56 | $this->assertSame(":", $tokens->current()); 57 | $this->assertSame(":", $tokens->key()); 58 | 59 | 60 | $this->assertSame("sin", $tokens->getNext($tokens::MACRO_STRING)); 61 | $this->assertSame("sin", $tokens->current()); 62 | $this->assertTrue($tokens->isPrev(":")); 63 | $this->assertSame(T_STRING, $tokens->key()); 64 | $this->assertTrue($tokens->is(T_STRING)); 65 | $this->assertTrue($tokens->is($tokens::MACRO_STRING)); 66 | $this->assertFalse($tokens->is($tokens::MACRO_EQUALS)); 67 | $this->assertFalse($tokens->is(T_DNUMBER)); 68 | $this->assertFalse($tokens->is(":")); 69 | $this->assertSame("(", $tokens->getNext("(", ")")); 70 | $this->assertTrue($tokens->hasBackList(T_STRING, ':')); 71 | $this->assertFalse($tokens->hasBackList(T_LNUMBER, ':')); 72 | 73 | $tokens->next(); 74 | $tokens->next(); 75 | $this->assertSame("+", $tokens->getNext($tokens::MACRO_BINARY)); 76 | 77 | $this->assertSame($code, $tokens->getSnippetAsString(-100, 100)); 78 | $this->assertSame('+', $tokens->getSnippetAsString(100, -100)); 79 | $this->assertSame('sin($x)+tan($x*$t)', $tokens->getSnippetAsString(-4, 6)); 80 | $this->assertSame('}', $tokens->end()->current()); 81 | } 82 | 83 | public function testComplexTokens() 84 | { 85 | $text = "one\\two"; 86 | $tokens = new Tokenizer($text); 87 | $this->assertSame("one", $tokens->current()); 88 | $this->assertSame("\\", $tokens->next()->current()); 89 | $this->assertSame("two", $tokens->next()->current()); 90 | $this->assertFalse($tokens->next()->valid()); 91 | 92 | $text = "\\one\\two"; 93 | 94 | $tokens = new Tokenizer($text); 95 | $this->assertSame("\\", $tokens->current()); 96 | $this->assertSame("one", $tokens->next()->current()); 97 | $this->assertSame("\\", $tokens->next()->current()); 98 | $this->assertSame("two", $tokens->next()->current()); 99 | $this->assertFalse($tokens->next()->valid()); 100 | } 101 | 102 | public function testSkip() 103 | { 104 | $text = "1 foo: bar ( 3 + double ) "; 105 | $tokens = new Tokenizer($text); 106 | 107 | $tokens->skip()->skip(T_STRING)->skip(':'); 108 | try { 109 | $tokens->skip(T_STRING)->skip('(')->skip(':'); 110 | } catch (\Exception $e) { 111 | $this->assertInstanceOf('Fenom\Error\UnexpectedTokenException', $e); 112 | $this->assertStringStartsWith("Unexpected token '3' in expression, expect ':'", $e->getMessage()); 113 | } 114 | $this->assertTrue($tokens->valid()); 115 | $this->assertSame("3", $tokens->current()); 116 | 117 | $this->assertSame(T_LNUMBER, $tokens->key()); 118 | $this->assertSame($tokens, $tokens->next()); 119 | $tokens->next(); 120 | $this->assertSame("double", $tokens->getAndNext()); 121 | $this->assertSame(")", $tokens->current()); 122 | $this->assertTrue($tokens->isLast()); 123 | $this->assertSame($tokens, $tokens->next()); 124 | $tokens->p = 1000; 125 | $this->assertSame($tokens, $tokens->next()); 126 | $tokens->p = -1000; 127 | $this->assertSame($tokens, $tokens->back()); 128 | } 129 | 130 | public function testFixFloats() { 131 | $text = "1..3"; 132 | $tokens = new Tokenizer($text); 133 | $this->assertTrue($tokens->is(T_LNUMBER)); 134 | $this->assertTrue($tokens->next()->is('.')); 135 | $this->assertTrue($tokens->next()->is('.')); 136 | $this->assertTrue($tokens->next()->is(T_LNUMBER)); 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /src/Fenom/Provider.php: -------------------------------------------------------------------------------- 1 | isFile()) { 42 | if (!str_starts_with($file->getBasename(), ".")) { 43 | unlink($file->getRealPath()); 44 | } 45 | } elseif ($file->isDir()) { 46 | rmdir($file->getRealPath()); 47 | } 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * Recursive remove directory 54 | * 55 | * @param string $path 56 | */ 57 | public static function rm(string $path) 58 | { 59 | self::clean($path); 60 | if (is_dir($path)) { 61 | rmdir($path); 62 | } 63 | } 64 | 65 | /** 66 | * @param string $template_dir directory of templates 67 | * @throws \LogicException if directory doesn't exist 68 | */ 69 | public function __construct(string $template_dir) 70 | { 71 | if ($_dir = realpath($template_dir)) { 72 | $this->_path = $_dir; 73 | } else { 74 | throw new \LogicException("Template directory {$template_dir} doesn't exists"); 75 | } 76 | } 77 | 78 | /** 79 | * Disable PHP cache for files. PHP cache some operations with files then script works. 80 | * @see http://php.net/manual/en/function.clearstatcache.php 81 | * @param bool $status 82 | */ 83 | public function setClearCachedStats(bool $status = true) { 84 | $this->_clear_cache = $status; 85 | } 86 | 87 | /** 88 | * Get source and mtime of template by name 89 | * @param string $tpl 90 | * @param float|null $time load last modified time 91 | * @return string 92 | */ 93 | public function getSource(string $tpl, ?float &$time): string 94 | { 95 | $tpl = $this->_getTemplatePath($tpl); 96 | if($this->_clear_cache) { 97 | clearstatcache(true, $tpl); 98 | } 99 | $time = filemtime($tpl); 100 | return file_get_contents($tpl); 101 | } 102 | 103 | /** 104 | * Get last modified of template by name 105 | * @param string $tpl 106 | * @return float 107 | */ 108 | public function getLastModified(string $tpl): float 109 | { 110 | $tpl = $this->_getTemplatePath($tpl); 111 | if($this->_clear_cache) { 112 | clearstatcache(true, $tpl); 113 | } 114 | return (float)filemtime($tpl); 115 | } 116 | 117 | /** 118 | * Get all names of templates from provider. 119 | * 120 | * @param string $extension all templates must have this extension, default .tpl 121 | * @return iterable 122 | */ 123 | public function getList(string $extension = "tpl"): iterable 124 | { 125 | $list = array(); 126 | $iterator = new \RecursiveIteratorIterator( 127 | new \RecursiveDirectoryIterator($this->_path, 128 | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS), 129 | \RecursiveIteratorIterator::CHILD_FIRST 130 | ); 131 | $path_len = strlen($this->_path); 132 | foreach ($iterator as $file) { 133 | /* @var \SplFileInfo $file */ 134 | if ($file->isFile() && $file->getExtension() == $extension) { 135 | $list[] = substr($file->getPathname(), $path_len + 1); 136 | } 137 | } 138 | return $list; 139 | } 140 | 141 | /** 142 | * Get template path 143 | * @param string $tpl 144 | * @return string 145 | * @throws \RuntimeException 146 | */ 147 | protected function _getTemplatePath(string $tpl): string 148 | { 149 | $path = realpath($this->_path . "/" . $tpl); 150 | if ($path && str_starts_with($path, $this->_path)) { 151 | return $path; 152 | } else { 153 | throw new \RuntimeException("Template $tpl not found"); 154 | } 155 | } 156 | 157 | /** 158 | * @param string $tpl 159 | * @return bool 160 | */ 161 | public function templateExists(string $tpl): bool 162 | { 163 | return ($path = realpath($this->_path . "/" . $tpl)) && str_starts_with($path, $this->_path); 164 | } 165 | 166 | /** 167 | * Verify templates (check change time) 168 | * 169 | * @param array $templates [template_name => modified, ...] By conversation, you may trust the template's name 170 | * @return bool 171 | */ 172 | public function verify(array $templates): bool 173 | { 174 | foreach ($templates as $template => $mtime) { 175 | $template = $this->_path . '/' . $template; 176 | if($this->_clear_cache) { 177 | clearstatcache(true, $template); 178 | } 179 | if (@filemtime($template) != $mtime) { 180 | return false; 181 | } 182 | 183 | } 184 | return true; 185 | } 186 | } -------------------------------------------------------------------------------- /docs/en/dev/schema.md: -------------------------------------------------------------------------------- 1 | How Fenom works 2 | =============== 3 | 4 | ``` 5 | 6 | use Fenom; 7 | use Fenom\Render; 8 | use Fenom\Template; 9 | use Fenom\Tokenizer; 10 | 11 | ______________________________ 12 | | | 13 | | Fenom::display($tpl, $var) | 14 | |____________________________| 15 | | 16 | | search the template 17 | ______________|___________________________ 18 | | Template loaded into Fenom::$_storage? | 19 | | Fenom::getTemplate($tpl) | 20 | |________________________________________| 21 | | | 22 | | yes | no 23 | ______________|__________ | 24 | | Render the template | | 25 | | Render::display($tpl) | | 26 | |_______________________| | 27 | | | 28 | | (hot start) | 29 | | ______________________________|__________________ 30 | | | Template already compiled and stored in cache | 31 | | | Fenom::getTemplate($template) | 32 | | |_______________________________________________| 33 | | | | 34 | | | yes | no 35 | | ____________|_______________ | 36 | | | Load template from cache | not found | 37 | | | Fenom::_load(...) |-------------->| 38 | | |__________________________| | 39 | | | | 40 | | | found | 41 | | ____________|___________ | 42 | | | Validate template | invalid | 43 | | | Render::isValid(...) |------------------>| 44 | | |______________________| | 45 | | | | 46 | | | valid | 47 | | ____________|____________ | 48 | | | Render the template | | 49 | |<----| Render::display(...) | | 50 | | |_______________________| | 51 | | | 52 | | _____________________________ ________|___________________ 53 | | | Initialize compiler | | Compile the template | 54 | | | Template::load($tpl) |<-----| Fenom::compile($tpl) | 55 | | |___________________________| |__________________________| 56 | | | 57 | | ____________|________________ 58 | | | Load template source | 59 | | | Provider::getSource($tpl) | 60 | | |___________________________| 61 | | | 62 | | ____________|______________ 63 | | | Start compilation | 64 | | | Template::compile($tpl) | 65 | | |_________________________| 66 | | | 67 | | ____________|______________ 68 | | | Search template tag | 69 | | | Template::compile($tpl) |<------------------------------------------------------| 70 | | |_________________________| | 71 | | | | | 72 | | | not found | found | 73 | | | _____________|_______________ _______________________________ | 74 | | | | Tokenize the tag's code | | Parse the tag | | 75 | | | | new Tokenizer($tag) |--->| Template::parseTag($tokens) | | 76 | | | |___________________________| |_____________________________| | 77 | | | | | | 78 | | | is tag | | is expression | 79 | | | _______________________________ | _______________|________________ | 80 | | | | Detect tag name | | | Detect expression | | 81 | | | | Template::parseAct($tokens) |<--- | Template::parseAct($tokens) | | 82 | | | | Get callback by tag name | | Parse expression | | 83 | | | | Fenom::getTag($tag_name) | | Template::parseExpr($tokens) | | 84 | | | |_____________________________| |______________________________| | 85 | | | | | | 86 | | | | found | | 87 | | | _______________|_______________ | | 88 | | | | Invoke callback | | | 89 | | | | Template::parseAct($tokens) | | | 90 | | | |_____________________________| | | 91 | | | | | | 92 | | | _______________|________________ | | 93 | | | | Append code to template | | | 94 | | | | Template::_appendCode($code) |<----------------------- | 95 | | | |______________________________| | 96 | | | | | 97 | | | _______________|___________ | 98 | | | | Finalize the tag | starts search next tag | 99 | | | | Template::compile($tpl) |>------------------------------------------------ 100 | | | |_________________________| 101 | | | 102 | | __|___________________________________ 103 | | | Store template to cache | 104 | | | Fenom::compile($tpl) | 105 | | | Store template to Fenom::$_storage | 106 | | | Fenom::getTemplate($tpl) | 107 | | |____________________________________| 108 | | | 109 | | ____________|_____________ 110 | | | Render the template | 111 | | | Template::display(...) | 112 | | |________________________| 113 | | | 114 | | | (cold start) 115 | __|_________|________ 116 | | | 117 | | DONE | 118 | |___________________| 119 | 120 | ``` -------------------------------------------------------------------------------- /docs/ru/dev/schema.md: -------------------------------------------------------------------------------- 1 | How Fenom works 2 | =============== 3 | 4 | ``` 5 | 6 | use Fenom; 7 | use Fenom\Render; 8 | use Fenom\Template; 9 | use Fenom\Tokenizer; 10 | 11 | ______________________________ 12 | | | 13 | | Fenom::display($tpl, $var) | 14 | |____________________________| 15 | | 16 | | search the template 17 | ______________|___________________________ 18 | | Template loaded into Fenom::$_storage? | 19 | | Fenom::getTemplate($tpl) | 20 | |________________________________________| 21 | | | 22 | | yes | no 23 | ______________|__________ | 24 | | Render the template | | 25 | | Render::display($tpl) | | 26 | |_______________________| | 27 | | | 28 | | (hot start) | 29 | | ______________________________|__________________ 30 | | | Template already compiled and stored in cache | 31 | | | Fenom::getTemplate($template) | 32 | | |_______________________________________________| 33 | | | | 34 | | | yes | no 35 | | ____________|_______________ | 36 | | | Load template from cache | not found | 37 | | | Fenom::_load(...) |-------------->| 38 | | |__________________________| | 39 | | | | 40 | | | found | 41 | | ____________|___________ | 42 | | | Validate template | invalid | 43 | | | Render::isValid(...) |------------------>| 44 | | |______________________| | 45 | | | | 46 | | | valid | 47 | | ____________|____________ | 48 | | | Render the template | | 49 | |<----| Render::display(...) | | 50 | | |_______________________| | 51 | | | 52 | | _____________________________ ________|___________________ 53 | | | Initialize compiler | | Compile the template | 54 | | | Template::load($tpl) |<-----| Fenom::compile($tpl) | 55 | | |___________________________| |__________________________| 56 | | | 57 | | ____________|________________ 58 | | | Load template source | 59 | | | Provider::getSource($tpl) | 60 | | |___________________________| 61 | | | 62 | | ____________|______________ 63 | | | Start compilation | 64 | | | Template::compile($tpl) | 65 | | |_________________________| 66 | | | 67 | | ____________|______________ 68 | | | Search template tag | 69 | | | Template::compile($tpl) |<------------------------------------------------------| 70 | | |_________________________| | 71 | | | | | 72 | | | not found | found | 73 | | | _____________|_______________ _______________________________ | 74 | | | | Tokenize the tag's code | | Parse the tag | | 75 | | | | new Tokenizer($tag) |--->| Template::parseTag($tokens) | | 76 | | | |___________________________| |_____________________________| | 77 | | | | | | 78 | | | is tag | | is expression | 79 | | | _______________________________ | _______________|________________ | 80 | | | | Detect tag name | | | Detect expression | | 81 | | | | Template::parseAct($tokens) |<--- | Template::parseAct($tokens) | | 82 | | | | Get callback by tag name | | Parse expression | | 83 | | | | Fenom::getTag($tag_name) | | Template::parseExpr($tokens) | | 84 | | | |_____________________________| |______________________________| | 85 | | | | | | 86 | | | | found | | 87 | | | _______________|_______________ | | 88 | | | | Invoke callback | | | 89 | | | | Template::parseAct($tokens) | | | 90 | | | |_____________________________| | | 91 | | | | | | 92 | | | _______________|________________ | | 93 | | | | Append code to template | | | 94 | | | | Template::_appendCode($code) |<----------------------- | 95 | | | |______________________________| | 96 | | | | | 97 | | | _______________|___________ | 98 | | | | Finalize the tag | starts search next tag | 99 | | | | Template::compile($tpl) |>------------------------------------------------ 100 | | | |_________________________| 101 | | | 102 | | __|___________________________________ 103 | | | Store template to cache | 104 | | | Fenom::compile($tpl) | 105 | | | Store template to Fenom::$_storage | 106 | | | Fenom::getTemplate($tpl) | 107 | | |____________________________________| 108 | | | 109 | | ____________|_____________ 110 | | | Render the template | 111 | | | Template::display(...) | 112 | | |________________________| 113 | | | 114 | | | (cold start) 115 | __|_________|________ 116 | | | 117 | | DONE | 118 | |___________________| 119 | 120 | ``` -------------------------------------------------------------------------------- /src/Fenom/Render.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class Render extends \ArrayObject 20 | { 21 | private static array $_props = [ 22 | "name" => "runtime", 23 | "base_name" => "", 24 | "scm" => false, 25 | "time" => 0, 26 | "depends" => [], 27 | "macros" => [] 28 | ]; 29 | /** 30 | * @var \Closure|null 31 | */ 32 | protected ?\Closure $_code = null; 33 | /** 34 | * Template name 35 | * @var string 36 | */ 37 | protected mixed $_name = 'runtime'; 38 | /** 39 | * Provider's schema 40 | * @var string|null 41 | */ 42 | protected ?string $_scm = null; 43 | /** 44 | * Basic template name 45 | * @var string 46 | */ 47 | protected string $_base_name = 'runtime'; 48 | /** 49 | * @var Fenom 50 | */ 51 | protected Fenom $_fenom; 52 | /** 53 | * Timestamp of compilation 54 | * @var float 55 | */ 56 | protected float $_time = 0.0; 57 | 58 | /** 59 | * @var array depends list 60 | */ 61 | protected array $_depends = []; 62 | 63 | /** 64 | * @var int template options (see Fenom options) 65 | */ 66 | protected int $_options = 0; 67 | 68 | /** 69 | * Template provider 70 | * @var ProviderInterface 71 | */ 72 | protected ProviderInterface $_provider; 73 | 74 | /** 75 | * @var \Closure[] 76 | */ 77 | protected array $_macros; 78 | 79 | /** 80 | * @param Fenom $fenom 81 | * @param \Closure $code template body 82 | * @param array $props 83 | */ 84 | public function __construct(Fenom $fenom, \Closure $code, array $props = array()) 85 | { 86 | parent::__construct(); 87 | $this->_fenom = $fenom; 88 | $props += self::$_props; 89 | $this->_name = $props["name"]; 90 | $this->_base_name = $props["base_name"]; 91 | $this->_scm = $props["scm"]; 92 | $this->_time = $props["time"]; 93 | $this->_depends = $props["depends"]; 94 | $this->_macros = $props["macros"]; 95 | $this->_code = $code; 96 | } 97 | 98 | /** 99 | * Get template storage 100 | * @return \Fenom 101 | */ 102 | public function getStorage(): Fenom 103 | { 104 | return $this->_fenom; 105 | } 106 | 107 | /** 108 | * Get list of dependencies. 109 | * @return array 110 | */ 111 | public function getDepends(): array 112 | { 113 | return $this->_depends; 114 | } 115 | 116 | /** 117 | * Get schema name 118 | * @return string|null 119 | */ 120 | public function getScm(): ?string 121 | { 122 | return $this->_scm; 123 | } 124 | 125 | /** 126 | * Get provider of template source 127 | * @return ProviderInterface 128 | */ 129 | public function getProvider(): ProviderInterface 130 | { 131 | return $this->_fenom->getProvider($this->_scm); 132 | } 133 | 134 | /** 135 | * Get name without schema 136 | * @return string 137 | */ 138 | public function getBaseName(): string 139 | { 140 | return $this->_base_name; 141 | } 142 | 143 | /** 144 | * Get parse options 145 | * @return int 146 | */ 147 | public function getOptions(): int 148 | { 149 | return $this->_options; 150 | } 151 | 152 | /** 153 | * @return string 154 | */ 155 | public function __toString() 156 | { 157 | return $this->_name; 158 | } 159 | 160 | /** 161 | * Get template name 162 | * @return string 163 | */ 164 | public function getName(): string 165 | { 166 | return $this->_name; 167 | } 168 | 169 | public function getTime() 170 | { 171 | return $this->_time; 172 | } 173 | 174 | 175 | /** 176 | * Validate template 177 | * @return bool 178 | */ 179 | public function isValid(): bool 180 | { 181 | foreach ($this->_depends as $scm => $templates) { 182 | $provider = $this->_fenom->getProvider($scm); 183 | if(count($templates) === 1) { 184 | if ($provider->getLastModified(key($templates)) !== $this->_time) { 185 | return false; 186 | } 187 | } else { 188 | if (!$provider->verify($templates)) { 189 | return false; 190 | } 191 | } 192 | } 193 | return true; 194 | } 195 | 196 | /** 197 | * Get internal macro 198 | * @param $name 199 | * @throws \RuntimeException 200 | * @return mixed 201 | */ 202 | public function getMacro($name): mixed 203 | { 204 | if (empty($this->_macros[$name])) { 205 | throw new \RuntimeException('macro ' . $name . ' not found'); 206 | } 207 | return $this->_macros[$name]; 208 | } 209 | 210 | /** 211 | * Execute template and write into output 212 | * @param array $values for template 213 | * @return array 214 | * @throws TemplateException 215 | */ 216 | public function display(array $values): array 217 | { 218 | try { 219 | $this->_code->__invoke($values, $this); 220 | } catch (\Throwable $e) { 221 | throw new Fenom\Error\TemplateException("unhandled exception in the template `{$this->getName()}`: {$e->getMessage()}", 0, $e); 222 | } 223 | return $values; 224 | } 225 | 226 | /** 227 | * Execute template and return result as string 228 | * @param array $values for template 229 | * @return string 230 | * @throws \Exception 231 | */ 232 | public function fetch(array $values): string 233 | { 234 | ob_start(); 235 | try { 236 | $this->display($values); 237 | return ob_get_clean(); 238 | } catch (\Exception $e) { 239 | ob_end_clean(); 240 | throw $e; 241 | } 242 | } 243 | 244 | /** 245 | * Stub 246 | * @param string $method 247 | * @param mixed $args 248 | * @throws \BadMethodCallException 249 | */ 250 | public function __call(string $method, mixed $args) 251 | { 252 | throw new \BadMethodCallException("Unknown method " . $method); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /docs/en/ext/extend.md: -------------------------------------------------------------------------------- 1 | Extends Fenom 2 | ============= 3 | 4 | *TODO* 5 | 6 | # Add tags 7 | 8 | В шаблонизаторе принято различать два типа тегов: _компиляторы_ и _функции_. 9 | Compilers invokes during compilation template to PHP source and have to 10 | Компиляторы вызываются во время преобразования кода шаблона в PHP код и возвращяю PHP код который будет вставлен вместо тега. 11 | А функции вызываются непременно в момент выполнения шаблона и возвращают непосредственно данные которые будут отображены. 12 | Среди тегов как и в HTML есть строчные и блоковые теги. 13 | 14 | ## Inline function 15 | 16 | Примитивное добавление функции можно осуществить следующим образом: 17 | 18 | ```php 19 | $fenom->addFunction(string $function_name, callable $callback[, callable $parser]); 20 | ``` 21 | 22 | В данном случае запускается стандартный парсер, который автоматически разберет аргументы тега, которые должны быть в формате HTML аттрибутов и отдаст их в функцию ассоциативным массивом: 23 | ```php 24 | $fenom->addFunction("some_function", function (array $params) { /* ... */ }); 25 | ``` 26 | При необходимости можно переопределить парсер на произвольный: 27 | ```php 28 | $fenom->addFunction("some_function", $some_function, function (Fenom\Tokenizer $tokenizer, Fenom\Template $template) { /* parse tag */}); 29 | ``` 30 | Существует более простой способ добавления произвольной функции: 31 | 32 | ```php 33 | $fenom->addFunctionSmarty(string $function_name, callable $callback); 34 | ``` 35 | 36 | В данном случае парсер сканирует список аргументов коллбека и попробует сопоставить с аргументами тега. 37 | 38 | ```php 39 | // ... class XYCalcs .. 40 | public static function calc($x, $y = 5) { /* ... */} 41 | // ... 42 | $fenom->addFunctionSmart('calc', 'XYCalcs::calc'); 43 | ``` 44 | then 45 | ```smarty 46 | {calc x=$top y=50} or {calc y=50 x=$top} is XYCalcs::calc($top, 50) 47 | {calc x=$top} or {calc $top} is XYCalcs::calc($top) 48 | ``` 49 | Таким образом вы успешно можете добавлять Ваши функции или методы. 50 | 51 | ## Block function 52 | 53 | Добавление блоковой функции аналогичен добавлению строковой за исключением того что есть возможность указать парсер для закрывающего тега. 54 | 55 | ```php 56 | $fenom->addBlockFunction(string $function_name, callable $callback[, callable $parser_open[, callable $parser_close]]); 57 | ``` 58 | 59 | Сам коллбек принимает первым аргументом контент между открывающим и закрывающим тегом, а вторым аргументом - ассоциативный массив из аргуметов тега: 60 | ```php 61 | $fenom->addBlockFunction('some_block_function', function ($content, array $params) { /* ... */}); 62 | ``` 63 | 64 | ## Inline compiler 65 | 66 | Добавление строчного компилятора осуществляеться очень просто: 67 | 68 | ```php 69 | $fenom->addCompiler(string $compiler, callable $parser); 70 | ``` 71 | 72 | Парсер должен принимать `Fenom\Tokenizer $tokenizer`, `Fenom\Template $template` и возвращать PHP код. 73 | Компилятор так же можно импортировать из класса автоматически 74 | 75 | ```php 76 | $fenom->addCompilerSmart(string $compiler, $storage); 77 | ``` 78 | 79 | `$storage` может быть как классом так и объектом. В данном случае шаблонизатор будет искать метод `tag{$compiler}`, который будет взят в качестве парсера тега. 80 | 81 | ## Block compiler 82 | 83 | Добавление блочного компилятора осуществяется двумя способами. Первый 84 | 85 | ```php 86 | $fenom->addBlockCompiler(string $compiler, array $parsers, array $tags); 87 | ``` 88 | 89 | где `$parser` ассоциативный массив `["open" => parser, "close" => parser]`, сождержащий парсер на открывающий и на закрывающий тег, а `$tags` содержит список внутренних тегов в формате `["tag_name"] => parser`, которые могут быть использованы только с этим компилятором. 90 | Второй способ добавления парсера через импортирование из класса или объекта методов: 91 | 92 | ```php 93 | $fenom->addBlockCompilerSmart(string $compiler, $storage, array $tags, array $floats); 94 | ``` 95 | 96 | # Add modifiers 97 | 98 | ``` 99 | $fenom->addModifier(string $modifier, callable $callback); 100 | ``` 101 | 102 | * `$modifier` - название модификатора, которое будет использоваться в шаблоне 103 | * `$callback` - коллбек, который будет вызван для изменения данных 104 | 105 | For example: 106 | 107 | ```smarty 108 | {$variable|my_modifier:$param1:$param2} 109 | ``` 110 | 111 | ```php 112 | $fenom->addModifier('my_modifier', function ($variable, $param1, $param2) { 113 | // ... 114 | }); 115 | ``` 116 | 117 | # Extends test operator 118 | 119 | ```php 120 | $fenom->addTest($name, $code); 121 | ?> 122 | ``` 123 | 124 | # Add template provider 125 | 126 | Бывает так что шаблны не хранятся на файловой сиситеме, а хранятся в некотором хранилище, например, в базе данных MySQL. 127 | В этом случае шаблонизатору нужно описать как забирать шаблоны из хранилища, как проверять дату изменения шаблона и где хранить кеш шаблонов (опционально). 128 | Эту задачу берут на себя Providers, это объекты реальзующие интерфейс `Fenom\ProviderInterface`. 129 | 130 | # Extends accessor 131 | 132 | # Extends cache 133 | 134 | Изначально Fenom не расчитывался на то что кеш скомпиленых шаблонов может располагаться не на файловой системе. 135 | Однако, в теории, есть возможность реализовать свое кеширование для скомпиленых шаблонов без переопределения шаблонизатора. 136 | Речь идет о своем протоколе, отличным от `file://`, который [можно определить](http://php.net/manual/en/class.streamwrapper.php) в PHP. 137 | 138 | Ваш протол должени иметь класс реализации протокола как указан в документации [Stream Wrapper](http://www.php.net/manual/en/class.streamwrapper.php). 139 | Класс протокола может иметь не все указанные в документации методы. Вот список методов, необходимых шаблонизатору: 140 | 141 | * [CacheStreamWrapper::stream_open](http://www.php.net/manual/en/streamwrapper.stream-open.php) 142 | * [CacheStreamWrapper::stream_write](http://www.php.net/manual/en/streamwrapper.stream-write.php) 143 | * [CacheStreamWrapper::stream_close](http://www.php.net/manual/en/streamwrapper.stream-close.php) 144 | * [CacheStreamWrapper::rename](http://www.php.net/manual/en/streamwrapper.rename.php) 145 | 146 | For `include`: 147 | 148 | * [CacheStreamWrapper::stream_stat](http://www.php.net/manual/en/streamwrapper.stream-stat.php) 149 | * [CacheStreamWrapper::stream_read](http://www.php.net/manual/en/streamwrapper.stream-read.php) 150 | * [CacheStreamWrapper::stream_eof](http://www.php.net/manual/en/streamwrapper.stream-eof.php) 151 | 152 | **Note** 153 | (On 2014-05-13) Zend OpCacher doesn't support custom protocols except `file://` and `phar://`. 154 | 155 | For example, 156 | 157 | ```php 158 | $this->setCacheDir("redis://hash/compiled/"); 159 | ``` 160 | 161 | * `$cache = fopen("redis://hash/compiled/XnsbfeDnrd.php", "w");` 162 | * `fwrite($cache, "...