Todo lo que siempre quiso saber sobre {iterateWhile}

La etiqueta {iterateWhile} es adecuada para varios trucos en ciclos foreach.

Supongamos que tenemos la siguiente tabla de base de datos, donde los elementos se dividen en categorías:

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

Por supuesto, dibujar elementos en un bucle foreach como una lista es fácil:

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

Pero, ¿qué hacer si se desea representar cada categoría en una lista separada? En otras palabras, cómo resolver la tarea de agrupar elementos de una lista lineal en un ciclo foreach. La salida debería tener este aspecto:

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

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

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

Le mostraremos con qué facilidad y elegancia se puede resolver la tarea con iterateWhile:

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

Mientras que {foreach} marca la parte externa del ciclo, es decir, el dibujo de las listas para cada categoría, las etiquetas {iterateWhile} indican la parte interna, es decir, los elementos individuales. La condición en la etiqueta final dice que la repetición continuará mientras el elemento actual y el siguiente pertenezcan a la misma categoría ($iterator->nextValue es siguiente elemento).

Si la condición se cumple siempre, todos los elementos se dibujan en el ciclo interior:

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

El resultado tendrá este aspecto:

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

¿Para qué sirve este uso de iterateWhile? ¿En qué se diferencia de la solución que mostramos al principio de este tutorial? La diferencia es que si la tabla está vacía y no contiene ningún elemento, no se mostrará vacía <ul></ul>.

Solución sin {iterateWhile}

Si resolviéramos la misma tarea con construcciones completamente básicas de sistemas de plantillas, por ejemplo en Twig, Blade, o PHP puro, la solución sería algo así:

{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}

Sin embargo, este código es incomprensible y poco intuitivo. La conexión entre las etiquetas HTML de apertura y cierre no está nada clara. No está claro a primera vista si hay un error. Y requiere variables auxiliares como $prevCatId.

En cambio, la solución con {iterateWhile} es limpia, clara, no necesita variables auxiliares y es infalible.

Condición en la etiqueta de cierre

Si especificamos una condición en la etiqueta de apertura {iterateWhile}, el comportamiento cambia: la condición (y el avance al elemento siguiente) se ejecuta al principio del ciclo interno, no al final. Así, mientras que {iterateWhile} sin condición se introduce siempre, {iterateWhile $cond} se introduce sólo cuando se cumple la condición $cond. Al mismo tiempo, el siguiente elemento se escribe en $item.

Esto es útil, por ejemplo, en una situación en la que se desea renderizar el primer elemento de cada categoría de una forma diferente, como:

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

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

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

Modifiquemos el código original, dibujamos el primer elemento y luego los elementos adicionales de la misma categoría en el bucle interno {iterateWhile}:

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

Bucles anidados

Podemos crear varios bucles internos en un ciclo e incluso anidarlos. De esta forma, por ejemplo, se podrían agrupar subcategorías.

Supongamos que hay otra columna en la tabla subCatId y además de que cada categoría esté en una separada <ul>, cada subcategoría estará en un <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}

Filtro |batch

La agrupación de elementos lineales también se realiza mediante un filtro batch, en lotes con un número fijo de elementos:

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

Puede sustituirse por iterateWhile de la siguiente manera:

<ul>
{foreach $items as $item}
	{iterateWhile}
		<li>{$item->name}</li>
	{/iterateWhile $iterator->counter0 % 3}
{/foreach}
</ul>
versión: 3.0