Alles, was Sie schon immer über Gruppierung wissen wollten

Bei der Arbeit mit Daten in Templates stoßen Sie oft auf die Notwendigkeit, diese nach bestimmten Kriterien zu gruppieren oder spezifisch anzuzeigen. Latte bietet zu diesem Zweck gleich mehrere leistungsstarke Werkzeuge.

Der Filter und die Funktion |group ermöglichen eine effiziente Gruppierung von Daten nach einem festgelegten Kriterium, der Filter |batch erleichtert die Aufteilung von Daten in feste Blöcke und der Tag {iterateWhile} bietet die Möglichkeit einer komplexeren Steuerung des Schleifenverlaufs mit Bedingungen. Jeder dieser Tags bietet spezifische Möglichkeiten zur Datenverarbeitung und wird so zu einem unverzichtbaren Werkzeug für die dynamische und strukturierte Darstellung von Informationen in Latte-Templates.

Filter und Funktion group

Stellen Sie sich eine Datenbanktabelle items mit Einträgen vor, die in Kategorien unterteilt sind:

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

Eine einfache Liste aller Einträge mit einem Latte-Template würde so aussehen:

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

Wenn wir jedoch möchten, dass die Einträge nach Kategorien gruppiert werden, müssen wir sie so aufteilen, dass jede Kategorie ihre eigene Liste hat. Das Ergebnis sollte dann wie folgt aussehen:

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

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

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

Die Aufgabe lässt sich einfach und elegant mit |group lösen. Als Parameter geben wir categoryId an, was bedeutet, dass die Einträge nach dem Wert von $item->categoryId in kleinere Arrays aufgeteilt werden (wenn $item ein Array wäre, würde $item['categoryId'] verwendet):

{foreach ($items|group: categoryId) as $categoryId => $categoryItems}
	<ul>
		{foreach $categoryItems as $item}
			<li>{$item->name}</li>
		{/foreach}
	</ul>
{/foreach}

Der Filter kann in Latte auch als Funktion verwendet werden, was uns eine alternative Syntax gibt: {foreach group($items, categoryId) ...}.

Wenn Sie Einträge nach komplexeren Kriterien gruppieren möchten, können Sie im Filterparameter eine Funktion verwenden. Zum Beispiel würde die Gruppierung von Einträgen nach der Länge des Namens so aussehen:

{foreach ($items|group: fn($item) => strlen($item->name)) as $items}
	...
{/foreach}

Es ist wichtig zu beachten, dass $categoryItems kein gewöhnliches Array ist, sondern ein Objekt, das sich wie ein Iterator verhält. Um auf das erste Element der Gruppe zuzugreifen, können Sie die Funktion first() verwenden.

Diese Flexibilität bei der Datengruppierung macht group zu einem außergewöhnlich nützlichen Werkzeug für die Datenpräsentation in Latte-Templates.

Verschachtelte Schleifen

Stellen wir uns vor, wir haben eine Datenbanktabelle mit einer weiteren Spalte subcategoryId, die Unterkategorien für einzelne Einträge definiert. Wir möchten jede Hauptkategorie in einer separaten <ul>-Liste und jede Unterkategorie in einer separaten verschachtelten <ol>-Liste anzeigen:

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

Verbindung mit Nette Database

Zeigen wir, wie man die Datengruppierung effektiv in Kombination mit Nette Database nutzt. Angenommen, wir arbeiten mit der Tabelle items aus dem Einführungsbeispiel, die über die Spalte categoryId mit dieser Tabelle categories verknüpft ist:

categoryId name
1 Fruits
2 Languages
3 Colors

Die Daten aus der Tabelle items laden wir mit dem Nette Database Explorer Befehl $items = $db->table('items'). Während der Iteration über diese Daten haben wir die Möglichkeit, nicht nur auf Attribute wie $item->name und $item->categoryId zuzugreifen, sondern dank der Verknüpfung mit der Tabelle categories auch auf die zugehörige Zeile darin über $item->category. An dieser Verknüpfung lässt sich eine interessante Anwendung demonstrieren:

{foreach ($items|group: category) as $category => $categoryItems}
	<h1>{$category->name}</h1>
	<ul>
		{foreach $categoryItems as $item}
			<li>{$item->name}</li>
		{/foreach}
	</ul>
{/foreach}

