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