nmtysh.log

Tech系のネタや日々の独り言などを書いています。

CakePHP 2.xのElementのCacheは入れ子にできない

CakePHPでViewのElementでキャッシュが利用できます
利用できますが、キャッシュ生成処理の処理上、入れ子になったElementで親と子の両方でCacheを使うとCacheがおかしくなります。

親のElement(ここではAとします)で、Elementの処理時にCacheのconfigを生成するのですが、render時に子のElement(ここではB)の処理時にAのCacheConfigが上書きされます。
そのため、Aのrender後、Cacheに書き込みますが、書き込み先がBのElementのキャッシュとなり、Aのキャッシュは生成されません。
次回(キャッシュの期限が有効の場合)、Aのrender時にAのキャッシュはありませんので、再びrenderが行われます。
よって、Aが呼ばれるたび、この書き込みがループしますので、A(B(A(B(...)))) と処理が行われるたびループします。

<?php
// lib/Cake/View/View.php#L398
public function element($name, $data = array(), $options = array()) {
()
    // キャッシュを利用する場合はキャッシュから読み込んでみる
    if (isset($options['cache'])) {
        $contents = $this->_elementCache($name, $data, $options);
        if ($contents !== false) {
            return $contents;
        }
    }
    $file = $this->_getElementFilename($name);
    if ($file) {
        // elementファイルを読み込んで処理する
        return $this->_renderElement($file, $data, $options);
    }
()
}

View.php#L398

<?php
protected function _elementCache($name, $data, $options) {
()
    $keys = array_merge(array($underscored, $name), array_keys($options), array_keys($data));
    $this->elementCacheSettings = array(
        'config' => $this->elementCache,
        'key' => implode('_', $keys)
    );
    if (is_array($options['cache'])) {
        $defaults = array(
            'config' => $this->elementCache,
            'key' => $this->elementCacheSettings['key']
        );
        $this->elementCacheSettings = array_merge($defaults, $options['cache']);
    }
    // Cacheのkeyを設定しているが、格納先がViewクラスのインスタンス変数になっている
    $this->elementCacheSettings['key'] = 'element_' . $this->elementCacheSettings['key'];
    return Cache::read($this->elementCacheSettings['key'], $this->elementCacheSettings['config']);
}

View.php#L1180

<?php
protected function _renderElement($file, $data, $options) {
()
    $element = $this->_render($file, array_merge($this->viewVars, $data));
()
    if (isset($options['cache'])) {
        Cache::write($this->elementCacheSettings['key'], $element, $this->elementCacheSettings['config']);
    }
    return $element;
}

View.php#L1215

この $this->_render() 内部から $this->_evaluate() が呼ばれているが、そこでElementファイルがincludeされている。
この時、Elementファイル内でCacheを利用する $this->element() があると一連の処理が再び呼ばれ、 $this->elementCacheSettings['key'] が上書きされてしまう。

<?php
protected function _evaluate($viewFile, $dataForView) {
    $this->__viewFile = $viewFile;
    extract($dataForView);
    ob_start();
    include $this->__viewFile;
    unset($this->__viewFile);
    return ob_get_clean();
}

View.php#L966

3.x系のView では処理が変わっているので、この問題は起きないと思われます。

2.x系を使っている環境では、入れ子になったElementでCacheを利用する際には親か子のどちらかでのみCacheを利用するように気をつけましょう。