Wszystko, co kiedykolwiek chciałeś wiedzieć o {iterateWhile}.
Znacznik {iterateWhile}
jest przydatny do wszelkiego rodzaju sztuczek w pętlach foreach.
Załóżmy, że mamy następującą tabelę bazy danych, w której przedmioty są skategoryzowane:
id | catId | name |
---|---|---|
1 | 1 | Apple |
2 | 1 | Banana |
3 | 2 | PHP |
4 | 3 | Green |
5 | 3 | Red |
6 | 3 | Blue |
Renderowanie elementów w pętli foreach jako listy jest oczywiście proste:
<ul>
{foreach $items as $item}
<li>{$item->name}</li>
{/foreach}
</ul>
Ale co jeśli chcielibyśmy, aby każda kategoria była na osobnej liście? Innymi słowy, rozwiązujemy problem, jak pogrupować elementy na liście liniowej w pętli foreach. Dane wyjściowe powinny wyglądać tak:
<ul>
<li>Apple</li>
<li>Banana</li>
</ul>
<ul>
<li>PHP</li>
</ul>
<ul>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
Zobaczymy jak łatwo i elegancko można rozwiązać to zadanie używając iterateWhile:
{foreach $items as $item}
<ul>
{iterateWhile}
<li>{$item->name}</li>
{/iterateWhile $item->catId === $iterator->nextValue->catId}
</ul>
{/foreach}
Podczas gdy {foreach}
oznacza zewnętrzną część pętli, czyli renderowanie list dla każdej kategorii,
znacznik {iterateWhile}
oznacza część wewnętrzną, czyli poszczególne elementy. Warunek w znaczniku end mówi,
że iteracja będzie trwała tak długo, jak długo bieżący i następny element należą do tej samej kategorii
($iterator->nextValue
jest następnym elementem).
Gdyby warunek był zawsze spełniony, wszystkie elementy byłyby renderowane w wewnętrznej pętli:
{foreach $items as $item}
<ul>
{iterateWhile}
<li>{$item->name}
{/iterateWhile true}
</ul>
{/foreach}
Wynik wyglądałby tak:
<ul>
<li>Apple</li>
<li>Banana</li>
<li>PHP</li>
<li>Green</li>
<li>Red</li>
<li>Blue</li>
</ul>
Jakie jest zastosowanie iterateWhile? Czym różni się ono od rozwiązania, które pokazaliśmy na samym początku tego
tutorialu? Różnica polega na tym, że jeśli tablica jest pusta i nie zawiera żadnych elementów, nie zostanie wypisana pusta
tablica <ul></ul>
.
Rozwiązanie bez {iterateWhile}
Gdybyśmy mieli rozwiązać to samo zadanie używając bardzo podstawowych systemów templatowania, na przykład w Twigu, Blade, czy czystym PHP, rozwiązanie wyglądałoby coś takiego:
{var $prevCatId = null}
{foreach $items as $item}
{if $item->catId !== $prevCatId}
{* kategoria zmieniona *}
{* zamknij poprzedni, <ul> jeśli nie jest pierwszym elementem *}
{if $prevCatId !== null}
</ul>
{/if}
{* otwórz nową listę *}
<ul>
{do $prevCatId = $item->catId}
{/if}
<li>{$item->name}</li>
{/foreach}
{if $prevCatId !== null}
{* zamknij ostatnią listę *}
</ul>
{/if}
Jednak ten kod jest niezrozumiały i nieintuicyjny. Związek pomiędzy otwierającymi i zamykającymi znacznikami HTML nie
jest wcale jasny. Nie widać na pierwszy rzut oka, czy jest jakiś błąd. I wymaga zmiennych pomocniczych, takich jak
$prevCatId
.
W przeciwieństwie do tego, rozwiązanie {iterateWhile}
jest czyste, jasne, nie potrzebuje zmiennych pomocniczych
i jest bloatproof.
Warunek w tagu otwierającym
Jeśli określisz warunek w znaczniku otwierającym {iterateWhile}
, to zachowanie się zmieni: warunek
(i przejście do następnego elementu) jest wykonywany na początku pętli wewnętrznej, a nie na końcu. O ile więc
{iterateWhile}
bez warunku jest wpisywany zawsze, to {iterateWhile $cond}
jest wpisywany dopiero po
spełnieniu warunku $cond
. I w tym samym czasie na stronę $item
wchodzi kolejny element.
Co jest przydatne np. jeśli chcemy wyrenderować pierwszy element w każdej kategorii w inny sposób, np:
<h1>Apple</h1>
<ul>
<li>Banana</li>
</ul>
<h1>PHP</h1>
<ul>
</ul>
<h1>Green</h1>
<ul>
<li>Red</li>
<li>Blue</li>
</ul>
Zmodyfikuj oryginalny kod, najpierw renderując pierwszy element, a następnie renderując pozostałe elementy z tej samej
kategorii w wewnętrznej pętli strony {iterateWhile}
:
{foreach $items as $item}
<h1>{$item->name}</h1>
<ul>
{iterateWhile $item->catId === $iterator->nextValue->catId}
<li>{$item->name}</li>
{/iterateWhile}
</ul>
{/foreach}
Pętle zagnieżdżone
Możemy tworzyć wiele pętli wewnętrznych w ramach jednej pętli, a nawet je zagnieżdżać. W ten sposób można by grupować np. podkategorie itp.
Załóżmy, że w tabeli subCatId
jest jeszcze jedna kolumna i oprócz tego, że każda kategoria jest w osobnej
<ul>
każda podkategoria w osobnym <ol>
:
{foreach $items as $item}
<ul>
{iterateWhile}
<ol>
{iterateWhile}
<li>{$item->name}
{/iterateWhile $item->subCatId === $iterator->nextValue->subCatId}
</ol>
{/iterateWhile $item->catId === $iterator->nextValue->catId}
</ul>
{/foreach}
Filtr |wszystkie
Filtr batch
obsługuje również grupowanie elementów liniowych w partie o stałej liczbie elementów:
<ul>
{foreach ($items|batch:3) as $batch}
{foreach $batch as $item}
<li>{$item->name}</li>
{/foreach}
{/foreach}
</ul>
Można go zastąpić iterateWhile w następujący sposób:
<ul>
{foreach $items as $item}
{iterateWhile}
<li>{$item->name}</li>
{/iterateWhile $iterator->counter0 % 3}
{/foreach}
</ul>