LoginSignup
20
15

More than 5 years have passed since last update.

【cakephp3】hasMany, belongsToManyアソシエーション先の 取得フィールド指定の方法

Last updated at Posted at 2016-03-02

CAKEPHP3 では,アソシエーションの種類により,取得するアソシエーション先のフィールドの指定方法が異なる.

belongsTo と hasOne (つまり JOIN にて結合するもの)では Query::select() にてアソシエーション先のフィールドを指定できるのだけれど,hasMany と belongsToMany ではそれができなかったので調べてみた.

まとめ

  • finderを使うといい.
  • 設定方法はとりあえず二つ見つけた(以下参照)
  • フィールドには外部キーを含まなければならない (CAKEPHP v3.2.3)
    • SQLログ上ではアソシエーション先のレコードが取得できているのに,Entity上では存在しない,というような現象が発生した.

方法1:アソシエーションの設定時にfinderを指定

アソシエーション先のテーブルクラスにて,finderメソッドを定義する.
定義したfinderで返すQueryに,Query::select()にて取得するフィールドを指定する.

FugasTable.php
class FugasTable extends Table
{
    // 'sample' finder
    public function findSample(Query $query, array $options) {
        return $query->select(['id', 'fuga_id', 'content']); // ここで設定
    }
}

そしてアソシエーションを設定する際に,オプション 'finder' を設定する.
設定値は,先ほど定義した sampleFinder を利用して欲しいので 'sample'とする.

HogesTable.php
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 の指定はしていないものとする.

HogesController.php
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()のどこかにてこのフィルタリングが行われているらしいところまではわかったけれどここまで.
実際にどの処理が原因なのか,またこの挙動は仕様なのかなどはわかりませんでした.(英語苦手)
フィルタリングは仕様としても,フィールドリストに外部キーが自動で組み込まれるとか例外投げるとかした方がいいような.

20
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
15