[CakePHP]model::find()メソッドの先にはお花畑が広がっていました

あっはっは〜つかまえてごらんボルジョワフゥ〜

ソースコードを読みます
難しい。非常にむつかしい。これはわからない。findっていうのはタイプが多くて、その上オプションもありよく使い方がみえない。というわけで読んで見たけど、やっぱりわからない。
マニュアル
http://book.cakephp.org/ja/view/73/Retrieving-Your-Data
ソースコード
http://api.cakephp.org/view_source/model/#line-1935

<?php
function find($conditions = null, $fields = array(), $order = null, $recursive = null) {
if (!is_string($conditions) || (is_string($conditions) && !array_key_exists($conditions, $this->_findMethods))) {
$type = 'first';
$query = array_merge(compact('conditions', 'fields', 'order', 'recursive'), array('limit' => 1));
} else {
list($type, $query) = array($conditions, $fields);
}

最初から気になった部分。メソッドが、
function find($conditions = null, $fields = array(), $order = null, $recursive = null)
ってなってるけど、マニュアルでは、
function find($type, $params)
となっている。少し悩んだけど$fieldsの引数が、廃止となったfindAllとかの書き方を対応してのことかなと思われ。

実際の使い方はソースコードにサンプルあったけど、

Eg: find('all', array(
'conditions' => array('name' => 'Thomas Anderson'),
'fields' => array('name', 'email'),
'order' => 'field3 DESC',
'recursive' => 2,
'group' => 'type'));

こんな感じでやるのが今のfindの正しい使い方。findメソッドの引数が4つあるのは気にせず、
find($type, $params)
の方で利用したほうがいいでしょう。$typeは、all / first / count / neighbors / list / threadedから。$paramsは、マニュアルで確認。

引数部分が長くなったけど次のif。||はor、&&はand、!はnotの意味と一緒。優先順位は忘れたけどたぶん記号が上。
で、$conditionsが文字列でないか、または文字列であるか、かつ、$conditionsが_findMethods配列のうちのキーとしてなければture、というもの。言葉にしにくいというか、ちょっと複雑な言い方になっちゃいましたね。
_findMethodsはこれ。

<?php
var $_findMethods = array(
'all' => true, 'first' => true, 'count' => true,
'neighbors' => true, 'list' => true, 'threaded' => true
);

簡単にいうと、$conditonsが(all / first / count / neighbors / list / threaded)のうちのどれにもあてはまらないならtureで、あてはまったらfalseになるってこと。!でnotを使ってる部分に注意。いつも思うけど見づらいよねビックリマーク。
array_key_existsはその文字列が、配列のキーとして存在するかどうか調べるといったもの。

tureは第1引数にallやlistが無いときの処理。find($type, $params)でのfind利用をする場合tureとなりませんので無視すればおっけ。
でも一応みときますと、tureの場合$typeはfirstになります。マニュアルでもデフォがfirstとかいてますしね。$queryにはそれぞれの引数を、compactで配列にして、さらにarray_mergeで配列と配列をマージしてます。配列が合体する、というイメージ。
falseの場合。 
list関数使ってます。これよくわからないんだよなあ。結局$conditionsが$typeになり、$fieldsが$queryになります。
次。

<?php
$db =& ConnectionManager::getDataSource($this->useDbConfig);
$this->findQueryType = $type;
$this->id = $this->getID();

ConnectionManager::getDataSource()メソッドなんですが、このへんはものすごくややこしかった。よくでてきますねこれ。データベースのデータを取得しているんだと思いますが。

みてみたけど複雑だたーよ。mysqlやpostgresを選別している部分もあったけどよくわからなかった。で、findQueryTypeには、$typeが代入されてますね。これはallとかlistとかあれ。
それからgetIDですが。コードみたところ現在のidを取得しているってことかな。配列であるならキーをのぞいている。
idってのはたぶんプライマリキー。これはユニークなキーですよね。ちょっとうろ覚えなんですが。ユニークなキーであるからして、それぞれの行をひとまとまりとして判断できる、という感じでしょうか。
次。

<?php
$query = array_merge(
array(
'conditions' => null, 'fields' => null, 'joins' => array(), 'limit' => null,
'offset' => null, 'order' => null, 'page' => null, 'group' => null, 'callbacks' => true
),
(array)$query
);

これは見たまんんまですね。array_maerge関数は配列の合体というか、上書きというイメージでもいいでしょう。$queryはもともとconditionsやfileldsなどをキーとした値が入っていて、それをマージでnullの配列に上書きという感じ。型キャストは念のためかな?

<?php
$query = array('conditions' => 'hoge', 'fields' => 'fuga');

$query = array_merge(
array(
'conditions' => null, 'fields' => null, 'joins' => array(), 'limit' => null,
'offset' => null, 'order' => null, 'page' => null, 'group' => null, 'callbacks' => true
),
(array)$query
);

//dump
var_dump($query);

で挙動がよくわかります。
次。

<?php
if ($type != 'all') {
if ($this->_findMethods[$type] === true) {
$query = $this->{'_find' . ucfirst($type)}('before', $query);
}
}

条件式は、2つもがっつくと、もし$typeがallでなく、そのうえもしfindMethodsのキー$typeがtureなら、という具合。だからall以外のfirst / count / neighbors / list / threadedであったら、ということでしょうね。
tureの場合、$queryにはそれぞれのメソッドの結果が入るというわけなんですが、さすがにそれを読むのはめんどくさいというわけでスルー。

$query = $this->{'_find' . ucfirst($type)}('before', $query);
ここでタイプ別のメソッドを実行することになります。たとえば$typeがlistだったとすれば、
$query = $this->_findList('before', $query);
ってなるようにできてますね。それからfindメソッドより下のソースをみてみてください。引数のbeforeってのが結構重要で、afterも後から出てきます。
それぞれのメソッドでbeforeとafterが設定されていますが。ようは前処理と後処理、ってことだと思います。でもやってることがよくわかんないのだわ。

たぶんそれぞれのタイプ別に$queryをいじったり、場合によってはそっちでデータを取得したりしている、とみえなくもないんだけど。何のためなのかわからない。
ここから先は$queryの前処理された場合になりますので複雑ですね。タイプがallの場合はそのまんまです。とりあえず次。

<?php
if (!is_numeric($query['page']) || intval($query['page']) < 1) {
$query['page'] = 1;
}
if ($query['page'] > 1 && !empty($query['limit'])) {
$query['offset'] = ($query['page'] - 1) * $query['limit'];
}
if ($query['order'] === null && $this->order !== null) {
$query['order'] = $this->order;
}
$query['order'] = array($query['order']);

$query['page']が数字じゃないか、または1以下なら、$query['page']に1を代入。
$query['page']が1より大きく、かつ$query['limit']が空でなければ。詳細めんどいのでtrue実行。
$query['order']がnullで、かつorderがnullじゃなければtrue。orderはクラス変数。
そして最後は二次元配列。
結局やっていることはわかっても、何のために何をやってるのかがわかりませぬ。そのためにはもっといろいろのぞかなければいけないけど、気力がなくなってきた。
ので次。

<?php
if ($query['callbacks'] === true || $query['callbacks'] === 'before') {
$return = $this->Behaviors->trigger($this, 'beforeFind', array($query), array(
'break' => true, 'breakOn' => false, 'modParams' => true
));
$query = (is_array($return)) ? $return : $query;

if ($return === false) {
return null;
}

$return = $this->beforeFind($query);
$query = (is_array($return)) ? $return : $query;

if ($return === false) {
return null;
}
}

わかりにくいと思ったのでインデントつけてます。
最初の条件式は$query['callbacks']がtrue、またはbeforeならtrue。このコールバックの値は、true、false、before、afterが入るのみのようです。おそらくtrueならbeforeとafterのコールバック実行するんじゃないかな。falseは何もしなくて、あとはbeforeのみかafterのみかということに。
trueの場合はこのメソッドで一番長いコード内容になってます。
まずBehaviors::trigger()メソッドつかってますね。結果を$returnに代入しています。でも何をやっているのかはわからないです。
その後の三項演算子部分。$returnが配列だったら、$returnを$queryに代入。配列でなければ、$queryをそのまま使う、という感じ。$returnというのは、先ほどのBehaviors::trigger()メソッドの結果を代入した変数。
trigger()メソッドの説明は、

Dispatches a behavior callback on all attached behavior objects

となってます。「すべてのビヘイビアオブジェクトに結びついたビエイビアコールバックを送り出すよー」的な訳でいいでしょうか。
しかしまー、内容がイメージできないわ。ごめんね保留。

で、次のifは$returnがfalseならnull返して、終り。find失敗ってことかな。
そしてbeforeFindメソッドを使って$returnに代入している部分。
それでbeforeFindなんですが、たぶんappのモデル部分でユーザが定義したメソッドを使っていることになりますね。model.phpには、

<?php
function beforeFind($queryData) {
return true;

しかないので、基本はappのモデル部分でオーバーライドされたbeforeFindメソッドの結果を返している、というのことでしょうか。何もモデルに定義してなかったら単にtrueを返すだけですが。
その後また$returnが配列かどうかチェックしてますね。そしてfalseだったらまたnullを返すと。

この辺はちょっとわかんないです。trigger()メソッドが何やってるのかわかればよかったんですが、ちょっときついなー。長くなるし。
というわけで次といきたいところだが。

<?php
$results = $db->read($this, $query);
$this->resetAssociations();
$this->findQueryType = null;

if ($query['callbacks'] === true || $query['callbacks'] === 'after') {
$results = $this->__filterResults($results);
}

if ($type === 'all') {
return $results;
} else {
if ($this->_findMethods[$type] === true) {
return $this->{'_find' . ucfirst($type)}('after', $query, $results);
}
}
}

挫折。ここは内容は理解できるけど、やってることが理解できない。そもそも途中からわかっていないよね。とりあえず時間おこう。

わからなかった部分
find($type, $params)の$paramsに使うそれぞれのキーの意味がわからない
http://book.cakephp.org/ja/view/73/Retrieving-Your-Data
にサンプルがあるとはいえ、よくわからない。これは大きい。pageは?limitは?offset?ここがよくわからないから何やってるかあやふやだった。主に使うキーって何なんだろう?
今思うとapiもしっかりみとけばなとは思うけどわからんなやっぱ。
http://api.cakephp.org/class/model#method-Modelfind

どこでデータを検索しているのかわからない
その$paramsのキーをもとにあれこれしてると思うんだけど、そのあれこれがわからない。firstにしてもcountにしてもlistにしても、それがいまいちつかめなかった。データベースのデータ取得は、

$db =& ConnectionManager::getDataSource($this->useDbConfig);

ここだと思うんですよね。それともこれって接続しただけ?mysql_connect()しただけでしょうか。みた限りではmysql_select_dbまでいってるんじゃないかって思ったけど。もしかして間違えたか。
それでとにかく検索です。私はmysql_query()しかしらないんですが、そういう系のあったかな。もしかしたら、

$results = $db->read($this, $query);

ここじゃないかなとも思ったけど、model::read()みても何もないんですよね。場所が違うンでしょうか?
それと$db->read()って何でしょう?$db->query()とかも使えるみたいだけど、なんで->でアクセスできるのですか。$db->nameなんてのもあった。

どこに影響しているのかわからないコード

$this->findQueryType = $type;
$this->id = $this->getID();

他にもあったのですが。無駄な部分はないと思うので他の部分で影響しているのだと思うのですが、どこか検討がつかない。

このようにわからないことが多く、これらの影響が大きかったと思います。

終り
難しく本当に見ただけだった。たぶん間違いだらけ。なんとかfindを使えるようになりたくてみてみたけど、最低query使ってる部分がわからないというのは痛い。目の前にあるのに、肝心の部分が見えない。
それからキーのconditionsやpageの影響もわからないし。んー、何かのcakeサンプルでデータ入れまくってfindしてみるのが良いのかな。とにもかくにも疲れた。
とりあえず変なところあったら教えてください。