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