Comment puis-je utiliser pause ou continuer dans la boucle for dans le modèle Twig?

97

J'essaye d'utiliser une boucle simple, dans mon vrai code cette boucle est plus complexe, et j'ai besoin de breakcette itération comme:

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

Comment puis-je utiliser le comportement de breakou continuedes structures de contrôle PHP dans Twig?

Victor Bocharsky
la source

Réponses:

125

Cela peut être presque fait en définissant une nouvelle variable comme indicateur d' breakitération:

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

Un exemple plus laid, mais fonctionnel pour 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 %}

Mais il n'y a pas de gain de performance, seulement un comportement similaire à celui des instructions intégrées breaket continuecomme en PHP plat.

Victor Bocharsky
la source
1
C'est utile. Dans mon cas, j'ai juste besoin de montrer / obtenir le premier résultat. Y a-t-il un moyen dans Twig d'obtenir juste la première valeur? Ceci est uniquement pour de meilleures performances.
Pathros
1
@pathros Pour obtenir la première valeur, utilisez le firstfiltre Twig
Victor Bocharsky
1
J'adore la note. J'ai essayé mes 10 dernières minutes pour trouver quelque chose qui n'est pas vraiment utile: D
Tree Nguyen
2
Il est à noter que cela n'interrompra pas l'exécution du code, tout ce qui se trouve ci set break = true- dessous sera exécuté à moins que vous ne le mettiez dans une elseinstruction. Voir twigfiddle.com/euio5w
Gus
2
@Gus Yep, c'est pourquoi je voulais mettre cette déclaration si set break = trueà la toute fin . Mais oui, cela dépend de votre code, alors merci de l'avoir mentionné pour clarifier
Victor Bocharsky
120

À partir de docs TWIG docs :

Contrairement à PHP, il n'est pas possible de rompre ou de continuer en boucle.

Mais reste:

Vous pouvez cependant filtrer la séquence lors de l'itération, ce qui vous permet de sauter des éléments.

Exemple 1 (pour les listes volumineuses, vous pouvez filtrer les publications en utilisant slice , slice(start, length)):

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

Exemple 2:

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

Vous pouvez même utiliser vos propres filtres TWIG pour des conditions plus complexes, comme:

{% for post in posts|onlySuperPosts %}
    <h2>{{ post.heading }}</h2>
{% endfor %}
NHG
la source
28
De plus, si vous voulez obtenir une boucle de rupture après 10 itérations, vous pouvez utiliser qc comme ça:{% for post in posts|slice(0,10) %}
NHG
5
OK, merci, j'ai probablement raté la Unlike in PHP, it's not possible to break or continue in a loop.lecture de la documentation. Mais je pense breaket continuec'est une bonne fonctionnalité, qu'il faudrait ajouter
Victor Bocharsky
Vous ne pouvez pas accéder à la variable de boucle dans l'instruction de boucle!
Maximus
ne fonctionne pas. longue liste, fordevrait être cassable après le premier coup. La réponse de @VictorBocharsky est juste
Vasilii Suricov
@VasiliiSuricov vous pouvez utiliser {% for post in posts|slice(0,10) %}pour de grandes listes. Voir mon premier commentaire. J'ai également mis à jour ma réponse.
NHG
12

Une façon de pouvoir utiliser {% break %}ou {% continue %}est d'écrire des TokenParsers pour eux.

Je l'ai fait pour le {% break %}jeton dans le code ci-dessous. Vous pouvez, sans trop de modifications, faire la même chose pour le {% 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")
            ;
        }
    }

Ensuite, vous pouvez simplement utiliser {% break %}pour sortir de boucles comme celle-ci:

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

Pour aller encore plus loin, vous pouvez écrire des analyseurs de jetons pour {% continue X %}et {% break X %}(où X est un entier> = 1) pour sortir / continuer plusieurs boucles comme en PHP .

Jules Lamur
la source
10
C'est juste exagéré. Les boucles Twig doivent supporter les pauses et se poursuivent nativement.
crafter
C'est bien si vous ne voulez pas / ne pouvez pas utiliser de filtres.
Daniel Dewhurst
La squirrelphp/twig-php-syntaxbibliothèque fournit {% break %}, {% break n %}et des {% continue %}jetons.
mts knn
@mtsknn et les auteurs ont utilisé et amélioré le code que j'ai écrit pour cette réponse!
Jules Lamur
@JulesLamur, vous avez dit «@mtsknn et les auteurs», mais je ne suis pas impliqué dans cette bibliothèque.
mts knn
9

De @NHG comment - fonctionne parfaitement

{% for post in posts|slice(0,10) %}
Basit
la source
@Basit si les articles sont classés par date?
Vasilii Suricov
6

J'ai trouvé une bonne solution pour continuer (j'adore l'exemple de pause ci-dessus). Ici, je ne veux pas lister "agence". En PHP, je "continuerais" mais en brindille, j'ai trouvé une alternative:

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

OU je l'ignore simplement s'il ne répond pas à mes critères:

{% for tr in time_reports %}
    {% if not tr.isApproved %}
        .....
    {% endif %}
{% endfor %}
payé par le poignet
la source