Tout ce que vous avez toujours voulu savoir sur le regroupement

Lorsque vous travaillez avec des données dans des modèles, vous rencontrez souvent le besoin de les grouper ou de les afficher spécifiquement selon certains critères. Latte propose à cet effet plusieurs outils puissants.

Le filtre et la fonction |group permettent de regrouper efficacement les données sur la base de critères spécifiques, tandis que le filtre |batch facilite la division des données en lots fixes et que la balise {iterateWhile} offre la possibilité d'un contrôle de cycle plus complexe à l'aide de conditions. Chacune de ces balises offre des options spécifiques pour travailler avec les données, ce qui en fait des outils indispensables pour l'affichage dynamique et structuré des informations dans les modèles Latte.

Filtre et fonction group

Imaginez une table de base de données items avec des articles divisés en catégories :

id categoryId name
1 1 Pomme
2 1 Banane
3 2 PHP
4 3 Vert
5 3 Rouge
6 3 Bleu

Une liste simple de tous les éléments utilisant un modèle Latte ressemblerait à ceci :

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

Toutefois, si nous voulons que les articles soient organisés en groupes par catégorie, nous devons les diviser de manière à ce que chaque catégorie ait sa propre liste. Le résultat serait alors le suivant :

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

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

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

Cette tâche peut être facilement et élégamment résolue en utilisant |group. Nous spécifions categoryId comme paramètre, ce qui signifie que les éléments seront divisés en tableaux plus petits en fonction de la valeur de $item->categoryId (si $item était un tableau, nous utiliserions $item['categoryId']) :

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

Le filtre peut également être utilisé comme une fonction dans Latte, ce qui nous donne une syntaxe alternative : {foreach group($items, categoryId) ...}.

Si vous souhaitez regrouper des éléments selon des critères plus complexes, vous pouvez utiliser une fonction dans le paramètre du filtre. Par exemple, le regroupement des éléments en fonction de la longueur de leur nom ressemblerait à ceci :

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

Il est important de noter que $categoryItems n'est pas un tableau ordinaire, mais un objet qui se comporte comme un itérateur. Pour accéder au premier élément du groupe, vous pouvez utiliser la fonction first() pour accéder au premier élément du groupe.

Cette flexibilité dans le regroupement des données fait de group un outil exceptionnellement utile pour présenter les données dans les modèles Latte.

Boucles imbriquées

Supposons que nous ayons une table de base de données avec une autre colonne subcategoryId qui définit les sous-catégories pour chaque article. Nous voulons afficher chaque catégorie principale dans une liste distincte et chaque sous-catégorie dans une boucle imbriquée distincte. <ul> distincte et chaque sous-catégorie dans une liste imbriquée distincte <ol> imbriquée :

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

Connexion avec la base de données Nette

Montrons comment utiliser efficacement le regroupement de données en combinaison avec Nette Database. Supposons que nous travaillions avec la table items de l'exemple initial, qui est connectée par la colonne categoryId à cette table categories:

CatégorieId Nom
1 Fruits
2 Langues
3 Couleurs

Nous chargeons les données de la table items à l'aide de la commande Nette Database Explorer $items = $db->table('items'). Au cours de l'itération sur ces données, nous avons la possibilité non seulement d'accéder à des attributs tels que $item->name et $item->categoryId, mais aussi, grâce à la connexion avec la table categories, à la ligne correspondante de cette table via $item->category. Cette connexion peut donner lieu à des utilisations intéressantes :

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

Dans ce cas, nous utilisons le filtre |group pour regrouper les données en fonction de la ligne connectée $item->category, et pas seulement en fonction de la colonne categoryId. Nous obtenons ainsi le site ActiveRow de la catégorie donnée dans la clé variable, ce qui nous permet d'afficher directement son nom à l'aide de {$category->name}. Il s'agit d'un exemple pratique de la manière dont le regroupement peut simplifier les modèles et faciliter la manipulation des données.

Filtre |batch

Le filtre vous permet de diviser une liste d'éléments en groupes avec un nombre prédéterminé d'éléments. Ce filtre est idéal pour les situations où vous souhaitez présenter des données en plusieurs groupes plus petits, par exemple pour une meilleure clarté ou une meilleure organisation visuelle sur la page.

Imaginons que nous ayons une liste d'éléments et que nous souhaitions les afficher dans des listes contenant chacune un maximum de trois éléments. L'utilisation du filtre |batch est très pratique dans ce cas :

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

Dans cet exemple, la liste $items est divisée en groupes plus petits, chaque groupe ($batch) contenant jusqu'à trois éléments. Chaque groupe est ensuite affiché dans une liste <ul> liste séparée.

Si le dernier groupe ne contient pas suffisamment d'éléments pour atteindre le nombre souhaité, le deuxième paramètre du filtre vous permet de définir ce qui viendra compléter ce groupe. Ceci est idéal pour aligner esthétiquement les éléments là où une ligne incomplète pourrait paraître désordonnée.

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

Étiquette {iterateWhile}

Nous allons démontrer les mêmes tâches que nous avons abordées avec le filtre |group en utilisant la balise {iterateWhile}. La principale différence entre les deux approches est que group traite et regroupe d'abord toutes les données d'entrée, tandis que {iterateWhile} contrôle la progression des cycles avec des conditions, de sorte que l'itération se produit de manière séquentielle.

Tout d'abord, nous dessinons un tableau avec des catégories en utilisant iterateWhile :

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

Alors que {foreach} marque la partie extérieure du cycle, c'est-à-dire l'établissement de listes pour chaque catégorie, la balise {iterateWhile} marque la partie intérieure, c'est-à-dire les éléments individuels. La condition de la balise end indique que la répétition se poursuivra tant que l'élément actuel et l'élément suivant appartiennent à la même catégorie ($iterator->nextValue est l'élément suivant).

Si la condition était toujours remplie, tous les éléments seraient dessinés dans le cycle interne :

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

Le résultat sera le suivant :

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

Quelle est l'utilité de iterateWhile dans ce cas ? Lorsque le tableau est vide et ne contient pas d'éléments, le message empty <ul></ul> n'est imprimée.

Si nous spécifions la condition dans la balise d'ouverture {iterateWhile}, le comportement change : la condition (et la transition vers l'élément suivant) est exécutée au début du cycle interne, et non à la fin. Ainsi, alors que vous entrez toujours dans {iterateWhile} sans condition, vous n'entrez dans {iterateWhile $cond} que lorsque la condition $cond est remplie. Au même moment, l'élément suivant est écrit dans $item.

Ceci est utile, par exemple, dans une situation où nous voulons rendre le premier élément de chaque catégorie différemment, comme ceci :

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

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

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

Nous modifions le code original de manière à rendre d'abord le premier élément, puis, dans le cycle interne {iterateWhile}, les autres éléments de la même catégorie :

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

Au sein d'un cycle, nous pouvons créer plusieurs boucles internes et même les imbriquer. De cette manière, les sous-catégories peuvent être regroupées, par exemple.

Supposons que le tableau comporte une autre colonne subcategoryId, et que, outre le fait que chaque catégorie se trouve dans une colonne distincte, chaque sous-catégorie se trouve dans une colonne distincte. <ul>chaque sous-catégorie dans une colonne distincte <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