¿Cómo puedo usar romper o continuar dentro del bucle for en la plantilla Twig?

97

Intento usar un bucle simple, en mi código real, este bucle es más complejo y necesito breakesta iteración como:

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

¿Cómo puedo utilizar el comportamiento del breako continuede estructuras de control de PHP en la ramita?

Victor Bocharsky
fuente

Respuestas:

125

Esto casi se puede hacer estableciendo una nueva variable como una bandera 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 %}

Un ejemplo más feo, pero funcional 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 %}

Pero no hay ganancias de rendimiento, solo un comportamiento similar al de las declaraciones integradas breaky continuecomo en PHP plano.

Victor Bocharsky
fuente
1
Es útil. En mi caso, solo necesito mostrar / obtener el primer resultado. ¿Hay alguna forma en Twig de obtener solo el primer valor? Esto es solo para mejorar el rendimiento.
Pathros
1
@pathros Para obtener el primer valor, use el firstfiltro twig
Victor Bocharsky
1
Amo la nota. He intentado mis últimos 10 minutos para encontrar algo que no es realmente útil: D
Tree Nguyen
2
Vale la pena señalar que esto no interrumpirá la ejecución del código, todo lo que se muestra set break = truea continuación se ejecutará a menos que lo ponga en una elsedeclaración. Ver twigfiddle.com/euio5w
Gus
2
@Gus Sí, es por eso que tenía la intención de poner esa declaración if set break = trueal final . Pero sí, depende de su código, así que gracias por mencionarlo para aclararlo
Victor Bocharsky
120

De los documentos TWIG docs :

A diferencia de PHP, no es posible romper o continuar en un bucle.

Pero aún:

Sin embargo, puede filtrar la secuencia durante la iteración, lo que le permite omitir elementos.

Ejemplo 1 (para grandes listas puede filtrar los mensajes usando rebanada , slice(start, length)):

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

Ejemplo 2:

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

Incluso puede usar sus propios filtros TWIG para condiciones más complejas, como:

{% for post in posts|onlySuperPosts %}
    <h2>{{ post.heading }}</h2>
{% endfor %}
NHG
fuente
28
Además, si desea lograr un ciclo de ruptura después de 10 iteraciones, puede usar algo así:{% for post in posts|slice(0,10) %}
NHG
5
Bien, gracias, probablemente me perdí Unlike in PHP, it's not possible to break or continue in a loop.cuando leí los documentos. Pero creo que breaky continuees una buena característica, que debería agregar
Victor Bocharsky
¡No puede acceder a la variable de bucle en la declaración de bucle!
Maximus
no funciona. lista larga, fordebería ser rompible después del primer golpe. La respuesta de @VictorBocharsky es correcta
Vasilii Suricov
@VasiliiSuricov puede utilizar {% for post in posts|slice(0,10) %}para listas enormes. Vea mi primer comentario. También actualicé mi respuesta.
NHG
12

Una forma de poder usar {% break %}o {% continue %}es escribir TokenParsers para ellos.

Lo hice por el {% break %}token en el siguiente código. Puede, sin muchas modificaciones, hacer lo mismo con el {% 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")
            ;
        }
    }

Luego, simplemente puede usar {% break %}para salir de bucles como este:

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

Para ir aún más lejos, puede escribir analizadores de tokens para {% continue X %}y {% break X %}(donde X es un número entero> = 1) para salir / continuar múltiples bucles como en PHP .

Jules Lamur
fuente
10
Eso es una exageración. Los bucles de ramitas deben soportar descansos y continuar de forma nativa.
Crafter
Esto es bueno si no quiere / no puede usar filtros.
Daniel Dewhurst
La squirrelphp/twig-php-syntaxbiblioteca ofrece {% break %}, {% break n %}y {% continue %}fichas.
mts knn
¡@mtsknn y los autores usaron y mejoraron el código que escribí para esta respuesta!
Jules Lamur
@JulesLamur, dijiste "@mtsknn y los autores", pero no estoy involucrado con esa biblioteca.
mts knn
9

Del comentario de @NHG - funciona perfectamente

{% for post in posts|slice(0,10) %}
Basit
fuente
@Basit si las publicaciones están ordenadas por fecha?
Vasilii Suricov
6

He encontrado una buena solución para continuar (me encanta el ejemplo de descanso anterior). Aquí no quiero incluir "agencia". En PHP, "continuaría" pero en twig, se me ocurrió una alternativa:

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

O simplemente lo omito si no cumple con mis criterios:

{% for tr in time_reports %}
    {% if not tr.isApproved %}
        .....
    {% endif %}
{% endfor %}
pagado por Cristo
fuente