1<?php 2 3/* 4 * This file is part of Twig. 5 * 6 * (c) Fabien Potencier 7 * (c) Armin Ronacher 8 * 9 * For the full copyright and license information, please view the LICENSE 10 * file that was distributed with this source code. 11 */ 12 13namespace Twig\TokenParser; 14 15use Twig\Error\SyntaxError; 16use Twig\Node\Expression\AssignNameExpression; 17use Twig\Node\Expression\ConstantExpression; 18use Twig\Node\Expression\GetAttrExpression; 19use Twig\Node\Expression\NameExpression; 20use Twig\Node\ForNode; 21use Twig\Token; 22use Twig\TokenStream; 23 24/** 25 * Loops over each item of a sequence. 26 * 27 * <ul> 28 * {% for user in users %} 29 * <li>{{ user.username|e }}</li> 30 * {% endfor %} 31 * </ul> 32 * 33 * @final 34 */ 35class ForTokenParser extends AbstractTokenParser 36{ 37 public function parse(Token $token) 38 { 39 $lineno = $token->getLine(); 40 $stream = $this->parser->getStream(); 41 $targets = $this->parser->getExpressionParser()->parseAssignmentExpression(); 42 $stream->expect(Token::OPERATOR_TYPE, 'in'); 43 $seq = $this->parser->getExpressionParser()->parseExpression(); 44 45 $ifexpr = null; 46 if ($stream->nextIf(Token::NAME_TYPE, 'if')) { 47 $ifexpr = $this->parser->getExpressionParser()->parseExpression(); 48 } 49 50 $stream->expect(Token::BLOCK_END_TYPE); 51 $body = $this->parser->subparse([$this, 'decideForFork']); 52 if ('else' == $stream->next()->getValue()) { 53 $stream->expect(Token::BLOCK_END_TYPE); 54 $else = $this->parser->subparse([$this, 'decideForEnd'], true); 55 } else { 56 $else = null; 57 } 58 $stream->expect(Token::BLOCK_END_TYPE); 59 60 if (\count($targets) > 1) { 61 $keyTarget = $targets->getNode(0); 62 $keyTarget = new AssignNameExpression($keyTarget->getAttribute('name'), $keyTarget->getTemplateLine()); 63 $valueTarget = $targets->getNode(1); 64 $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); 65 } else { 66 $keyTarget = new AssignNameExpression('_key', $lineno); 67 $valueTarget = $targets->getNode(0); 68 $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); 69 } 70 71 if ($ifexpr) { 72 $this->checkLoopUsageCondition($stream, $ifexpr); 73 $this->checkLoopUsageBody($stream, $body); 74 } 75 76 return new ForNode($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, $lineno, $this->getTag()); 77 } 78 79 public function decideForFork(Token $token) 80 { 81 return $token->test(['else', 'endfor']); 82 } 83 84 public function decideForEnd(Token $token) 85 { 86 return $token->test('endfor'); 87 } 88 89 // the loop variable cannot be used in the condition 90 protected function checkLoopUsageCondition(TokenStream $stream, \Twig_NodeInterface $node) 91 { 92 if ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression && 'loop' == $node->getNode('node')->getAttribute('name')) { 93 throw new SyntaxError('The "loop" variable cannot be used in a looping condition.', $node->getTemplateLine(), $stream->getSourceContext()); 94 } 95 96 foreach ($node as $n) { 97 if (!$n) { 98 continue; 99 } 100 101 $this->checkLoopUsageCondition($stream, $n); 102 } 103 } 104 105 // check usage of non-defined loop-items 106 // it does not catch all problems (for instance when a for is included into another or when the variable is used in an include) 107 protected function checkLoopUsageBody(TokenStream $stream, \Twig_NodeInterface $node) 108 { 109 if ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression && 'loop' == $node->getNode('node')->getAttribute('name')) { 110 $attribute = $node->getNode('attribute'); 111 if ($attribute instanceof ConstantExpression && \in_array($attribute->getAttribute('value'), ['length', 'revindex0', 'revindex', 'last'])) { 112 throw new SyntaxError(sprintf('The "loop.%s" variable is not defined when looping with a condition.', $attribute->getAttribute('value')), $node->getTemplateLine(), $stream->getSourceContext()); 113 } 114 } 115 116 // should check for parent.loop.XXX usage 117 if ($node instanceof ForNode) { 118 return; 119 } 120 121 foreach ($node as $n) { 122 if (!$n) { 123 continue; 124 } 125 126 $this->checkLoopUsageBody($stream, $n); 127 } 128 } 129 130 public function getTag() 131 { 132 return 'for'; 133 } 134} 135 136class_alias('Twig\TokenParser\ForTokenParser', 'Twig_TokenParser_For'); 137