Все, що ви завжди хотіли знати про групування

Працюючи з даними в шаблонах, ви часто стикаєтеся з необхідністю групувати їх або відображати за певними критеріями. Для цього Latte пропонує кілька потужних інструментів.

Фільтр і функція |group дозволяють ефективно групувати дані на основі заданих критеріїв, тоді як фільтр |batch полегшує розбиття даних на фіксовані партії, а тег {iterateWhile} надає можливість більш складного управління циклами з умовами. Кожен з цих тегів пропонує специфічні можливості для роботи з даними, що робить їх незамінними інструментами для динамічного і структурованого відображення інформації в шаблонах Latte.

Фільтр і функція group

Уявіть собі таблицю бази даних items з елементами, розділеними на категорії:

id categoryId name
1 1 1 Яблуко    
2 1 Банан
3 2 PHP
4 3 Зелений
5 3 Червоний    
6 3 Синій    

Простий список всіх елементів з використанням шаблону 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

Покажемо, як ефективно використовувати групування даних у поєднанні з базою даних Nette. Припустимо, що ми працюємо з таблицею items з початкового прикладу, яка підключена через стовпець categoryId до цієї таблиці categories:

categoryId name
1 Фрукти
2 Мови
3 кольори  

Завантажуємо дані з таблиці 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}
версію: 3.0