Foreach props, range iterator and more

This commit is contained in:
bzick 2016-05-06 23:04:08 +03:00
parent 1559adf030
commit ba4ba548ff
11 changed files with 283 additions and 96 deletions

View File

@ -29,7 +29,7 @@ Templater already has own autoload-function, to register call method `Fenom::reg
Fenom::registerAutoload();
```
### Usage
### Setup
There is two way to create Fenom instance:
@ -56,7 +56,7 @@ Now Fenom is ready to work and now you can to configure it:
$fenom->setOptions($options);
```
**Short way.** Creating an object via factory method
**Short way.** Creating an object via factory method with arguments from long way.
```php
$fenom = Fenom::factory($template_dir, $template_cache_dir, $options);
@ -64,3 +64,6 @@ $fenom = Fenom::factory($template_dir, $template_cache_dir, $options);
Now Fenom is ready to work.
### Usage
### Example

View File

@ -38,9 +38,15 @@
{/foreach}
```
Получение номера (индекса) итерации
Получение номера (индекса) итерации, начиная с 0
```smarty
{foreach $list as $value}
<div>№{$value:index}: {$value}</div>
{/foreach}
или
{foreach $list as $value index=$index}
<div>№{$index}: {$value}</div>
{/foreach}
@ -49,21 +55,21 @@
Определение первого элемента
```smarty
{foreach $list as $value first=$first}
<div>{if $first} first item {/if} {$value}</div>
{foreach $list as $value}
<div>{if $value:first} first item {/if} {$value}</div>
{/foreach}
```
Переменная `$first` будет иметь значение **TRUE**, если текущая итерация является первой.
Переменная `$value:first` будет иметь значение **TRUE**, если текущая итерация является первой.
Определение последнего элемента
```smarty
{foreach $list as $value last=$last}
<div>{if $last} last item {/if} {$value}</div>
{foreach $list as $value}
<div>{if $value:last} last item {/if} {$value}</div>
{/foreach}
```
Переменная `$last` будет иметь значение **TRUE**, если текущая итерация является последней.
Переменная `$value:last` будет иметь значение **TRUE**, если текущая итерация является последней.
**Замечание:**
Использование `last` требует от `$list` быть **countable**.

View File

@ -10,7 +10,7 @@ $fenom->setOptions(Fenom::AUTO_RELOAD);
$fenom->addModifier('firstimg', function ($img) {
return $img;
});
var_dump($fenom->compileCode('{block "pb"}- {$a} -{/block} =={paste "pb"}==')->getTemplateCode());
var_dump($fenom->compileCode('{foreach $list as $k => $e last=$l first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {if $l}last{/if} {/foreach}')->getTemplateCode());
//var_dump($fenom->compile("bug158/main.tpl", [])->getTemplateCode());
//var_dump($fenom->display("bug158/main.tpl", []));
// $fenom->getTemplate("problem.tpl");

View File

