Skip to content
Browse files

added the ability to set default values for macro arguments (closes #447

)
  • Loading branch information...
1 parent a59dcde commit 4647913e500839e7d2d72f48385c23ddc3715b92 @fabpot committed
View
1 CHANGELOG
@@ -1,5 +1,6 @@
* 1.12.0 (2012-XX-XX)
+ * added the ability to set default values for macro arguments
* added support for named arguments for filters, tests, and functions
* moved filters/functions/tests syntax errors to the parser
* added support for extended ternary operator syntaxes
View
12 doc/templates.rst
@@ -493,6 +493,9 @@ For bigger sections it makes sense to mark a block :doc:`raw<tags/raw>`.
Macros
------
+.. versionadded:: 1.12
+ Support for default argument values was added in Twig 1.12.
+
Macros are comparable with functions in regular programming languages. They
are useful to reuse often used HTML fragments to not repeat yourself.
@@ -528,6 +531,15 @@ current namespace via the :doc:`from<tags/from>` tag and optionally alias them:
<dd>{{ input_field('password', '', 'password') }}</dd>
</dl>
+A default value can also be defined for macro arguments when not provided in a
+macro call:
+
+.. code-block:: jinja
+
+ {% macro input(name, value = "", type = "text", size = 20) %}
+ <input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}" />
+ {% endmacro %}
+
Expressions
-----------
View
50 lib/Twig/ExpressionParser.php
@@ -452,22 +452,44 @@ public function parseArguments($namedArguments = false, $definition = false)
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
}
- $value = $this->parseExpression();
+ if ($definition) {
+ $token = $stream->expect(Twig_Token::NAME_TYPE, null, 'An argument must be a name');
+ $value = new Twig_Node_Expression_Name($token->getValue(), $this->parser->getCurrentToken()->getLine());
+ } else {
+ $value = $this->parseExpression();
+ }
$name = null;
if ($namedArguments && $stream->test(Twig_Token::OPERATOR_TYPE, '=')) {
$token = $stream->next();
if (!$value instanceof Twig_Node_Expression_Name) {
- throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given', get_class($value)), $token->getLine());
+ throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given', get_class($value)), $token->getLine(), $this->parser->getFilename());
}
$name = $value->getAttribute('name');
- $value = $definition ? $this->parsePrimaryExpression() : $this->parseExpression();
+
+ if ($definition) {
+ $value = $this->parsePrimaryExpression();
+
+ if (!$this->checkConstantExpression($value)) {
+ throw new Twig_Error_Syntax(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $this->parser->getFilename());
+ }
+ } else {
+ $value = $this->parseExpression();
+ }
}
- if (null === $name) {
- $args[] = $value;
- } else {
+ if ($definition) {
+ if (null === $name) {
+ $name = $value->getAttribute('name');
+ $value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine());
+ }
$args[$name] = $value;
+ } else {
+ if (null === $name) {
+ $args[] = $value;
+ } else {
+ $args[$name] = $value;
+ }
}
}
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
@@ -539,4 +561,20 @@ protected function getFilterNodeClass($name, $line)
return $filter instanceof Twig_Filter_Node ? $filter->getClass() : 'Twig_Node_Expression_Filter';
}
+
+ // checks that the node only contains "constant" elements
+ protected function checkConstantExpression(Twig_NodeInterface $node)
+ {
+ if (!($node instanceof Twig_Node_Expression_Constant || $node instanceof Twig_Node_Expression_Array)) {
+ return false;
+ }
+
+ foreach ($node as $n) {
+ if (!$this->checkConstantExpression($n)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
View
29 lib/Twig/Node/Macro.php
@@ -29,14 +29,27 @@ public function __construct($name, Twig_NodeInterface $body, Twig_NodeInterface
*/
public function compile(Twig_Compiler $compiler)
{
- $arguments = array();
- foreach ($this->getNode('arguments') as $argument) {
- $arguments[] = '$_'.$argument->getAttribute('name').' = null';
+ $compiler
+ ->addDebugInfo($this)
+ ->write(sprintf("public function get%s(", $this->getAttribute('name')))
+ ;
+
+ $count = count($this->getNode('arguments'));
+ $pos = 0;
+ foreach ($this->getNode('arguments') as $name => $default) {
+ $compiler
+ ->raw('$_'.$name.' = ')
+ ->subcompile($default)
+ ;
+
+ if (++$pos < $count) {
+ $compiler->raw(', ');
+ }
}
$compiler
- ->addDebugInfo($this)
- ->write(sprintf("public function get%s(%s)\n", $this->getAttribute('name'), implode(', ', $arguments)), "{\n")
+ ->raw(")\n")
+ ->write("{\n")
->indent()
;
@@ -48,11 +61,11 @@ public function compile(Twig_Compiler $compiler)
->indent()
;
- foreach ($this->getNode('arguments') as $argument) {
+ foreach ($this->getNode('arguments') as $name => $default) {
$compiler
->write('')
- ->string($argument->getAttribute('name'))
- ->raw(' => $_'.$argument->getAttribute('name'))
+ ->string($name)
+ ->raw(' => $_'.$name)
->raw(",\n")
;
}
View
2 lib/Twig/TokenParser/Macro.php
@@ -33,7 +33,7 @@ public function parse(Twig_Token $token)
$stream = $this->parser->getStream();
$name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
- $arguments = $this->parser->getExpressionParser()->parseArguments();
+ $arguments = $this->parser->getExpressionParser()->parseArguments(true, true);
$stream->expect(Twig_Token::BLOCK_END_TYPE);
$this->parser->pushLocalScope();
View
57 test/Twig/Tests/ExpressionParserTest.php
@@ -239,6 +239,63 @@ public function testMacroCallDoesNotSupportNamedArguments()
/**
* @expectedException Twig_Error_Syntax
+ * @expectedExceptionMessage An argument must be a name. Unexpected token "string" of value "a" ("name" expected) in "index" at line 1
+ */
+ public function testMacroDefinitionDoesNotSupportNonNameVariableName()
+ {
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
+ $parser = new Twig_Parser($env);
+
+ $parser->parse($env->tokenize('{% macro foo("a") %}{% endmacro %}', 'index'));
+ }
+
+ /**
+ * @expectedException Twig_Error_Syntax
+ * @expectedExceptionMessage A default value for an argument must be a constant (a boolean, a string, a number, or an array) in "index" at line 1
+ * @dataProvider getMacroDefinitionDoesNotSupportNonConstantDefaultValues
+ */
+ public function testMacroDefinitionDoesNotSupportNonConstantDefaultValues($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 getMacroDefinitionDoesNotSupportNonConstantDefaultValues()
+ {
+ return array(
+ array('{% macro foo(name = "a #{foo} a") %}{% endmacro %}'),
+ array('{% macro foo(name = [["b", "a #{foo} a"]]) %}{% endmacro %}'),
+ );
+ }
+
+ /**
+ * @dataProvider getMacroDefinitionSupportsConstantDefaultValues
+ */
+ public function testMacroDefinitionSupportsConstantDefaultValues($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 getMacroDefinitionSupportsConstantDefaultValues()
+ {
+ return array(
+ array('{% macro foo(name = "aa") %}{% endmacro %}'),
+ array('{% macro foo(name = 12) %}{% endmacro %}'),
+ array('{% macro foo(name = true) %}{% endmacro %}'),
+ array('{% macro foo(name = ["a"]) %}{% endmacro %}'),
+ array('{% macro foo(name = [["a"]]) %}{% endmacro %}'),
+ array('{% macro foo(name = {a: "a"}) %}{% endmacro %}'),
+ array('{% macro foo(name = {a: {b: "a"}}) %}{% endmacro %}'),
+ );
+ }
+
+ /**
+ * @expectedException Twig_Error_Syntax
* @expectedExceptionMessage The function "cycl" does not exist. Did you mean "cycle" in "index" at line 1
*/
public function testUnknownFunction()
View
16 test/Twig/Tests/Fixtures/macros/default_values.test
@@ -0,0 +1,16 @@
+--TEST--
+macro
+--TEMPLATE--
+{% from _self import test %}
+
+{% macro test(a, b = 'bar') -%}
+{{ a }}{{ b }}
+{%- endmacro %}
+
+{{ test('foo') }}
+{{ test('bar', 'foo') }}
+--DATA--
+return array();
+--EXPECT--
+foobar
+barfoo
View
8 test/Twig/Tests/Node/MacroTest.php
@@ -37,16 +37,20 @@ public function testCompile($node, $source, $environment = null)
public function getTests()
{
$body = new Twig_Node_Text('foo', 1);
- $arguments = new Twig_Node(array(new Twig_Node_Expression_Name('foo', 1)), array(), 1);
+ $arguments = new Twig_Node(array(
+ 'foo' => new Twig_Node_Expression_Constant(null, 1),
+ 'bar' => new Twig_Node_Expression_Constant('Foo', 1),
+ ), array(), 1);
$node = new Twig_Node_Macro('foo', $body, $arguments, 1);
return array(
array($node, <<<EOF
// line 1
-public function getfoo(\$_foo = null)
+public function getfoo(\$_foo = null, \$_bar = "Foo")
{
\$context = \$this->env->mergeGlobals(array(
"foo" => \$_foo,
+ "bar" => \$_bar,
));
\$blocks = array();

0 comments on commit 4647913

Please sign in to comment.
Something went wrong with that request. Please try again.