14

In PHP you've got the possibility to break from a loop or continue to the next iteration. I was wondering if you've got the same functionality in Symfony's Twig.

For example, in PHP I am able to do:

foreach ($array as $key => $value) {
    if ($value == 'something') {
        continue;
    } elseif ($value == 'somethingElse') {
        break;
    }
    echo $value;
}

Is there something similiar in Twig? For example something like:

{% for value in array %}
    {% if value == 'something' %}
    {% continue %}
    {% endif %}
    {% if value == 'somethingElse' %}
    {% break %}
    {% endif %}
    {{ value }}
{% endfor %}
Peter
  • 8,776
  • 6
  • 62
  • 95
  • 2
    From the [docs](http://twig.sensiolabs.org/doc/tags/for.html) - "Unlike in PHP, it's not possible to break or continue in a loop. You can however filter the sequence during iteration which allows you to skip items. The following example skips all the users which are not active:" – Jay Blanchard Feb 24 '15 at 13:46
  • @JayBlanchard So, basically that means it will always iterate over all items contained in the array/object? – Peter Feb 24 '15 at 13:48
  • 1
    Yes - but you can get creative with the filter to emulate the a break. – Jay Blanchard Feb 24 '15 at 13:49
  • 4
    possible duplicate of [How can I use break or continue within for in twig template?](http://stackoverflow.com/questions/21672796/how-can-i-use-break-or-continue-within-for-in-twig-template) – Peter Feb 24 '15 at 13:51
  • 1
    I use Angular.JS for that ! look here how to : [http://stackoverflow.com/questions/33853419/search-filter-angular-js-inside-twig-loop](http://stackoverflow.com/questions/33853419/search-filter-angular-js-inside-twig-loop) – CristiC777 Nov 11 '16 at 13:09
  • 1
    I want to give CristiC777 answer more upvotes. Twig is a simple templating language on purpose, it's about presentation. If you can't do something in Twig, you've probably got some funky logic that needs a rewrite. If you absolutely must "process" the data, would using an embedded controller (https://symfony.com/doc/current/templating/embedding_controllers.html) solve your issue? I.E. You don't worry about the logic on the page but get/parse the data set as intended in a controller environment? – Simon Nov 18 '16 at 06:50

6 Answers6

15

You could do something like this, in order to simulate the pattern:

{% set breakLoop = false %}

{% for value in array if breakLoop == false %}
    {% if value == 'somethingElse' %}
        {% breakLoop = true %}
    {% endif %}

    {% if value != 'something' and breakLoop == false %}


        {{ value }}
    {% endif %}
{% endfor %}

Just wrap the code inside the condition to not continue.

For breaking use a variable visible from outside the for loop.

You could also write your own, custom for loop, I guess.

ILCAI
  • 1,164
  • 2
  • 15
  • 35
9

I've read all the answers and I agree with them but aren't totally right, so I decided to write mine too.

First of all, as @CristiC777 pointed in his message, if you reach the case where you need to break a for, you are doing something wrong before this point. You probably can fix this just putting a limit on your queries or unsetting data from your arrays. This is a better solution because you will improve the response time and save server memory.

Twig views need to be silly. If you put a bunch of conditions and variables into them, you will only make them unreadable and unmaintenable.

If, for some reason, you cannot change the previous code, as @Edgar Alloro pointed, Twig allows you to put conditions on a for (since 1.2). Your example will change to something like this:

{% set keepFor = true %}

{% for value in array if keepFor %}

    {% if value != 'valueExpected' %}

        {% keepFor = false %}

    {% endif %}

    {{ value }}

{% endfor %}

You can also do your own implementation, specially if you don't have Twig 1.2. If you have Twig 1.2 or above I do not recommend this because the for will iterate the entire array and you will spend more memory:

{% set keepFor = true %}

{% for value in array %}

    {% if keepFor %}

        {% if value != 'valueExpected' %}

            {% keepFor = false %}

        {% endif %}

        {{ value }}

    {% endif %}

{% endfor %}
8

First of all !
prepare your data in controller and send just what you need in twig !
because twig is view, and is not recommend to play with big lists. Think about you can find yourself in the situation when you load in view a lot of objects or entities that you don't use ..

so if you still want do have a hard life use Edgar Alloro solution with a variable declared before loop. Or I know this iteration has LastIndex try to set that when you want to brake the loop..

Have fun ! ;)

CristiC777
  • 481
  • 11
  • 20
6

according to documentation, it's not possible to break or continue in a loop. You can however filter the sequence during iteration which allows you to skip items. The following example skips all the users which are not active:

<ul>
{% for user in users if user.active %}
    <li>{{ user.username }}</li>
{% endfor %}
</ul>
yakob abada
  • 1,101
  • 14
  • 20
5

Create a TwigExtension using these classes:

  • 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;
            try { // This "try" is because look() will throws a PHP exception if $this->current - $i is negative (where $this is $stream).
                for ($i = 1; true; $i++) {
                    $token = $stream->look(-$i);
                    if ($token->test(\Twig_Token::NAME_TYPE, 'for')) {
                        $currentForLoop++;
                    } else if ($token->test(\Twig_Token::NAME_TYPE, 'endfor')) {
                        $currentForLoop--;
                    }
                }
            } catch (\Exception $e) {
            }
    
            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")
            ;
        }
    }
    

Then you can simply use {% break %} to get out of loops like this:

{% set var = ['foo', 'bar'] %}
{% for v in var %}
    {{ v }}
    {% break %}
{% endfor %}

I don't have enough time to code it, but you could write the continue block the same way.

To go even further, you may handle {% continue X %} and {% break X %} to get out/continue multiple loops like in PHP.

If someone wants to do it and share it, feel free to edit my answer.

Jules Lamur
  • 2,078
  • 1
  • 15
  • 25
0
{% set break = false %}

{% for value in array  if not break  %}
    {% if value == 'something' %}
    {% continue %}
    {% endif %}
    {% if value == 'somethingElse' %}
    {% set break = true %}
    {% endif %}
    {{ value }}
{% endfor %}
viral barot
  • 631
  • 4
  • 8