Všechno, co jste kdy chtěli vědět o iterateWhile

Značka {iterateWhile} se hodí na nejrůznější kejkle ve foreach cyklech.

Dejme tomu, že máme následující databázovou tabulku, kde jsou položky rozdělené do kategorií:

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

Vykreslit položky ve foreach cyklu jako seznam je samozřejmě snadné:

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

Ale co kdybychom chtěli, aby každá kategorie byla v samostatném seznamu? Jinými slovy, řešíme úkol, jak seskupit položky v lineárním seznamu ve foreach cyklu do skupin. Výstup by měl vypadat takto:

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

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

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

Rovnou si ukážeme, jak snadno a elegantně se dá úkol vyřešit pomocí iterateWhile:

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

Zatímco {foreach} označuje vnější část cyklu, tedy vykreslování seznamů pro každou kategorii, tak značka {iterateWhile} označuje vnitřní část, tedy jednotlivé položky. Podmínka v koncové značce říká, že opakování bude probíhat do té doby, dokud aktuální i následující prvek patří do stejné kategorie ($iterator->nextValue je následující položka).

Kdyby podmínka byla splněná vždy, tak se ve vnitřním cyklu vykreslí všechny prvky:

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

Výsledek bude vypadat takto:

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

K čemu je takové použití iterateWhile dobré? Jak se liší od řešení, které jsme si ukázali úplně na začátku tohoto návodu? Rozdíl je v tom, že když bude tabulka prázdná a nebude obsahovat žádné prvky, nevypíše se prázdné <ul></ul>.

Řešení bez {iterateWhile}

Pokud bychom stejný úkol řešili zcela základními prostředky šablonovacích systému, například v Twig, Blade, nebo čistém PHP, vypadalo by řešení cca takto:

{var $prevCatId = null}
{foreach $items as $item}
	{if $item->catId !== $prevCatId}
		{* změnila se kategorie *}

		{* uzavřeme předchozí <ul>, pokud nejde o první položku *}
		{if $prevCatId !== null}
			</ul>
		{/if}

		{* otevřeme nový seznam *}
		<ul>

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

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

{if $prevCatId !== null}
	{* uzavřeme poslední seznam *}
	</ul>
{/if}

Tento kód je však nesrozumitelný a neintuitivní. Není vůbec jasná vazba mezi otevíracími a zavíracími HTML značkami. Není na první pohled vidět, jestli tam není nějaká chyba. A vyžaduje pomocné proměnné jako $prevCatId.

Oproti tomu řešení s {iterateWhile} je čisté, přehledné, nepotřebujeme pomocné proměnné a je blbuvzdorné.

Podmínka v otevírací značce

Pokud uvedeme podmínku v otevírací značce {iterateWhile}, tak se chování změní: podmínka (a přechod na další prvek) se vykoná už na začátku vnitřního cyklu, nikoliv na konci. Tedy zatímco do {iterateWhile} bez podmínky se vstoupí vždy, do {iterateWhile $cond} jen při splnění podmínky $cond. A zároveň se s tím do $item zapíše následující prvek.

Což se hodí například v situaci, kdy budeme chtít první prvek v každé kategorii vykreslit jiným způsobem, například takto:

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

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

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

Původní kód upravíme tak, že nejprve vykreslíme první položku a poté ve vnitřním cyklu {iterateWhile} vykreslíme další položky ze stejné kategorie:

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

Vnořené smyčky

V rámci jednoho cyklu můžeme vytvářet více vnitřních smyček a dokonce je zanořovat. Takto by se daly seskupovat třeba podkategorie atd.

Dejme tomu, že v tabulce bude ještě další sloupec subCatId a kromě toho, že každá kategorie bude v samostatném <ul>, každá každý podkategorie samostatném <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}

Filtr |batch

Seskupování lineárních položek obstarává také filtr batch, a to do dávek s pevným počtem prvků:

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

Lze jej nahradit za iterateWhile tímto způsobem:

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