@ -166,6 +166,7 @@ class Fenom
"esplit" => 'Fenom\Modifier::esplit',
"join" => 'Fenom\Modifier::join',
"in" => 'Fenom\Modifier::in',
"range" => 'Fenom\Modifier::range',
);
/**

View File

@ -10,6 +10,7 @@
namespace Fenom;
use Doctrine\Instantiator\Exception\InvalidArgumentException;
use Fenom\Error\CompileException;
use Fenom\Error\InvalidUsageException;
use Fenom\Error\UnexpectedTokenException;
@ -132,70 +133,53 @@ class Compiler
*/
public static function foreachOpen(Tokenizer $tokens, Tag $scope)
{
$p = array("index" => false, "first" => false, "last" => false);
$key = null;
$before = $body = array();
$prepend = "";
if ($tokens->is('[')) {
$scope["else"] = false;
$scope["key"] = null;
$scope["prepend"] = "";
$scope["before"] = array();
$scope["after"] = array();
$scope["body"] = array();
if ($tokens->is('[')) { // array
$count = 0;
$from = $scope->tpl->parseArray($tokens, $count);
$check = $count;
} else {
$from = $scope->tpl->parseExpr($tokens, $is_var);
$scope['from'] = $scope->tpl->parseArray($tokens, $count);
$scope['check'] = $count;
$scope["var"] = $scope->tpl->tmpVar();
$scope['prepend'] = $scope["var"].' = '.$scope['from'].';';
$scope['from'] = $scope["var"];
} else { // expression
$scope['from'] = $scope->tpl->parseExpr($tokens, $is_var);
if($is_var) {
$check = '!empty('.$from.') && (is_array('.$from.') || '.$from.' instanceof \Traversable)';
$scope['check'] = '!empty('.$scope['from'].') && (is_array('.$scope['from'].') || '.$scope['from'].' instanceof \Traversable)';
} else {
$scope["var"] = $scope->tpl->tmpVar();
$prepend = $scope["var"].' = '.$from.';';
$from = $scope["var"];
$check = 'is_array('.$from.') && count('.$from.') || ('.$from.' instanceof \Traversable)';
$scope['prepend'] = $scope["var"].' = '.$scope['from'].';';
$scope['from'] = $scope["var"];
$scope['check'] = 'is_array('.$scope['from'].') && count('.$scope['from'].') || ('.$scope['from'].' instanceof \Traversable)';
}
}
$tokens->get(T_AS);
$tokens->next();
$value = $scope->tpl->parseVariable($tokens);
if ($tokens->is(T_DOUBLE_ARROW)) {
if($tokens->is(T_AS)) {
$tokens->next();
$key = $value;
$value = $scope->tpl->parseVariable($tokens);
if ($tokens->is(T_DOUBLE_ARROW)) {
$tokens->next();
$scope["key"] = $value;
$scope["value"] = $scope->tpl->parseVariable($tokens);
} else {
$scope["value"] = $value;
}
} else {
$scope["value"] = '$_un';
}
$scope["after"] = array();
$scope["else"] = false;
while ($token = $tokens->key()) {
$param = $tokens->get(T_STRING);
if (!isset($p[$param])) {
throw new InvalidUsageException("Unknown parameter '$param' in {foreach}");
}
$var_name = self::foreachProp($scope, $param);
$tokens->getNext("=");
$tokens->next();
$p[$param] = $scope->tpl->parseVariable($tokens);
$scope['before'][] = $scope->tpl->parseVariable($tokens)." = &". $var_name;
}
if ($p["index"]) {
$before[] = $p["index"] . ' = 0';
$scope["after"][] = $p["index"] . '++';
}
if ($p["first"]) {
$before[] = $p["first"] . ' = true';
$scope["after"][] = $p["first"] . ' && (' . $p["first"] . ' = false )';
}
if ($p["last"]) {
$before[] = $p["last"] . ' = false';
$scope["uid"] = "v" . $scope->tpl->i++;
$before[] = '$' . $scope["uid"] . " = count($from)";
$body[] = 'if(!--$' . $scope["uid"] . ') ' . $p["last"] . ' = true';
}
$before = $before ? implode("; ", $before) . ";" : "";
$body = $body ? implode("; ", $body) . ";" : "";
$scope["after"] = $scope["after"] ? implode("; ", $scope["after"]) . ";" : "";
if ($key) {
return "$prepend if($check) {\n $before foreach($from as $key => $value) { $body";
} else {
return "$prepend if($check) {\n $before foreach($from as $value) { $body";
}
return '';
}
/**
@ -208,7 +192,40 @@ class Compiler
public static function foreachElse($tokens, Tag $scope)
{
$scope["no-break"] = $scope["no-continue"] = $scope["else"] = true;
return " {$scope['after']} } } else {";
$after = $scope["after"] ? implode("; ", $scope["after"]) . ";" : "";
return " {$after} } } else {";
}
/**
* @param Tag $scope
* @param string $prop
* @return string
* @throws CompileException
*/
public static function foreachProp(Tag $scope, $prop) {
if(empty($scope["props"][$prop])) {
$var_name = $scope["props"][$prop] = $scope->tpl->tmpVar()."_".$prop;
switch($prop) {
case "index":
$scope["before"][] = $var_name . ' = 0';
$scope["after"][] = $var_name . '++';
break;
case "first":
$scope["before"][] = $var_name . ' = true';
$scope["after"][] = $var_name . ' && (' . $var_name . ' = false )';
break;
case "last":
$scope["before"][] = $var_name . ' = false';
$scope["uid"] = $scope->tpl->tmpVar();
$scope["before"][] = $scope["uid"] . " = count({$scope["from"]})";
$scope["body"][] = 'if(!--' . $scope["uid"] . ') ' . $var_name . ' = true';
break;
default:
throw new CompileException("Unknown foreach property '$prop'");
}
}
return $scope["props"][$prop];
}
/**
@ -221,10 +238,20 @@ class Compiler
*/
public static function foreachClose($tokens, Tag $scope)
{
$before = $scope["before"] ? implode("; ", $scope["before"]) . ";" : "";
$head = $scope["body"] ? implode("; ", $scope["body"]) . ";" : "";
$body = $scope->getContent();
if ($scope["key"]) {
$code = "<?php {$scope["prepend"]} if({$scope["check"]}) {\n $before foreach({$scope["from"]} as {$scope["key"]} => {$scope["value"]}) { $head?>$body";
} else {
$code = "<?php {$scope["prepend"]} if({$scope["check"]}) {\n $before foreach({$scope["from"]} as {$scope["value"]}) { $head?>$body";
}
$scope->replaceContent($code);
if ($scope["else"]) {
return '}';
} else {
return " {$scope['after']} } }";
$after = $scope["after"] ? implode("; ", $scope["after"]) . ";" : "";
return " {$after} } }";
}
}

View File

@ -284,10 +284,13 @@ class Modifier
* @param string|int $from
* @param string|int $to
* @param int $step
* @return array
* @return RangeIterator
*/
public static function range($from, $to, $step = 1) {
$v = range($from, $to, $step);
return $v ? $v : array();
if($from instanceof RangeIterator) {
return $from->setStep($to);
} else {
return new RangeIterator($from, $to, $step);
}
}
}

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

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

View File

@ -219,7 +219,7 @@ class Tag extends \ArrayObject
}
/**
* Return content of block
* Returns tag's content
*
* @throws \LogicException
* @return string
@ -230,7 +230,7 @@ class Tag extends \ArrayObject
}
/**
* Cut scope content
* Cut tag's content
*
* @return string
* @throws \LogicException
@ -243,7 +243,7 @@ class Tag extends \ArrayObject
}
/**
* Replace scope content
* Replace tag's content
*
* @param $new_content
*/

