如何在Twig模板的for循环中使用break或continue?

125

我尝试使用一个简单的循环,在我的实际代码中这个循环更加复杂,我需要用break来结束迭代:

{% for post in posts %}
    {% if post.id == 10 %}
        {# break #}
    {% endif %}
    <h2>{{ post.heading }}</h2>
{% endfor %}

我该如何在Twig中运用PHP控制结构breakcontinue的行为?

5个回答

174

可以通过设置一个新的变量作为标记,在迭代时用 break 做到几乎这样:

{% set break = false %}
{% for post in posts if not break %}
    <h2>{{ post.heading }}</h2>
    {% if post.id == 10 %}
        {% set break = true %}
    {% endif %}
{% endfor %}

更加丑陋但可行的continue示例:

{% set continue = false %}
{% for post in posts %}
    {% if post.id == 10 %}
        {% set continue = true %}
    {% endif %}
    {% if not continue %}
        <h2>{{ post.heading }}</h2>
    {% endif %}
    {% if continue %}
        {% set continue = false %}
    {% endif %}
{% endfor %}

但是没有性能收益,与内置的 breakcontinue 语句类似,只有相似的行为,就像在基础的 PHP 中一样。


1
这很有用。在我的情况下,我只需要显示/获取第一个结果。在Twig中是否有一种方法可以仅获取第一个值?这仅是为了更好的性能目的。 - Pathros
1
喜欢这个笔记。刚刚费了我十分钟找了些没什么用的东西:D - Tree Nguyen
3
值得注意的是,这不会中断代码的执行,除非您将 set break = true 放在 else 语句中,否则下面的任何内容都将被执行。请参阅 https://twigfiddle.com/euio5w。 - Gus
2
@Gus 是的,这就是我想在最后放置带有 set break = true 的 if 语句的原因。但是,这取决于你的代码,所以感谢你提到它以澄清。 - Victor Bocharsky
4
我使用twig 3.x,并且遇到了这个方法if not break的问题。我收到了这个错误Uncaught Twig\Error\SyntaxError: Unexpected token "name" of value "if" - ZenithS
显示剩余3条评论

145

来自 TWIG 2.x 文档文档

与 PHP 不同的是,在循环中无法使用 break 或 continue。

但是:

您可以在迭代期间过滤序列,从而跳过项目。

示例1(对于巨大的列表,您可以使用slice过滤器来过滤帖子,slice(start, length)):

{% for post in posts|slice(0,10) %}
    <h2>{{ post.heading }}</h2>
{% endfor %}

例2同样适用于TWIG 3.0:

{% for post in posts if post.id < 10 %}
    <h2>{{ post.heading }}</h2>
{% endfor %}

您甚至可以使用自己的TWIG筛选器来处理更复杂的条件,例如:

{% for post in posts|onlySuperPosts %}
    <h2>{{ post.heading }}</h2>
{% endfor %}

28
此外,如果你想在进行10次迭代后跳出循环,可以使用类似这样的代码:{% for post in posts|slice(0,10) %} - NHG
5
好的,谢谢。阅读文档时我可能漏掉了“与 PHP 不同,在循环中不可能使用 break 或 continue 语句”的部分。但我认为 break 和 continue 是很好的功能,应该加入其中。 - Victor Bocharsky
循环语句中无法访问循环变量! - Maximus
不起作用。列表太长,“for”循环应该在第一次命中后就可以打破。@VictorBocharsky的答案是正确的。 - Vasilii Suricov
4
提醒一下,虽然在2.0版本中还可以使用,但Twig在3.0版本中删除了{% for ... if ... %}语句。 - Dave Lancea
显示剩余8条评论

15
一种使用 {% break %}{% continue %} 的方法是编写相应的 TokenParser。我已经为 {% break %} token 编写了下面的代码。您可以进行少量修改,以同样的方式实现 {% continue %}
  • AppBundle\Twig\AppExtension.php:

    namespace AppBundle\Twig;
    
    class AppExtension extends \Twig_Extension
    {
        function getTokenParsers() {
            return array(
                new BreakToken(),
            );
        }
    
        public function getName()
        {
            return 'app_extension';
        }
    }
    
  • AppBundle\Twig\BreakToken.php:

    namespace AppBundle\Twig;
    
    class BreakToken extends \Twig_TokenParser
    {
        public function parse(\Twig_Token $token)
        {
            $stream = $this->parser->getStream();
            $stream->expect(\Twig_Token::BLOCK_END_TYPE);
    
            // Trick to check if we are currently in a loop.
            $currentForLoop = 0;
    
            for ($i = 1; true; $i++) {
                try {
                    // if we look before the beginning of the stream
                    // the stream will throw a \Twig_Error_Syntax
                    $token = $stream->look(-$i);
                } catch (\Twig_Error_Syntax $e) {
                    break;
                }
    
                if ($token->test(\Twig_Token::NAME_TYPE, 'for')) {
                    $currentForLoop++;
                } else if ($token->test(\Twig_Token::NAME_TYPE, 'endfor')) {
                    $currentForLoop--;
                }
            }
    
    
            if ($currentForLoop < 1) {
                throw new \Twig_Error_Syntax(
                    'Break tag is only allowed in \'for\' loops.',
                    $stream->getCurrent()->getLine(),
                    $stream->getSourceContext()->getName()
                );
            }
    
            return new BreakNode();
        }
    
        public function getTag()
        {
            return 'break';
        }
    }
    
  • AppBundle\Twig\BreakNode.php:

    namespace AppBundle\Twig;
    
    class BreakNode extends \Twig_Node
    {
        public function compile(\Twig_Compiler $compiler)
        {
            $compiler
                ->write("break;\n")
            ;
        }
    }
    

那么,你可以简单地使用{% break %}来退出像这样的循环:

{% for post in posts %}
    {% if post.id == 10 %}
        {% break %}
    {% endif %}
    <h2>{{ post.heading }}</h2>
{% endfor %}

要更进一步,您可以编写令牌解析器来处理 {% continue X %}{% break X %}(其中X是大于等于1的整数),以像PHP一样跳出/继续多个循环

14
这就有点过了。Twig循环应该原生支持break和continue。 - crafter
如果您不想/无法使用过滤器,这很好。 - Daniel Dewhurst
1
squirrelphp/twig-php-syntax 提供了 {% break %}, {% break n %}{% continue %} 标记。 - Matias Kinnunen
1
@mtsknn和作者们使用并改进了我为这个答案编写的代码! - Jules Lamur
@JulesLamur,没问题。我现在看了一下库代码(BreakOrContinueTokenParser.php),很明显是基于你的代码的。 - Matias Kinnunen
显示剩余3条评论

8

来自@NHG评论 - 完美运作

{% for post in posts|slice(0,10) %}

@Basit 如果帖子按日期排序呢? - Vasilii Suricov
-1. 在许多情况下无法工作。例如,在OP的代码中,如果帖子没有按id排序,或者某些id没有帖子怎么办? - Philippe-André Lorin

6

我发现了一个很好的解决方案来代替"continue"(喜欢上面的break示例)。 在这里,我不想列出"agency"。在PHP中,我会使用"continue",但在Twig中,我想到了另一种替代方法:

{% for basename, perms in permsByBasenames %} 
    {% if basename == 'agency' %}
        {# do nothing #}
    {% else %}
        <a class="scrollLink" onclick='scrollToSpot("#{{ basename }}")'>{{ basename }}</a>
    {% endif %}
{% endfor %}

如果不符合我的标准,我就直接跳过它:

{% for tr in time_reports %}
    {% if not tr.isApproved %}
        .....
    {% endif %}
{% endfor %}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接