25
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

CakePHP3のcontainを操る

Posted at

CakePHP3でデータベースを引くときに、他のテーブルを取り込めるcontainという機能があります。単にデータを取ってくるだけでなく、更に細かい制御も行えます。

containの必要性

CakePHP3ではモデルクラスもTableとEntityに分かれたことで、1レコードがEntityオブジェクトとなったのですが、その結果発生しうるのがN+1問題です。Entityの中から関連づいたテーブルを参照しようとすると、それぞれのEntityごとにクエリが飛ぶため、クエリの数が膨れ上がってしまうのです。

これを防ぐために、データベースから関連するレコードを一気に引いてしまう、という手法があって、以下のどちらかで実装されています。

  • 関連する別テーブルとJOINすることで、まとめてデータを引く
  • 関連付けするキーを使って別のテーブルを引いて、あとから合成する

CakePHP3で、このような「まとめて引く」ための操作を行うのがcontainです。

containの制御の必要性

モデルを内部的に使う場合にしても、不要な列まで取ってくる必要はありません。そしてさらに、Crudプラグインを使う場合、引いてからデータを加工するというのが面倒になるので、クエリ段階で調節できればそれが最適解となります。

具体的な書き方

基本編

まず、containメソッドはクエリオブジェクトにあるものなので、$someTable->find()->contain()のように呼び出します。containの引数には配列を与えますが、普通の配列と連想配列が混ざったような、慣れるまでは不思議な形で与えます。

  • 'OtherItems'のようなキーだけ→そのテーブルをcontainする
  • 'AnotherItems' => ['YetOtherItem']のような「キー => 配列」の指定→キーのテーブルへ、さらに配列の中身をcontainする(このネストは何度でも繰り返せます)

列を絞る

そして、containに与える配列に'テーブル名' => function($q){}のようなクロージャを与えると、$qにはcontain先のテーブルについてのクエリオブジェクトが来ますので、ここで各種の制御ができます。

たとえば、「Usersで必要な列はidnameだけ」というような場合、以下のように書けばそうとれます。

$this->Items->find()
    ->contain(['Users' => function($q){
        return $q->select(['id', 'name']);
    }]);

なお、列を絞るときには注意点が2つあります。

  • リレーションに必要な列は必ず入れてください(そうしないとリレーションを成立させられなくなります)。
  • Virtual Propertyを仕掛けてある場合など、Entity側でコードを書いている場合、「列が減った状態」でもエラーにならないか確認の必要があります。

また、このようにコールバックを取ったcontainに別なリレーションをcontainしたい場合は、$q->contain()というようにチェーンさせてください。

Virtual Propertyの制御

「Crudに使うときには不要」ということでVirtual Propertyを止めたいこともあるかもしれませんが、これを行うときはEntityオブジェクト自体を書き換える必要が出てきます。ということで、formatResults()という関数の出番です。ここでは、ProductにあるVirtual Propertyをを全部止める、という例で考えてみます。

$this->Transactions->find()
    ->contain(['Products'=> function($q){
        return $q->formatResults(function($products){
            return $products->map(function($product){
                $product->virtualProperties([]);
                return $product;
            });
        });
    }]);

ご覧のとおり、これを行うだけで3重のクロージャが必要となってしまいます。ただ、「全部止める」だけであればクロージャを使いまわせますので、何かのヘルパーで生成して流用、というのが現実的ではないかと思います。

25
24
1

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
25
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?