vendor/twig/twig/src/TokenParser/MacroTokenParser.php line 46

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of Twig.
  4. *
  5. * (c) Fabien Potencier
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Twig\TokenParser;
  11. use Twig\Error\SyntaxError;
  12. use Twig\Node\BodyNode;
  13. use Twig\Node\EmptyNode;
  14. use Twig\Node\Expression\ArrayExpression;
  15. use Twig\Node\Expression\ConstantExpression;
  16. use Twig\Node\Expression\Unary\NegUnary;
  17. use Twig\Node\Expression\Unary\PosUnary;
  18. use Twig\Node\Expression\Variable\LocalVariable;
  19. use Twig\Node\MacroNode;
  20. use Twig\Node\Node;
  21. use Twig\Token;
  22. /**
  23. * Defines a macro.
  24. *
  25. * {% macro input(name, value, type, size) %}
  26. * <input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
  27. * {% endmacro %}
  28. *
  29. * @internal
  30. */
  31. final class MacroTokenParser extends AbstractTokenParser
  32. {
  33. public function parse(Token $token): Node
  34. {
  35. $lineno = $token->getLine();
  36. $stream = $this->parser->getStream();
  37. $name = $stream->expect(Token::NAME_TYPE)->getValue();
  38. $arguments = $this->parseDefinition();
  39. $stream->expect(Token::BLOCK_END_TYPE);
  40. $this->parser->pushLocalScope();
  41. $body = $this->parser->subparse([$this, 'decideBlockEnd'], true);
  42. if ($token = $stream->nextIf(Token::NAME_TYPE)) {
  43. $value = $token->getValue();
  44. if ($value != $name) {
  45. throw new SyntaxError(\sprintf('Expected endmacro for macro "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext());
  46. }
  47. }
  48. $this->parser->popLocalScope();
  49. $stream->expect(Token::BLOCK_END_TYPE);
  50. $this->parser->setMacro($name, new MacroNode($name, new BodyNode([$body]), $arguments, $lineno));
  51. return new EmptyNode($lineno);
  52. }
  53. public function decideBlockEnd(Token $token): bool
  54. {
  55. return $token->test('endmacro');
  56. }
  57. public function getTag(): string
  58. {
  59. return 'macro';
  60. }
  61. private function parseDefinition(): ArrayExpression
  62. {
  63. $arguments = new ArrayExpression([], $this->parser->getCurrentToken()->getLine());
  64. $stream = $this->parser->getStream();
  65. $stream->expect(Token::OPERATOR_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
  66. while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) {
  67. if (\count($arguments)) {
  68. $stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
  69. // if the comma above was a trailing comma, early exit the argument parse loop
  70. if ($stream->test(Token::PUNCTUATION_TYPE, ')')) {
  71. break;
  72. }
  73. }
  74. $token = $stream->expect(Token::NAME_TYPE, null, 'An argument must be a name');
  75. $name = new LocalVariable($token->getValue(), $this->parser->getCurrentToken()->getLine());
  76. if ($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) {
  77. $default = $this->parser->parseExpression();
  78. } else {
  79. $default = new ConstantExpression(null, $this->parser->getCurrentToken()->getLine());
  80. $default->setAttribute('is_implicit', true);
  81. }
  82. if (!$this->checkConstantExpression($default)) {
  83. throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping).', $token->getLine(), $stream->getSourceContext());
  84. }
  85. $arguments->addElement($default, $name);
  86. }
  87. $stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
  88. return $arguments;
  89. }
  90. // checks that the node only contains "constant" elements
  91. private function checkConstantExpression(Node $node): bool
  92. {
  93. if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression
  94. || $node instanceof NegUnary || $node instanceof PosUnary
  95. )) {
  96. return false;
  97. }
  98. foreach ($node as $n) {
  99. if (!$this->checkConstantExpression($n)) {
  100. return false;
  101. }
  102. }
  103. return true;
  104. }
  105. }