Все, що ви завжди хотіли знати про {iterateWhile}

Тег {iterateWhile} підходить для різних трюків у циклах foreach.

Припустимо, у нас є така таблиця бази даних, у якій елементи розділені на категорії:

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

Звичайно, вивести елементи в циклі foreach у вигляді списку дуже просто:

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

Але що робити, якщо ви хочете вивести кожну категорію в окремий список? Інакше кажучи, як розв'язати задачу групування елементів із лінійного списку в циклі foreach. Виведення має мати такий вигляд:

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

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

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

Ми покажемо вам, як легко й елегантно можна вирішити це завдання за допомогою iterateWhile:

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

Якщо {foreach} позначає зовнішню частину циклу, тобто складання списків для кожної категорії, то теги {iterateWhile} вказують на внутрішню частину, тобто на окремі елементи. Умова в тезі end говорить, що повторення триватиме доти, доки поточний і наступний елемент належать до однієї категорії ($iterator->nextValue – наступний елемент).

Якщо умова завжди виконується, то у внутрішньому циклі малюються всі елементи:

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

Результат матиме такий вигляд:

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

Чим корисне таке використання iterateWhile? Чим воно відрізняється від рішення, яке ми показали на самому початку цього посібника? Різниця в тому, що якщо таблиця порожня і не містить елементів, то вона не буде виводитися порожньою <ul></ul>.

Рішення без {iterateWhile}

Якби ми вирішували ту саму задачу за допомогою абсолютно базових конструкцій систем шаблонів, наприклад, у Twig, Blade або чистому PHP, то рішення виглядало б приблизно так:

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

Однак цей код незрозумілий і неінтуїтивний. Зв'язок між HTML-тегами, що відкривають і закривають, абсолютно не зрозумілий. З першого погляду не зрозуміло, чи є помилка. І для цього потрібні допоміжні змінні, такі як $prevCatId.

На відміну від цього, рішення з {iterateWhile} чисте, зрозуміле, не потребує допоміжних змінних і є надійним.

Умова в закриваючому тезі

Якщо вказати умову у відкриваючому тезі {iterateWhile}, то поведінка змінюється: умова (і перехід до наступного елемента) виконується на початку внутрішнього циклу, а не в кінці. Таким чином, якщо {iterateWhile} без умови вводиться завжди, то {iterateWhile $cond} вводиться тільки при виконанні умови $cond. Водночас, наступний елемент записується в $item.

Це корисно, наприклад, у ситуації, коли потрібно по-різному відобразити перший елемент у кожній категорії, наприклад:

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

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

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

Змінимо вихідний код, ми відтворюємо перший елемент, а потім додаткові елементи з тієї ж категорії у внутрішньому циклі {iterateWhile}:

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

Вкладені цикли

Ми можемо створити кілька внутрішніх циклів в одному циклі і навіть вкласти їх один в одного. Таким чином, наприклад, можна згрупувати підкатегорії.

Припустімо, що в таблиці subCatId є ще один стовпчик, і на додаток до того, що кожна категорія перебуватиме в окремому стовпчику, кожна підкатегорія перебуватиме в окремому стовпчику. <ul>, кожна підкатегорія буде знаходитися в окремій колонці <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}

Фільтр | пакетний

Групування лінійних елементів також забезпечується фільтром batch, у партії з фіксованою кількістю елементів:

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

Його можна замінити на iterateWhile таким чином:

<ul>
{foreach $items as $item}
	{iterateWhile}
		<li>{$item->name}</li>
	{/iterateWhile $iterator->counter0 % 3}
{/foreach}
</ul>
версію: 3.0