こんにちは、初めて投稿します。
CakePHP経験は1ヶ月くらいの未熟者です。
現在開発中のPJでCakePHP1.2.0.7692 RC3を使用しています。
ちまたで1.2RCxは遅いと言われるのを耳にしたので、実際のところどうなのかを調べてみましたので参考までにレポートします。
今回はモデル関係の事案なのでこちらにポストしました。
まずサイト全体のパフォーマンスを計測してみたのですが、他で使用した(簡素な)手作りMVCフレームワークの動作と比べ、全体的にだいぶ遅いように感じました。
ページごとにabなどで調べてみた結果、複雑なモデルのアソシエーションを多様しているページ(コントローラ)がかなり重いです。
例えばアソシエーションの無いモデルのみを使用しているページは速いですが、Model->belongsToなどできっちり関連づけているモデルを使用してると遅いです。
遅いページの例:
<?php
$ModelA->belongsTo(ModelB, ModelC)
$ModelB->belongsTo(ModelD, ModelE)
$ModelC->belongsTo(ModelF, ModelG)
....
$FrontController->uses = (ModelA, ModelB, ModelC);
?>
コントローラでusesしているモデルは、そのコントローラ内のメソッドで使うかどうかに関わらずすべてのインスタンスが生成されるようです。
例えば上の例の場合、FrontControllerの起動時にはModelA〜ModelCまで生成されます。
さらに、モデルの生成時にはアソシエーションされる側のモデルも生成されるようですので、結局ModelA〜ModelGの7インスタンスが生成されることになります。
モデルから関連モデルの再起処理において「深さ」のような判断基準は持っていないようですので、ロードすべき関連モデルがなくなるまで続きます。
実際の関連モデルの生成は必要になったときにだけ行えばいいかもと思い、下記のようなパッチを書いてみました。
/app/models/app_model.php に追加して /cake/libs/models/model.php のメソッドをオーバーライドしてます。
<?php
/**
* アソシエーションされる関連モデル情報をストアする
*
* @access private
*/
var $_preserve_linked_models = array();
/**
* Model::__constructLinkedModel() をオーバーライド
* ここではModelのインスタンスを作成せずに関連モデル情報をストアするのみ
*
* @param string $assoc Association name
* @param string $className Class name
* @author ogasyo
* @access private
*/
function __constructLinkedModel($assoc, $className = null) {
if(empty($className)) {
$className = $assoc;
}
if (!isset($this->{$assoc}) || $this->{$assoc}->name !== $className) {
$model = array('class' => $className, 'alias' => $assoc);
$this->_preserve_linked_models[$assoc] = $model;
}
}
/**
* マジックメソッド__getの実装
* 関連Model情報があればModelのインスタンスを作成して返す
*
* @param string $name Property name
* @return mixed
* @author ogasyo
* @access private
*/
function __get($name) {
if (isset($this->_preserve_linked_models[$name]) && (!isset($this->{$name}) || $this->{$name}->name !== $this->_preserve_linked_models[$name]['class'])) {
if (PHP5) {
$this->{$name} = ClassRegistry::init($this->_preserve_linked_models[$name]);
} else {
$this->{$name} =& ClassRegistry::init($this->_preserve_linked_models[$name]);
}
$this->tableToModel[$this->{$name}->table] = $name;
}
if (!isset($this->{$name})) {
trigger_error(sprintf(__('%s::%s() could not get property[%s]. not defined.', true), __CLASS__, __METHOD__, $name), E_USER_WARNING);
return;
}
return $this->{$name};
}
/**
* マジックメソッド__setの実装
* 関連Model情報があればModelのインスタンスを作成して値をセットする
*
* @param string $name Property name
* @param mixed $value Property value
* @return void
* @author ogasyo
* @access private
*/
function __set($name, $value) {
if (isset($this->_preserve_linked_models[$name]) && (!isset($this->{$name}) || $this->{$name}->name !== $this->_preserve_linked_models[$name]['class'])) {
if (PHP5) {
$this->{$name} = ClassRegistry::init($this->_preserve_linked_models[$name]);
} else {
$this->{$name} =& ClassRegistry::init($this->_preserve_linked_models[$name]);
}
$this->tableToModel[$this->{$name}->table] = $name;
}
$this->{$name} = $value;
}
?>
理屈は、Modelクラスでやってる関連モデルのインスタンス生成時に、実際の生成処理は行わず関連づけの情報だけ対象モデルにセットしてます。
あとはまだ未初期化の関連モデル($ModelA->{ModelB})へアクセスしたときに、マジックメソッドget&setでインスタンスを生成し返してます。
# __set()のvalueにPHP4方式でリファレンスを渡すと__get()でエラーになります。<=PHP5なので気にしない
# ex. $ModelA->ModelB =& new HogeClass();
このパッチ適用前は、システム内のほとんど全モデルのインスタンスが生成されていましたが、メソッドごとで実際に使用するモデルのみを生成するようになりました。
パフォーマンススコアはDB構成、実装方法などで全く違うと思うのでここでは割愛します。
一通りの機能テストは行って一応オールグリーンでしたが、もしCakePHP的にこれやっちゃダメよ、な箇所などがあればご指摘願えると幸いです。