CAKEPHP3 では,アソシエーションの種類により,取得するアソシエーション先のフィールドの指定方法が異なる.
belongsTo と hasOne (つまり JOIN にて結合するもの)では Query::select()
にてアソシエーション先のフィールドを指定できるのだけれど,hasMany と belongsToMany ではそれができなかったので調べてみた.
まとめ
- finderを使うといい.
- 設定方法はとりあえず二つ見つけた(以下参照)
- フィールドには外部キーを含まなければならない (CAKEPHP v3.2.3)
- SQLログ上ではアソシエーション先のレコードが取得できているのに,Entity上では存在しない,というような現象が発生した.
方法1:アソシエーションの設定時にfinder
を指定
アソシエーション先のテーブルクラスにて,finderメソッドを定義する.
定義したfinderで返すQuery
に,Query::select()
にて取得するフィールドを指定する.
class FugasTable extends Table
{
// 'sample' finder
public function findSample(Query $query, array $options) {
return $query->select(['id', 'fuga_id', 'content']); // ここで設定
}
}
そしてアソシエーションを設定する際に,オプション 'finder' を設定する.
設定値は,先ほど定義した sampleFinder
を利用して欲しいので 'sample'
とする.
class HogesTable extends Table
{
/*
* One hoges has many fugas.
*/
public function initialize(array $config)
{
$this->hasMany('Fugas', [
'finder' => 'sample', // finderを指定
]);
}
}
これにて,HogesのアソシエーションによりFugaエンティティを取得する際には,クエリがFuga::findSample()を経由するため,Query::select()
により取得フィールドを指定することができる.
方法2:Query::contain()
にて指定する
こちらでは,HogesTableにてFugasTableに対するアソシエーションの finder の指定はしていないものとする.
class HogesController extends AppController
{
public function index()
{
$query = $this->Hoges
->find()
->contain(['Fugas' => function($query){
return $query->select(['id', 'fuga_id', 'content']); // ここで設定
}]);
$hoges = $query->all();
}
}
これにて Fugaエンティティの取得フィールドが設定される.
なお,ここで FugasTableで定義したfinderを呼んでもいい.
// return $query->select(['id', 'fuga_id', 'content']); // ここで定義
return $query->find('sample'); // finderを呼び出す
注意: 外部キーは取得フィールドに含ませるべし(v3.2.3)
Cake\ORM\Association::__construct()
を見て,'finder'のオプション値があったことから,方法1はすぐに見つかったのだが,そのあとで思うように行かずに何時間か停まってた.
方法1にてreturn $query->select(['id', 'content']);
というようなことをしていたのだけれど,Fugaエンティティが取得できなかったのだ.
しかも,DebugKitのSQL Logを見ると,クエリは実行されて一つのレコードを獲得していることがわかった.
いろいろ試してみた結果,hasManyやbelongsToManyにて追加実行されるSQLの結果は,元のエンティティに格納される際に外部キーによりフィルタリングされるらしいことがわかった.
外部キーのフィールドを取得しないと,このフィルタリングで弾かれてしまう.
なので例えばfinderにて
$query->orWhere(['Fugas.id']);
などとしても,アソシエーション先の外部キーがこちらのバインディングキーと一致するレコードしか取得エンティティには含まれない.
原因箇所
おそらくCake\ORM\EagerLoader::LoadExternal()
のどこかにてこのフィルタリングが行われているらしいところまではわかったけれどここまで.
実際にどの処理が原因なのか,またこの挙動は仕様なのかなどはわかりませんでした.(英語苦手)
フィルタリングは仕様としても,フィールドリストに外部キーが自動で組み込まれるとか例外投げるとかした方がいいような.