or just set the 'autoescape' option to 'false'.
* removed the "without loop" attribute for the "for" tag (not needed anymore
as the Optimizer take care of that for most cases)
+ * arrays and hashes have now a different syntax
+ * arrays keep the same syntax with square brackets: [1, 2]
+ * hashes now use curly braces (["a": "b"] should now be written as {"a": "b"})
+ * support for "arrays with keys" and "hashes without keys" is not supported anymore ([1, "foo": "bar"] or {"foo": "bar", 1})
Changes:
{% set foo = [1, 2] %}
- {% set foo = ['foo': 'bar'] %}
+ {% set foo = {'foo': 'bar'} %}
{% set foo = 'foo' ~ 'bar' %}
{# the foo template will have access to the variables from the current context and the foo one #}
{% include 'foo' with ['foo': 'bar'] %}
- {% set vars = ['foo': 'bar'] %}
+ {% set vars = {'foo': 'bar'} %}
{% include 'foo' with vars %}
You can disable access to the context by appending the `only` keyword:
writing the number down. If a dot is present the number is a float,
otherwise an integer.
- * `[foo, bar]` (new in Twig 0.9.5): Arrays are defined by a sequence of
+ * `["foo", "bar"]` (new in Twig 0.9.5): Arrays are defined by a sequence of
expressions separated by a comma (`,`) and wrapped with squared brackets
- (`[]`). As an array element can be any valid expression, arrays can be
- nested. Like PHP, arrays can also have named items (hashes) like `['foo':
- 'foo', 'bar': 'bar']`. You can even mix and match both syntaxes: `['foo':
- 'foo', 'bar']`.
+ (`[]`).
+
+ * `{"foo": "bar"}` (new in Twig 0.9.10): Hashes are defined by a list of keys
+ and values separated by a comma (`,`) and wrapped with curly braces (`{}`).
+ A value can be any valid expression.
* `true` / `false`: `true` represents the true value, `false`
represents the false value.
* `none`: `none` represents no specific value (the equivalent of `null` in
PHP). This is the value returned when a variable does not exist.
+Arrays and hashes can be nested:
+
+ [Twig]
+ {% set foo = [1, {"foo": "bar"}] %}
+
### Math
Twig allows you to calculate with values. This is rarely useful in templates
(placeholders follows the `printf` notation):
[twig]
- {# string is a format string like: I like %s and %s. #}
+ {# string is a format string like: #}
{{ string|format(foo, "bar") }}
{# returns I like foo and bar. (if the foo parameter equals to the foo string) #}
[twig]
{# string is a format string like: I like %this% and %that%. #}
- {{ string|replace(['%this%': foo, '%that%': "bar"]) }}
+ {{ string|replace({'%this%': foo, '%that%': "bar"}) }}
{# returns I like foo and bar. (if the foo parameter equals to the foo string) #}
### `cycle`
default:
if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) {
$node = $this->parseArrayExpression();
+ } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) {
+ $node = $this->parseHashExpression();
} else {
throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::getTypeAsString($token->getType()), $token->getValue()), $token->getLine());
}
// trailing ,?
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
- $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
-
- return new Twig_Node_Expression_Array($elements, $this->parser->getCurrentToken()->getLine());
+ break;
}
}
- // hash or array element?
- if (
- $stream->test(Twig_Token::STRING_TYPE)
- ||
- $stream->test(Twig_Token::NUMBER_TYPE)
- )
- {
- if ($stream->look()->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
- // hash
- $key = $stream->next()->getValue();
- $stream->next();
+ $elements[] = $this->parseExpression();
+ }
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
- $elements[$key] = $this->parseExpression();
+ return new Twig_Node_Expression_Array($elements, $stream->getCurrent()->getLine());
+ }
- continue;
+ public function parseHashExpression()
+ {
+ $stream = $this->parser->getStream();
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, '{');
+ $elements = array();
+ while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
+ if (!empty($elements)) {
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',');
+
+ // trailing ,?
+ if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
+ break;
}
- $stream->rewind();
}
- $elements[] = $this->parseExpression();
+ if (!$stream->test(Twig_Token::STRING_TYPE) && !$stream->test(Twig_Token::NUMBER_TYPE)) {
+ throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string or a number (unexpected token "%s" of value "%s"', Twig_Token::getTypeAsString($stream->getCurrent()->getType()), $stream->getCurrent()->getValue()), $stream->getCurrent()->getLine());
+ }
+
+ $key = $stream->next()->getValue();
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':');
+ $elements[$key] = $this->parseExpression();
}
- $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, '}');
- return new Twig_Node_Expression_Array($elements, $this->parser->getCurrentToken()->getLine());
+ return new Twig_Node_Expression_Array($elements, $stream->getCurrent()->getLine());
}
public function parsePostfixExpression($node)
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2010 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Grammar_Hash extends Twig_Grammar
+{
+ public function __toString()
+ {
+ return sprintf('<%s:array>', $this->name);
+ }
+
+ public function parse(Twig_Token $token)
+ {
+ return $this->parser->getExpressionParser()->parseHashExpression();
+ }
+}
--- /dev/null
+<?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.
+ */
+
+class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider getTestsForArray
+ */
+ public function testArrayExpression($template, $expected)
+ {
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
+ $stream = $env->tokenize($template, 'index');
+ $parser = new Twig_Parser($env);
+
+ $this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode(0)->getNode('expr'));
+ }
+
+ /**
+ * @expectedException Twig_Error_Syntax
+ * @dataProvider getFailingTestsForArray
+ */
+ public function testArraySyntaxError($template)
+ {
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
+ $parser = new Twig_Parser($env);
+
+ $parser->parse($env->tokenize($template, 'index'));
+ }
+
+ public function getFailingTestsForArray()
+ {
+ return array(
+ array('{{ [1, "a": "b"] }}'),
+ array('{{ {a: "b"} }}'),
+ array('{{ {"a": "b", 2} }}'),
+ );
+ }
+
+ public function getTestsForArray()
+ {
+ return array(
+ // simple array
+ array('{{ [1, 2] }}', new Twig_Node_Expression_Array(array(
+ new Twig_Node_Expression_Constant(1, 1),
+ new Twig_Node_Expression_Constant(2, 1),
+ ), 1),
+ ),
+
+ // array with trailing ,
+ array('{{ [1, 2, ] }}', new Twig_Node_Expression_Array(array(
+ new Twig_Node_Expression_Constant(1, 1),
+ new Twig_Node_Expression_Constant(2, 1),
+ ), 1),
+ ),
+
+ // simple hash
+ array('{{ {"a": "b", "b": "c"} }}', new Twig_Node_Expression_Array(array(
+ 'a' => new Twig_Node_Expression_Constant('b', 1),
+ 'b' => new Twig_Node_Expression_Constant('c', 1),
+ ), 1),
+ ),
+
+ // hash with trailing ,
+ array('{{ {"a": "b", "b": "c", } }}', new Twig_Node_Expression_Array(array(
+ 'a' => new Twig_Node_Expression_Constant('b', 1),
+ 'b' => new Twig_Node_Expression_Constant('c', 1),
+ ), 1),
+ ),
+
+ // hash in an array
+ array('{{ [1, {"a": "b", "b": "c"}] }}', new Twig_Node_Expression_Array(array(
+ new Twig_Node_Expression_Constant(1, 1),
+ new Twig_Node_Expression_Array(array(
+ 'a' => new Twig_Node_Expression_Constant('b', 1),
+ 'b' => new Twig_Node_Expression_Constant('c', 1),
+ ), 1),
+ ), 1),
+ ),
+
+ // array in a hash
+ array('{{ {"a": [1, 2], "b": "c"} }}', new Twig_Node_Expression_Array(array(
+ 'a' => new Twig_Node_Expression_Array(array(
+ new Twig_Node_Expression_Constant(1, 1),
+ new Twig_Node_Expression_Constant(2, 1),
+ ), 1),
+ 'b' => new Twig_Node_Expression_Constant('c', 1),
+ ), 1),
+ ),
+ );
+ }
+}
{{ [1, 2]|join(',') }}
{{ ['foo', "bar"]|join(',') }}
-{{ [1, 'foo': 'bar']|join(',') }}
-{{ [1, 'foo': 'bar']|keys|join(',') }}
+{{ {0: 1, 'foo': 'bar'}|join(',') }}
+{{ {0: 1, 'foo': 'bar'}|keys|join(',') }}
{# nested arrays #}
-{% set a = [1, 2, [1, 2], 'foo': ['foo': 'bar']] %}
+{% set a = [1, 2, [1, 2], {'foo': {'foo': 'bar'}}] %}
{{ a[2]|join(',') }}
-{{ a["foo"]|join(',') }}
+{{ a[3]["foo"]|join(',') }}
{# works even if [] is used inside the array #}
{{ [foo[bar]]|join(',') }}
--TEMPLATE--
{% include "foo.twig" %}
{% include "foo.twig" only %}
-{% include "foo.twig" with ['foo1': 'bar'] %}
-{% include "foo.twig" with ['foo1': 'bar'] only %}
+{% include "foo.twig" with {'foo1': 'bar'} %}
+{% include "foo.twig" with {'foo1': 'bar'} only %}
--TEMPLATE(foo.twig)--
{% for k, v in _context %}{{ k }},{% endfor %}
--DATA--
--TEST--
"include" tag accept variables
--TEMPLATE--
-{% include "foo.twig" with ['foo': 'bar'] %}
+{% include "foo.twig" with {'foo': 'bar'} %}
{% include "foo.twig" with vars %}
--TEMPLATE(foo.twig)--
{{ foo }}