Jak mogę użyć przerwy lub kontynuować w pętli for w szablonie Twig?

97

Próbuję użyć prostej pętli, w moim prawdziwym kodzie ta pętla jest bardziej złożona i potrzebuję breaktej iteracji, takiej jak:

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

Jak mogę używać zachowania breaklub continuestruktur kontrolnych PHP w Twig?

Victor Bocharsky
źródło

Odpowiedzi:

126

Można to prawie zrobić, ustawiając nową zmienną jako flagę do breakiteracji:

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

Brzydszy, ale działający przykład dla 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 %}

Ale nie ma zysku z wydajności, tylko podobne zachowanie do wbudowanego breaki continueinstrukcji, jak w płaskim PHP.

Victor Bocharsky
źródło
1
To jest użyteczne. W moim przypadku wystarczy pokazać / uzyskać pierwszy wynik. Czy w Twig jest sposób, aby uzyskać tylko pierwszą wartość? To jest tylko dla lepszej wydajności.
Pathros
1
@pathros Aby uzyskać pierwszą wartość, użyj firstfiltru twig: twig.sensiolabs.org/doc/filters/first.html
Victor Bocharsky
1
Uwielbiam tę notatkę. Przez ostatnie 10 minut próbowałem znaleźć coś, co nie jest zbyt pomocne: D
Tree Nguyen
2
Warto zauważyć, że nie zakłóci to wykonania kodu, wszystko poniżej set break = truezostanie wykonane, chyba że umieścisz to w elseinstrukcji. Zobacz twigfiddle.com/euio5w
Gus
2
@Gus Tak, właśnie dlatego miałem zamiar umieścić to stwierdzenie if set break = truena samym końcu . Ale tak, to zależy od twojego kodu, więc dziękuję za wspomnienie o tym w celu wyjaśnienia
Victor Bocharsky
121

Z dokumentów TWIG docs :

W przeciwieństwie do PHP, nie można przerywać ani kontynuować w pętli.

Ale nadal:

Możesz jednak filtrować sekwencję podczas iteracji, co pozwala na pomijanie elementów.

Przykład 1 (dla ogromnych list można filtrować wiadomości za pomocą plasterka , slice(start, length)):

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

Przykład 2:

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

Możesz nawet użyć własnych filtrów TWIG dla bardziej złożonych warunków, takich jak:

{% for post in posts|onlySuperPosts %}
    <h2>{{ post.heading }}</h2>
{% endfor %}
NHG
źródło
28
Ponadto, jeśli chcesz uzyskać pętlę przerwania po 10 iteracjach, możesz użyć czegoś takiego:{% for post in posts|slice(0,10) %}
NHG
5
OK, dzięki, prawdopodobnie tęskniłem za Unlike in PHP, it's not possible to break or continue in a loop.czytaniem dokumentów. Ale myślę, że breaki continuejest to dobra
funkcja
Nie możesz uzyskać dostępu do zmiennej pętli w instrukcji pętli!
Maximus
nie działa. długa lista, forpowinna dać się złamać po pierwszym trafieniu. Odpowiedź @VictorBocharsky'ego jest prawidłowa
Vasilii Suricov
@VasiliiSuricov możesz używać {% for post in posts|slice(0,10) %}do tworzenia ogromnych list. Zobacz mój pierwszy komentarz. Zaktualizowałem również moją odpowiedź.
NHG
12

Sposób, aby móc używać {% break %}lub {% continue %}pisać TokenParserdla nich.

Zrobiłem to dla {% break %}tokena w poniższym kodzie. Możesz bez wielu modyfikacji zrobić to samo dla {% 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")
            ;
        }
    }

Następnie możesz po prostu użyć, {% break %}aby wyjść z pętli w następujący sposób:

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

Aby pójść jeszcze dalej, możesz napisać parsery tokenów dla {% continue X %}i {% break X %}(gdzie X jest liczbą całkowitą> = 1), aby uzyskać / kontynuować wiele pętli, jak w PHP .

Jules Lamur
źródło
10
To po prostu przesada. Pętle Twig powinny wspierać przerwy i kontynuować natywnie.
crafter
Jest to miłe, jeśli nie chcesz / nie możesz używać filtrów.
Daniel Dewhurst,
squirrelphp/twig-php-syntaxBiblioteka zapewnia{% break %} , {% break n %}i {% continue %}tokeny.
mts knn
@mtsknn i autorzy wykorzystali i ulepszyli kod, który napisałem dla tej odpowiedzi!
Jules Lamur
@JulesLamur, powiedziałeś „@mtsknn i autorzy”, ale ja nie jestem związany z tą biblioteką.
mts knn
9

Z komentarza @NHG - działa idealnie

{% for post in posts|slice(0,10) %}
Basit
źródło
@Basit, jeśli posty są uporządkowane według daty?
Vasilii Suricov
6

Znalazłem dobre obejście, aby kontynuować (uwielbiam powyższą próbkę przerwy). Tutaj nie chcę wymieniać „agencji”. W PHP "kontynuuję", ale w gałązce wymyśliłem alternatywę:

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

LUB po prostu go pomijam, jeśli nie spełnia moich kryteriów:

{% for tr in time_reports %}
    {% if not tr.isApproved %}
        .....
    {% endif %}
{% endfor %}
płatny dla ucznia
źródło