Help us understand the problem. What is going on with this article?

CakePHP3のcontainを操る

More than 3 years have passed since last update.

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重のクロージャが必要となってしまいます。ただ、「全部止める」だけであればクロージャを使いまわせますので、何かのヘルパーで生成して流用、というのが現実的ではないかと思います。

jkr_2255
qiitadon
Qiitadon(β)から生まれた Qiita ユーザー・コミュニティです。
https://qiitadon.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした