Como posso usar break ou continue within for loop no modelo Twig?

97

Tento usar um loop simples, no meu código real esse loop é mais complexo e preciso fazer breakessa iteração como:

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

Como posso usar o comportamento de breakou continuede estruturas de controle de PHP no Twig?

Victor Bocharsky
fonte

Respostas:

125

Isso pode ser quase feito definindo uma nova variável como um sinalizador para breakiterar:

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

Um exemplo mais feio, mas que funciona para 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 %}

Mas não lucro de desempenho, apenas comportamento semelhante ao embutido breake continueinstruções como em PHP simples.

Victor Bocharsky
fonte
1
É útil. No meu caso, só preciso mostrar / obter o primeiro resultado. Existe uma maneira no Twig de obter apenas o primeiro valor? Isso é apenas para fins de melhor desempenho.
Pathros de
1
@pathros Para obter o primeiro valor, use o firstfiltro twig: twig.sensiolabs.org/doc/filters/first.html
Victor Bocharsky
1
Amei a nota. Tenho tentado nos últimos 10 minutos para encontrar algo que não é realmente útil: D
Tree Nguyen
2
É importante notar que isso não irá interromper a execução do código, qualquer coisa abaixo set break = trueserá executada a menos que você coloque em uma elseinstrução. Consulte twigfiddle.com/euio5w
Gus
2
@Gus Sim, é por isso que eu queria colocar essa declaração if set break = trueno final . Mas sim, depende do seu código, então obrigado por mencioná-lo para esclarecer
Victor Bocharsky
120

Dos docs TWIG docs :

Ao contrário do PHP, não é possível interromper ou continuar em um loop.

Mas ainda:

No entanto, você pode filtrar a sequência durante a iteração, o que permite pular itens.

Exemplo 1 (para listas enormes você pode filtrar mensagens usando fatia , slice(start, length)):

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

Exemplo 2:

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

Você pode até usar os próprios filtros TWIG para condições mais complexas, como:

{% for post in posts|onlySuperPosts %}
    <h2>{{ post.heading }}</h2>
{% endfor %}
NHG
fonte
28
Além disso, se você quiser obter break loop após 10 iterações, poderá usar sth assim:{% for post in posts|slice(0,10) %}
NHG
5
OK, obrigado, provavelmente perdi Unlike in PHP, it's not possible to break or continue in a loop.quando li os documentos. Mas eu acho breake continueé um bom recurso, que precisaria adicionar
Victor Bocharsky
Você não pode acessar a variável de loop na instrução de loop!
Máximo
não funciona. lista longa, fordeve ser quebrável após o primeiro acerto. A resposta de @VictorBocharsky está certa
Vasilii Suricov
@VasiliiSuricov você pode usar {% for post in posts|slice(0,10) %}para listas enormes. Veja meu primeiro comentário. Eu também atualizei minha resposta.
NHG de
12

Uma forma de poder usar {% break %}ou {% continue %}é escreverTokenParser s para eles.

Fiz isso para o {% break %}token no código abaixo. Você pode, sem muitas modificações, fazer a mesma coisa para o {% 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")
            ;
        }
    }

Em seguida, você pode simplesmente usar {% break %}para sair de loops como este:

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

Para ir ainda mais longe, você pode escrever analisadores de token para {% continue X %}e {% break X %}(onde X é um inteiro> = 1) para obter / continuar vários loops como no PHP .

Jules Lamur
fonte
10
Isso é um exagero. Os loops de galhos devem apoiar as quebras e continuar nativamente.
crafter
Isso é bom se você não quiser / não puder usar filtros.
Daniel Dewhurst,
A squirrelphp/twig-php-syntaxbiblioteca oferece {% break %}, {% break n %}e {% continue %}tokens.
mts knn
@mtsknn e os autores usaram e melhoraram o código que escrevi para esta resposta!
Jules Lamur
@JulesLamur, você disse "@mtsknn e os autores", mas não estou envolvido com essa biblioteca.
mts knn
9

Do comentário @NHG - funciona perfeitamente

{% for post in posts|slice(0,10) %}
Base
fonte
@Basit se as postagens são ordenadas por data?
Vasilii Suricov
6

Eu encontrei uma boa solução para continuar (adorei o exemplo acima). Aqui eu não quero listar "agência". No PHP, eu "continuaria", mas no twig, encontrei uma alternativa:

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

OU eu simplesmente pulo se não atender aos meus critérios:

{% for tr in time_reports %}
    {% if not tr.isApproved %}
        .....
    {% endif %}
{% endfor %}
pago pelo Cristo
fonte