ようこそゲストさん

CPA-LABテクニカル

メッセージ欄

分類 【recursive-Cake1.2】 で検索

一覧で表示する

2008/02/15(金) recursiveの隠しオプション

ここでは、recursiveが、多くの人が思うような動作にするための隠しオプションをご紹介する。recursive はややこしい。かなりの人が混乱しているように思う。
ただし、お断りしておくと、このオプションは実は隠されているのでなく、複雑なテーブル間連携を別途記述するためのオプションを流用する、というだけの話です。
できれば、recursiveを正しく理解するを読んでいただいたほうがいいかと思います。

本当は違うけど多くのCakePHP初学者が思い込むパラメータ
recursiv=0 とすると、アソシエーションしているデータはとってこない。
という動作にするにはどうするか。

結論  CakePHP 1.2 ですよ!

まず、結論。
モデルのアソシエーションの記述に以下のように external  を付け加えてください。
他のオプションを設定している方は、それは消去せず、externalオプションだけを付け加えてください。
ただし、決して、 finderQuery オプションは設定してはなりません。


(例)
class Spok extends AppModel{

var $belongsTo = array( 'Kirk' => array( 'extenrl' => true ));

}

すると、recursive=0 の場合、アソシエーションはとってきません。このオプションを設定しない場合の recursive= -1 と同じになります。
もちろん、recursive= 1 にすれば、第1次アソシエーションを取得します。recursive= 2 にすれば、第2次アソシエーションも取得します(以下同様)。

注意!

  1. こうすると、単純な hasOne,belongsTo の連携の場合も、複数回のSQL文を発行します。つまり、パフォーマンスは(かなり)悪くなります。
  2. external = false としても、true と同じ動作をします。このオプションを生かさない場合は、 null または、設定しない、としてください。
  3. この使い方は、externalオプションの本来の使い方ではありません。将来のバージョンアップ時の互換性についてはわかりません。
このオプションは、finderQueryオプションを設定するものです。これは、副問い合わせのサブクエリーをここに記述して、external=trueにしておくと、このサブクエリーを使って、アソシエートする、ということのために用意されているオプションと考えられます。
参考事例
'Virtual' associations Options -googleディスカッション-CakePHP  もうひとつの例-googleディスカッション

図で表すと。。。

クリックすると拡大されます。
recursiveを正しく理解するの図と比べるといいかと。


CakePHP_recursive_attention2.jpg

この件のヒント


すべては、dbo_source.php 内の function read にある。
$external = isset($assocData['external']);
でした。
なんで、こんなことやってんのやろ?と思ったのがすべての始まり。

現実的には


現実的には、このような方法をとるのでなく、正しくrecursiveを理解するのがよいでしょう。データ構造によっては、なんせ、発行するSQL文の量がはんぱじゃないですから。&本来の使い方じゃないですから。
recursiveの正しい使い方は -> recursiveを正しく理解する

2008/02/14(木) recursiveの正しい理解CakePHP

CakePHPは、データベースのモデルのテーブル設定において、他のテーブル(モデル)に対してアソシエーション(リレーション、連携)の設定をしておけば、当該カレントテーブルの情報を取得するときに、同時に、アソシエーション設定したデータもとってきてくれる。当該カレントモデルのfindAll,findをするだけで、他の連携をとってくれるありがたい機能です。
同時に、recursive というパラメータがあり、再帰的にこのアソシエーションを追っかけてくれる回数を制御するパラメータなのだけど、WEBで検索をすると、かなり混乱しているように見受けられた。(結果オーライの記事が多い)。
なので、ここで、recursiveを正しく使うための基礎知識を若干述べたいな~。
というか自分が理解するために調べたのを覚書として書いておくということだけど。
まず、結論は、図をみていただくのが一番早い。

figure 1(クリックで画面いっぱいに表示します。)
CakePHP_recursive_attention.jpg

結論


結論は図の通りです。

recursive取得可能な範囲
-1カレントテーブルのみ(単体)
0カレントテーブル+hasOne,belongsToの第1次アソシエーションテーブル
1カレントテーブル+すべてのの第1次アソシエーションテーブル
2上記 + subのsub,即ち第2次アソシエーションテーブル
3以上上記 + 第3(以上)次のアソシエーションテーブル*1

ATTENTTIONの部分を見てください。
ATTENTTION 1 にあるように、hasOneとbelongsToについては、recursive=0 であっても、アソシエーションを追跡します。


