Tutto quello che avete sempre voluto sapere sul raggruppamento

Quando si lavora con i dati nei modelli, spesso si ha la necessità di raggrupparli o di visualizzarli in modo specifico in base a determinati criteri. A questo scopo, Latte offre diversi strumenti potenti.

Il filtro e la funzione |group consentono di raggruppare efficacemente i dati in base a criteri specifici, mentre il filtro |batch facilita la suddivisione dei dati in lotti fissi e il tag {iterateWhile} offre la possibilità di un controllo del ciclo più complesso con condizioni. Ognuno di questi tag offre opzioni specifiche per lavorare con i dati, rendendoli strumenti indispensabili per la visualizzazione dinamica e strutturata delle informazioni nei modelli Latte.

Filtro e funzione group

Immaginate una tabella del database items con articoli suddivisi in categorie:

id categoryId name
1 1 Mela
2 1 Banana
3 2 PHP
4 3 Verde
5 3 Rosso
6 3 Blu

Un semplice elenco di tutti gli elementi utilizzando un modello Latte si presenterebbe come segue:

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

Tuttavia, se vogliamo che gli articoli siano organizzati in gruppi per categoria, dobbiamo dividerli in modo che ogni categoria abbia un proprio elenco. Il risultato sarebbe quindi simile a questo:

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

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

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

Il compito può essere risolto in modo semplice ed elegante utilizzando |group. Specifichiamo categoryId come parametro, il che significa che gli elementi saranno suddivisi in array più piccoli in base al valore di $item->categoryId (se $item fosse un array, useremmo $item['categoryId']):

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

Il filtro può essere usato anche come funzione in Latte, con una sintassi alternativa: {foreach group($items, categoryId) ...}.

Se si desidera raggruppare gli elementi in base a criteri più complessi, è possibile utilizzare una funzione nel parametro del filtro. Ad esempio, il raggruppamento degli elementi in base alla lunghezza del loro nome può avvenire in questo modo:

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

È importante notare che $categoryItems non è un normale array, ma un oggetto che si comporta come un iteratore. Per accedere al primo elemento del gruppo, si può usare la funzione first() la funzione.

Questa flessibilità nel raggruppamento dei dati rende group uno strumento eccezionalmente utile per la presentazione dei dati nei modelli Latte.

Cicli annidati

Supponiamo di avere una tabella di database con un'altra colonna subcategoryId che definisce le sottocategorie per ogni articolo. Vogliamo visualizzare ogni categoria principale in un elenco separato e ogni <ul> e ogni sottocategoria in un elenco annidato separato <ol> separato:

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

Connessione con il database Nette

Mostriamo come utilizzare efficacemente il raggruppamento dei dati in combinazione con Nette Database. Supponiamo di lavorare con la tabella items dell'esempio iniziale, che è collegata attraverso la colonna categoryId a questa tabella categories:

categoryId name
1 Frutta
2 Lingue
3 Colori

Carichiamo i dati dalla tabella items utilizzando il comando $items = $db->table('items') di Nette Database Explorer. Durante l'iterazione su questi dati, abbiamo la possibilità non solo di accedere ad attributi come $item->name e $item->categoryId, ma, grazie alla connessione con la tabella categories, anche alla relativa riga in essa contenuta tramite $item->category. Questa connessione può rivelarsi interessante:

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

In questo caso, utilizziamo il filtro |group per raggruppare in base alla riga collegata $item->category, non solo in base alla colonna categoryId. In questo modo si ottiene il ActiveRow della categoria data nella chiave variabile, consentendo di visualizzarne direttamente il nome utilizzando {$category->name}. Questo è un esempio pratico di come il raggruppamento possa semplificare i modelli e facilitare la gestione dei dati.

Filtro |batch

Il filtro consente di suddividere un elenco di elementi in gruppi con un numero predeterminato di elementi. Questo filtro è ideale per le situazioni in cui si desidera presentare i dati in diversi gruppi più piccoli, ad esempio per una migliore chiarezza o organizzazione visiva della pagina.

Immaginiamo di avere un elenco di elementi e di volerli visualizzare in elenchi, ciascuno contenente un massimo di tre elementi. L'uso del filtro |batch è molto pratico in questo caso:

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

In questo esempio, l'elenco $items viene suddiviso in gruppi più piccoli, ciascuno dei quali ($batch) contiene un massimo di tre elementi. Ogni gruppo viene quindi visualizzato in un elenco <ul> elenco separato.

Se l'ultimo gruppo non contiene abbastanza elementi per raggiungere il numero desiderato, il secondo parametro del filtro consente di definire con cosa verrà integrato questo gruppo. Questo è ideale per allineare esteticamente gli elementi in cui una riga incompleta potrebbe sembrare disordinata.

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

Tag {iterateWhile}

Dimostreremo gli stessi compiti che abbiamo affrontato con il filtro |group utilizzando il tag {iterateWhile}. La differenza principale tra i due approcci è che group elabora e raggruppa prima tutti i dati in ingresso, mentre {iterateWhile} controlla l'avanzamento dei cicli con le condizioni, quindi l'iterazione avviene in modo sequenziale.

Per prima cosa, disegniamo una tabella con le categorie usando iterateWhile:

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

Mentre {foreach} segna la parte esterna del ciclo, cioè il disegno di liste per ogni categoria, il tag {iterateWhile} segna la parte interna, cioè i singoli elementi. La condizione nel tag end dice che la ripetizione continuerà finché l'elemento corrente e quello successivo appartengono alla stessa categoria ($iterator->nextValue è l'elemento successivo).

Se la condizione fosse sempre soddisfatta, tutti gli elementi verrebbero disegnati nel ciclo interno:

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

Il risultato sarà simile a questo:

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

A cosa serve iterareWhile in questo modo? Quando la tabella è vuota e non contiene elementi, non viene stampato alcun elemento vuoto. <ul></ul> viene stampato.

Se si specifica la condizione nel tag di apertura {iterateWhile}, il comportamento cambia: la condizione (e la transizione all'elemento successivo) viene eseguita all'inizio del ciclo interno, non alla fine. Così, mentre si entra sempre in {iterateWhile} senza condizioni, si entra in {iterateWhile $cond} solo quando la condizione $cond è soddisfatta. Allo stesso tempo, l'elemento successivo viene scritto in $item.

Questo è utile, ad esempio, in una situazione in cui si vuole rendere il primo elemento di ogni categoria in modo diverso, come in questo caso:

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

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

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

Modifichiamo il codice originale in modo da rendere prima il primo elemento e poi nel ciclo interno {iterateWhile} rendiamo gli altri elementi della stessa categoria:

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

All'interno di un ciclo, possiamo creare più cicli interni e persino annidarli. In questo modo, si possono raggruppare le sottocategorie, per esempio.

Supponiamo che la tabella abbia un'altra colonna subcategoryId, e che oltre a ciascuna categoria sia in una sezione separata <ul>, ogni sottocategoria in un ciclo separato <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}
versione: 3.0