概要
Eagerローディングについて整理しつつ、動的プロパティが何をしているのか追ってみた。
Eagerローディングについて振り返る
Eagerローディング
N+1クエリ問題の解決法として使われる。
-
1+ループ回数分のクエリが発生する
$articles = Article::all(); foreach ($articles as $article) { $comment = $article->comment; }
-
2つのクエリで済む
$articles = Article::with('comment')->get(); foreach ($articles as $article) { $comment = $article->comment; }
取得するリレーション先に対して、制約をかけることも出来る。
$articles = Article::with([
'tags' => function ($query) {
$query->where('slug', 'like', '%business%');
}
])->find(1);
リレーション先のデータの取得
Eagerロード実行後はクエリビルダを用いてリレーション先データを取得することが出来ない。
例として、制約をかけたEagerロード済みのクエリを、クエリビルダと動的プロパティで取得した結果を dd()
で確認して比較する。
$articles = Article::with([
'tags' => function ($query) {
$query->where('slug', 'like', '%business%');
}
])->find(1);
dd([$articles->tags, $articles->tags()->get()]);
クエリビルダで取得すると制約がかかっていない(Eagerロードされていない)データが取れる。
array:2 [▼
0 => Illuminate\Database\Eloquent\Collection {#383 ▼
#items: array:2 [▶]
}
1 => Illuminate\Database\Eloquent\Collection {#367 ▼
#items: array:4 [▶]
}
]
さらに制約をかけたい時
動的プロパティで取得するとコレクションとして返ってくるので、コレクションの操作で制約をかける。
$tags = $articles->tags;
$tags = $tags->filter(function ($tag) {
return $tag->id > 2;
});
なぜ動的プロパティでしか取得出来ないのか
動的プロパティについての公式ドキュメントの記述
動的プロパティは「遅延ロード」されます。つまり実際にアクセスされた時にだけそのリレーションのデータはロードされます。そのため開発者は多くの場合にEagerローディングを使い、モデルをロードした後にアクセスするリレーションを前もってロードしておきます。Eagerロードはモデルのリレーションをロードするため実行されるSQLクエリを大幅に減らしてくれます。
Laravel 6.x Eloquent:リレーション
クエリビルダで取得する時
belongsToMany()
について追ってみたところ BelongsToMany
をnewしていた。
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function tags()
{
return $this->belongsToMany('App\Models\Tag');
}
動的プロパティでデータを取得する時
呼び出したプロパティが既にロードされているリレーションであれば、新しくロードしない。
つまり with
で登録された(ロードされた)リレーションシップであるのならばそれを使う。
/**
* Get a relationship.
*
* @param string $key
* @return mixed
*/
public function getRelationValue($key)
{
// If the key already exists in the relationships array, it just means the
// relationship has already been loaded, so we'll just return it out of
// here because there is no need to query within the relations twice.
if ($this->relationLoaded($key)) {
return $this->relations[$key];
}
// If the "attribute" exists as a method on the model, we will just assume
// it is a relationship and will load and return results from the query
// and hydrate the relationship's value on the "relationships" array.
if (method_exists($this, $key)) {
return $this->getRelationshipFromMethod($key);
}
}
If the key already exists in the relationships array, it just means the relationship has already been loaded, so we'll just return it out of here because there is no need to query within the relations twice.
プロパティ名がリレーションシップ配列に存在する場合、既にロードされているということであり、2度リレーションシップを実行する必要がないのでここから返す。
そのため動的プロパティで呼び出す必要がある。
おわり
コードを追ってみることで、動的プロパティが実際何をしているのか知れてよかった。