hasOne,belongsTo と hasMany は、recursive において異なる挙動をします。


つまり、
「recursive は、アソシエーションの階層を規定するものである」
という理解は間違っていることがわかります。
それが言えるのは、recursive が2以上の時であって、-1 ~ 1 の間は、特別に考えなくてはなりません。

しかし多くの人が間違えて理解しているようです。私自身も最初の頃は、ATTENTTION 1 → ATTENTTION 2 の位置にあるように思っており、大変とまどいました。

おそらくcakePHPのデータ構造に合わせるために、このような仕様にしたのだとは思うのですが、かなり戸惑いますよね。


実は、英語のマニュアルにも(例)が書かれています。
$recursive

This sets the number of levels you wish Cake to fetch associated model data in find() and findAll() operations.

Imagine you have Groups which have many Users which in turn have many Articles.

Model::recursive options $recursive = -1 No associated data is fetched.
$recursive = 0 Cake fetches Group data
$recursive = 1 Cake fetches a Group and its associated Users
$recursive = 2 Cake fetches a Group, its associated Users, and the Users'
associated Articles
CakePHP Manual-Model

若干誤解を生みそうな説明文にとれますが、私が英語に弱いため、正確なところはわかりません。ただ、上記の例は参考になるでしょう。


また、以下のサイトのように recursiv=0 には意味がない!ということにする理解もいいかも知れません。
アソシエーションさせたくない場合の設定方法


なんせ、CakePHP1.2のデフォルトも $this->recursive = 1  ですから。

背景


ソースを読むとわかるのですが、hasOneとbelongsToについては、SQL文にて LEFT JOINして、一度のSQL文の発行で、データを取ってきます。
hasManyとhasAndBelongsToManyについては、一度、カレントテーブルだけのSQL文を発行し、その結果の プライマリーキーを 次の 「in句」 に使用しています。つまり2回のSQL文を発行しています。

このことから、CakePHPは、いわゆる「副問い合わせ(サブクエリー)」については、(自動アソシエーションについては)サポートしていない、と理解できます。たぶんそれは、様々なデータベースに合わせるために仕方がないのでしょう(副問い合わせをサポートしていないデータベースもあるため)。
(追記)これも想像ですが、サポートしていないDBあるというよりも、たぶん、副問い合わせにすると、途中経過のテーブルの情報を抜き出しにくい、ということもあるのかなと思いました。


recursiveがどうしてこのような仕様になったのかは、私にはわかりません。多くの人が単純に考えるように 「recursive=0 で、単体テーブルだけが取得可能」 という仕様のほうが理解しやすいとは思うのですが。。。。。
確かに recursive=0 で、hasOneとbelongsTo を取得するのは、CakePHPの内部プログラムの処理としては、再帰的処理をせず、LEFT JOIN するだけなので、内部的な処理の仕方からの仕様となっていると思います。
内部的にはrecursive変数は、いったん、execute した結果のプライマリーキーを利用して、再帰的に、次のSQL文を作成する、というパラメータになっているのです。hasOne,belongsToは、最初のSQL文で、目的を達成してしまうので、recursive=0でも動作するようになっています。

この仕様の理由の想像
想像でしかないのですが、きっと初期の初期のCakePHPのDBOは、LEFT JOINさえ使わず、hasOne,belongsToにしても、副問い合わせで処理しようとしたのではないでしょうか。
別の理由として考えられるのは、hasOneとbelongsTo だけの追跡をしたい時には、recursive=0 を使えるということかも知れません。

まあ、とにかく
hasOne,belognsToの機能は、
  1. SQL文1回のLEFT JOIN
  2. SQL文1回の副問い合わせ
  3. SQL文2回の疑似副問い合わせ

のどれでも実現可能なので、ユーザーからみるとわからないですね。

ユーザー視点でみれば、recursive=0 で、hasOneを取ってくるのは、違和感はありますよね。
ただし、この記事を読んだ方は、recursiveについて理解したと思うので、今後は迷わないですよね。



以上のようなことを想像したのは実は、次のオプションを発見したからでした。

recursive の隠し?オプション


