エレメントの Eager-Loading

テンプレートがエレメントのリストをループしていて、それらのエレメントそれぞれで1つ以上のサブエレメントを表示しなければならない場合、ページのパフォーマンスが低下する高い可能性があります。

例えば、ここにエントリのリストをループし、それぞれのエントリに関連付けられた画像を表示するテンプレートがあります。

{% set entries = craft.entries()
    .section('news')
    .all() %}

{% for entry in entries %}
    {# Get the related asset, if there is one #}
    {% set image = entry.assetsField.one() %}
    {% if image %}
        <img src="{{ image.url }}" alt="{{ image.title }}">
    {% endif %}
{% endfor %}

これは N+1 クエリの問題を示しています。エントリを取り出すために実行されたクエリの他に、関連付けられたアセットを探すために それぞれの エントリで追加のクエリが実行されています。そのため、必要なクエリの総数は N(エントリ数)+ 1(最初にエントリを取得するクエリ)となります。50エントリがある場合、この問題なさそうに見えるテンプレートコードは、そのページのために51クエリ分のコストがかかります。

しかし、すべての希望が失われたわけではありません。判定基準のパラメータ with を利用した eager-loading によって、これを解決することができます。

with パラメータの目的は、必要とするサブエレメントを事前に Craft へ伝えることで、可能な限り少ないクエリですべてのエレメントを前もって取得できるようにすることです。

先の例に with パラメータを適用する方法は、次の通りです。

{% set entries = craft.entries()
    .section('news')
    .with(['assetsField'])
    .all() %}

{% for entry in entries %}
    {# Get the eager-loaded asset, if there is one #}
    {% set image = entry.assetsField[0] ?? null %}
    {% if image %}
        <img src="{{ image.url }}" alt="{{ image.title }}">
    {% endif %}
{% endfor %}

このレンプレートコードは、たった3クエリしかコストがかかりません。エントリを取り出すための1つ、どのアセットを eager-loaded すべきか決定するための1つ、そして、アセットを取り出すための1つです。そして、エントリにはそれぞれに関連付けられたアセットのデータが自動的に入ります。

Eager-Loaded エレメントへのアクセス

eager-loaded エレメントへのアクセスは、lazy-loaded エレメントへのアクセスとは少し異なる働きをします。

with パラメータを適用する前後の例で、変数 image をどのように割り当てているかを見てみましょう。

{# Before: #}
{% set image = entry.assetsField.one() %}

{# After: #}
{% set image = entry.assetsField[0] ?? null %}

アセットを eager-loaded していない 場合、entry.assetsField は関連付けられたアセットを返すよう事前設定されたアセットクエリを提供します。

しかしながら、アセットを eager-loaded している 場合、entry.assetsField は eager-loaded されたアセットの配列で上書きされます。そのため、one()all()、またはその他のエレメントクエリのメソッドを利用することができません。代わりに、あなたは標準の配列構文に耐えなければなりません。この例では、最初のアセットを entry.assetsField[0] で取得しています。そして、関連付けられたアセットがない場合のデフォルト値(null)を定義するため、Twig の null 合体演算子 (??) を使っています。

エレメントの複数セットの Eager-Loading

エレメントの最上位のリストで eager-load しておきたいエレメントの複数セットがある場合は、with パラメータに値を追加してください。

{% set entries = craft.entries
    .section('news')
    .with([
        'assetsField',
        'matrixField'
    ])
    .all() %}

{% for entry in entries %}
    {# Get the eager-loaded asset, if there is one #}
    {% set image = entry.assetsField[0] ?? null %}
    {% if image %}
        <img src="{{ image.url }}" alt="{{ image.title }}">
    {% endif %}

    {# Loop through any eager-loaded Matrix blocks #}
    {% for block in entry.matrixField %}
        {{ block.textField }}
    {% endfor %}
{% endfor %}

エレメントのネストされたセットの Eager-Loading

次の構文を利用して、エレメントの ネストされた セットを読み込むこともできます。

{% set entries = craft.entries()
    .section('news')
    .with([
        'entriesField.assetsField'
    ])
    .all() %}

{% for entry in entries %}
    {# Loop through any eager-loaded sub-entries #}
    {% for relatedEntry in entry.entriesField %}
        {# Get the eager-loaded asset, if there is one #}
        {% set image = relatedEntry.assetsField[0] ?? null %}
        {% if image %}
            <img src="{{ image.url }}" alt="{{ image.title }}">
        {% endif %}
    {% endfor %}
{% endfor %}

Eager-Loaded エレメントのカスタムパラメータを定義する

そのキーを(キー、および、適用されるべき基準パラメータを定義するオブジェクトを含む)2つの値を持つ配列に置き換えることによって、eager-loaded するときにエレメントへ適用されるカスタムの基準パラメータを定義できます。

{% set entries = craft.entries()
    .section('news')
    .with([
        ['assetsField', { kind: 'image' }]
    ])
    .all() %}

エレメントのネストされたセットで eager-loading する場合、eager-loading パスの任意のレベルでパラメータを適用することができます。

{% set entries = craft.entries()
    .section('news')
    .with([
        ['entriesField', { authorId: 5 }],
        ['entriesField.assetsField', { kind: 'image' }]
    ])
    .all() %}

行列ブロックに関連するエレメントの Eager-Loading

行列ブロックから関連フィールドを eager-loading するための構文は、他のコンテキストと少し異なります。関連フィールドのハンドルの前に、ブロックタイプのハンドルを付ける必要があります。

{% set blocks = entry.matrixField
    .with(['blockType:assetsField'])
    .all() %}

この理由は、異なるブロックタイプである限り、行列フィールドでは同じハンドルを共有する複数のサブフィールドをそれぞれに持つことができるためです。eager-loading キーの一部にブロックタイプのハンドルを必要とすることで、行列が正しいエレメントを eager-loading していると確信できます。

これは行列ブロック自体が eager-loaded されている場合にも、当てはまります。

{% set entries = craft.entries()
    .section('news')
    .with(['matrixField.blockType:assetsField'])
    .all() %}

イメージ変換インデックスの Eager-Loading

アセットをセットしたループでそれぞれに画像の変形を適用する場合、別の N+1 問題が発生します。 それぞれのトランスフォームに対して、Craft がすでに変換された画像が存在するか確認するためにクエリを実行する必要があります。

この問題は、アセット基準パラメータの withTransforms で解決することができます。

{% set assets = entry.assetsField
    .withTransforms([
        'heroImage',
        { width: 100, height: 100 }
    ])
    .all() %}

eager-load したいそれぞれのトランスフォームの定義は、文字列(「設定 > アセット > 画像の変形」で定義されたトランスフォームのハンドル)、 または、トランスフォームプロパティを定義したオブジェクトのいずれかです。

withTransforms パラメータを使っても、テンプレート内の変換された画像にアクセスする方法には影響を与えません。

イメージ変換インデックスは、eager-loaded されるアセットにも eager-loaded できます。

{% set entries = craft.entries()
    .with([
        ['assetsField', {
            withTransforms: ['heroImage']
        }]
    ])
    .all() %}