Все, что вы когда-либо хотели знать о группировке
При работе с данными в шаблонах вы часто можете столкнуться с необходимостью их группировки или специфического отображения по определенным критериям. Latte для этой цели предлагает сразу несколько мощных инструментов.
Фильтр и функция |group
позволяют эффективно группировать
данные по заданному критерию, фильтр |batch
облегчает разделение
данных на фиксированные порции, а тег {iterateWhile}
предоставляет
возможность более сложного управления ходом циклов с условиями.
Каждый из этих тегов предлагает специфические возможности для работы
с данными, что делает их незаменимыми инструментами для динамического
и структурированного отображения информации в шаблонах Latte.
Фильтр и функция group
Представьте себе таблицу базы данных items
с записями,
разделенными на категории:
id | categoryId | name |
---|---|---|
1 | 1 | Apple |
2 | 1 | Banana |
3 | 2 | PHP |
4 | 3 | Green |
5 | 3 | Red |
6 | 3 | Blue |
Простой список всех записей с помощью шаблона Latte выглядел бы так:
<ul>
{foreach $items as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
Однако, если бы мы хотели, чтобы записи были упорядочены по группам в соответствии с категорией, нам нужно было бы разделить их так, чтобы каждая категория имела свой собственный список. Результат тогда должен выглядеть следующим образом:
<ul>
<li>Apple</li>
<li>Banana</li>
</ul>
<ul>
<li>PHP</li>
</ul>
<ul>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
Задачу можно легко и элегантно решить с помощью |group
. В
качестве параметра укажем categoryId
, что означает, что записи будут
разделены на меньшие массивы по значению $item->categoryId
(если бы
$item
был массивом, использовалось бы $item['categoryId']
):
{foreach ($items|group: categoryId) as $categoryId => $categoryItems}
<ul>
{foreach $categoryItems as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
{/foreach}
Фильтр можно в Latte использовать и как функцию, что дает нам
альтернативный синтаксис: {foreach group($items, categoryId) ...}
.
Если вы хотите группировать записи по более сложным критериям, вы можете использовать функцию в параметре фильтра. Например, группировка записей по длине имени выглядела бы так:
{foreach ($items|group: fn($item) => strlen($item->name)) as $items}
...
{/foreach}
Важно осознать, что $categoryItems
— это не обычный массив, а объект,
который ведет себя как итератор. Для доступа к первой записи группы вы
можете использовать функцию first()
.
Эта гибкость в группировке данных делает group
исключительно
полезным инструментом для представления данных в шаблонах Latte.
Вложенные циклы
Представим, что у нас есть таблица базы данных с дополнительным
столбцом subcategoryId
, который определяет подкатегории отдельных
записей. Мы хотим отобразить каждую главную категорию в отдельном
списке <ul>
и каждую подкатегорию в отдельном вложенном
списке <ol>
:
{foreach ($items|group: categoryId) as $categoryItems}
<ul>
{foreach ($categoryItems|group: subcategoryId) as $subcategoryItems}
<ol>
{foreach $subcategoryItems as $item}
<li>{$item->name}
{/foreach}
</ol>
{/foreach}
</ul>
{/foreach}
Связь с Nette Database
Давайте покажем, как эффективно использовать группировку данных в
комбинации с Nette Database. Предположим, мы работаем с таблицей items
из
вводного примера, которая через столбец categoryId
связана с этой
таблицей categories
:
categoryId | name |
---|---|
1 | Fruits |
2 | Languages |
3 | Colors |
Данные из таблицы items
загрузим с помощью Nette Database Explorer командой
$items = $db->table('items')
. Во время итерации по этим данным у нас есть
возможность доступа не только к атрибутам, таким как $item->name
и
$item->categoryId
, но и, благодаря связи с таблицей categories
, к
связанной строке в ней через $item->category
. На этой связи можно
продемонстрировать интересное использование:
{foreach ($items|group: category) as $category => $categoryItems}
<h1>{$category->name}</h1>
<ul>
{foreach $categoryItems as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
{/foreach}
В этом случае мы используем фильтр |group
для группировки по
связанной строке $item->category
, а не только по столбцу categoryId
.
Благодаря этому в переменной ключа находится непосредственно
ActiveRow
данной категории, что позволяет нам напрямую выводить ее
имя с помощью {$category->name}
. Это практический пример того, как
группировка может сделать шаблоны более понятными и облегчить работу
с данными.
Фильтр |batch
Фильтр позволяет разделить список элементов на группы с заранее определенным количеством элементов. Этот фильтр идеален для ситуаций, когда вы хотите представить данные в нескольких меньших группах, например, для лучшей наглядности или визуального расположения на странице.
Представим, что у нас есть список записей, и мы хотим отобразить их в
списках, где каждый содержит не более трех записей. Использование
фильтра |batch
в таком случае очень практично:
<ul>
{foreach ($items|batch: 3) as $batch}
{foreach $batch as $item}
<li>{$item->name}</li>
{/foreach}
{/foreach}
</ul>
В этом примере список $items
разделен на меньшие группы, причем
каждая группа ($batch
) содержит до трех записей. Каждая группа
затем отображается в отдельном <ul>
списке.
Если последняя группа не содержит достаточного количества элементов для достижения требуемого числа, второй параметр фильтра позволяет определить, чем будет дополнена эта группа. Это идеально подходит для эстетического выравнивания элементов там, где неполный ряд мог бы выглядеть неупорядоченно.
{foreach ($items|batch: 3, '—') as $batch}
...
{/foreach}
Тег {iterateWhile}
Те же задачи, которые мы решали с фильтром |group
, покажем с
использованием тега {iterateWhile}
. Главное различие между обоими
подходами заключается в том, что group
сначала обрабатывает и
группирует все входные данные, в то время как {iterateWhile}
управляет
ходом циклов с условиями, так что итерация происходит постепенно.
Сначала отобразим таблицу с категориями с помощью iterateWhile:
{foreach $items as $item}
<ul>
{iterateWhile}
<li>{$item->name}</li>
{/iterateWhile $item->categoryId === $iterator->nextValue->categoryId}
</ul>
{/foreach}
В то время как {foreach}
обозначает внешнюю часть цикла, то есть
отображение списков для каждой категории, тег {iterateWhile}
обозначает внутреннюю часть, то есть отдельные записи. Условие в
закрывающем теге говорит, что повторение будет происходить до тех пор,
пока текущий и следующий элемент принадлежат к одной и той же
категории ($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}
, то
поведение изменится: условие (и переход к следующему элементу)
выполнится уже в начале внутреннего цикла, а не в конце. То есть, в то
время как в {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->categoryId === $iterator->nextValue->categoryId}
<li>{$item->name}</li>
{/iterateWhile}
</ul>
{/foreach}
В рамках одного цикла мы можем создавать несколько внутренних циклов и даже вкладывать их. Так можно было бы группировать, например, подкатегории и т.д.
Предположим, что в таблице будет еще один столбец subcategoryId
, и
кроме того, что каждая категория будет в отдельном <ul>
, каждая
подкатегория будет в отдельном <ol>
:
{foreach $items as $item}
<ul>
{iterateWhile}
<ol>
{iterateWhile}
<li>{$item->name}
{/iterateWhile $item->subcategoryId === $iterator->nextValue->subcategoryId}
</ol>
{/iterateWhile $item->categoryId === $iterator->nextValue->categoryId}
</ul>
{/foreach}