実は、アソシエーションの設定項目の中には、私や皆さんが誤解していたように、recursive=0で、単体テーブルしか取得しない、という動作をするオプションがあります。
recursive の隠しオプション
ただし、単純なhasOne,belongsToでも、再帰的にSQL文を発行しますので、たくさんのクエリーが投げられることとなり、基本的には速度の低下を招きますので、ご留意を。
また、このオプションの本来の使い方ではない方法です。詳しくは、上記記事をご参照ください。




留意点

自動取得の条件

figure 1 の図は、あくまでも


「取得可能なテーブルの範囲」


なので、取得する field名 に 当該subTableのフィールドを指定しない場合は、例え、hasOne,belogsToであっても、recursive=0 では取得できません。
ちゃんとsubTableのフィールド名を指定するか、もしくは、フィールド全部を取得する、という設定でないと自動的には取得できません。


もし、subTableのフィールド名を指定しない場合には、CakePHP の findAll で効率良く recursiveのような事態になってしまいます。
つまり、subTableのフィールド名を指定しない&&recursive=2以上の値を指定してしまうと、recursive=0 クラスにおいて、LEFT JOIN しない単体でのSQL文を発行したうえで、その結果のプライマリーキーをin句に代入して 再帰的(recursive) に次のSQL文を発行してしまうわけです。


したがって、指定しないフィールドを期待するのはよくない、ということです(当たり前ですが)。CakePHPの場合、recursiveの値によって、指定しないフィールドを取ってくるかどうかが変わってくるのですから、思わぬバグ?を生むことになりかねません。
必ず、フィールドを指定するという方針にしておけば、間違いは少なくなるでしょう。
ただし、フィールドを指定しない、即ち、すべてのフィールドを取得する、という設定も悪くはありません。むしろ、この方が、無駄なメモリを使ったとしても、より簡単に動作するプログラムが作れるかも知れません。というより、そうやって、楽するのが、フレームワークを使う利点ですよね。

hasAndBelongsToManyについて
ソースを読む限り、hasAndBelongsToManyについては、hasManyと同じ考え方でいけると思うのですが、テストしていないのでわかりません。
ちなみに、hasAndBelongsToManyって HABTM と言うのですね。知りませんでした。

セルフジョインについて
self-joinについてのロジックまでは追いかけませんでした。たぶん、同じ結論だとは思うのですが。。。。
セルフジョインって、階層的なメニューみたいなのをリレーショナルDBで実装するには必要なんですかね。

検証したバージョン

CakePHP version 1.2.0.6311 beta,SQLite 2

参考にさせていただいたサイト

なお、今回の記事を書くにあたっては以下のサイトを参考にさせていただきました。
CakePHPではまったこと17(belongsTo)
$recursive オプション設定方法
cakePHPのfindByにrecursiveを設定する方法
CakePHPのModelのわかりにくいところ
アソシエーションさせたくない場合の設定方法
hasAndBelongsToMany以下の配列
アソシエーション 関連モデルを読み込まないようにする 階層指定

*1 : 2と3は性質は同じです。わかりやすさのため分けてみました。

  • recursiveの隠しオプション CPA-LABテクニカル spok
    ここでは、recursiveが、多くの人が思うような動作にするための隠しオプションをご紹介する。recursive はややこしい。かなりの人が混乱しているように思う。ただし、お断りしておくと、このオプションは実は隠されているのでなく、複雑なテーブル間連携を...
  • [CakePHP]unbindModelでコンパクトなfindを心がける labolo
    Railsに影響を受けているcakePHPは当然、強力なDBの操作に関する機能を提供してくれている。簡単に言うと、DBの各テーブルの関連を記述しておけば、1つデータを引くだけでその関連データも一緒に提供してくれる。 この機能はとっても便利なんですが、その...
  • Dynamic Drawの品格 CPA-LABテクニカル spok
    知る人ぞ知る有名ソフトDynamic DrawDynamic Draw Professional図を書くときにいつも使わせてもらっています。とにかく操作が直感的なので、たいへん便利。「recursiveの正しい理解CakePHP」の図も、Dynamic D...
  • recursiveで悩むな、Containableを使え へびにっき
    CakePHPのModelが備える recursive 機能は分かりにくい上に効率が悪い。代わりにContainableビヘイビアを使うべき。今後新たに作り始めるならAppModelに次のように書いても良いくらい。class AppModel extend...
  • CakePHPでpaginateを使ってハマる 今感じていること
    そもそもこれまであまりpaginateを使ったことがなかったので、使用に至るまでにも時間食ってしまった。で、やっとこ動き始めたと思ったら。...