Everything You Always Wanted to Know About iterateWhile

The tag {iterateWhile} is suitable for various tricks in foreach cycles.

Suppose we have the following database table, where the items are divided into categories:

id catId name
1 1 Apple
2 1 Banana
3 2 PHP
4 3 Green
5 3 Red
6 3 Blue

Of course, drawing items in a foreach loop as a list is easy:

<ul>
{foreach $items as $item}
	<li>{$item->name}</li>
{/foreach}
</ul>

But what to do if you want render each category in a separate list? In other words, how to solve the task of grouping items from a linear list in a foreach cycle. The output should look like this:

<ul>
	<li>Apple</li>
	<li>Banana</li>
</ul>

<ul>
	<li>PHP</li>
</ul>

<ul>
	<li>Green</li>
	<li>Red</li>
	<li>Blue</li>
</ul>

We will show you how easily and elegantly the task can be solved with iterateWhile:

{foreach $items as $item}
	<ul>
		{iterateWhile}
			<li>{$item->name}</li>
		{/iterateWhile $item->catId === $iterator->nextValue->catId}
	</ul>
{/foreach}

While {foreach} marks the outer part of the cycle, ie the drawing of lists for each category, the tags {iterateWhile} indicate the inner part, ie the individual items. The condition in the end tag says that the repetition will continue as long as the current and the next element belong to the same category ($iterator->nextValue is next item).

If the condition is always met, then all elements are drawn in the inner cycle:

{foreach $items as $item}
	<ul>
		{iterateWhile}
			<li>{$item->name}
		{/iterateWhile true}
	</ul>
{/foreach}

The result will look like this:

<ul>
	<li>Apple</li>
	<li>Banana</li>
	<li>PHP</li>
	<li>Green</li>
	<li>Red</li>
	<li>Blue</li>
</ul>

What good is such an use of iterateWhile? How it differs from the solution we showed at the very beginning of this tutorial? The difference is that if the table is empty and does not contain any elements, it will not render empty <ul></ul>.

Solution without {iterateWhile}

If we solved the same task with completely basic constructions of template systems, for example in Twig, Blade, or pure PHP, the solution would look something like this:

{var $prevCatId = null}
{foreach $items as $item}
	{if $item->catId !== $prevCatId}
		{* the category has changed *}

		{* we close the previous <ul>, if it is not the first item *}
		{if $prevCatId !== null}
			</ul>
		{/if}

		{* we will open a new list *}
		<ul>

		{do $prevCatId = $item->catId}
	{/if}

	<li>{$item->name}</li>
{/foreach}

{if $prevCatId !== null}
	{* we close the last list *}
	</ul>
{/if}

However, this code is incomprehensible and unintuitive. The connection between the opening and closing HTML tags is not clear at all. It is not clear at first glance if there is a mistake. And it requires auxiliary variables like $prevCatId.

In contrast, the solution with {iterateWhile} is clean, clear, does not need auxiliary variables and is foolproof.

Condition in the Closing Tag

If we specify a condition in the opening tag {iterateWhile}, the behavior changes: the condition (and the advance to the next element) is executed at the beginning of the inner cycle, not at the end. Thus, while {iterateWhile} without condition is always entered, {iterateWhile $cond} is entered only when condition $cond is met. At the same time, the following element is written to $item.

This is useful, for example, in a situation where you want to render the first element in each category in a different way, such as:

<h1>Apple</h1>
<ul>
	<li>Banana</li>
</ul>

<h1>PHP</h1>
<ul>
</ul>

<h1>Green</h1>
<ul>
	<li>Red</li>
	<li>Blue</li>
</ul>

Lets modify the original code, we draw first item and then additional items from the same category in the inner loop {iterateWhile}:

{foreach $items as $item}
	<h1>{$item->name}</h1>
	<ul>
		{iterateWhile $item->catId === $iterator->nextValue->catId}
			<li>{$item->name}</li>
		{/iterateWhile}
	</ul>
{/foreach}

Nested Loops

We can create multiple inner loops in one cycle and even nest them. In this way, for example, subcategories could be grouped.

Suppose there is another column in the table subCatId and in addition to each category being in a separate <ul>, each subcategory will be in a separate <ol>:

{foreach $items as $item}
	<ul>
		{iterateWhile}
			<ol>
				{iterateWhile}
					<li>{$item->name}
				{/iterateWhile $item->subCatId === $iterator->nextValue->subCatId}
			</ol>
		{/iterateWhile $item->catId === $iterator->nextValue->catId}
	</ul>
{/foreach}

Filter |batch

The grouping of linear items is also provided by a filter batch, into batches with a fixed number of elements:

<ul>
{foreach ($items|batch:3) as $batch}
	{foreach $batch as $item}
		<li>{$item->name}</li>
	{/foreach}
{/foreach}
</ul>

It can be replaced with iterateWhile as follows:

<ul>
{foreach $items as $item}
	{iterateWhile}
		<li>{$item->name}</li>
	{/iterateWhile $iterator->counter0 % 3}
{/foreach}
</ul>