mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-11 18:06:46 +00:00
Merge branch '1.x' into 2.x
* 1.x: added the key to the map and filter filters added support for iterators removed fn in front of arrow functions changed arrow syntax added "filter", "map", and "reduce" filters simplified code fixed CS fixed partial output leak when a PHP fatal error occurs
This commit is contained in:
@@ -224,6 +224,8 @@
|
||||
|
||||
* 1.41.0 (2019-XX-XX)
|
||||
|
||||
* added "filter", "map", and "reduce" filters (and support for arrow functions)
|
||||
* fixed partial output leak when a PHP fatal error occurs
|
||||
* optimized context access on PHP 7.4
|
||||
|
||||
* 1.40.1 (2019-04-29)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
``filter``
|
||||
=========
|
||||
|
||||
.. versionadded:: 1.41
|
||||
The ``filter`` filter was added in Twig 1.41.
|
||||
|
||||
The ``filter`` filter filters elements of a sequence or a mapping using an arrow
|
||||
function. The arrow function receives the value of the sequence or mapping:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
{% set sizes = [34, 36, 38, 40, 42] %}
|
||||
|
||||
{% for v in sizes|filter(v => v > 38) -%}
|
||||
{{ v }}
|
||||
{% endfor %}
|
||||
{# output 40 42 #}
|
||||
|
||||
{% set sizes = {
|
||||
xs: 34,
|
||||
s: 36,
|
||||
m: 38,
|
||||
l: 40,
|
||||
xl: 42,
|
||||
} %}
|
||||
|
||||
{% for k, v in sizes|filter(v => v > 38) -%}
|
||||
{{ k }} = {{ v }}
|
||||
{% endfor %}
|
||||
{# output l = 40 xl = 42 #}
|
||||
|
||||
The arrow function also receives the key as a second argument:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
{% for k, v in sizes|filter((v, k) => v > 38 and k != "xl") -%}
|
||||
{{ k }} = {{ v }}
|
||||
{% endfor %}
|
||||
{# output l = 40 #}
|
||||
|
||||
Note that the arrow function has access to the current context.
|
||||
|
||||
Arguments
|
||||
---------
|
||||
|
||||
* ``array``: The sequence or mapping
|
||||
* ``arrow``: The arrow function
|
||||
@@ -13,6 +13,7 @@ Filters
|
||||
date_modify
|
||||
default
|
||||
escape
|
||||
filter
|
||||
first
|
||||
format
|
||||
join
|
||||
@@ -21,10 +22,12 @@ Filters
|
||||
last
|
||||
length
|
||||
lower
|
||||
map
|
||||
merge
|
||||
nl2br
|
||||
number_format
|
||||
raw
|
||||
reduce
|
||||
replace
|
||||
reverse
|
||||
round
|
||||
|
||||
@@ -19,7 +19,7 @@ define it with the optional first parameter:
|
||||
|
||||
{{ [1, 2, 3]|join('|') }}
|
||||
{# outputs 1|2|3 #}
|
||||
|
||||
|
||||
A second parameter can also be provided that will be the separator used between
|
||||
the last two items of the sequence:
|
||||
|
||||
@@ -27,7 +27,7 @@ the last two items of the sequence:
|
||||
|
||||
{{ [1, 2, 3]|join(', ', ' and ') }}
|
||||
{# outputs 1, 2 and 3 #}
|
||||
|
||||
|
||||
Arguments
|
||||
---------
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
``map``
|
||||
=======
|
||||
|
||||
.. versionadded:: 1.41
|
||||
The ``map`` filter was added in Twig 1.41.
|
||||
|
||||
The ``map`` filter applies an arrow function to the elements of a sequence or a
|
||||
mapping. The arrow function receives the value of the sequence or mapping:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
{% set people = [
|
||||
{first: "Bob", last: "Smith"},
|
||||
{first: "Alice", last: "Dupond"},
|
||||
] %}
|
||||
|
||||
{{ people|map(p => "#{p.first} #{p.last}")|join(', ') }}
|
||||
{# outputs Bob Smith, Alice Dupond #}
|
||||
|
||||
The arrow function also receives the key as a second argument:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
{% set people = {
|
||||
"Bob": "Smith",
|
||||
"Alice": "Dupond",
|
||||
} %}
|
||||
|
||||
{{ people|map((first, last) => "#{first} #{last}")|join(', ') }}
|
||||
{# outputs Bob Smith, Alice Dupond #}
|
||||
|
||||
Note that the arrow function has access to the current context.
|
||||
|
||||
Arguments
|
||||
---------
|
||||
|
||||
* ``array``: The sequence or mapping
|
||||
* ``arrow``: The arrow function
|
||||
@@ -0,0 +1,33 @@
|
||||
``reduce``
|
||||
=========
|
||||
|
||||
.. versionadded:: 1.41
|
||||
The ``reduce`` filter was added in Twig 1.41.
|
||||
|
||||
The ``reduce`` filter iteratively reduces a sequence or a mapping to a single
|
||||
value using an arrow function, so as to reduce it to a single value. The arrow
|
||||
function receives the return value of the previous iteration and the current
|
||||
value of the sequence or mapping:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
{% set numbers = [1, 2, 3] %}
|
||||
|
||||
{{ numbers|reduce((carry, v) => carry + v) }}
|
||||
{# output 6 #}
|
||||
|
||||
The ``reduce`` filter takes an ``initial`` value as a second argument:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
{{ numbers|reduce((carry, v) => carry + v, 10) }}
|
||||
{# output 16 #}
|
||||
|
||||
Note that the arrow function has access to the current context.
|
||||
|
||||
Arguments
|
||||
---------
|
||||
|
||||
* ``array``: The sequence or mapping
|
||||
* ``arrow``: The arrow function
|
||||
* ``initial``: The initial value
|
||||
@@ -15,6 +15,7 @@ namespace Twig;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\ArrowFunctionExpression;
|
||||
use Twig\Node\Expression\AssignNameExpression;
|
||||
use Twig\Node\Expression\Binary\ConcatBinary;
|
||||
use Twig\Node\Expression\BlockReferenceExpression;
|
||||
@@ -60,8 +61,12 @@ class ExpressionParser
|
||||
$this->binaryOperators = $env->getBinaryOperators();
|
||||
}
|
||||
|
||||
public function parseExpression($precedence = 0)
|
||||
public function parseExpression($precedence = 0, $allowArrow = false)
|
||||
{
|
||||
if ($allowArrow && $arrow = $this->parseArrow()) {
|
||||
return $arrow;
|
||||
}
|
||||
|
||||
$expr = $this->getPrimary();
|
||||
$token = $this->parser->getCurrentToken();
|
||||
while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) {
|
||||
@@ -90,6 +95,64 @@ class ExpressionParser
|
||||
return $expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrowFunctionExpression|null
|
||||
*/
|
||||
private function parseArrow()
|
||||
{
|
||||
$stream = $this->parser->getStream();
|
||||
|
||||
// short array syntax (one argument, no parentheses)?
|
||||
if ($stream->look(1)->test(Token::ARROW_TYPE)) {
|
||||
$line = $stream->getCurrent()->getLine();
|
||||
$token = $stream->expect(Token::NAME_TYPE);
|
||||
$names = [new AssignNameExpression($token->getValue(), $token->getLine())];
|
||||
$stream->expect(Token::ARROW_TYPE);
|
||||
|
||||
return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
|
||||
}
|
||||
|
||||
// first, determine if we are parsing an arrow function by finding => (long form)
|
||||
$i = 0;
|
||||
if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, '(')) {
|
||||
return null;
|
||||
}
|
||||
++$i;
|
||||
while (true) {
|
||||
// variable name
|
||||
++$i;
|
||||
if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ',')) {
|
||||
break;
|
||||
}
|
||||
++$i;
|
||||
}
|
||||
if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ')')) {
|
||||
return null;
|
||||
}
|
||||
++$i;
|
||||
if (!$stream->look($i)->test(Token::ARROW_TYPE)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// yes, let's parse it properly
|
||||
$token = $stream->expect(Token::PUNCTUATION_TYPE, '(');
|
||||
$line = $token->getLine();
|
||||
|
||||
$names = [];
|
||||
while (true) {
|
||||
$token = $stream->expect(Token::NAME_TYPE);
|
||||
$names[] = new AssignNameExpression($token->getValue(), $token->getLine());
|
||||
|
||||
if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$stream->expect(Token::PUNCTUATION_TYPE, ')');
|
||||
$stream->expect(Token::ARROW_TYPE);
|
||||
|
||||
return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
|
||||
}
|
||||
|
||||
private function getPrimary(): AbstractExpression
|
||||
{
|
||||
$token = $this->parser->getCurrentToken();
|
||||
@@ -485,7 +548,7 @@ class ExpressionParser
|
||||
if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
|
||||
$arguments = new Node();
|
||||
} else {
|
||||
$arguments = $this->parseArguments(true);
|
||||
$arguments = $this->parseArguments(true, false, true);
|
||||
}
|
||||
|
||||
$class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
|
||||
@@ -512,7 +575,7 @@ class ExpressionParser
|
||||
*
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
public function parseArguments($namedArguments = false, $definition = false)
|
||||
public function parseArguments($namedArguments = false, $definition = false, $allowArrow = false)
|
||||
{
|
||||
$args = [];
|
||||
$stream = $this->parser->getStream();
|
||||
@@ -527,7 +590,7 @@ class ExpressionParser
|
||||
$token = $stream->expect(/* Token::NAME_TYPE */ 5, null, 'An argument must be a name');
|
||||
$value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine());
|
||||
} else {
|
||||
$value = $this->parseExpression();
|
||||
$value = $this->parseExpression(0, $allowArrow);
|
||||
}
|
||||
|
||||
$name = null;
|
||||
@@ -544,7 +607,7 @@ class ExpressionParser
|
||||
throw new SyntaxError(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getSourceContext());
|
||||
}
|
||||
} else {
|
||||
$value = $this->parseExpression();
|
||||
$value = $this->parseExpression(0, $allowArrow);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,7 +676,7 @@ class ExpressionParser
|
||||
$class = $this->getTestNodeClass($test);
|
||||
$arguments = null;
|
||||
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
|
||||
$arguments = $this->parser->getExpressionParser()->parseArguments(true);
|
||||
$arguments = $this->parseArguments(true);
|
||||
}
|
||||
|
||||
return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine());
|
||||
|
||||
@@ -229,6 +229,9 @@ final class CoreExtension extends AbstractExtension
|
||||
new TwigFilter('merge', 'twig_array_merge'),
|
||||
new TwigFilter('batch', 'twig_array_batch'),
|
||||
new TwigFilter('column', 'twig_array_column'),
|
||||
new TwigFilter('filter', 'twig_array_filter'),
|
||||
new TwigFilter('map', 'twig_array_map'),
|
||||
new TwigFilter('reduce', 'twig_array_reduce'),
|
||||
|
||||
// string/array filters
|
||||
new TwigFilter('reverse', 'twig_reverse_filter', ['needs_environment' => true]),
|
||||
@@ -1728,4 +1731,36 @@ function twig_array_column($array, $name): array
|
||||
|
||||
return array_column($array, $name);
|
||||
}
|
||||
|
||||
function twig_array_filter($array, $arrow)
|
||||
{
|
||||
if (\is_array($array)) {
|
||||
if (\PHP_VERSION_ID >= 50600) {
|
||||
return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH);
|
||||
}
|
||||
|
||||
return array_filter($array, $arrow);
|
||||
}
|
||||
|
||||
return new \CallbackFilterIterator($array, $arrow);
|
||||
}
|
||||
|
||||
function twig_array_map($array, $arrow)
|
||||
{
|
||||
$r = [];
|
||||
foreach ($array as $k => $v) {
|
||||
$r[$k] = $arrow($v, $k);
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
function twig_array_reduce($array, $arrow, $initial = null)
|
||||
{
|
||||
if (!\is_array($array)) {
|
||||
$array = iterator_to_array($array);
|
||||
}
|
||||
|
||||
return array_reduce($array, $arrow, $initial);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ function twig_var_dump(Environment $env, $context, ...$vars)
|
||||
return;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
ob_start(function () { return ''; });
|
||||
|
||||
if (!$vars) {
|
||||
$vars = [];
|
||||
|
||||
+6
-1
@@ -304,8 +304,13 @@ class Lexer
|
||||
}
|
||||
}
|
||||
|
||||
// arrow function
|
||||
if ('=' === $this->code[$this->cursor] && '>' === $this->code[$this->cursor + 1]) {
|
||||
$this->pushToken(Token::ARROW_TYPE, '=>');
|
||||
$this->moveCursor('=>');
|
||||
}
|
||||
// operators
|
||||
if (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) {
|
||||
elseif (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) {
|
||||
$this->pushToken(/* Token::OPERATOR_TYPE */ 8, preg_replace('/\s+/', ' ', $match[0]));
|
||||
$this->moveCursor($match[0]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Node;
|
||||
|
||||
/**
|
||||
* Represents an arrow function.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ArrowFunctionExpression extends AbstractExpression
|
||||
{
|
||||
public function __construct(AbstractExpression $expr, Node $names, $lineno, $tag = null)
|
||||
{
|
||||
parent::__construct(['expr' => $expr, 'names' => $names], [], $lineno, $tag);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->raw('function (')
|
||||
;
|
||||
foreach ($this->getNode('names') as $i => $name) {
|
||||
if ($i) {
|
||||
$compiler->raw(', ');
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('$__')
|
||||
->raw($name->getAttribute('name'))
|
||||
->raw('__')
|
||||
;
|
||||
}
|
||||
$compiler
|
||||
->raw(') use ($context) { ')
|
||||
;
|
||||
foreach ($this->getNode('names') as $name) {
|
||||
$compiler
|
||||
->raw('$context["')
|
||||
->raw($name->getAttribute('name'))
|
||||
->raw('"] = $__')
|
||||
->raw($name->getAttribute('name'))
|
||||
->raw('__; ')
|
||||
;
|
||||
}
|
||||
$compiler
|
||||
->raw('return ')
|
||||
->subcompile($this->getNode('expr'))
|
||||
->raw('; }')
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ class MacroNode extends Node
|
||||
->outdent()
|
||||
->write("]);\n\n")
|
||||
->write("\$blocks = [];\n\n")
|
||||
->write("ob_start();\n")
|
||||
->write("ob_start(function () { return ''; });\n")
|
||||
->write("try {\n")
|
||||
->indent()
|
||||
->subcompile($this->getNode('body'))
|
||||
|
||||
@@ -58,7 +58,7 @@ class SetNode extends Node implements NodeCaptureInterface
|
||||
} else {
|
||||
if ($this->getAttribute('capture')) {
|
||||
$compiler
|
||||
->write("ob_start();\n")
|
||||
->write("ob_start(function () { return ''; });\n")
|
||||
->subcompile($this->getNode('values'))
|
||||
;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class SpacelessNode extends Node implements NodeOutputInterface
|
||||
{
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write("ob_start();\n")
|
||||
->write("ob_start(function () { return ''; });\n")
|
||||
->subcompile($this->getNode('body'))
|
||||
->write("echo trim(preg_replace('/>\s+</', '><', ob_get_clean()));\n")
|
||||
;
|
||||
|
||||
+3
-3
@@ -223,7 +223,7 @@ abstract class Template
|
||||
*/
|
||||
public function renderParentBlock($name, array $context, array $blocks = [])
|
||||
{
|
||||
ob_start();
|
||||
ob_start(function () { return ''; });
|
||||
$this->displayParentBlock($name, $context, $blocks);
|
||||
|
||||
return ob_get_clean();
|
||||
@@ -244,7 +244,7 @@ abstract class Template
|
||||
*/
|
||||
public function renderBlock($name, array $context, array $blocks = [], $useBlocks = true)
|
||||
{
|
||||
ob_start();
|
||||
ob_start(function () { return ''; });
|
||||
$this->displayBlock($name, $context, $blocks, $useBlocks);
|
||||
|
||||
return ob_get_clean();
|
||||
@@ -375,7 +375,7 @@ abstract class Template
|
||||
public function render(array $context)
|
||||
{
|
||||
$level = ob_get_level();
|
||||
ob_start();
|
||||
ob_start(function () { return ''; });
|
||||
try {
|
||||
$this->display($context);
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@@ -92,7 +92,7 @@ final class TemplateWrapper
|
||||
{
|
||||
$context = $this->env->mergeGlobals($context);
|
||||
$level = ob_get_level();
|
||||
ob_start();
|
||||
ob_start(function () { return ''; });
|
||||
try {
|
||||
$this->template->displayBlock($name, $context);
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@@ -36,6 +36,7 @@ final class Token
|
||||
const PUNCTUATION_TYPE = 9;
|
||||
const INTERPOLATION_START_TYPE = 10;
|
||||
const INTERPOLATION_END_TYPE = 11;
|
||||
const ARROW_TYPE = 12;
|
||||
|
||||
/**
|
||||
* @param int $type The type of the token
|
||||
@@ -155,6 +156,9 @@ final class Token
|
||||
case self::INTERPOLATION_END_TYPE:
|
||||
$name = 'INTERPOLATION_END_TYPE';
|
||||
break;
|
||||
case self::ARROW_TYPE:
|
||||
$name = 'ARROW_TYPE';
|
||||
break;
|
||||
default:
|
||||
throw new \LogicException(sprintf('Token of type "%s" does not exist.', $type));
|
||||
}
|
||||
@@ -198,6 +202,8 @@ final class Token
|
||||
return 'begin of string interpolation';
|
||||
case self::INTERPOLATION_END_TYPE:
|
||||
return 'end of string interpolation';
|
||||
case self::ARROW_TYPE:
|
||||
return 'arrow function';
|
||||
default:
|
||||
throw new \LogicException(sprintf('Token of type "%s" does not exist.', $type));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
--TEST--
|
||||
A string in parentheses cannot be confused with an arrow function
|
||||
--TEMPLATE--
|
||||
{{ ["foo", "bar"]|join(("f")) }}
|
||||
--DATA--
|
||||
return []
|
||||
--EXPECT--
|
||||
foofbar
|
||||
@@ -0,0 +1,36 @@
|
||||
--TEST--
|
||||
"filter" filter
|
||||
--TEMPLATE--
|
||||
{% set offset = 3 %}
|
||||
|
||||
{% for k, v in [1, 5, 3, 4, 5]|filter((v) => v > offset) -%}
|
||||
{{ k }} = {{ v }}
|
||||
{% endfor %}
|
||||
|
||||
{% for k, v in {a: 1, b: 2, c: 5, d: 8}|filter(v => v > offset) -%}
|
||||
{{ k }} = {{ v }}
|
||||
{% endfor %}
|
||||
|
||||
{% for k, v in [1, 5, 3, 4, 5]|filter(v => v > offset) -%}
|
||||
{{ k }} = {{ v }}
|
||||
{% endfor %}
|
||||
|
||||
{% for k, v in it|filter((v) => v > offset) -%}
|
||||
{{ k }} = {{ v }}
|
||||
{% endfor %}
|
||||
--DATA--
|
||||
return ['it' => new \ArrayIterator(['a' => 1, 'b' => 2, 'c' => 5, 'd' => 8])]
|
||||
--EXPECT--
|
||||
1 = 5
|
||||
3 = 4
|
||||
4 = 5
|
||||
|
||||
c = 5
|
||||
d = 8
|
||||
|
||||
1 = 5
|
||||
3 = 4
|
||||
4 = 5
|
||||
|
||||
c = 5
|
||||
d = 8
|
||||
@@ -0,0 +1,20 @@
|
||||
--TEST--
|
||||
"filter" filter (PHP 5.6 required)
|
||||
--CONDITION--
|
||||
version_compare(phpversion(), '5.6.0', '>=')
|
||||
--TEMPLATE--
|
||||
{% set offset = 3 %}
|
||||
|
||||
{% for k, v in {a: 1, b: 2, c: 5, d: 8}|filter((v, k) => (v > offset) and (k != "d")) -%}
|
||||
{{ k }} = {{ v }}
|
||||
{% endfor %}
|
||||
|
||||
{% for k, v in it|filter((v, k) => (v > offset) and (k != "d")) -%}
|
||||
{{ k }} = {{ v }}
|
||||
{% endfor %}
|
||||
--DATA--
|
||||
return ['it' => new \ArrayIterator(['a' => 1, 'b' => 2, 'c' => 5, 'd' => 8])]
|
||||
--EXPECT--
|
||||
c = 5
|
||||
|
||||
c = 5
|
||||
@@ -0,0 +1,41 @@
|
||||
--TEST--
|
||||
"map" filter
|
||||
--TEMPLATE--
|
||||
{% set offset = 3 %}
|
||||
|
||||
{% for k, v in [1, 2]|map((item) => item + 2 ) -%}
|
||||
{{ k }} = {{ v }}
|
||||
{% endfor %}
|
||||
|
||||
{% for k, v in {a: 1, b: 2}|map((item) => item ~ "*" ) -%}
|
||||
{{ k }} = {{ v }}
|
||||
{% endfor %}
|
||||
|
||||
{% for k, v in {a: 1, b: 2}|map((item, k) => item ~ "*" ~ k ) -%}
|
||||
{{ k }} = {{ v }}
|
||||
{% endfor %}
|
||||
|
||||
{% for k, v in [1, 2]|map(item => item + 2 ) -%}
|
||||
{{ k }} = {{ v }}
|
||||
{% endfor %}
|
||||
|
||||
{% for k, v in it|map(item => item + 2 ) -%}
|
||||
{{ k }} = {{ v }}
|
||||
{% endfor %}
|
||||
--DATA--
|
||||
return ['it' => new \ArrayIterator([1, 2])]
|
||||
--EXPECT--
|
||||
0 = 3
|
||||
1 = 4
|
||||
|
||||
a = 1*
|
||||
b = 2*
|
||||
|
||||
a = 1*a
|
||||
b = 2*b
|
||||
|
||||
0 = 3
|
||||
1 = 4
|
||||
|
||||
0 = 3
|
||||
1 = 4
|
||||
@@ -0,0 +1,14 @@
|
||||
--TEST--
|
||||
"reduce" filter
|
||||
--TEMPLATE--
|
||||
{% set offset = 3 %}
|
||||
|
||||
{{ [1, -1, 4]|reduce((carry, item) => carry + item + offset, 10) }}
|
||||
|
||||
{{ it|reduce((carry, item) => carry + item + offset, 10) }}
|
||||
--DATA--
|
||||
return ['it' => new \ArrayIterator([1, -1, 4])]
|
||||
--EXPECT--
|
||||
23
|
||||
|
||||
23
|
||||
@@ -51,7 +51,7 @@ public function macro_foo(\$__foo__ = null, \$__bar__ = "Foo", ...\$__varargs__)
|
||||
|
||||
\$blocks = [];
|
||||
|
||||
ob_start();
|
||||
ob_start(function () { return ''; });
|
||||
try {
|
||||
echo "foo";
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ EOF
|
||||
$node = new SetNode(true, $names, $values, 1);
|
||||
$tests[] = [$node, <<<EOF
|
||||
// line 1
|
||||
ob_start();
|
||||
ob_start(function () { return ''; });
|
||||
echo "foo";
|
||||
\$context["foo"] = ('' === \$tmp = ob_get_clean()) ? '' : new Markup(\$tmp, \$this->env->getCharset());
|
||||
EOF
|
||||
|
||||
@@ -32,7 +32,7 @@ class Twig_Tests_Node_SpacelessTest extends NodeTestCase
|
||||
return [
|
||||
[$node, <<<EOF
|
||||
// line 1
|
||||
ob_start();
|
||||
ob_start(function () { return ''; });
|
||||
echo "<div> <div> foo </div> </div>";
|
||||
echo trim(preg_replace('/>\s+</', '><', ob_get_clean()));
|
||||
EOF
|
||||
|
||||
Reference in New Issue
Block a user