諸事情あって、バタバタしているyandoです。
18時過ぎに自分の番である事に気がついてしまいましたが、この記事はCakePHP アドベントカレンダーの9日目です。
CakePHP3で一新されたORMは「結果が配列からオブジェクトになった」というだけではない違いがあります。
それが Eager loading と Lazy loading です。この概念を理解していないとORMの機能を間違って使ってしまうかもしれません。
何が起きるの? N+1問題
ORMからクエリを実行した時にJOINを使ったクエリを実行するか、シンプルなクエリを実行するかのルールが分かりますか?
従来のCakePHPではJOINの条件などに応じて自動的に決定されており、関連データを取得するためのクエリが大量に実行される場合がありました。たとえば画面に表示している20件のデータを取得するクエリを実行し、その後に20件のデータに紐づく関連データを取得するクエリが20回実行されるという形です。これを N+1 問題と呼びます。
Eager Loding
Eager LoadingはJOINを使って可能な限り、一度のクエリで関連データの取得まで一括で行います。
これによりクエリの実行回数は最小になりますが、扱うデータが多い場合はクエリが複雑になる場合があります。
実際のデータの構造によってはこのアプローチを離れる必要があります。
コントローラーからORMを使う場合などは基本的にEager Loadingとなります。
従来のCakePHPと同じ感覚で使うと、より複雑なクエリが少ない回数実行されることになります。
Lazy Loading
Lazy LoadingはJOINを使わずにシンプルなクエリを実行します。画面に表示した20件のデータのうち、何らかのカテゴリのデータについては、さらに別テーブルのデータを参照するというような場合です。
CakePHP3ではエンティティクラスからクエリを実行する形にすることで必要な時に後からクエリを実行する事ができるようになりました。
Lazy loadingはORMの機能としてまだ実装されていませんが、エンティティクラスにメソッドを追加する事で実現できるようになっています。
namespace App\Model\Entity;
use Cake\ORM\Entity;
use Cake\ORM\TableRegistry;
class Article extends Entity {
protected function _getComments() {
$comments = TableRegistry::get('Comments');
return $comments->find('all')
->where(['article_id' => $this->id])
->all();
}
}
上記のようなメソッドをエンティティに作成しておくと、アクセサ経由で下記のように暗黙的に実行できます。
$article = $this->Articles->findById($id);
foreach ($article->comments as $comment) {
echo $comment->body;
}