Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to stop or break free from a Twig loop?

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 %}
like image 474
Peter Avatar asked Feb 24 '15 13:02

Peter


4 Answers

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.

like image 166
Jules Lamur Avatar answered Oct 04 '22 14:10

Jules Lamur


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.

like image 15
Edgar Alloro Avatar answered Oct 07 '22 14:10

Edgar Alloro


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 %}
like image 9
NullPointerException Avatar answered Oct 07 '22 16:10

NullPointerException


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 ! ;)

like image 8
CristiC777 Avatar answered Oct 07 '22 15:10

CristiC777