読者です 読者をやめる 読者になる 読者になる

WEB系エンジニアの勉強日記

Baby steps to Giant strides!

CakephpのLazyModelの仕組み

Cakephp 1.2, 1.3 のModelのロードを最適化するプラグイン LazyModel.

GitHub - Phally/lazy_model: Lazy model loading for CakePHP 1.2 & 1.3.

Cakephp 2.0 以降では標準化されているようです。

概要

モデルに紐づく関連するモデルを実際に使用する際に初期化する。

標準の動作では、1つのモデルを初期化する際に、関連するモデルチェーンを全て初期化している。

システムが中、大規模になってくると、このプラグインを入れるだけで処理速度が結構改善されるらしい。

どうやって実現しているかを見て行きます。

関連するモデルデータの整理

Model#__construct をオーバーライドして、 $map プロパティに関連するモデルのリストを格納している。

/**
     * Overrides the Model constructor to make an inventory of models it can use lazy loading on.
     *
     * @param mixed $id Set this ID for this model on startup, can also be an array of options, see Model::__construct().
     * @param string $table Name of database table to use.
     * @param string $ds DataSource connection name.
     * @return void
     * @access public
     */
    public function __construct($id = false, $table = null, $ds = null) {
        foreach ($this->__associations as $type) {
            foreach ((array)$this->{$type} as $key => $properties) {
                if ($type != 'hasAndBelongsToMany') {
                    $this->map($key, $properties);
                } elseif (is_array($properties) && isset($properties['with'])) {
                    list($primaryAlias, $void) = $this->map($key, $properties);
                    list($joinAlias, $void) = $this->map(0, (is_array($properties['with'])) ? key($properties['with']) : $properties['with']);
                    $this->joinModels[$joinAlias] = $primaryAlias;
                }
            }
        }
        parent::__construct($id, $table, $ds);
    }

標準の関連モデルのインスタンス化処理を避ける

関連するモデルの初期化する処理、Model#__constructLinkedModel をオーバーライドして 、$map に保有されているモデル名の場合は初期化処理を行わないようにしている。

/**
     * Overrides Model::__constructLinkedModel() so it won't instantiate the entire model chain. It will only
     * instantiate the HABTM associations that use an automodel, because there are two models needed to figure
     * out what the name is of the join model. To avoid this, use your own join model using `with` or don't use
     * HABTM at all.
     *
     * @param string $assoc Association name.
     * @param string $className Class name.
     * @return void
     * @access public
     */
public function __constructLinkedModel($assoc, $className = null) {
        if (!isset($this->map[$assoc])) {
            parent::__constructLinkedModel($assoc, $className);
        }
    }

実際に呼び出された際に初期化する

マジックメソッド __get をLazyModelに定義して、 指定されたモデル名が$mapに含まれていた場合に、モデルの初期化処理を行う。

マジックメソッドについては下記を参照。 http://php.net/manual/ja/language.oop5.overloading.php

/**
     * Magic method which instantiates a model when it is called and when it is mapped for lazy loading.
     *
     * @param string $alias Name of the property.
     * @return mixed Value of the property.
     */
    public function &__get($alias) {
        if (!property_exists($this, $alias) && isset($this->map[$alias])) {
            $this->constructLazyLinkedModel($alias, $this->map[$alias]);
        }
        return $this->{$alias};
    }