diff --git a/.gitignore b/.gitignore
index 0a4e790..90bb033 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,17 +1,9 @@
.idea
vendor
-coverage
build
composer.lock
composer.phar
tests/resources/compile/*
!.gitkeep
tests/resources/template/*
-!.gitkeep
-sandbox/compiled/*
-!.gitkeep
-benchmark/compile/*
-benchmark/templates/inheritance/smarty
-benchmark/templates/inheritance/twig
-benchmark/sandbox/compiled/*
-!.gitkeep
\ No newline at end of file
+sandbox/compiled/*
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 96f162c..2a82ed6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,24 @@
Changelog
=========
+## 2.4.0 (2015-01-02)
+
+- Fix bugs #120, #104, #119
+- Add `~~` operator. Concatenation with space.
+- Improve #126. Disable clearcachestats() by default in Fenom\Provider. clearcachestats() may be enabled.
+- Improve accessors (unnamed system variable). Now possible add, redefine yours accessors.
+- ++Docs
+- ++Tests
+
+### 2.3.1 (2014-11-06)
+
+- Fix #122
+
+### 2.3.1 (2014-08-27)
+
+- Fix #105
+- ++Tests
+
## 2.3.0 (2014-08-08)
- Add tags {set} and {add}
diff --git a/docs/en/readme.md b/docs/en/readme.md
index 9e3d358..00e0920 100644
--- a/docs/en/readme.md
+++ b/docs/en/readme.md
@@ -1,8 +1,6 @@
Documentation
=============
-**Please, help translate documentation to english or fix typos. [Read more](./helpme.md).**
-
### Fenom
* [Quick start](./start.md)
@@ -11,7 +9,13 @@ Documentation
* [For developers](./dev/readme.md)
* [Configuration](./configuration.md)
* [Syntax](./syntax.md)
-* [Operators](./operators.md)
+ * [Variables](./syntax.md#Variables)
+ * [Values](./syntax.md#Values)
+ * [Arrays](./syntax.md#Arrays)
+ * [Operators](./operators.md)
+ * [Modificators](./syntax.md#Modificators)
+ * [Tags](./syntax.md#Tags)
+ * [Tag configuration](./syntax.md#Tag-configuration)
***
@@ -19,7 +23,7 @@ Documentation
[Usage](./syntax.md#tags)
-* [var](./tags/var.md) — define variable
+* [set](./tags/var.md), `add` and `var` — define variables
* [if](./tags/if.md), `elseif` and `else` — conditional statement
* [foreach](./tags/foreach.md), `foreaelse`, `break` and `continue` — traversing items in an array or object
* [for](./tags/for.md), `forelse`, `break` and `continue` — loop statement
@@ -72,7 +76,7 @@ Documentation
* [Comparison operators](./operators.md#comparison-operators) — `>`, `>=`, `<`, `<=`, `==`, `!=`, `!==`, `<>`
* [Bitwise operators](./operators.md#bitwise-operators) — `|`, `&`, `^`, `~$var`, `>>`, `<<`
* [Assignment operators](./operators.md#assignment-operators) — `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `>>=`, `<<=`
-* [String concatenation operator](./operators.md#string-operator) — `$str1 ~ $str2`
+* [String concatenation operators](./operators.md#string-operators) — `$str1 ~ $str2`, `$str1 ~~ $str2`, `$str1 ~= $str2`
* [Ternary operators](./operators.md#ternary-operators) — `$a ? $b : $c`, `$a ! $b : $c`, `$a ?: $c`, `$a !: $c`
* [Check operators](./operators.md#check-operators) — `$var?`, `$var!`
* [Test operator](./operators.md#test-operator) — `is`, `is not`
diff --git a/docs/en/syntax.md b/docs/en/syntax.md
index 7c1edb4..aa5803a 100644
--- a/docs/en/syntax.md
+++ b/docs/en/syntax.md
@@ -76,16 +76,16 @@ Below is complex example:
### System variable
-Unnamed system variable starts with `$.` and allows access to global variables and template information:
+Unnamed system variable starts with `$.` and allows access to global system variables and template information:
-* `$.get` is `$_GET`.
-* `$.post` is `$_POST`.
-* `$.cookie` is `$_COOKIE`.
-* `$.session` is `$_SESSION`.
-* `$.globals` is `$GLOBALS`.
-* `$.request` is `$_REQUEST`.
-* `$.files` is `$_FILES`.
-* `$.server` is `$_SERVER`.
+* `$.get` — array `$_GET`.
+* `$.post` — array `$_POST`.
+* `$.cookie` — array `$_COOKIE`.
+* `$.session` — array `$_SESSION`.
+* `$.globals` — array `$GLOBALS`.
+* `$.request` — array `$_REQUEST`.
+* `$.files` — array `$_FILES`.
+* `$.server` — array `$_SERVER`.
* `$.env` is `$_ENV`.
* `$.tpl.name` returns current template name.
* `$.tpl.schema` returns current schema of the template.
@@ -98,6 +98,165 @@ Unnamed system variable starts with `$.` and allows access to global variables a
{/if}
```
+
+Безименная системная переменная начинается с `$.` и предоставляет доступ к глобальным системным переменным и системной информации:
+
+* `$.env` — array `$_ENV`.
+* `$.get` — array `$_GET`.
+* `$.post` — array `$_POST`.
+* `$.files` — array `$_FILES`.
+* `$.cookie` — array `$_COOKIE`.
+* `$.server` — array `$_SERVER`.
+* `$.session` — array `$_SESSION`.
+* `$.globals` — array `$GLOBALS`.
+* `$.request` — array `$_REQUEST`.
+* `$.tpl.name` returns name for current template.
+* `$.tpl.basename` returns name without schema for current template.
+* `$.tpl.scm` returns schema for current template.
+* `$.tpl.options` returns options as integer for current template.
+* `$.tpl.depends`
+* `$.tpl.time` returns last modified timestamp for current template
+* `$.version` returns Fenom version.
+* `$.const` returns the value of a PHP constant: `$.const.PHP_EOL` get value of constant `PHP_EOL`.
+ Supported namespace for constants, use dot instead of back-slash for namespace separators: `$.const.Storage.FS::DIR_SEPARATOR` get value of constant `Storage\FS::DIR_SEPARATOR`.
+ But if constant `Storage\FS::DIR_SEPARATOR` does not exists then constant `Storage\FS\DIR_SEPARATOR` will be taken.
+* `$.php`call PHP static method. `$.php.Storage.FS::put($filename, $data)` calls method `Storage\FS::put($filename, $data)`.
+ `$.php.Storage.FS.put($filename, $data)` `Storage\FS\put($filename, $data)`
+* System function `$.fetch($name, $values)` calls Fenom::fetch() in template. `$name` — template name,
+ `$values` — additional variables.
+* also you may [add](./ext/extend.md#Extends-system-variable) yours system variables and functions.
+
+
+## Scalar values
+
+### Strings
+
+A string literal can be specified in two different ways: double quotes (`"string"`) and single quotes (`'string'`).
+
+#### Double quotes
+
+If the string is enclosed in double-quotes `"`, Fenom will interpret more escape sequences for special characters:
+
+
+| Последовательность | Значение |
+|---------------------|----------|
+| `\n` | linefeed (LF or 0x0A (10) in ASCII)
+| `\r` | carriage return (CR or 0x0D (13) in ASCII)
+| `\t` | horizontal tab (HT or 0x09 (9) in ASCII)
+| `\v` | vertical tab (VT or 0x0B (11) in ASCII)
+| `\f` | form feed (FF or 0x0C (12) in ASCII)
+| `\\` | backslash
+| `\$` | dollar sign
+| `\"` | double-quote
+| `\[0-7]{1,3}` | the sequence of characters matching the regular expression is a character in octal notation
+| `\x[0-9A-Fa-f]{1,2}`| the sequence of characters matching the regular expression is a character in hexadecimal notation
+
+The most important feature of double-quoted strings is the fact that variable names will be expanded.
+There are two types of syntax: a simple one and a complex one. The simple syntax is the most common and convenient.
+It provides a way to embed a variable, an array value, or an object property in a string with a minimum of effort.
+The complex syntax can be recognised by the curly braces surrounding the expression.
+
+##### Simple syntax
+
+If a dollar sign `$` is encountered, the parser will greedily take as many tokens as possible to form a valid variable name.
+Enclose the variable name in curly braces to explicitly specify the end of the name.
+
+```smarty
+{"Hi, $username!"} outputs "Hi, Username!"
+```
+
+For anything more complex, you should use the complex syntax.
+
+##### Complex syntax
+
+This isn't called complex because the syntax is complex, but because it allows for the use of complex expressions.
+Any scalar variable, array element or object property with a string representation can be included via this syntax.
+Simply write the expression the same way as it would appear outside the string, and then wrap it in `{` and `}`.
+Since `{` can not be escaped, this syntax will only be recognised when the `$` immediately follows the `{`.
+Use `{\$` to get a literal `{$`. Some examples to make it clear:
+
+
+```smarty
+{"Hi, {$user.name}!"} outputs: Hi, Username!
+{"Hi, {$user->name}!"} outputs: Hi, Username!
+{"Hi, {$user->getName()}!"} outputs: Hi, Username!
+{"Hi, {\$user->name}!"} outputs: Hi, {\$user->name}!
+```
+
+Allows modifiers and operators:
+
+```smarty
+{"Hi, {$user.name|up}!"} outputs: Hi, USERNAME!
+{"Hi, {$user.name|up ~ " (admin)"}!"} outputs: Hi, USERNAME (admin)!
+```
+
+#### Single quotes
+
+The simplest way to specify a string is to enclose it in single quotes (the character `'`).
+To specify a literal single quote, escape it with a backslash (`\`).
+To specify a literal backslash, double it (`\\`).
+All other instances of backslash will be treated as a literal backslash: this means that the other escape sequences you might be used to, such as `\r` or `\n`, will be output literally as specified rather than having any special meaning.
+
+```smarty
+{'Hi, $foo'} outputs: 'Hi, $foo'
+{'Hi, {$foo}'} outputs: 'Hi, {$foo}'
+{'Hi, {$user.name}'} outputs: 'Hi, {$user.name}'
+{'Hi, {$user.name|up}'} outputs: "Hi, {$user.name|up}"
+```
+
+### Integers
+
+Integers can be specified in decimal (base 10), hexadecimal (base 16), octal (base 8) or binary (base 2) notation, optionally preceded by a sign (- or +).
+
+To use octal notation, precede the number with a 0 (zero).
+To use hexadecimal notation precede the number with 0x.
+To use binary notation precede the number with 0b.
+
+``smarty
+{var $a = 1234} decimal number
+{var $a = -123} a negative number
+{var $a = 0123} octal number (equivalent to 83 decimal)
+{var $a = 0x1A} hexadecimal number (equivalent to 26 decimal)
+{var $a = 0b11111111} binary number (equivalent to 255 decimal)
+```
+
+**Notice**
+Binary notation (`0b1011011`) unavailable on PHP older than 5.3.
+
+**Notice**
+The size of an integer is platform-dependent, although a maximum value of about two billion is the usual value (that's 32 bits signed).
+64-bit platforms usually have a maximum value of about 9E18
+
+**Warning**
+If an invalid digit is given in an octal integer (i.e. 8 or 9), the rest of the number is ignored.
+
+### Floating point numbers
+
+Floating point numbers (also known as "floats", "doubles", or "real numbers") can be specified using any of the following syntaxes:
+
+```smarty
+{var $a = 1.234}
+{var $b = 1.2e3}
+{var $c = 7E-10}
+```
+
+### Booleans
+
+This is the simplest type. A boolean expresses a truth value. It can be either TRUE or FALSE.
+To specify a boolean literal, use the constants TRUE or FALSE. Both are case-insensitive.
+
+
+```smarty
+{set $a = true}
+```
+
+### NULL
+
+The special NULL value represents a variable with no value. NULL is the only possible value of type null.
+
+------
+
+
### Variable operations
Fenom supports math, logic, comparison, containment, test, concatenation operators...
@@ -206,6 +365,97 @@ Floating point numbers (also known as "floats", "doubles", or "real numbers") ca
{var $c = 7E-10}
```
+### Operators
+
+Fenom supports operators on values:
+
+* Arithmetic operators — `+`, `-`, `*`, `/`, `%`
+* Logical operators — `||`, `&&`, `!$var`, `and`, `or`, `xor`
+* Comparison operators — `>`, `>=`, `<`, `<=`, `==`, `!=`, `!==`, `<>`
+* Bitwise operators — `|`, `&`, `^`, `~$var`, `>>`, `<<`
+* Assignment operators — `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `>>=`, `<<=`
+* String concatenation operators — `$str1 ~ $str2`, `$str1 ~~ $str2`, `$str1 ~= $str2`
+* Ternary operators — `$a ? $b : $c`, `$a ! $b : $c`, `$a ?: $c`, `$a !: $c`
+* Check operators — `$var?`, `$var!`
+* Test operator — `is`, `is not`
+* Containment operator — `in`, `not in`
+
+About [operators](./operators.md).
+
+## Arrays
+
+An array can be created using the `[]` language construct. It takes any number of comma-separated `key => value` pairs as arguments.
+```
+[
+ key => value,
+ key2 => value2,
+ key3 => value3,
+ ...
+]
+```
+The comma after the last array element is optional and can be omitted.
+This is usually done for single-line arrays, i.e. `[1, 2]` is preferred over `[1, 2, ]`.
+For multi-line arrays on the other hand the trailing comma is commonly used, as it allows easier addition of new elements at the end.
+
+```smarty
+{set $array = [
+ "foo" => "bar",
+ "bar" => "foo",
+]}
+```
+
+The key can either be an integer or a string. The value can be of any type.
+
+Additionally the following key casts will occur:
+
+* Strings containing valid integers will be cast to the integer type. E.g. the key "8" will actually be stored under 8.
+ On the other hand "08" will not be cast, as it isn't a valid decimal integer.
+* Floats are also cast to integers, which means that the fractional part will be truncated.
+ E.g. the key 8.7 will actually be stored under 8.
+* Bools are cast to integers, too, i.e. the key true will actually be stored under 1 and the key false under 0.
+* Null will be cast to the empty string, i.e. the key null will actually be stored under "".
+* Arrays and objects can not be used as keys. Doing so will result in a warning: Illegal offset type.
+
+If multiple elements in the array declaration use the same key, only the last one will be used as all others are overwritten.
+
+An existing array can be modified by explicitly setting values in it.
+This is done by assigning values to the array, specifying the key after dot or in brackets.
+The key can also be omitted, resulting in an empty pair of brackets (`[]`).
+
+```smarty
+{set $arr.key = value}
+{set $arr[] = value} {* append value to end of array *}
+```
+
+If `$arr` doesn't exist yet, it will be created, so this is also an alternative way to create an array.
+This practice is however discouraged because if `$arr` already contains some value (e.g. string from request variable)
+then this value will stay in the place and `[]` may actually stand for string access operator.
+It is always better to initialize variable by a direct assignment.
+
+## Constants
+
+A constant is an identifier (name) for a simple value in PHP.
+As the name suggests, that value cannot change during the execution of the script.
+A constant is case-sensitive by default. By convention, constant identifiers are always uppercase.
+
+## PHP functions and methods
+
+**TODO**
+
+```smarty
+{$.php.some_function($a, $b, $c)}
+```
+
+```smarty
+{$.php.MyClass::method($a, $b, $c)}
+```
+
+
+```smarty
+{$.php.My.NS.some_function($a, $b, $c)}
+{$.php.My.NS.MyClass::method($a, $b, $c)}
+```
+
## Modifiers
@@ -229,63 +479,7 @@ These parameters follow the modifier name and are separated by a : (colon).
## Tags
-Basically, tag seems like
-
-```smarty
-{FUNCNAME attr1 = "val1" attr2 = $val2}
-```
-
-Tags starts with name and may have attributes
-
-Это общий формат функций, но могут быть исключения, например функция [{var}](./tags/var.md), использованная выше.
-
-```smarty
-{include file="my.tpl"}
-{var $foo=5}
-{if $user.loggined}
- Welcome, {$user.name}!
-{else}
- Who are you?
-{/if}
-```
-
-В общем случае аргументы принимают любой формат переменных, в том числе результаты арифметических операций и модификаторов.
-
-```smarty
-{funct arg=true}
-{funct arg=5}
-{funct arg=1.2}
-{funct arg='string'}
-{funct arg="string this {$var}"}
-{funct arg=[1,2,34]}
-{funct arg=$x}
-{funct arg=$x.c}
-```
-
-```smarty
-{funct arg="ivan"|upper}
-{funct arg=$a.d.c|lower}
-```
-
-```smarty
-{funct arg=1+2}
-{funct arg=$a.d.c+4}
-{funct arg=($a.d.c|count+4)/3}
-```
-
-## Static method support
-
-By default static methods are allowed in templates
-
-```smarty
-{Lib\Math::multiple x=3 y=4} static method as tag
-{Lib\Math::multiple(3,4)} inline static method
-{12 + Lib\Math::multiple(3,4)}
-{12 + 3|Lib\Math::multiple:4} static method as modifier
-```
-
-You may disable call static methods in template, see in [security options](./settings.md) option `deny_static`
-
+**TODO**
## Ignoring template code
@@ -360,7 +554,7 @@ Tags to allow any number of spaces
### Tag options
-TODO
+**TODO**
| name | code | type | description |
| ------- | ---- | ----- | ------------ |
diff --git a/docs/ru/configuration.md b/docs/ru/configuration.md
index 22b22b6..cf40471 100644
--- a/docs/ru/configuration.md
+++ b/docs/ru/configuration.md
@@ -36,7 +36,8 @@ $fenom->setOptions($options);
| *force_include* | `Fenom::FORCE_INCLUDE` | стараться по возможности вставить код дочернего шаблона в родительский при подключении шаблона | повышает производительность, увеличивает размер файлов в кеше, уменьшает количество файлов в кеше |
| *auto_escape* | `Fenom::AUTO_ESCAPE` | автоматически экранировать HTML сущности при выводе переменных в шаблон | понижает производительность |
| *force_verify* | `Fenom::FORCE_VERIFY` | автоматически проверять существование переменной перед использованием в шаблоне | понижает производительность |
-| *disable_statics* | `Fenom::DENY_STATICS` | отключает воззможность вызова статических методов в шаблоне | |
+| *disable_php_calls* | `Fenom::DENY_PHP_CALLS` | отключает возможность вызова статических методов и функций в шаблоне | |
+| *disable_statics* | `Fenom::DENY_STATICS` | устаревшее название disable_php_calls | |
| *strip* | `Fenom::AUTO_STRIP` | удаляет лишиние пробелы в шаблоне | уменьшает размер кеша |
```php
@@ -48,5 +49,9 @@ $fenom->setOptions(array(
$fenom->setOptions(Fenom::AUTO_RELOAD | Fenom::FORCE_INCLUDE);
```
+```php
+$fenom->addCallFilter('View\Widget\*::get*')
+```
+
**Замечание**
По умолчанию все параметры деактивированы.
diff --git a/docs/ru/ext/extend.md b/docs/ru/ext/extend.md
index 476ec46..2f9071b 100644
--- a/docs/ru/ext/extend.md
+++ b/docs/ru/ext/extend.md
@@ -1,8 +1,6 @@
Расширение Fenom
================
-*TODO*
-
# Добавление тегов
В шаблонизаторе принято различать два типа тегов: _компиляторы_ и _функции_.
@@ -116,11 +114,27 @@ $fenom->addModifier('my_modifier', function ($variable, $param1, $param2) {
# Расширение тестовго оператора
```php
-$fenom->addTest($name, $code);
-?>
+$fenom->addTest(string $name, string $code);
+```
+`$code` - PHP код для условия, с маркером для замены на значение или переменную.
+Например, тест на целое число `is int` можно добавить как `$fenom->addTest('int', 'is_int(%s)')`.
+В шаблоне тесты выглядит как `{$a is int}`, а после компиляции выглядит приблизительно так - `is_int($a)`.
+
+# Расширение глобальной переменной или функции
+
+Fenom обладает определенным набором глобальных переменных и функций. Однако их может не хватать для удобной работы с шаблонами.
+В этом случае потребуется добавить, переопределить или удалить существующие глобальные переменные или функции.
+Метод `Fenom::addAccessor(string $name, callable $parser)` позволяет добавить свой обработчик на не известную глобальную переменную или функцию.
+
+```php
+$fenom->addAccessor('project', function (Fenom\Tokenizer $tokens) { /* code */ });
```
-# Расширение глобальной переменной
+Указанный вторым аргументом, парсер будет вызван при встречи компилятором конструкции `{$.project}`.
+Парсеры вызываются только на момент сборки шаблона, а не во время его выполенения.
+
+Через метод `Fenom::addAccessor($name, $parser)` можно переопределить уже любую другую существующую глобальную переменную или функцию.
+Метод `Fenom::removeAccessor($name)` позволяет удалить любую определенную глобальную переменную или функцию по ее имени.
# Источники шаблонов
diff --git a/docs/ru/operators.md b/docs/ru/operators.md
index baabad7..fc859a4 100644
--- a/docs/ru/operators.md
+++ b/docs/ru/operators.md
@@ -127,11 +127,13 @@ Fenom поддерживает префиксные и постфиксные о
* `--$a` - префиксный декремент, уменьшает $a на единицу, затем возвращает значение $a.
* `$a--` - постфиксный декремент, возвращает значение $a, затем уменьшает $a на единицу.
-### Строковый оператор
+### Строковые операторы
Оператор объединения `~` возвращает строку, представляющую собой соединение левого и правого аргумента.
-`$a ~ $b` - возвращает результат объединения сток `$a` и `$b`
+* `$a ~ $b` - возвращает результат объединения сток `$a` и `$b`
+* `$a ~~ $b` - возвращает результат объединения сток `$a` и `$b` через пробел
+* `$a ~= $b` - присвоение с объединением
### Тернарные операторы
diff --git a/docs/ru/readme.md b/docs/ru/readme.md
index 66f97aa..6c509ed 100644
--- a/docs/ru/readme.md
+++ b/docs/ru/readme.md
@@ -24,7 +24,7 @@
### Теги
-[Использование](./syntax.md#tags) тегов.
+[Использование](./syntax.md#Теги) тегов.
* [set](./tags/set.md), `add` и `var` — определение значения переменной
* [if](./tags/if.md), `elseif` и `else` — условный оператор
@@ -40,7 +40,7 @@
* [autoescape](./tags/autoescape.md) — экранирует фрагмент шаблона
* [raw](./tags/raw.md) — отключает экранирование фрагмента шаблона
* [unset](./tags/unset.md) — удаляет переменные
-* или [добавте](./ext/extend.md#add-tags) свои
+* или [добавте](./ext/extend.md#Добавление-тегов) свои
***
@@ -68,7 +68,7 @@
* [join](./mods/join.md) — объединяет массив в строку
* так же разрешены функции: `json_encode`, `json_decode`, `count`, `is_string`, `is_array`, `is_numeric`, `is_int`, `is_object`,
`strtotime`, `gettype`, `is_double`, `ip2long`, `long2ip`, `strip_tags`, `nl2br`
-* или [добавте](./ext/extend.md#add-modifiers) свои
+* или [добавте](./ext/extend.md#Добавление-модификаторов) свои
***
@@ -79,7 +79,7 @@
* [Операторы сравнения](./operators.md#Операторы-сравнения) — `>`, `>=`, `<`, `<=`, `==`, `!=`, `!==`, `<>`
* [Битовые операторы](./operators.md#Битовые-операторы) — `|`, `&`, `^`, `~$var`, `>>`, `<<`
* [Операторы присвоения](./operators.md#Операторы-присвоения) — `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `>>=`, `<<=`
-* [Строковый оператор](./operators.md#Строковый-оператор) — `$str1 ~ $str2`
+* [Строковые операторы](./operators.md#Строковые-операторы) — `$str1 ~ $str2`, `$str1 ~~ $str2`, `$str1 ~= $str2`
* [Тернарные операторы](./operators.md#Тернарные-операторы) — `$a ? $b : $c`, `$a ! $b : $c`, `$a ?: $c`, `$a !: $c`
* [Проверяющие операторы](./operators.md#Проверяющие-операторы) — `$var?`, `$var!`
* [Оператор тестирования](./operators.md#Оператор-тестирования) — `is`, `is not`
diff --git a/docs/ru/syntax.md b/docs/ru/syntax.md
index 2fb4f03..bf725ab 100644
--- a/docs/ru/syntax.md
+++ b/docs/ru/syntax.md
@@ -2,7 +2,7 @@
=========
По синтаксису шаблона Fenom похож на [Smarty](http://www.smarty.net/), но обладает рядом улучшений.
-Все теги шаблонизатора заключаются в фигурные скобки: `{` — открытие тега и `}` — закрытие тега.
+Все теги шаблонизатора заключаются в фигрные скобки: `{` — открытие тега и `}` — закрытие тега.
**Замечание**
Хоть Fenom и позаимствовал синтаксис Smarty, но он не заимствовал теги Smarty как есть.
@@ -29,7 +29,7 @@
```
-Переменные могут быть массивом. В этом случае обращение по ключу происходит через оператор `.` или, как в PHP, через операторы `[` и `]`
+Переменные могут быть массивом. В этом случае обращение по ключу происходит через опертор `.` или, как в PHP, через операторы `[` и `]`
```smarty
```
@@ -38,12 +38,12 @@
```
-В случае объекта, доступ к его свойствам осуществляется так как и в PHP — через оператор `->`:
+В случае объекта, доступ к его свойствам осущесвляется так как и в PHP — через оператор `->`:
```smarty
```
-Методы, как и свойства можно вызвать через оператор `->`, передав в метод любые аргументы:
+Методы, как и свойства можно вызвать через оператор `->`, передав в метод любые рагументы:
```smarty
```
@@ -53,7 +53,7 @@
Что бы избежать фатальной ошибки определите метод `__call` у класса объекта.
Вызов методов в шаблоне можно вообще выключить в [настройках](./docs/configuration.md).
-Ниже приведены комбинированные примеры работы с переменными:
+Ниже приведены комбинированые примеры работы с переменными:
```smarty
{$foo.bar.baz}
@@ -79,27 +79,34 @@
### Системная переменная
-Безымянная системная переменная начинается с `$.` и предоставляет доступ к глобальным системным переменным и системной информации:
+Безименная системная переменная начинается с `$.` и предоставляет доступ к глобальным системным переменным и системной информации:
-* `$.get` — `$_GET`.
-* `$.post` — `$_POST`.
-* `$.cookie` — `$_COOKIE`.
-* `$.session` — `$_SESSION`.
-* `$.globals` — `$GLOBALS`.
-* `$.request` — `$_REQUEST`.
-* `$.files` — `$_FILES`.
-* `$.server` — `$_SERVER`.
-* `$.env` — `$_ENV`.
+* `$.env` — массив `$_ENV`.
+* `$.get` — массив `$_GET`.
+* `$.post` — массив `$_POST`.
+* `$.files` — массив `$_FILES`.
+* `$.cookie` — массив `$_COOKIE`.
+* `$.server` — массив `$_SERVER`.
+* `$.session` — массив `$_SESSION`.
+* `$.globals` — массив `$GLOBALS`.
+* `$.request` — массив `$_REQUEST`.
* `$.tpl.name` возвращает текущее название шаблона.
-* `$.tpl.schema` возвращает код провайдера шаблона.
+* `$.tpl.basename` возвращает текущее название шаблона без схемы.
+* `$.tpl.scm` возвращает схему шаблона.
+* `$.tpl.options` возвращает параметры шбалона в виде целого числа.
+* `$.tpl.depends` возвращает массив шаблонов на которые ссылается текущий шаблон.
+* `$.tpl.time` возвращает штамп времени когда шаблон последний раз менялся
* `$.version` возвращает версию Fenom.
-* `$.const` обращение к PHP константе: `$.const.PHP_EOL` .
-
-```smarty
-{if $.get.debug? && $.const.DEBUG}
- ...
-{/if}
-```
+* `$.const` обращение к PHP константе: `$.const.PHP_EOL` обращение к константе `PHP_EOL`. Поддерживается пространство имен
+ которое разделяется через точку: `$.const.Storage.FS::DIR_SEPARATOR` обращение к PHP константе `Storage\FS::DIR_SEPARATOR`
+ если такой констатнты нет будет взята константа `Storage\FS\DIR_SEPARATOR`.
+* `$.php` обращение к статическомому методу. `$.php.Storage.FS::put($filename, $data)` обращение к методу `Storage\FS::put($filename, $data)`.
+ `$.php.Storage.FS.put($filename, $data)` `Storage\FS\put($filename, $data)`
+* Системная функция `$.fetch($name, $values)` реализует метод Fenom::fetch() в шаблоне. `$name` — имя шаблона,
+ `$values` — дополнительные переменные, которые будут добавлены к существующим.
+ Функция позволяет получить резуьтат работы шаблона в переменную.
+* так же вы можете [добавить](./ext/extend.md#Расширение-глобальной-переменной-или-функции) свои системные переменные и функции
+
## Скалярные значения
@@ -110,7 +117,20 @@
#### Двойные кавычки
Если строка заключена в двойные кавычки `"`, Fenom распознает большее количество управляющих последовательностей для специальных символов:
-`\n`, `\r`, `\t`, `\v`, `\e`, `\f`, `\\`, `\$`, `\"`, `\[0-7]{1,3}`, `\x[0-9A-Fa-f]{1,2}`.
+
+| Последовательность | Значение |
+|---------------------|----------|
+| `\n` | новая строка (LF или 0x0A (10) в ASCII)
+| `\r` | возврат каретки (CR или 0x0D (13) в ASCII)
+| `\t` | горизонтальная табуляция (HT или 0x09 (9) в ASCII)
+| `\v` | вертикальная табуляция (VT или 0x0B (11) в ASCII)
+| `\f` | подача страницы (FF или 0x0C (12) в ASCII)
+| `\\` | обратная косая черта
+| `\$` | знак доллара
+| `\"` | двойная кавычка
+| `\[0-7]{1,3}` | последовательность символов, соответствующая регулярному выражению символа в восьмеричной системе счисления
+| `\x[0-9A-Fa-f]{1,2}`| последовательность символов, соответствующая регулярному выражению символа в шестнадцатеричной системе счисления
+
Но самым важным свойством строк в двойных кавычках является обработка переменных.
Существует два типа синтаксиса: простой и сложный. Простой синтаксис более легок и удобен.
Он дает возможность обработки переменной, значения массива или свойства объекта с минимумом усилий.
@@ -168,22 +188,23 @@
### Целые числа
Целые числа могут быть указаны в десятичной (основание 10), шестнадцатеричной (основание 16),
-восьмеричной (основание 8) или двоичной (основание 2) системе счисления, с необязательным предшествующим знаком (- или +).
+восьмеричной (основание 8) или двоичной (основание 2) системе счисления, с необязательным предшествующим знаком (`-` или `+`).
-Для записи в восьмеричной системе счисления, необходимо поставить пред числом 0 (ноль). Для записи в шестнадцатеричной системе счисления, необходимо поставить перед числом 0x.
+Для записи в восьмеричной системе счисления, необходимо поставить пред числом 0 (ноль).
+Для записи в шестнадцатеричной системе счисления, необходимо поставить перед числом 0x.
Для записи в двоичной системе счисления, необходимо поставить перед числом 0b
```smarty
-{var $a = 1234} // десятичное число
-{var $a = -123} // отрицательное число
-{var $a = 0123} // восьмеричное число (эквивалентно 83 в десятичной системе)
-{var $a = 0x1A} // шестнадцатеричное число (эквивалентно 26 в десятичной системе)
-{var $a = 0b11111111} // двоичное число (эквивалентно 255 в десятичной системе)
+{var $a = 1234} десятичное число
+{var $a = -123} отрицательное число
+{var $a = 0123} восьмеричное число (эквивалентно 83 в десятичной системе)
+{var $a = 0x1A} шестнадцатеричное число (эквивалентно 26 в десятичной системе)
+{var $a = 0b11111111} двоичное число (эквивалентно 255 в десятичной системе)
```
**Замечение**
Двоичная запись числа (`0b1011011`) не доступна на старых версиях PHP — 5.3 или ниже.
-Попытка использовать на старых версия PHP приведет к исключению при компиляциях.
+Попытка исользовать на старых версия PHP приведет к исключению при компиляциях.
**Замечение**
Размер целого числа зависит от платформы, хотя, как правило, максимальное значение примерно равно 2 миллиардам (это 32-битное знаковое).
@@ -207,14 +228,16 @@
Это простейший тип. Булевое выражает истинность значения. Он может быть либо TRUE либо FALSE.
Для указания булевого значения, используйте ключевое слово TRUE или FALSE. Оба регистро-независимы.
+```smarty
{set $a = true}
+```
### NULL
Специальное значение NULL представляет собой переменную без значения. NULL - это единственно возможное значение типа null.
-Обычно возникают путаницы между NULL и FALSE, так как по роли они похожи, но различаются по принципу:
-NULL - это отсутствие присутствия, а FALSE - присутствие отсутствия.
+Обычно возникают путаницы между NULL и FALSE, так как по роли они похожи, но разлицаются по принципу:
+NULL - это отсутствие присутствия, а FALSE - присутвие отсутствия.
### Операции
@@ -253,7 +276,6 @@ NULL - это отсутствие присутствия, а FALSE - прису
"foo" => "bar",
"bar" => "foo",
]}
-
```
`key` может быть либо целым числом, либо строкой. `value` может быть любого типа.
@@ -264,7 +286,7 @@ NULL - это отсутствие присутствия, а FALSE - прису
* Числа с плавающей точкой также будут преобразованы к числу, т.е. дробная часть будет отброшена. Например, ключ со значением `8.7` будет в действительности сохранен со значением `8`.
* Булев также преобразовываются к целому числу. Например, ключ со значением `true` будет сохранен со значением `1` и ключ со значением `false` будет сохранен со значением `0`.
* NULL будет преобразован к пустой строке. Например, ключ со значением `null` будет в действительности сохранен со значением `""`.
-* Массивы (тип array) и объекты (тип object) не могут использоваться в качестве ключей. При подобном использовании компилятор будет генерировать предупреждение: Недопустимый тип смещения (Illegal offset type).
+* Массивы (тип array) и объекты (тип object) не могут использоваться в качестве ключей. При подобном использовании будет генерироваться предупреждение: Недопустимый тип смещения (Illegal offset type).
Если несколько элементов в объявлении массива используют одинаковый ключ, то только последний будет использоваться, а все другие будут перезаписаны.
@@ -283,9 +305,40 @@ NULL - это отсутствие присутствия, а FALSE - прису
Однако такой способ применять не рекомендуется, так как если переменная `$arr` уже содержит некоторое значение (например, строку),
то это значение останется на месте и `[]` может на самом деле означать доступ к символу в строке. Лучше инициализировать переменную путем явного присваивания значения.
+## Константы
+
+Константы - это идентификаторы (имена) простых значений, определенные в PHP.
+Исходя из их названия, нетрудно понять, что их значение не может изменяться в ходе выполнения шаблона.
+Имена констант чувствительны к регистру. По принятому соглашению, имена констант всегда пишутся в верхнем регистре.
+Константы доступны из любого шаблона через глобальную переменную {$.const.*}: `{$.const.PHP_EOL}`.
+В шаблоне определить константу нельзя.
+
+## PHP функции и методы
+
+Fenom предоставляет возможноть обращаться к функиям и методам самого PHP. Использование их в шаблонах не рекомендуется.
+Используя системную переменную `$.php` можно вызвать любую функцию или метод в шаблоне:
+
+```smarty
+{$.php.some_function($a, $b, $c)}
+```
+метод вызывается иначе
+
+```smarty
+{$.php.MyClass::method($a, $b, $c)}
+```
+
+пространство имен указывается перед функций или классом, разделяя точкой вместо обратного слеша:
+
+```smarty
+{$.php.My.NS.some_function($a, $b, $c)}
+{$.php.My.NS.MyClass::method($a, $b, $c)}
+```
+
+Вызов функции и методов можно выключить настройкой `???` или ограничить.
+
## Модификаторы
-Модификаторы переменных могут быть применены к переменным, пользовательским функциям или строкам.
+Модификаторы переменных могут быть прмменены к переменным, пользовательским функциям или строкам.
Для их применения надо после модифицируемого значения указать символ `|` (вертикальная черта) и название модификатора.
Так же модификаторы могут принимать параметры, которые влияют на их поведение.
Эти параметры следуют за названием модификатора и разделяются `:` (двоеточием).
@@ -304,14 +357,14 @@ NULL - это отсутствие присутствия, а FALSE - прису
## Теги
-Все сущности шаблона можно разбить на две группы:
+Все сущности шаблона можно разжелить на две группы:
* заполнитель (placeholder) — вывод переменной в шаблоне, например `{$name}`
-* тег — конструкция выполняющая некоторые действия, выглядит как именованный заполнитель (placeholder), например `{include $name}`
+* тег — конструкция, выполняющаяя некоторые действия, которая выглядит как именованный заполнитель (placeholder), например `{include $name}`
Теги так же можно разделить на две группы:
-* Функии. Тег функции вызывает пользовательскую функцию во время выполнения шаблона, результат функции будет выведен вместо тега.
+* Функии. Тег функции вызывает пользовательскую во время выполнения шаблона, результат функции будет выведен вместо тега.
Пользовательские функции являются дополнительными и могут быть индивидуальными. Они могут быть изменены по вашему желанию, также вы можете создать новые.
* Компиляторы. В отличии от функций компиляторы вызываются во время компиляции шаблона и возвращают PHP код, который описывает некоторое действие.
Компиляторы и формируют основные конструкции типа `if`, `foreach` и т.д.
diff --git a/docs/ru/tags/include.md b/docs/ru/tags/include.md
index 72d0622..f296de8 100644
--- a/docs/ru/tags/include.md
+++ b/docs/ru/tags/include.md
@@ -18,6 +18,12 @@
Все значения присвоенных переменных восстанавливаются после того, как подключаемый шаблон отработал.
Это значит, что вы можете использовать все переменные из подключающего шаблона в подключаемом, но изменения переменных внутри подключаемого шаблона не будут видны внутри подключающего шаблона после команды {include}.
+Если требуется сохранить результат отрисовки шаблона в переменную то используйте `$.fetch($templates, $values)`:
+
+```smarty
+{set $data = $.fetch('user.tpl', ["name" => $data.name, "email" => $data.email])}
+```
+
### {insert}
В отличии от `{include}` тег `{insert}` не вызывает дочерний шаблон во время отрисовки, в ставляет код дочернего шаблона в родительский на момент компиляции.
diff --git a/sandbox/fenom.php b/sandbox/fenom.php
index 4aff4aa..40d91f5 100644
--- a/sandbox/fenom.php
+++ b/sandbox/fenom.php
@@ -1,8 +1,10 @@
display('greeting.tpl');
\ No newline at end of file
+$fenom = Fenom::factory(__DIR__.'/templates', __DIR__.'/compiled');
+$fenom->setOptions(Fenom::AUTO_RELOAD | Fenom::AUTO_STRIP);
+echo($fenom->compile("problem.tpl", false)->getBody());
+// $fenom->getTemplate("problem.tpl");
\ No newline at end of file
diff --git a/src/Fenom.php b/src/Fenom.php
index 554f40b..a8779e4 100644
--- a/src/Fenom.php
+++ b/src/Fenom.php
@@ -13,11 +13,12 @@ use Fenom\Template;
/**
* Fenom Template Engine
*
+ *
* @author Ivan Shalganov
*/
class Fenom
{
- const VERSION = '2.0';
+ const VERSION = '2.4';
/* Actions */
const INLINE_COMPILER = 1;
const BLOCK_COMPILER = 5;
@@ -35,8 +36,13 @@ class Fenom
const DISABLE_CACHE = 0x400;
const FORCE_VERIFY = 0x800;
const AUTO_TRIM = 0x1000; // reserved
- const DENY_STATICS = 0x2000;
+ const DENY_PHP_CALLS = 0x2000;
const AUTO_STRIP = 0x4000;
+ /**
+ * Use DENY_PHP_CALLS
+ * @deprecated
+ */
+ const DENY_STATICS = 0x2000;
/* Default parsers */
const DEFAULT_CLOSE_COMPILER = 'Fenom\Compiler::stdClose';
@@ -62,6 +68,7 @@ class Fenom
"auto_escape" => self::AUTO_ESCAPE,
"force_verify" => self::FORCE_VERIFY,
"auto_trim" => self::AUTO_TRIM,
+ "disable_php_calls" => self::DENY_PHP_CALLS,
"disable_statics" => self::DENY_STATICS,
"strip" => self::AUTO_STRIP,
);
@@ -81,6 +88,11 @@ class Fenom
*/
public $tag_filters = array();
+ /**
+ * @var string[]
+ */
+ public $call_filters = array();
+
/**
* @var callable[]
*/
@@ -340,6 +352,24 @@ class Fenom
'third' => '!(%s %% 3)'
);
+ protected $_accessors = array(
+ 'get' => 'Fenom\Accessor::getVar',
+ 'env' => 'Fenom\Accessor::getVar',
+ 'post' => 'Fenom\Accessor::getVar',
+ 'request' => 'Fenom\Accessor::getVar',
+ 'cookie' => 'Fenom\Accessor::getVar',
+ 'globals' => 'Fenom\Accessor::getVar',
+ 'server' => 'Fenom\Accessor::getVar',
+ 'session' => 'Fenom\Accessor::getVar',
+ 'files' => 'Fenom\Accessor::getVar',
+ 'tpl' => 'Fenom\Accessor::tpl',
+ 'version' => 'Fenom\Accessor::version',
+ 'const' => 'Fenom\Accessor::constant',
+ 'php' => 'Fenom\Accessor::php',
+ 'tag' => 'Fenom\Accessor::Tag',
+ 'fetch' => 'Fenom\Accessor::Fetch',
+ );
+
/**
* Just factory
*
@@ -375,6 +405,9 @@ class Fenom
$this->_provider = $provider;
}
+ public function setCachePerms() {
+ }
+
/**
* Set compile directory
*
@@ -510,12 +543,7 @@ class Fenom
* @param array $tags
* @return Fenom
*/
- public function addBlockCompiler(
- $compiler,
- $open_parser,
- $close_parser = self::DEFAULT_CLOSE_COMPILER,
- array $tags = array()
- ) {
+ public function addBlockCompiler($compiler, $open_parser, $close_parser = self::DEFAULT_CLOSE_COMPILER, array $tags = array()) {
$this->_actions[$compiler] = array(
'type' => self::BLOCK_COMPILER,
'open' => $open_parser,
@@ -602,12 +630,8 @@ class Fenom
* @param callable|string $parser_close
* @return Fenom
*/
- public function addBlockFunction(
- $function,
- $callback,
- $parser_open = self::DEFAULT_FUNC_OPEN,
- $parser_close = self::DEFAULT_FUNC_CLOSE
- ) {
+ public function addBlockFunction($function, $callback, $parser_open = self::DEFAULT_FUNC_OPEN, $parser_close = self::DEFAULT_FUNC_CLOSE)
+ {
$this->_actions[$function] = array(
'type' => self::BLOCK_FUNCTION,
'open' => $parser_open,
@@ -772,6 +796,53 @@ class Fenom
return $this->_options;
}
+ /**
+ * Add global accessor ($.)
+ * @param string $name
+ * @param callable $parser
+ * @return Fenom
+ */
+ public function addAccessor($name, $parser)
+ {
+ $this->_accessors[$name] = $parser;
+ return $this;
+ }
+
+ /**
+ * Remove accessor
+ * @param string $name
+ * @return Fenom
+ */
+ public function removeAccessor($name)
+ {
+ unset($this->_accessors[$name]);
+ return $this;
+ }
+
+ /**
+ * Get an accessor
+ * @param string $name
+ * @return callable
+ */
+ public function getAccessor($name) {
+ if(isset($this->_accessors[$name])) {
+ return $this->_accessors[$name];
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Add filter for $.php accessor.
+ * Uses glob syntax.
+ * @param string $pattern
+ * @return $this
+ */
+ public function addCallFilter($pattern) {
+ $this->call_filters[] = $pattern;
+ return $this;
+ }
+
/**
* @param bool|string $scm
* @return Fenom\ProviderInterface
@@ -803,7 +874,8 @@ class Fenom
/**
* Execute template and write result into stdout
*
- * @param string $template name of template
+ * @param string|array $template name of template.
+ * If it is array of names of templates they will be extended from left to right.
* @param array $vars array of data for template
* @return Fenom\Render
*/
@@ -814,7 +886,8 @@ class Fenom
/**
*
- * @param string $template name of template
+ * @param string|array $template name of template.
+ * If it is array of names of templates they will be extended from left to right.
* @param array $vars array of data for template
* @return mixed
*/
@@ -826,7 +899,8 @@ class Fenom
/**
* Creates pipe-line of template's data to callback
* @note Method not works correctly in old PHP 5.3.*
- * @param string $template name of the template
+ * @param string|array $template name of the template.
+ * If it is array of names of templates they will be extended from left to right.
* @param callable $callback template's data handler
* @param array $vars
* @param float $chunk amount of bytes of chunk
@@ -969,7 +1043,7 @@ class Fenom
}
/**
- * Flush internal memory template cache
+ * Flush internal template in-memory-cache
*/
public function flush()
{
@@ -982,6 +1056,7 @@ class Fenom
public function clearAllCompiles()
{
\Fenom\Provider::clean($this->_compile_dir);
+ $this->flush();
}
/**
diff --git a/src/Fenom/Accessor.php b/src/Fenom/Accessor.php
new file mode 100644
index 0000000..2330537
--- /dev/null
+++ b/src/Fenom/Accessor.php
@@ -0,0 +1,143 @@
+ '$_GET',
+ 'post' => '$_POST',
+ 'session' => '$_SESSION',
+ 'cookie' => '$_COOKIE',
+ 'request' => '$_REQUEST',
+ 'files' => '$_FILES',
+ 'globals' => '$GLOBALS',
+ 'server' => '$_SERVER',
+ 'env' => '$_ENV'
+ );
+
+ /**
+ * Accessor for global variables
+ * @param Tokenizer $tokens
+ * @param Template $tpl
+ */
+ public static function getVar(Tokenizer $tokens, Template $tpl)
+ {
+ $name = $tokens->prev[Tokenizer::TEXT];
+ if(isset(self::$vars[$name])) {
+ $var = $tpl->parseVariable($tokens, self::$vars[$name]);
+ return "(isset($var) ? $var : null)";
+ } else {
+ throw new UnexpectedTokenException($tokens->back());
+ }
+ }
+
+ /**
+ * Accessor for template information
+ * @param Tokenizer $tokens
+ */
+ public static function tpl(Tokenizer $tokens)
+ {
+ $method = $tokens->skip('.')->need(T_STRING)->getAndNext();
+ if(method_exists('Fenom\Render', 'get'.$method)) {
+ return '$tpl->get'.ucfirst($method).'()';
+ } else {
+ throw new UnexpectedTokenException($tokens->back());
+ }
+ }
+
+ public static function version()
+ {
+ return 'Fenom::VERSION';
+ }
+
+ /**
+ * @param Tokenizer $tokens
+ * @return string
+ */
+ public static function constant(Tokenizer $tokens)
+ {
+ $const = array($tokens->skip('.')->need(Tokenizer::MACRO_STRING)->getAndNext());
+ while($tokens->is('.')) {
+ $const[] = $tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext();
+ }
+ $const = implode('\\', $const);
+ if($tokens->is(T_DOUBLE_COLON)) {
+ $const .= '::'.$tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext();
+ }
+ return '@constant('.var_export($const, true).')';
+
+ }
+
+ /**
+ * @param Tokenizer $tokens
+ * @param Template $tpl
+ * @return string
+ */
+ public static function php(Tokenizer $tokens, Template $tpl)
+ {
+ $callable = array($tokens->skip('.')->need(Tokenizer::MACRO_STRING)->getAndNext());
+ while($tokens->is('.')) {
+ $callable[] = $tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext();
+ }
+ $callable = implode('\\', $callable);
+ if($tokens->is(T_DOUBLE_COLON)) {
+ $callable .= '::'.$tokens->next()->need(Tokenizer::MACRO_STRING)->getAndNext();
+ }
+ $call_filter = $tpl->getStorage()->call_filters;
+ if($call_filter) {
+ foreach($call_filter as $filter) {
+ if(!fnmatch(addslashes($filter), $callable)) {
+ throw new \LogicException("Callback ".str_replace('\\', '.', $callable)." is not available by settings");
+ }
+ }
+ }
+ if(!is_callable($callable)) {
+ throw new \RuntimeException("PHP method ".str_replace('\\', '.', $callable).' does not exists.');
+ }
+ if($tokens->is('(')) {
+ $arguments = 'array'.$tpl->parseArgs($tokens).'';
+ } else {
+ $arguments = 'array()';
+ }
+ return 'call_user_func_array('.var_export($callable, true).', '.$arguments.')';
+
+ }
+
+ /**
+ * Accessor {$.fetch(...)}
+ * @param Tokenizer $tokens
+ * @param Template $tpl
+ * @return string
+ */
+ public static function fetch(Tokenizer $tokens, Template $tpl)
+ {
+ $tokens->skip('(');
+ $name = $tpl->parsePlainArg($tokens, $static);
+ if($static) {
+ if(!$tpl->getStorage()->templateExists($static)) {
+ throw new \RuntimeException("Template $static not found");
+ }
+ }
+ if($tokens->is(',')) {
+ $tokens->skip()->need('[');
+ $vars = $tpl->parseArray($tokens) . ' + $var';
+ } else {
+ $vars = '$var';
+ }
+ $tokens->skip(')');
+ return '$tpl->getStorage()->fetch('.$name.', '.$vars.')';
+ }
+}
\ No newline at end of file
diff --git a/src/Fenom/Compiler.php b/src/Fenom/Compiler.php
index 3d18c4c..32a07c3 100644
--- a/src/Fenom/Compiler.php
+++ b/src/Fenom/Compiler.php
@@ -241,7 +241,7 @@ class Compiler
*/
public static function forOpen(Tokenizer $tokens, Tag $scope)
{
- $p = array(
+ $p = array(
"index" => false,
"first" => false,
"last" => false,
diff --git a/src/Fenom/Provider.php b/src/Fenom/Provider.php
index 6ea55db..e9b28fd 100644
--- a/src/Fenom/Provider.php
+++ b/src/Fenom/Provider.php
@@ -9,8 +9,6 @@
*/
namespace Fenom;
-use Fenom\ProviderInterface;
-
/**
* Base template provider
* @author Ivan Shalganov
@@ -19,6 +17,8 @@ class Provider implements ProviderInterface
{
private $_path;
+ protected $_clear_cache = false;
+
/**
* Clean directory from files
*
@@ -75,6 +75,15 @@ class Provider implements ProviderInterface
}
}
+ /**
+ * Disable PHP cache for files. PHP cache some operations with files then script works.
+ * @see http://php.net/manual/en/function.clearstatcache.php
+ * @param bool $status
+ */
+ public function setClearCachedStats($status = true) {
+ $this->_clear_cache = $status;
+ }
+
/**
* Get source and mtime of template by name
* @param string $tpl
@@ -84,7 +93,9 @@ class Provider implements ProviderInterface
public function getSource($tpl, &$time)
{
$tpl = $this->_getTemplatePath($tpl);
- clearstatcache(true, $tpl);
+ if($this->_clear_cache) {
+ clearstatcache(true, $tpl);
+ }
$time = filemtime($tpl);
return file_get_contents($tpl);
}
@@ -96,7 +107,10 @@ class Provider implements ProviderInterface
*/
public function getLastModified($tpl)
{
- clearstatcache(true, $tpl = $this->_getTemplatePath($tpl));
+ $tpl = $this->_getTemplatePath($tpl);
+ if($this->_clear_cache) {
+ clearstatcache(true, $tpl);
+ }
return filemtime($tpl);
}
@@ -158,7 +172,10 @@ class Provider implements ProviderInterface
public function verify(array $templates)
{
foreach ($templates as $template => $mtime) {
- clearstatcache(true, $template = $this->_path . '/' . $template);
+ $template = $this->_path . '/' . $template;
+ if($this->_clear_cache) {
+ clearstatcache(true, $template);
+ }
if (@filemtime($template) !== $mtime) {
return false;
}
diff --git a/src/Fenom/Render.php b/src/Fenom/Render.php
index e126d75..48b260a 100644
--- a/src/Fenom/Render.php
+++ b/src/Fenom/Render.php
@@ -90,8 +90,7 @@ class Render extends \ArrayObject
$this->_time = $props["time"];
$this->_depends = $props["depends"];
$this->_macros = $props["macros"];
-// $this->_blocks = $props["blocks"];
- $this->_code = $code;
+ $this->_code = $code;
}
/**
@@ -249,19 +248,6 @@ class Render extends \ArrayObject
public function __get($name)
{
- if ($name == 'info') {
- return array(
- 'name' => $this->_name,
- 'schema' => $this->_scm,
- 'time' => $this->_time
- );
- } else {
- return null;
- }
- }
-
- public function __isset($name)
- {
- return $name == 'info';
+ return $this->$name = null;
}
}
diff --git a/src/Fenom/Template.php b/src/Fenom/Template.php
index 1396a9d..2b9de83 100644
--- a/src/Fenom/Template.php
+++ b/src/Fenom/Template.php
@@ -268,7 +268,7 @@ class Template extends Render
throw new CompileException("Unclosed tag" . (count($_names) > 1 ? "s" : "") . ": " . implode(
", ",
$_names
- ), 0, 1, $this->_name, $scope->line); // $scope already defined there!
+ ), 0, 1, $this->_name, $scope->line); // for PHPStorm: $scope already defined there!
}
$this->_src = ""; // cleanup
if ($this->_post) {
@@ -343,8 +343,9 @@ class Template extends Render
$text = str_replace("", '' . PHP_EOL, $text);
}
if($this->_options & Fenom::AUTO_STRIP) {
- $text = preg_replace('/\s+/uS', ' ', $text);
- $text = preg_replace('/\s*([\pP\pS]+)\s*/uS', '$1', $text);
+
+ $text = preg_replace('/\s+/uS', ' ', str_replace(array("\r", "\n"), " ", $text));
+// $text = preg_replace('/\s*([\pP\pS]+)\s*/uS', '$1', $text);
}
$this->_body .= $text;
}
@@ -662,10 +663,6 @@ class Template extends Render
// parse term
$term = $this->parseTerm($tokens, $var); // term of the expression
if ($term !== false) {
- if ($this->_options & Fenom::FORCE_VERIFY) {
- $term = '(isset(' . $term . ') ? ' . $term . ' : null)';
- $var = false;
- }
if ($tokens->is('|')) {
$term = $this->parseModifier($tokens, $term);
$var = false;
@@ -728,6 +725,10 @@ class Template extends Render
if ($tokens->is(T_LNUMBER, T_DNUMBER)) {
$concat[] = "strval(" . $this->parseTerm($tokens) . ")";
} else {
+ if($tokens->is('~')) {
+ $tokens->next();
+ $concat[] = " ";
+ }
if(!$concat[] = $this->parseTerm($tokens)) {
throw new UnexpectedTokenException($tokens);
}
@@ -775,23 +776,37 @@ class Template extends Render
}
return $this->parseScalar($tokens, true);
} elseif ($tokens->is(T_VARIABLE)) {
- $code = $unary . $this->parseVariable($tokens);
+ $code = $this->parseVariable($tokens);
if ($tokens->is("(") && $tokens->hasBackList(T_STRING, T_OBJECT_OPERATOR)) {
if ($this->_options & Fenom::DENY_METHODS) {
throw new \LogicException("Forbidden to call methods");
}
- $code = $this->parseChain($tokens, $code);
+ return $this->parseChain($tokens, $code);
} elseif ($tokens->is(Tokenizer::MACRO_INCDEC)) {
- $code .= $tokens->getAndNext();
+ if($this->_options & Fenom::FORCE_VERIFY) {
+ return $unary . '(isset(' . $code . ') ? ' . $code . $tokens->getAndNext() . ' : null)';
+ } else {
+ return $unary . $code . $tokens->getAndNext();
+ }
} else {
- $is_var = true;
+ if($this->_options & Fenom::FORCE_VERIFY) {
+ return $unary . '(isset(' . $code . ') ? ' . $code . ' : null)';
+ } else {
+ $is_var = true;
+ return $unary . $code;
+ }
}
- return $code;
} elseif ($tokens->is('$')) {
- $var = $this->parseAccessor($tokens, $is_var);
+ $is_var = false;
+ $var = $this->parseAccessor($tokens);
return $unary . $var;
} elseif ($tokens->is(Tokenizer::MACRO_INCDEC)) {
- return $unary . $tokens->getAndNext() . $this->parseVariable($tokens);
+ if($this->_options & Fenom::FORCE_VERIFY) {
+ $var = $this->parseVariable($tokens);
+ return $unary . '(isset(' . $var . ') ? ' . $tokens->getAndNext() . $this->parseVariable($tokens).' : null)';
+ } else {
+ return $unary . $tokens->getAndNext() . $this->parseVariable($tokens);
+ }
} elseif ($tokens->is("(")) {
$tokens->next();
$code = $unary . "(" . $this->parseExpr($tokens) . ")";
@@ -913,44 +928,18 @@ class Template extends Render
/**
* Parse accessor
+ * @param Tokenizer $tokens
+ * @return string
*/
- public function parseAccessor(Tokenizer $tokens, &$is_var)
+ public function parseAccessor(Tokenizer $tokens)
{
- $is_var = false;
- $vars = array(
- 'get' => '$_GET',
- 'post' => '$_POST',
- 'session' => '$_SESSION',
- 'cookie' => '$_COOKIE',
- 'request' => '$_REQUEST',
- 'files' => '$_FILES',
- 'globals' => '$GLOBALS',
- 'server' => '$_SERVER',
- 'env' => '$_ENV',
- 'tpl' => '$tpl->info'
- );
- if ($this->_options & Fenom::DENY_ACCESSOR) {
- throw new \LogicException("Accessor are disabled");
+ $accessor = $tokens->need('$')->next()->need('.')->next()->current();
+ $callback = $this->getStorage()->getAccessor($accessor);
+ if($callback) {
+ return call_user_func($callback, $tokens->next(), $this);
+ } else {
+ throw new \RuntimeException("Unknown accessor '$accessor'");
}
- $key = $tokens->need('$')->next()->need('.')->next()->current();
- $tokens->next();
- if (isset($vars[$key])) {
- $is_var = true;
- return $this->parseVariable($tokens, $vars[$key]);
- }
- switch ($key) {
- case 'const':
- $tokens->need('.')->next();
- $var = '@constant(' . var_export($this->parseName($tokens), true) . ')';
- break;
- case 'version':
- $var = '\Fenom::VERSION';
- break;
- default:
- throw new UnexpectedTokenException($tokens->back());
- }
-
- return $var;
}
/**
diff --git a/src/Fenom/Tokenizer.php b/src/Fenom/Tokenizer.php
index bc67cde..4a0ad6d 100644
--- a/src/Fenom/Tokenizer.php
+++ b/src/Fenom/Tokenizer.php
@@ -11,17 +11,6 @@ namespace Fenom;
use Fenom\Error\UnexpectedTokenException;
-/**
- * for PHP <5.4 compatible
- */
-defined('T_INSTEADOF') || define('T_INSTEADOF', 341);
-defined('T_TRAIT') || define('T_TRAIT', 355);
-defined('T_TRAIT_C') || define('T_TRAIT_C', 365);
-/**
- * for PHP <5.5 compatible
- */
-defined('T_YIELD') || define('T_YIELD', 267);
-
/**
* Each token have structure
* - Token (constant T_* or text)
@@ -93,154 +82,62 @@ class Tokenizer
*/
public static $macros = array(
self::MACRO_STRING => array(
- \T_ABSTRACT => 1,
- \T_ARRAY => 1,
- \T_AS => 1,
- \T_BREAK => 1,
- \T_BREAK => 1,
- \T_CASE => 1,
- \T_CATCH => 1,
- \T_CLASS => 1,
- \T_CLASS_C => 1,
- \T_CLONE => 1,
- \T_CONST => 1,
- \T_CONTINUE => 1,
- \T_DECLARE => 1,
- \T_DEFAULT => 1,
- \T_DIR => 1,
- \T_DO => 1,
- \T_ECHO => 1,
- \T_ELSE => 1,
- \T_ELSEIF => 1,
- \T_EMPTY => 1,
- \T_ENDDECLARE => 1,
- \T_ENDFOR => 1,
- \T_ENDFOREACH => 1,
- \T_ENDIF => 1,
- \T_ENDSWITCH => 1,
- \T_ENDWHILE => 1,
- \T_EVAL => 1,
- \T_EXIT => 1,
- \T_EXTENDS => 1,
- \T_FILE => 1,
- \T_FINAL => 1,
- \T_FOR => 1,
- \T_FOREACH => 1,
- \T_FUNCTION => 1,
- \T_FUNC_C => 1,
- \T_GLOBAL => 1,
- \T_GOTO => 1,
- \T_HALT_COMPILER => 1,
- \T_IF => 1,
- \T_IMPLEMENTS => 1,
- \T_INCLUDE => 1,
- \T_INCLUDE_ONCE => 1,
- \T_INSTANCEOF => 1,
- \T_INSTEADOF => 1,
- \T_INTERFACE => 1,
- \T_ISSET => 1,
- \T_LINE => 1,
- \T_LIST => 1,
- \T_LOGICAL_AND => 1,
- \T_LOGICAL_OR => 1,
- \T_LOGICAL_XOR => 1,
- \T_METHOD_C => 1,
- \T_NAMESPACE => 1,
- \T_NS_C => 1,
- \T_NEW => 1,
- \T_PRINT => 1,
- \T_PRIVATE => 1,
- \T_PUBLIC => 1,
- \T_PROTECTED => 1,
- \T_REQUIRE => 1,
- \T_REQUIRE_ONCE => 1,
- \T_RETURN => 1,
- \T_RETURN => 1,
- \T_STRING => 1,
- \T_SWITCH => 1,
- \T_THROW => 1,
- \T_TRAIT => 1,
- \T_TRAIT_C => 1,
- \T_TRY => 1,
- \T_UNSET => 1,
- \T_USE => 1,
- \T_VAR => 1,
- \T_WHILE => 1,
- \T_YIELD => 1
+ \T_ABSTRACT => 1, \T_ARRAY => 1, \T_AS => 1, \T_BREAK => 1,
+ \T_BREAK => 1, \T_CASE => 1, \T_CATCH => 1, \T_CLASS => 1,
+ \T_CLASS_C => 1, \T_CLONE => 1, \T_CONST => 1, \T_CONTINUE => 1,
+ \T_DECLARE => 1, \T_DEFAULT => 1, \T_DIR => 1, \T_DO => 1,
+ \T_ECHO => 1, \T_ELSE => 1, \T_ELSEIF => 1, \T_EMPTY => 1,
+ \T_ENDDECLARE => 1, \T_ENDFOR => 1, \T_ENDFOREACH => 1, \T_ENDIF => 1,
+ \T_ENDSWITCH => 1, \T_ENDWHILE => 1, \T_EVAL => 1, \T_EXIT => 1,
+ \T_EXTENDS => 1, \T_FILE => 1, \T_FINAL => 1, \T_FOR => 1,
+ \T_FOREACH => 1, \T_FUNCTION => 1, \T_FUNC_C => 1, \T_GLOBAL => 1,
+ \T_GOTO => 1, \T_HALT_COMPILER => 1, \T_IF => 1, \T_IMPLEMENTS => 1,
+ \T_INCLUDE => 1, \T_INCLUDE_ONCE => 1, \T_INSTANCEOF => 1, 341 /* T_INSTEADOF */ => 1,
+ \T_INTERFACE => 1, \T_ISSET => 1, \T_LINE => 1, \T_LIST => 1,
+ \T_LOGICAL_AND => 1, \T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_METHOD_C => 1,
+ \T_NAMESPACE => 1, \T_NS_C => 1, \T_NEW => 1, \T_PRINT => 1,
+ \T_PRIVATE => 1, \T_PUBLIC => 1, \T_PROTECTED => 1, \T_REQUIRE => 1,
+ \T_REQUIRE_ONCE => 1, \T_RETURN => 1, \T_RETURN => 1, \T_STRING => 1,
+ \T_SWITCH => 1, \T_THROW => 1, 355 /* T_TRAIT */ => 1, 365 /* T_TRAIT_C */ => 1,
+ \T_TRY => 1, \T_UNSET => 1, \T_USE => 1, \T_VAR => 1,
+ \T_WHILE => 1, 267 /* T_YIELD */ => 1
),
self::MACRO_INCDEC => array(
- \T_INC => 1,
- \T_DEC => 1
+ \T_INC => 1, \T_DEC => 1
),
self::MACRO_UNARY => array(
- "!" => 1,
- "~" => 1,
- "-" => 1
+ "!" => 1, "~" => 1, "-" => 1
),
self::MACRO_BINARY => array(
- \T_BOOLEAN_AND => 1,
- \T_BOOLEAN_OR => 1,
- \T_IS_GREATER_OR_EQUAL => 1,
- \T_IS_EQUAL => 1,
- \T_IS_IDENTICAL => 1,
- \T_IS_NOT_EQUAL => 1,
- \T_IS_NOT_IDENTICAL => 1,
- \T_IS_SMALLER_OR_EQUAL => 1,
- \T_LOGICAL_AND => 1,
- \T_LOGICAL_OR => 1,
- \T_LOGICAL_XOR => 1,
- \T_SL => 1,
- \T_SR => 1,
- "+" => 1,
- "-" => 1,
- "*" => 1,
- "/" => 1,
- ">" => 1,
- "<" => 1,
- "^" => 1,
- "%" => 1,
+ \T_BOOLEAN_AND => 1, \T_BOOLEAN_OR => 1, \T_IS_GREATER_OR_EQUAL => 1,
+ \T_IS_EQUAL => 1, \T_IS_IDENTICAL => 1, \T_IS_NOT_EQUAL => 1,
+ \T_IS_NOT_IDENTICAL => 1, \T_IS_SMALLER_OR_EQUAL => 1, \T_LOGICAL_AND => 1,
+ \T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1, \T_SL => 1,
+ \T_SR => 1, "+" => 1, "-" => 1,
+ "*" => 1, "/" => 1, ">" => 1,
+ "<" => 1, "^" => 1, "%" => 1,
"&" => 1
),
self::MACRO_BOOLEAN => array(
- \T_LOGICAL_OR => 1,
- \T_LOGICAL_XOR => 1,
- \T_BOOLEAN_AND => 1,
- \T_BOOLEAN_OR => 1,
+ \T_LOGICAL_OR => 1, \T_LOGICAL_XOR => 1,
+ \T_BOOLEAN_AND => 1, \T_BOOLEAN_OR => 1,
\T_LOGICAL_AND => 1
),
self::MACRO_MATH => array(
- "+" => 1,
- "-" => 1,
- "*" => 1,
- "/" => 1,
- "^" => 1,
- "%" => 1,
- "&" => 1,
- "|" => 1
+ "+" => 1, "-" => 1, "*" => 1,
+ "/" => 1, "^" => 1, "%" => 1,
+ "&" => 1, "|" => 1
),
self::MACRO_COND => array(
- \T_IS_EQUAL => 1,
- \T_IS_IDENTICAL => 1,
- ">" => 1,
- "<" => 1,
- \T_SL => 1,
- \T_SR => 1,
- \T_IS_NOT_EQUAL => 1,
- \T_IS_NOT_IDENTICAL => 1,
- \T_IS_SMALLER_OR_EQUAL => 1,
+ \T_IS_EQUAL => 1, \T_IS_IDENTICAL => 1, ">" => 1,
+ "<" => 1, \T_SL => 1, \T_SR => 1,
+ \T_IS_NOT_EQUAL => 1, \T_IS_NOT_IDENTICAL => 1, \T_IS_SMALLER_OR_EQUAL => 1,
),
self::MACRO_EQUALS => array(
- \T_AND_EQUAL => 1,
- \T_DIV_EQUAL => 1,
- \T_MINUS_EQUAL => 1,
- \T_MOD_EQUAL => 1,
- \T_MUL_EQUAL => 1,
- \T_OR_EQUAL => 1,
- \T_PLUS_EQUAL => 1,
- \T_SL_EQUAL => 1,
- \T_SR_EQUAL => 1,
- \T_XOR_EQUAL => 1,
- '=' => 1,
+ \T_AND_EQUAL => 1, \T_DIV_EQUAL => 1, \T_MINUS_EQUAL => 1,
+ \T_MOD_EQUAL => 1, \T_MUL_EQUAL => 1, \T_OR_EQUAL => 1,
+ \T_PLUS_EQUAL => 1, \T_SL_EQUAL => 1, \T_SR_EQUAL => 1,
+ \T_XOR_EQUAL => 1, '=' => 1,
),
self::MACRO_SCALAR => array(
\T_LNUMBER => 1,
diff --git a/tests/TestCase.php b/tests/TestCase.php
index f1b40a4..d20effd 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -161,13 +161,13 @@ class TestCase extends \PHPUnit_Framework_TestCase
$this->fail("Code $code must be invalid");
}
- public function assertRender($tpl, $result, $debug = false)
+ public function assertRender($tpl, $result, array $vars = array(), $debug = false)
{
$template = $this->fenom->compileCode($tpl);
if ($debug) {
print_r("\nDEBUG $tpl:\n" . $template->getBody());
}
- $this->assertSame($result, $template->fetch($this->values));
+ $this->assertSame($result, $template->fetch($vars + $this->values));
return $template;
}
@@ -276,9 +276,13 @@ class TestCase extends \PHPUnit_Framework_TestCase
}
}
+const HELPER_CONSTANT = 'helper.const';
+
class Helper
{
+ const CONSTANT = "helper.class.const";
+
public $word = 'helper';
public function __construct($word)
@@ -306,3 +310,7 @@ class Helper
}
}
+function helper_func($string, $pad = 10) {
+ return str_pad($string, $pad, ".");
+}
+
diff --git a/tests/cases/Fenom/AccessorTest.php b/tests/cases/Fenom/AccessorTest.php
new file mode 100644
index 0000000..53949d3
--- /dev/null
+++ b/tests/cases/Fenom/AccessorTest.php
@@ -0,0 +1,230 @@
+exec('{$.'.$var.'.one}', self::getVars(), "{$var}1");
+ $this->exec('{$.'.$var.'.undefined}', self::getVars(), "");
+ }
+
+ public static function providerTpl()
+ {
+ return array(
+ array("name"),
+ array("scm"),
+ array("basename"),
+ array("options"),
+ array("time"),
+ );
+ }
+
+ /**
+ * @dataProvider providerTpl
+ * @param string $name
+ */
+ public function testTpl($name)
+ {
+ $this->tpl("accessor.tpl", '{$.tpl.'.$name.'}');
+ $tpl = $this->fenom->setOptions(\Fenom::FORCE_VERIFY)->getTemplate('accessor.tpl');
+ $this->assertSame(strval($tpl->{"get$name"}()), $tpl->fetch(self::getVars()));
+ }
+
+ public function testVersion()
+ {
+ $this->assertRender('{$.version}', \Fenom::VERSION);
+ }
+
+ public static function providerConst()
+ {
+ return array(
+ array("$.const.PHP_VERSION_ID", PHP_VERSION_ID),
+ array('$.const.UNDEFINED', ''),
+ array("$.const.FENOM_RESOURCES", FENOM_RESOURCES),
+ array("$.const.Fenom.HELPER_CONSTANT", HELPER_CONSTANT),
+ array("$.const.Fenom.UNDEFINED", ''),
+ array("$.const.Fenom::VERSION", \Fenom::VERSION),
+ array("$.const.Fenom::UNDEFINED", ''),
+ array("$.const.Fenom.Helper::CONSTANT", Helper::CONSTANT),
+ array("$.const.Fenom.Helper::UNDEFINED", ''),
+ );
+ }
+
+ /**
+ * @dataProvider providerConst
+ * @param $tpl
+ * @param $value
+ * @group const
+ */
+ public function testConst($tpl, $value)
+ {
+ $this->assertRender('{'.$tpl.'}', strval($value));
+ }
+
+
+ public static function providerPHP() {
+ return array(
+ array('$.php.strrev("string")', strrev("string")),
+ array('$.php.strrev("string")', strrev("string"), 'str*'),
+ array('$.php.strrev("string")', strrev("string"), 'strrev'),
+ array('$.php.get_current_user', get_current_user()),
+ array('$.php.Fenom.helper_func("string", 12)', helper_func("string", 12)),
+ array('$.php.Fenom.helper_func("string", 12)', helper_func("string", 12), 'Fenom\\*'),
+ array('$.php.Fenom.helper_func("string", 12)', helper_func("string", 12), 'Fenom\helper_func'),
+ array('$.php.Fenom.helper_func("string", 12)', helper_func("string", 12), '*helper_func'),
+ array('$.php.Fenom.helper_func("string", 12)', helper_func("string", 12), '*'),
+ array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string")),
+ array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\*'),
+ array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\TestCase*'),
+ array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\TestCase::*'),
+ array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\*::dots'),
+ array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\*::*'),
+ array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), 'Fenom\TestCase::dots'),
+ array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), '*::dots'),
+ array('$.php.Fenom.TestCase::dots("string")', TestCase::dots("string"), '*'),
+ );
+ }
+
+ /**
+ * @dataProvider providerPHP
+ * @group php
+ */
+ public function testPHP($tpl, $result, $mask = null) {
+ if($mask) {
+ $this->fenom->addCallFilter($mask);
+ }
+ $this->assertRender('{'.$tpl.'}', $result);
+ }
+
+ public static function providerPHPInvalid() {
+ return array(
+ array('$.php.aaa("string")', 'Fenom\Error\CompileException', 'PHP method aaa does not exists'),
+ array('$.php.strrev("string")', 'Fenom\Error\SecurityException', 'Callback strrev is not available by settings', 'strrevZ'),
+ array('$.php.strrev("string")', 'Fenom\Error\SecurityException', 'Callback strrev is not available by settings', 'str*Z'),
+ array('$.php.strrev("string")', 'Fenom\Error\SecurityException', 'Callback strrev is not available by settings', '*Z'),
+ array('$.php.Fenom.aaa("string")', 'Fenom\Error\CompileException', 'PHP method Fenom.aaa does not exists'),
+ array('$.php.Fenom.helper_func("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.helper_func is not available by settings', 'Reflection\*'),
+ array('$.php.Fenom.helper_func("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.helper_func is not available by settings', 'Fenom\*Z'),
+ array('$.php.Fenom.helper_func("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.helper_func is not available by settings', 'Fenom\*::*'),
+ array('$.php.TestCase::aaa("string")', 'Fenom\Error\CompileException', 'PHP method TestCase::aaa does not exists'),
+ array('$.php.Fenom.TestCase::aaa("string")', 'Fenom\Error\CompileException', 'PHP method Fenom.TestCase::aaa does not exists'),
+ array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Reflection\*'),
+ array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Fenom\*Z'),
+ array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Fenom\*::get*'),
+ array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Fenom\TestCase::get*'),
+ array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', 'Fenom\TestCase::*Z'),
+ array('$.php.Fenom.TestCase::dots("string")', 'Fenom\Error\SecurityException', 'Callback Fenom.TestCase::dots is not available by settings', '*::*Z'),
+ );
+ }
+
+ /**
+ * @dataProvider providerPHPInvalid
+ * @group php
+ */
+ public function testPHPInvalid($tpl, $exception, $message, $methods = null) {
+ if($methods) {
+ $this->fenom->addCallFilter($methods);
+ }
+ $this->execError('{'.$tpl.'}', $exception, $message);
+ }
+
+
+ public static function providerAccessor()
+ {
+ return array(
+ array('{$.get.one}', 'get1'),
+ array('{$.post.one}', 'post1'),
+ array('{$.request.one}', 'request1'),
+ array('{$.session.one}', 'session1'),
+ array('{$.files.one}', 'files1'),
+ array('{$.globals.one}', 'globals1'),
+ array('{$.cookie.one}', 'cookie1'),
+ array('{$.server.one}', 'server1'),
+ array('{"string"|append:"_":$.get.one}', 'string_get1'),
+ array('{$.get.one?}', '1'),
+ array('{$.get.one is set}', '1'),
+ array('{$.get.two is empty}', '1'),
+ array('{$.version}', \Fenom::VERSION),
+ array('{$.tpl.name}', 'runtime.tpl'),
+ array('{$.tpl.time}', '0'),
+ array('{$.tpl.schema}', ''),
+ );
+ }
+
+ public static function providerAccessorInvalid()
+ {
+ return array(
+ array('{$.nope.one}', 'Fenom\Error\CompileException', "Unexpected token 'nope'"),
+ array('{$.get.one}', 'Fenom\Error\SecurityException', 'Accessor are disabled', \Fenom::DENY_ACCESSOR),
+ );
+ }
+
+ public static function providerFetch()
+ {
+ return array(
+ array('{$.fetch("welcome.tpl")}'),
+ array('{set $tpl = "welcome.tpl"}{$.fetch($tpl)}'),
+ array('{$.fetch("welcome.tpl", ["username" => "Bzick", "email" => "bzick@dev.null"])}'),
+ array('{set $tpl = "welcome.tpl"}{$.fetch($tpl, ["username" => "Bzick", "email" => "bzick@dev.null"])}'),
+ );
+ }
+
+ /**
+ * @group fetch
+ * @dataProvider providerFetch
+ */
+ public function testFetch($code)
+ {
+ $this->tpl('welcome.tpl', 'Welcome, {$username} ({$email})');
+ $values = array('username' => 'Bzick', 'email' => 'bzick@dev.null');
+ $this->assertRender($code, $this->fenom->fetch('welcome.tpl', $values), $values);
+ }
+
+ public static function providerFetchInvalid()
+ {
+ return array(
+ array('{$.fetch("welcome_.tpl")}', 'Fenom\Error\CompileException', "Template welcome_.tpl not found"),
+ array('{$.fetch("welcome_.tpl", [])}', 'Fenom\Error\CompileException', "Template welcome_.tpl not found"),
+ );
+ }
+
+ /**
+ * @group fetchInvalid
+ * @dataProvider providerFetchInvalid
+ */
+ public function testFetchInvalidTpl($tpl, $exception, $message) {
+ $this->execError($tpl, $exception, $message);
+ }
+}
\ No newline at end of file
diff --git a/tests/cases/Fenom/SandboxTest.php b/tests/cases/Fenom/SandboxTest.php
new file mode 100644
index 0000000..30492c7
--- /dev/null
+++ b/tests/cases/Fenom/SandboxTest.php
@@ -0,0 +1,29 @@
+assertEquals([1, 2, 4, "as" => 767, "df" => ["qert"]], [1, 2, 4, "as" => 767, "df" => ["qet"]]);
+// $this->fenom->addBlockCompiler('php', 'Fenom\Compiler::nope', function ($tokens, Tag $tag) {
+// return 'cutContent();
+// });
+// $this->tpl('welcome.tpl', '{$a}');
+// try {
+// var_dump($this->fenom->compileCode('{$.fetch("welcome.tpl", ["a" => 1])}')->getBody());
+// } catch (\Exception $e) {
+// print_r($e->getMessage() . "\n" . $e->getTraceAsString());
+// while ($e->getPrevious()) {
+// $e = $e->getPrevious();
+// print_r("\n\n" . $e->getMessage() . " in {$e->getFile()}:{$e->getLine()}\n" . $e->getTraceAsString());
+// }
+// }
+// exit;
+ }
+
+}
\ No newline at end of file
diff --git a/tests/cases/Fenom/TemplateTest.php b/tests/cases/Fenom/TemplateTest.php
index 42db34a..cd8a093 100644
--- a/tests/cases/Fenom/TemplateTest.php
+++ b/tests/cases/Fenom/TemplateTest.php
@@ -17,15 +17,7 @@ class TemplateTest extends TestCase
{
parent::setUp();
$this->tpl('welcome.tpl', 'Welcome, {$username} ({$email})');
- $_GET['one'] = 'get1';
- $_POST['one'] = 'post1';
- $_REQUEST['one'] = 'request1';
- $_FILES['one'] = 'files1';
- $_SERVER['one'] = 'server1';
- $_SESSION['one'] = 'session1';
- $GLOBALS['one'] = 'globals1';
- $_ENV['one'] = 'env1';
- $_COOKIE['one'] = 'cookie1';
+
}
public static function providerVars()
@@ -441,6 +433,7 @@ class TemplateTest extends TestCase
'if: block1 end'
),
array('if: {if $unexist} block1 {else} block2 {/if} end', $a, 'if: block2 end', Fenom::FORCE_VERIFY),
+ array('if: {if !$unexist} block1 {else} block2 {/if} end', $a, 'if: block1 end', Fenom::FORCE_VERIFY),
);
}
@@ -1217,40 +1210,6 @@ class TemplateTest extends TestCase
);
}
- public static function providerAccessor()
- {
- return array(
- array('{$.get.one}', 'get1'),
- array('{$.post.one}', 'post1'),
- array('{$.request.one}', 'request1'),
- array('{$.session.one}', 'session1'),
- array('{$.files.one}', 'files1'),
- array('{$.globals.one}', 'globals1'),
- array('{$.cookie.one}', 'cookie1'),
- array('{$.server.one}', 'server1'),
- array('{$.const.PHP_EOL}', PHP_EOL),
- array('{$.const.MY}', ''),
- array('{$.version}', Fenom::VERSION),
- array('{"string"|append:"_":$.get.one}', 'string_get1'),
- array('{$.get.one?}', '1'),
- array('{$.get.one is set}', '1'),
- array('{$.get.two is empty}', '1'),
- array('{$.version}', Fenom::VERSION),
- array('{$.tpl?}', '1'),
- array('{$.tpl.name}', 'runtime.tpl'),
- array('{$.tpl.time}', '0'),
- array('{$.tpl.schema}', ''),
- );
- }
-
- public static function providerAccessorInvalid()
- {
- return array(
- array('{$.nope.one}', 'Fenom\Error\CompileException', "Unexpected token 'nope'"),
- array('{$.get.one}', 'Fenom\Error\SecurityException', 'Accessor are disabled', Fenom::DENY_ACCESSOR),
- );
- }
-
public function providerStatic()
{
return array(
@@ -1622,19 +1581,19 @@ class TemplateTest extends TestCase
* @group accessor
* @dataProvider providerAccessor
*/
- public function testAccessor($code, $result)
- {
- $this->exec($code, self::getVars(), $result);
- }
+// public function testAccessor($code, $result)
+// {
+// $this->exec($code, self::getVars(), $result);
+// }
/**
* @group accessor
* @dataProvider providerAccessorInvalid
*/
- public function testAccessorInvalid($code, $exception, $message, $options = 0)
- {
- $this->execError($code, $exception, $message, $options);
- }
+// public function testAccessorInvalid($code, $exception, $message, $options = 0)
+// {
+// $this->execError($code, $exception, $message, $options);
+// }
/**
* @group static
diff --git a/tests/cases/FenomTest.php b/tests/cases/FenomTest.php
index dc125b6..b4076c7 100644
--- a/tests/cases/FenomTest.php
+++ b/tests/cases/FenomTest.php
@@ -96,6 +96,7 @@ class FenomTest extends \Fenom\TestCase
public function testCheckMTime()
{
$this->fenom->setOptions(Fenom::FORCE_COMPILE);
+ $this->fenom->getProvider()->setClearCachedStats();
$this->tpl('custom.tpl', 'Custom template');
$this->assertSame("Custom template", $this->fenom->fetch('custom.tpl', array()));
$tpl = $this->fenom->getTemplate('custom.tpl');
@@ -324,7 +325,7 @@ class FenomTest extends \Fenom\TestCase
number {\$num.1}
TPL;
- $this->assertRender($tpl, '');
+ $this->assertRender($tpl, '');
}
}