View File

@ -314,12 +314,12 @@ class Template extends Render
}
/**
* Generate temporary internal template variable
* Generate name of temporary internal template variable (may be random)
* @return string
*/
public function tmpVar()
{
return sprintf('$t%x_%x', $this->_crc, $this->i++);
return sprintf('$t%x_%x', $this->_crc ? $this->_crc : mt_rand(0, 0x7FFFFFFF), $this->i++);
}
/**
@ -892,7 +892,7 @@ class Template extends Render
}
if(($allows & self::TERM_RANGE) && $tokens->is('.') && $tokens->isNext('.')) {
$tokens->next()->next();
$code = 'range('.$code.', '.$this->parseTerm($tokens, $var, self::TERM_MODS).')';
$code = '(new \Fenom\RangeIterator('.$code.', '.$this->parseTerm($tokens, $var, self::TERM_MODS).'))';
$is_var = false;
}
return $code;
@ -918,7 +918,7 @@ class Template extends Render
}
/**
* Parse variable name: $a, $a.b, $a.b['c']
* Parse variable name: $a, $a.b, $a.b['c'], $a:index
* @param Tokenizer $tokens
* @param $var
* @return string
@ -927,8 +927,19 @@ class Template extends Render
public function parseVariable(Tokenizer $tokens, $var = null)
{
if (!$var) {
$var = '$var["' . substr($tokens->get(T_VARIABLE), 1) . '"]';
$tokens->next();
if($tokens->isNext('@')) {
// $v = $tokens->get(T_VARIABLE);
$prop = $tokens->next()->next()->get(T_STRING);
if($tag = $this->getParentScope("foreach")) {
$tokens->next();
return Compiler::foreachProp($tag, $prop);
} else {
throw new UnexpectedTokenException($tokens);
}
} else {
$var = '$var["' . substr($tokens->get(T_VARIABLE), 1) . '"]';
$tokens->next();
}
}
while ($t = $tokens->key()) {
if ($t === ".") {

View File

@ -10,25 +10,26 @@ class SandboxTest extends TestCase {
*/
public function test()
{
return;
$this->fenom->setOptions(\Fenom::FORCE_VERIFY);
$this->fenom->addAccessorSmart('q', 'Navi::$q', \Fenom::ACCESSOR_VAR);
// return;
// $this->fenom->setOptions(\Fenom::FORCE_VERIFY);
// $this->fenom->addAccessorSmart('q', 'Navi::$q', \Fenom::ACCESSOR_VAR);
// $this->assertEquals([1, 2, 4, "as" => 767, "df" => ["qert"]], [1, 2, 4, "as" => 767, "df" => ["qet"]]);
// $this->fenom->addBlockCompiler('php', 'Fenom\Compiler::nope', function ($tokens, Tag $tag) {
// return '<?php ' . $tag->cutContent();
// });
// $this->tpl('welcome.tpl', '{$a}');
// var_dump($this->fenom->compileCode('{set $a=$one|min:0..$three|max:4}')->getBody());
try {
var_dump($this->fenom->compileCode('{set $.q.ddqd->d() = 333}')->getBody());
} catch (\Exception $e) {
print_r($e->getMessage() . "\n" . $e->getTraceAsString());
while ($e->getPrevious()) {
$e = $e->getPrevious();
print_r("\n\n" . $e->getMessage() . " in {$e->getFile()}:{$e->getLine()}\n" . $e->getTraceAsString());
}
}
exit;
// try {
// var_dump($this->fenom->compileCode('{foreach $a as $k}A{$k:first}{foreachelse}B{/foreach}')->getBody());
// } catch (\Exception $e) {
// print_r($e->getMessage() . "\n" . $e->getTraceAsString());
// while ($e->getPrevious()) {
// $e = $e->getPrevious();
// print_r("\n\n" . $e->getMessage() . " in {$e->getFile()}:{$e->getLine()}\n" . $e->getTraceAsString());
// }
// }
// exit;
}
}

