Tutto quello che avete sempre voluto sapere su {iterateWhile}

Il tag {iterateWhile} è adatto a vari trucchi nei cicli foreach.

Supponiamo di avere la seguente tabella di database, in cui gli articoli sono divisi in categorie:

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

Naturalmente, disegnare gli elementi in un ciclo foreach come elenco è facile:

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

Ma cosa fare se si vuole rendere ogni categoria in un elenco separato? In altre parole, come risolvere il compito di raggruppare gli elementi di un elenco lineare in un ciclo foreach. L'output dovrebbe essere simile a questo:

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

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

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

Vi mostreremo come questo compito possa essere risolto in modo semplice ed elegante con iterateWhile:

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

Mentre {foreach} segna la parte esterna del ciclo, cioè il disegno delle liste per ogni categoria, i tag {iterateWhile} indicano la parte interna, cioè i singoli elementi. La condizione nel tag end dice che la ripetizione continuerà finché l'elemento corrente e quello successivo appartengono alla stessa categoria ($iterator->nextValue è l'elemento successivo).

Se la condizione è sempre soddisfatta, allora tutti gli elementi vengono disegnati nel ciclo interno:

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

Il risultato sarà simile a questo:

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

A cosa serve questo uso di iterateWhile? In cosa differisce dalla soluzione mostrata all'inizio di questo tutorial? La differenza è che se la tabella è vuota e non contiene alcun elemento, non verrà resa vuota <ul></ul>.

Soluzione senza {iterateWhile}

Se risolvessimo lo stesso compito con costruzioni completamente elementari di sistemi di template, per esempio in Twig, Blade o PHP puro, la soluzione sarebbe simile a questa:

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

		{* chiudiamo la precedente <ul>, se non è il primo elemento *}
		{if $prevCatId !== null}
			</ul>
		{/if}

		{* apriremo una nuova lista *}
	<ul>

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

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

{if $prevCatId !== null}
	{* chiudiamo l'ultima lista *}
	</ul>
{/if}

Tuttavia, questo codice è incomprensibile e poco intuitivo. Il collegamento tra i tag HTML di apertura e di chiusura non è affatto chiaro. Non è chiaro a prima vista se c'è un errore. E richiede variabili ausiliarie come $prevCatId.

Al contrario, la soluzione con {iterateWhile} è pulita, chiara, non necessita di variabili ausiliarie ed è a prova di errore.

Condizione nel tag di chiusura

Se si specifica una condizione nel tag di apertura {iterateWhile}, il comportamento cambia: la condizione (e l'avanzamento all'elemento successivo) viene eseguita all'inizio del ciclo interno, non alla fine. Così, mentre {iterateWhile} senza condizione viene sempre inserito, {iterateWhile $cond} viene inserito solo quando la condizione $cond è soddisfatta. Allo stesso tempo, il seguente elemento viene scritto in $item.

Questo è utile, ad esempio, in una situazione in cui si vuole rendere il primo elemento di ogni categoria in modo diverso, come ad esempio:

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

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

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

Modifichiamo il codice originale, disegniamo il primo elemento e poi altri elementi della stessa categoria nel ciclo interno {iterateWhile}:

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

Cicli annidati

È possibile creare più cicli interni in un ciclo e persino annidarli. In questo modo, ad esempio, si possono raggruppare le sottocategorie.

Supponiamo che ci sia un'altra colonna nella tabella subCatId e che, oltre ad avere ogni categoria in una sezione separata, ogni sottocategoria sia in una sezione separata. <ul>ogni sottocategoria sarà in una colonna separata <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

Il raggruppamento degli elementi lineari è fornito anche da un filtro batch, in lotti con un numero fisso di elementi:

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

Può essere sostituito da iterateWhile come segue:

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