グループ化について知りたかったことすべて

テンプレートでデータを扱う際、特定の基準に従ってデータをグループ化したり、特別に表示したりする必要性にしばしば直面します。Latte はこの目的のために、いくつかの強力なツールを提供しています。

フィルタと関数 |group は、指定された基準に従ってデータを効率的にグループ化することを可能にし、フィルタ |batch はデータを固定サイズのバッチに分割することを容易にし、タグ {iterateWhile} は条件付きでループの進行をより複雑に制御する可能性を提供します。 これらの各タグは、データを扱うための特定の可能性を提供し、Latte テンプレートで情報を動的かつ構造化して表示するための不可欠なツールになります。

フィルタと関数 group

カテゴリに分割された項目を持つデータベーステーブル items を想像してください:

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

Latte テンプレートを使用したすべての項目の簡単なリストは次のようになります:

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

しかし、項目をカテゴリ別にグループ化して表示したい場合は、各カテゴリが独自のリストを持つように分割する必要があります。結果は次のようになります:

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

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

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

このタスクは |group を使用して簡単かつエレガントに解決できます。パラメータとして categoryId を指定します。これは、項目が $item->categoryId の値に基づいて小さな配列に分割されることを意味します($item が配列の場合、$item['categoryId'] が使用されます):

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

Latte ではフィルタを関数としても使用できるため、代替構文 {foreach group($items, categoryId) ...} が得られます。

より複雑な基準で項目をグループ化したい場合は、フィルタのパラメータに関数を使用できます。例えば、名前の長さで項目をグループ化すると次のようになります:

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

$categoryItems は通常の配列ではなく、イテレータのように動作するオブジェクトであることに注意することが重要です。グループの最初の項目にアクセスするには、first() 関数を使用できます。

このデータのグループ化における柔軟性により、group は Latte テンプレートでデータを表示するための非常に便利なツールになります。

ネストされたループ

個々の項目のサブカテゴリを定義する別の列 subcategoryId を持つデータベーステーブルがあるとします。各メインカテゴリを個別の <ul> リストに表示し、各サブカテゴリを個別のネストされた <ol> リストに表示したいとします:

{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 との連携

Nette Database と組み合わせてデータのグループ化を効果的に活用する方法を示しましょう。冒頭の例の items テーブルを扱っていると仮定します。これは、列 categoryId を介して次の categories テーブルに接続されています:

categoryId name
1 Fruits
2 Languages
3 Colors

items テーブルからのデータは、Nette Database Explorer コマンド $items = $db->table('items') を使用して読み込みます。これらのデータを反復処理する間、$item->name$item->categoryId などの属性にアクセスできるだけでなく、categories テーブルとの接続のおかげで、$item->category を介して関連する行にもアクセスできます。この接続で興味深い使用法を示すことができます:

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

この場合、|group フィルタを使用して、列 categoryId だけでなく、接続された行 $item->category に基づいてグループ化します。これにより、キー変数に直接そのカテゴリの ActiveRow が含まれるため、{$category->name} を使用してその名前を直接出力できます。これは、グループ化がテンプレートをどのように明確にし、データの操作を容易にするかを示す実用的な例です。

フィルタ |batch

フィルタを使用すると、要素のリストを事前に決定された数の要素を持つグループに分割できます。このフィルタは、例えば、ページのより良い明瞭さや視覚的な配置のために、データを複数の小さなグループで表示したい場合に理想的です。

項目のリストがあり、それぞれ最大 3 つの項目を含むリストで表示したいとします。このような場合、|batch フィルタの使用は非常に実用的です:

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

この例では、リスト $items は小さなグループに分割され、各グループ($batch)には最大 3 つの項目が含まれます。各グループはその後、個別の <ul> リストに表示されます。

最後のグループに必要な数の要素が含まれていない場合、フィルタの 2 番目のパラメータを使用すると、このグループを何で補完するかを定義できます。これは、不完全な行が乱雑に見える可能性がある場合に、要素を美的に整列させるのに理想的です。

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

タグ {iterateWhile}

|group フィルタで解決したのと同じタスクを、{iterateWhile} タグを使用して示します。両方のアプローチの主な違いは、group が最初に入力データ全体を処理してグループ化するのに対し、{iterateWhile} は条件付きでループの進行を制御するため、反復が段階的に行われることです。

まず、iterateWhile を使用してカテゴリを持つテーブルを描画します:

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

{foreach} はループの外側の部分、つまり各カテゴリのリストの描画を示し、{iterateWhile} タグは内側の部分、つまり個々の項目を示します。 終了タグの条件は、現在および次の要素が同じカテゴリに属する限り($iterator->nextValue次の項目です)、繰り返しが続くことを示しています。

条件が常に満たされる場合、内側のループですべての要素が描画されます:

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

結果は次のようになります:

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

このような iterateWhile の使用は何に役立ちますか?テーブルが空で要素が含まれていない場合、空の <ul></ul> は出力されません。

開始タグ {iterateWhile} に条件を指定すると、動作が変わります:条件(および次の要素への移行)は、終了時ではなく、内側のループの開始時に実行されます。 したがって、条件なしの {iterateWhile} には常にエントリしますが、{iterateWhile $cond} には条件 $cond が満たされた場合にのみエントリします。同時に、次の要素が $item に書き込まれます。

これは、例えば、各カテゴリの最初の要素を異なる方法で描画したい場合に便利です。例えば、次のように:

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

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

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

元のコードを修正して、最初に最初の項目を描画し、次に内側のループ {iterateWhile} で同じカテゴリの他の項目を描画します:

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

1 つのループ内で、複数の内側のループを作成し、それらをネストすることもできます。このようにして、サブカテゴリなどをグループ化できます。

テーブルに別の列 subcategoryId があり、各カテゴリが個別の <ul> にあるだけでなく、各サブカテゴリが個別の <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}
バージョン: 3.0