In diesem Fall verwenden wir den Filter |group, um nach der verknüpften Zeile $item->category zu gruppieren, nicht nur nach der Spalte categoryId. Dadurch haben wir in der Schlüsselvariable $category direkt die ActiveRow der jeweiligen Kategorie, was uns ermöglicht, ihren Namen direkt mit {$category->name} auszugeben. Dies ist ein praktisches Beispiel dafür, wie Gruppierung Templates übersichtlicher machen und die Arbeit mit Daten erleichtern kann.

Filter |batch

Der Filter ermöglicht es, eine Liste von Elementen in Gruppen mit einer vorbestimmten Anzahl von Elementen aufzuteilen. Dieser Filter ist ideal für Situationen, in denen Sie Daten in mehreren kleineren Gruppen präsentieren möchten, beispielsweise zur besseren Übersichtlichkeit oder visuellen Anordnung auf der Seite.

Stellen Sie sich vor, Sie haben eine Liste von Einträgen und möchten sie in Listen anzeigen, wobei jede Liste maximal drei Einträge enthält. Die Verwendung des Filters |batch ist in einem solchen Fall sehr praktisch:

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

In diesem Beispiel wird die Liste $items in kleinere Gruppen aufgeteilt, wobei jede Gruppe ($batch) bis zu drei Einträge enthält. Jede Gruppe wird dann in einer separaten <ul>-Liste angezeigt.

Wenn die letzte Gruppe nicht genügend Elemente enthält, um die gewünschte Anzahl zu erreichen, ermöglicht der zweite Parameter des Filters, zu definieren, womit diese Gruppe aufgefüllt wird. Dies ist ideal für die ästhetische Ausrichtung von Elementen, wo eine unvollständige Reihe unordentlich wirken könnte.

{foreach ($items|batch: 3, '—') as $batch}
	...
{/foreach}

Tag {iterateWhile}

Die gleichen Aufgaben, die wir mit dem Filter |group gelöst haben, zeigen wir mit dem Tag {iterateWhile}. Der Hauptunterschied zwischen den beiden Ansätzen besteht darin, dass group zuerst alle Eingabedaten verarbeitet und gruppiert, während {iterateWhile} den Verlauf von Schleifen mit Bedingungen steuert, sodass die Iteration schrittweise erfolgt.

Zuerst rendern wir die Tabelle mit Kategorien mithilfe von iterateWhile:

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

Während {foreach} den äußeren Teil der Schleife markiert, also das Rendern der Listen für jede Kategorie, markiert der Tag {iterateWhile} den inneren Teil, also die einzelnen Einträge. Die Bedingung im End-Tag {/iterateWhile} besagt, dass die Wiederholung so lange fortgesetzt wird, wie das aktuelle und das nächste Element zur selben Kategorie gehören ($iterator->nextValue ist das nächstes Element).

Wenn die Bedingung immer erfüllt wäre, würden alle Elemente in der inneren Schleife gerendert:

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

Das Ergebnis würde so aussehen:

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

Wozu ist eine solche Verwendung von iterateWhile gut? Wenn die Tabelle leer ist und keine Elemente enthält, wird kein leeres <ul></ul> ausgegeben.

Wenn wir die Bedingung im öffnenden Tag {iterateWhile} angeben, ändert sich das Verhalten: Die Bedingung (und der Übergang zum nächsten Element) wird bereits am Anfang der inneren Schleife ausgeführt, nicht am Ende. Das heißt, während man in {iterateWhile} ohne Bedingung immer eintritt, tritt man in {iterateWhile $cond} nur ein, wenn die Bedingung $cond erfüllt ist. Und gleichzeitig wird das nächste Element in $item geschrieben.

Das ist zum Beispiel nützlich, wenn wir das erste Element jeder Kategorie anders rendern möchten, zum Beispiel so:

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

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

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

Wir passen den ursprünglichen Code so an, dass wir zuerst den ersten Eintrag rendern und dann in der inneren Schleife {iterateWhile} weitere Einträge derselben Kategorie rendern:

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

Innerhalb einer Schleife können wir mehrere innere Schleifen erstellen und sie sogar verschachteln. So könnten beispielsweise Unterkategorien usw. gruppiert werden.

Angenommen, in der Tabelle gibt es noch eine weitere Spalte subcategoryId und neben der Tatsache, dass jede Kategorie in einem separaten <ul> steht, steht jede Unterkategorie in einem separaten <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}
Version: 3.0