Все, что вы всегда хотели знать о {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>