View File

@ -696,11 +696,11 @@ class TemplateTest extends TestCase
'Fenom\Error\CompileException',
"Unexpected end of expression"
),
array(
'Foreach: {foreach $list} {$e}, {/foreach} end',
'Fenom\Error\CompileException',
"Unexpected end of expression"
),
// array(
// 'Foreach: {foreach $list} {$e}, {/foreach} end',
// 'Fenom\Error\CompileException',
// "Unexpected end of expression"
// ),
// array(
// 'Foreach: {foreach $list+1 as $e} {$e}, {/foreach} end',
// 'Fenom\Error\CompileException',
@ -754,7 +754,7 @@ class TemplateTest extends TestCase
array(
'Foreach: {foreach $list as $e unknown=1} {$e}, {/foreach} end',
'Fenom\Error\CompileException',
"Unknown parameter 'unknown'"
"Unknown foreach property 'unknown'"
),
array(
'Foreach: {foreach $list as $e index=$i+1} {$e}, {/foreach} end',
@ -1316,6 +1316,7 @@ class TemplateTest extends TestCase
return array(
array('Foreach: {foreach $list as $e} {$e}, {/foreach} end', $a, 'Foreach: one, two, three, end'),
array('Foreach: {foreach $list as $e} {$e},{break} break {/foreach} end', $a, 'Foreach: one, end'),
array('Foreach: {foreach $list} 1, {/foreach} end', $a, 'Foreach: 1, 1, 1, end'),
array(
'Foreach: {foreach $list as $e} {$e},{continue} continue {/foreach} end',
$a,
@ -1350,11 +1351,21 @@ class TemplateTest extends TestCase
$a,
'Foreach: 0: one, 1: two, 2: three, end'
),
array(
'Foreach: {foreach $list as $e} {$e@index}: {$e}, {/foreach} end',
$a,
'Foreach: 0: one, 1: two, 2: three, end'
),
array(
'Foreach: {foreach $list as $k => $e index=$i} {$i}: {$k} => {$e}, {/foreach} end',
$a,
'Foreach: 0: 1 => one, 1: 2 => two, 2: 3 => three, end'
),
array(
'Foreach: {foreach $list as $k => $e} {$e@index}: {$k} => {$e}, {/foreach} end',
$a,
'Foreach: 0: 1 => one, 1: 2 => two, 2: 3 => three, end'
),
array(
'Foreach: {foreach $empty as $k => $e index=$i} {$i}: {$k} => {$e}, {foreachelse} empty {/foreach} end',
$a,
@ -1365,11 +1376,21 @@ class TemplateTest extends TestCase
$a,
'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, end'
),
array(
'Foreach: {foreach $list as $k => $e} {if $e@first}first{/if} {$e@index}: {$k} => {$e}, {/foreach} end',
$a,
'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, end'
),
array(
'Foreach: {foreach $list as $k => $e last=$l first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {if $l}last{/if} {/foreach} end',
$a,
'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, last end'
),
array(
'Foreach: {foreach $list as $k => $e} {if $e@first}first{/if} {$e@index}: {$k} => {$e}, {if $e@last}last{/if} {/foreach} end',
$a,
'Foreach: first 0: 1 => one, 1: 2 => two, 2: 3 => three, last end'
),
array(
'Foreach: {foreach $empty as $k => $e last=$l first=$f index=$i} {if $f}first{/if} {$i}: {$k} => {$e}, {if $l}last{/if} {foreachelse} empty {/foreach} end',
$a,
@ -1566,9 +1587,9 @@ class TemplateTest extends TestCase
{
return array(
array('{set $a=1..3}', "1,2,3,"),
array('{set $a="a".."f"}', "a,b,c,d,e,f,"),
array('{set $a=1.."f"}', "1,0,"),
array('{set $a="a"..2}', "0,1,2,"),
// array('{set $a="a".."f"}', "a,b,c,d,e,f,"),
// array('{set $a=1.."f"}', "1,0,"),
// array('{set $a="a"..2}', "0,1,2,"),
array('{set $a=$one..$three}', "1,2,3,"),
array('{set $a=$one..3}', "1,2,3,"),
array('{set $a=1..$three}', "1,2,3,"),
@ -1576,16 +1597,28 @@ class TemplateTest extends TestCase
array('{set $a=$one..++$three}', "1,2,3,4,"),
array('{set $a=$one--..$three++}', "1,2,3,"),
array('{set $a=--$one..++$three}', "0,1,2,3,4,"),
array('{set $a="a"|up.."f"|up}', "A,B,C,D,E,F,"),
// array('{set $a="a"|up.."f"|up}', "A,B,C,D,E,F,"),
array('{set $a=$one|min:0..$three|max:4}', "0,1,2,3,4,"),
array('{set $a=$one|min:0..4}', "0,1,2,3,4,"),
array('{set $a=0..$three|max:4}', "0,1,2,3,4,"),
array('{set $a=0..$three|max:4}', "0,1,2,3,4,"),
array('{set $a=range(1,3)}', "1,2,3,"),
array('{set $a=range(1,3, 2)}', "1,3,"),
array('{set $a=range(1..3, 2)}', "1,3,"),
array('{set $a=range(1..3, 3)}', "1,"),
array('{set $a=range(1,3, -1)}', "3,2,1,"),
array('{set $a=range(1,3, -2)}', "3,1,"),
array('{set $a=range(1..3, -2)}', "3,1,"),
array('{set $a=range(1..3, -3)}', "3,"),
);
}
/**
* @dataProvider providerRange
* @group testRange
* @group dev
* @param string $code
* @param string $result
*/