Как я могу использовать break или continue внутри цикла for в шаблоне Twig?

97

Я пытаюсь использовать простой цикл, в моем реальном коде этот цикл более сложный, и мне нужна breakэта итерация, например:

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

Как я могу использовать поведение управляющих структур PHP breakили continueуправляющих структур PHP в Twig?

Виктор Бочарский
источник

Ответы:

126

Это почти можно сделать, установив новую переменную в качестве флага для 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 %}

Но нет нет прибыли производительности, только подобное поведение для встроенных breakи continueзаявления , как в плоском PHP.

Виктор Бочарский
источник
1
Это полезно. В моем случае мне просто нужно показать / получить первый результат. Есть ли в Twig способ получить только первое значение? Это только для повышения производительности.
Pathros
1
@pathros Чтобы получить первое значение, используйте firstфильтр веточки: twig.sensiolabs.org/doc/filters/first.html
Виктор Бочарский
1
Люблю записку. Пытался последние 10 минут найти что-то, что не очень полезно: D
Tree Nguyen
2
Стоит отметить, что это не нарушит выполнение кода, все, что ниже, set break = trueбудет выполнено, если вы не поместите это в elseоператор. См. Twigfiddle.com/euio5w
Gus
2
@Gus Ага, вот почему я хотел поставить это if set break = trueв самом конце . Но да, это зависит от вашего кода, поэтому спасибо, что упомянули его для пояснения,
Виктор Бочарский
121

Из документов TWIG docs :

В отличие от PHP, невозможно прервать или продолжить цикл.

Но все равно:

Однако вы можете фильтровать последовательность во время итерации, что позволяет пропускать элементы.

Пример 1 (для больших списков можно фильтровать сообщения , используя фрагмент , slice(start, length)):

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

Пример 2:

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

Вы даже можете использовать собственные фильтры TWIG для более сложных условий, например:

{% for post in posts|onlySuperPosts %}
    <h2>{{ post.heading }}</h2>
{% endfor %}
NHG
источник
28
Более того, если вы хотите получить цикл прерывания после 10 итераций, вы можете использовать что-то вроде этого:{% for post in posts|slice(0,10) %}
NHG
5
ОК, спасибо, наверное, пропустил, Unlike in PHP, it's not possible to break or continue in a loop.когда читал документы. Но я думаю breakи continueесть хорошие черты, которые нужно было бы добавить
Виктор Бочарский
Вы не можете получить доступ к переменной цикла в операторе цикла!
Maximus
не работает. длинный список, forдолжен быть разбитым после первого нажатия. @VictorBocharsky - правильный ответ
Василий Суриков
@VasiliiSuricov можно использовать {% for post in posts|slice(0,10) %}для огромных списков. См. Мой первый комментарий. Я также обновил свой ответ.
NHG
12

Способ использовать {% break %}или {% continue %}- написать TokenParserза них.

Я сделал это для {% break %}токена в приведенном ниже коде. Вы можете без особых модификаций сделать то же самое для {% 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 .

Жюль Ламур
источник
10
Это просто перебор. Петли Twig должны поддерживать разрывы и продолжаться изначально.
крафтером
Это хорошо, если вы не хотите / не можете использовать фильтры.
Дэниел Дьюхерст
squirrelphp/twig-php-syntaxБиблиотека предоставляет {% break %}, {% break n %}и {% continue %}жетоны.
mts knn
@mtsknn и авторы использовали и улучшили код, который я написал для этого ответа!
Жюль Ламур
@JulesLamur, вы сказали «@mtsknn и авторы», но я не связан с этой библиотекой.
mts knn
9

Из комментария @NHG - отлично работает

{% for post in posts|slice(0,10) %}
Басит
источник
@Basit, если посты упорядочены по дате?
Василий Суриков
6

Я нашел хороший обходной путь для продолжения (мне нравится приведенный выше образец перерыва). Здесь я не хочу перечислять «агентства». В PHP я бы "продолжил", но в веточке я придумал альтернативу:

{% 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 %}
платный
источник