Gruplama hakkında bilmek istediğiniz her şey
Şablonlarda veriyle çalışırken sıklıkla öğeleri gruplamanız, onları partilere bölmeniz veya bir koşula göre üzerlerinde dolaşmanız gerekir. Latte bunun için üç araç sunar; her biri biraz farklı bir duruma uygundur.
|group filtresi öğeleri belirli bir ölçüte göre gruplar, |batch filtresi onları sabit boyutlu
partilere böler ve {iterateWhile} etiketi veriyi adım adım dolaşıp iç döngüyü ne zaman keseceğine kendisi
karar verir. Bunları tek tek ele alacağız.
group filtresi ve fonksiyonu
Araç iki biçimde kullanılabilir: bir filtre olarak $items|group: … ya da bir fonksiyon olarak
group($items, …). Anlamsal olarak eşdeğerdirler; okunabilirliğe göre seçin.
Öğeleri çeşitli kategorilere ait olan bir items veritabanı tablosu düşünün:
| id | categoryId | name |
|---|---|---|
| 1 | 1 | Apple |
| 2 | 1 | Banana |
| 3 | 2 | PHP |
| 4 | 3 | Green |
| 5 | 3 | Red |
| 6 | 3 | Blue |
Latte şablonu kullanarak tüm öğelerin basit bir listesi şöyle görünürdü:
<ul>
{foreach $items as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
Ancak öğeleri kategoriye göre gruplar halinde istersek, her kategorinin kendi listesi olacak şekilde onları bölmemiz gerekir. İstenen sonuç şöyle görünürdü:
<ul>
<li>Apple</li>
<li>Banana</li>
</ul>
<ul>
<li>PHP</li>
</ul>
<ul>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
Bu görev |group kullanılarak kolay ve zarif biçimde çözülebilir. Parametre olarak categoryId
belirtiriz; bu, öğelerin $item->categoryId değerine göre daha küçük dizilere bölüneceği anlamına gelir
($item bir dizi olsaydı $item['categoryId'] kullanılırdı):
{foreach ($items|group: categoryId) as $categoryId => $categoryItems}
<ul>
{foreach $categoryItems as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
{/foreach}
Öğeleri daha karmaşık ölçütlere göre gruplamak isterseniz, filtre parametresinde bir fonksiyon kullanabilirsiniz. Her grubun anahtarı o zaman fonksiyonun dönüş değeri olur; örneğin ad uzunluğuna göre gruplarken karakter sayısı olacaktır:
{foreach ($items|group: fn($item) => strlen($item->name)) as $length => $group}
...
{/foreach}
Şunu belirtmek önemlidir: her grup ($categoryItems dahil) sıradan bir dizi değil, bir yineleyici gibi davranan
bir nesnedir; yani öğelere indeksle erişemezsiniz, örneğin $categoryItems[0]. Ancak öğeleri
count($categoryItems) ile sayabilirsiniz ve gruptaki ilk öğeye erişmek için first() fonksiyonunu kullanın.
Bu esneklik, |group'u veriyi sunmak için son derece yararlı bir araç yapar.
İç içe döngüler
Veritabanı tablomuzda her öğe için alt kategori tanımlayan ek bir subcategoryId sütunu olduğunu
düşünelim. Her ana kategoriyi ayrı bir <ul> listesinde, o ana kategorideki her alt kategoriyi de ayrı bir
iç içe <ol> listesinde göstermek istiyoruz:
{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 ile birlikte
Veri gruplamanın Nette Database ile birlikte nasıl etkili kullanılacağını gösterelim. Giriş örneğindeki
items tablosuyla çalıştığımızı ve onun categoryId sütunu üzerinden şu categories
tablosuna bağlı olduğunu varsayalım:
| categoryId | name |
|---|---|
| 1 | Fruits |
| 2 | Languages |
| 3 | Colors |
Veriyi items tablosundan Nette Database Explorer ile $items = $db->table('items') komutuyla
yükleriz. Bu veri üzerinde dolaşırken yalnızca $item->name ve $item->categoryId gibi
niteliklere değil, categories tablosuyla olan ilişki sayesinde $item->category ile ilgili satıra
da erişebiliriz. Bu ilişki ilginç kullanımlara olanak tanır:
{foreach ($items|group: category) as $category => $categoryItems}
<h1>{$category->name}</h1>
<ul>
{foreach $categoryItems as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
{/foreach}
Bu durumda |group filtresini yalnızca categoryId sütununa göre değil, ilişkili satır
$item->category'ye göre gruplamak için kullanırız. Sonuç olarak anahtar ($category) doğrudan
o kategorinin ActiveRow nesnesini taşır; bu da adını {$category->name} ile göstermemize ve
categories'e ayrı bir sorgu yapmadan başka herhangi bir sütuna erişmemize olanak tanır.
|batch filtresi
Filtre, bir öğe listesini sabit boyutlu partilere böler. Izgara düzenleri, sütun yerleşimleri veya her tür görsel gruplama için kullanışlıdır.
Öğeleri, her listenin en fazla üç öğe içerdiği listelerde göstermek istediğimizi düşünelim:
{foreach ($items|batch: 3) as $batch}
<ul>
{foreach $batch as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
{/foreach}
Bu örnekte $items listesi, her grubun ($batch) en fazla üç öğe içerdiği daha küçük gruplara
bölünür. Sonra her parti ayrı bir <ul> listesinde gösterilir.
Son grup, istenen sayıya ulaşacak kadar öğe içermiyorsa, filtrenin ikinci parametresi bu grubun neyle tamamlanacağını tanımlamanızı sağlar. Bu, eksik bir satırın düzensiz görünebileceği durumlarda öğeleri estetik biçimde hizalamak için idealdir.
{foreach ($items|batch: 3, '—') as $batch}
...
{/foreach}
{iterateWhile} etiketi
|group filtresiyle çözdüğümüz aynı görevleri {iterateWhile} etiketiyle göstereceğiz. İki
yaklaşım arasındaki temel fark şudur: |group önce tüm girdi verisini işleyip gruplarken,
{iterateWhile} döngünün ilerlemesini bir koşulla denetler ve yineleme sırayla ilerler.
Önce kategorili tabloyu {iterateWhile} ile render edelim:
{foreach $items as $item}
<ul>
{iterateWhile}
<li>{$item->name}</li>
{/iterateWhile $item->categoryId === $iterator->nextValue->categoryId}
</ul>
{/foreach}
{foreach} döngünün dış bölümünü, yani her kategori için liste çizmeyi işaretlerken,
{iterateWhile} etiketi iç bölümü, yani tek tek öğeleri işaretler. Kapanış etiketindeki koşul, geçerli ve
sonraki öğe aynı kategoriye ait olduğu sürece yinelemenin süreceğini söyler ($iterator->nextValue sonraki öğedir; son öğede iç döngü biter, çünkü sonraki öğe
yoktur – Latte koşulu değerlendirmeden önce $iterator->hasNext()'i denetler, bu yüzden null
ile karşılaştırmaya hiç gelinmez).
Koşul her zaman doğru olsaydı, tüm öğeler ilk <ul>'un içinde render edilirdi:
{foreach $items as $item}
<ul>
{iterateWhile}
<li>{$item->name}
{/iterateWhile true}
</ul>
{/foreach}
Sonuç şöyle görünürdü:
<ul>
<li>Apple</li>
<li>Banana</li>
<li>PHP</li>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
{iterateWhile}'ı böyle kullanmanın yararı ne? <ul> dıştaki {foreach}'in
içinde olduğundan, girdi boş olduğunda hiçbir şey render edilmez; yalnız başına bir <ul></ul>
kalmaz. {iterateWhile} olmadan aynı durumu, etiketi açmadan önce bir {if} ile ya da
{foreachelse} ile ele almanız gerekirdi.
Koşulu açılış {iterateWhile} etiketinde belirtirsek davranış değişir: koşul (ve sonraki öğeye geçiş)
iç döngünün sonunda değil başında gerçekleştirilir. Yani koşulsuz {iterateWhile}'a her zaman girersiniz,
ama {iterateWhile $cond}'a yalnızca $cond koşulu sağlandığında girersiniz. Aynı zamanda sonraki
öğe $item'a yazılır.
Bu, her kategorideki ilk öğeyi diğerlerinden farklı render etmek istediğimiz durumlarda yararlıdır; örneğin şöyle:
<h1>Apple</h1>
<ul>
<li>Banana</li>
</ul>
<h1>PHP</h1>
<ul>
</ul>
<h1>Green</h1>
<ul>
<li>Red</li>
<li>Blue</li>
</ul>
(PHP kategorisindeki boş <ul></ul>, yalnızca mekanizmanın bir gösterimidir; gerçek kodda
<ul> render'ını bir {if} ile halledersiniz.)
Özgün kodu, önce öğeyi bir başlık olarak render edecek, sonra aynı kategorideki sonraki öğeleri liste öğesi olarak
render etmek için iç {iterateWhile} döngüsünü kullanacak şekilde değiştiririz:
{foreach $items as $item}
<h1>{$item->name}</h1>
<ul>
{iterateWhile $item->categoryId === $iterator->nextValue?->categoryId}
<li>{$item->name}</li>
{/iterateWhile}
</ul>
{/foreach}
Tek bir döngünün içinde birden fazla iç döngü oluşturabilir, hatta onları iç içe geçirebiliriz. Böylece aynı anda birden fazla düzeyde gruplayabilirsiniz; örneğin kategorilerin altındaki alt kategorileri.
Tabloda bir de subcategoryId sütunu olduğunu ve her kategorinin ayrı bir <ul> içinde
olmasının yanı sıra her alt kategorinin ayrı bir <ol> içinde olacağını varsayalım:
{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}