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 @@
Hello, Bzick.
``` -Переменные могут быть массивом. В этом случае обращение по ключу происходит через оператор `.` или, как в PHP, через операторы `[` и `]` +Переменные могут быть массивом. В этом случае обращение по ключу происходит через опертор `.` или, как в PHP, через операторы `[` и `]` ```smarty
Hello, {$user.name}.
``` @@ -38,12 +38,12 @@
Hello, {$user['name']}.
``` -В случае объекта, доступ к его свойствам осуществляется так как и в PHP — через оператор `->`: +В случае объекта, доступ к его свойствам осущесвляется так как и в PHP — через оператор `->`: ```smarty
Hello, {$user->name}.
``` -Методы, как и свойства можно вызвать через оператор `->`, передав в метод любые аргументы: +Методы, как и свойства можно вызвать через оператор `->`, передав в метод любые рагументы: ```smarty
Hello, {$user->getName()}.
``` @@ -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, '
number one
'); + $this->assertRender($tpl, '
number one
'); } }