[CakePHP2]後からアソシエーションを制御する方法いろいろ

昨日は@chitokuさんの「CakePHP 2 のプラグインを Travis CI でテストする」でした。テスト大事ですね。

CakePHP2のアソシエーション

CakePHP2のfindはデフォルトで一つ先のアソシエーションまで取得する仕様になっています。そのため、何も考えずにアソシエーションを張りすぎると他テーブルへのjoinが多くなりクエリが重くなりがちです。

最近不要なhasManyアソシエーションが張られていたため大量のレコードを取得してしまいページの読み込みに問題が出ていた箇所の対応をしたため、後からアソシエーションを変更する方法についてまとめてみることにしました。

サンプル

projects というテーブルにあるデータを取得する場合を考えてみます。 projects はあらかじめ backersrewards に対するhasManyアソシエーションを持っているものとします。

app/Model/Project.php
<?php
class Project extends AppModel {
  public $hasMany = [
    'Backer',
    'Reward'
  ];
}
app/Model/Backer.php
<?php
class Backer extends AppModel {
  public $belongsTo = [
    'Project'
  ];
}
app/Model/Reward.php
<?php
class Reward extends AppModel {
  public $belongsTo = [
    'Project'
  ];
}

この状態で projects テーブルをfindすると以下のような結果が得られます。

$this->Project->find('all'); // 何も考えずにfind
result
(
  [Project] => Array
    (
      ...
    )
  [Backer] => Array
    (
      [0] => Array
        (
          ...
        )
      ...
    )
  [Reward] => Array
    (
      [0] => Array
        (
          ...
        )
      ...
    )
)

recursive

recursiveを使うとjoinする階層を制御することができます。CakePHP2ではデフォルトで一つ先までjoinが行われるため、そのテーブルのデータのみ取得したい場合は recursive => -1 とする良いでしょう。

// recursive => -1 で projects テーブルのデータのみ取得
$this->Project->find('all', ['recursive' => -1]);
result
(
  [Project] => Array
    (
      ...
    )
)

bindmodel/unbindmodel

たとえば、 backers は取得したいが rewards はいらないという場合、recursiveでは対応できません。そんな時はunbindmodelが便利です。

bindmodelやunbindmodelを使うと特定のモデルのアソシエーションをつけたり外したりすることができます。モデル単位で制御できるためrecursiveより細かな制御が可能です。

// あらかじめ Reward とのアソシエーションを外しておく
$this->Project->unbindmodel([
  'hasMany' => ['Reward']
]);
$this->Project->find('all');
result
(
  [Project] => Array
    (
      ...
    )
  [Backer] => Array
    (
      [0] => Array
        (
          ...
        )
      ...
    )
)

基本的にunbindmodelが有効なのは一度きりですが、アクション内で永続化させたい場合は第二引数にfalseを指定することができます。

// このアクション内では永続的に Reward とのアソシエーションを外す
$this->Project->unbindmodel([
  'hasMany' => ['Reward']
], false);
$this->Project->find('all');
...

contain

ContainableBehaviorを使うと必要なテーブルのアソシエーションのみ取得することができます。

新しいビヘイビアを使用する場合、モデルの $actsAs プロパティにビヘイビアの指定を追加します。AppModelに追加しておくと楽かもしれません。

app/Model/AppModel.php
<?php
class AppModel extends Model {
  public $actsAs = ['Containable'];
}

動的にビヘイビアを追加することも可能です。

this->Project->Behaviors->load('Containable');

以下はContainableBehaviorを使用した例になります。

// 取得するモデルを Backer に絞る
$this->Project->contain(['Backer']);
$this->Project->find('all');
result
(
  [Project] => Array
    (
      ...
    )
  [Backer] => Array
    (
      [0] => Array
        (
          ...
        )
      ...
    )
)

最後に

このようにCakePHP2では柔軟なアソシエーション制御が可能となっています。不必要なデータまで引っ張ってきてないか定期的に発行されているSQLを見直してみると良いかもしれません(自戒)。

明日は@tsyamaさんで「CakePHP3とjquery-uiのSortableでフォームをグリグリ動かす」です!

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.