壊れたメガネ

ホッチキスの達人の意識の高いブログ。

PHPの出力をキャッシュして、ついでに圧縮はしない。

PHPの出力をキャッシュしてみる



@tanatonさんがPHPネタを書いてらしたので、私も便乗することにした。
@tanatonさんはご自身で作られたWEBサービスで必要に迫られて、キャッシングのためのクラスをお書きになられたということです。


なるほど、たしかに高い負荷のかかるWEBサービスではいろんなレベルでのキャッシングと言うのは必須だろうと共感し、わたしも同じくPHPでそれっぽいクラスを書いてみた。


@tanatonさんの記事を受けて書いた私のクラスは@tanatonさんのそれと全く同じというわけでは無く、ちょっと気になった点を私自身がそのインターフェースを受け入れやすい様に書いたもの。
@tanatonさんはCodeIgniterというフレームワークのキャッシングAPIを再現することに重きをおいてかかれています。
私の書いたものは、私自身が使い易いように書かれています。
つまり異なる目的において作られたこれらのコードは単純に比較出来ないかと思います。

(というか、キャッシュってこんなもんでいいの?と言う疑問はのこる。。。)


気になった点



  • コンストラクタでExceptionをgoto文的に使っている点

    別エントリ Exceptionについて考えてみたでちょっと書いています。

  • デストラクタで出力する点

    コード中に出力を行っている記述が無いのに、実際に出力が行われるのは見通しが悪くなる気がする。
    そのインスタンスへのリファレンスを破棄して出力するぐらいなら、相応のメソッドをコールした方が何をやっているのか明確になる気がする。

  • キャッシュの保存にgzを用いている点

    PHPにおけるZlibサポートは、デフォルトでは利用できません。
    PHP: インストール手順(Zlib) - Manual


  • キャッシュのパスにmd5値を用いている点

    必要が感じられない。


  • 面白いと思った点



    CentOS5.5環境で試したのですが、たしかにファイルのctimeを未来に設定することが出来ました。
    私も@tanatonさんが用いたこの手法でキャッシュのタイムスタンプを実装しています。



    1 #!/usr/bin/php
    2 <?php
    3 require_once 'CacheFactory.php';
    4 require_once 'Cache.php';
    5
    6 // キャッシュの有効期限を10秒に設定
    7 Cache::setExpireLimit(10);
    8
    9 // my-cacheという名前のキャッシュファイルを作る。
    10 $cache = CacheFactory::getInstance()->factory("my-cache");
    11
    12 // キャッシュの有効期限が切れている場合。
    13 if ($cache->isExpired()) {
    14 print "有効期限切れてるので作ります。" . PHP_EOL;
    15 $data = "this data has been cached at time(" . time() . ")" . PHP_EOL;
    16 $cache->setData($data); // キャッシュデータをセット
    17 $cache->save(); // キャッシュをファイルに保存
    18 } else {
    19 print "期限は有効です。" . PHP_EOL;
    20 }
    21
    22 // キャッシュデータを出力
    23 print $cache->getData();
    24
    25 class CacheFactory {
    26
    27 private static $_instance = null;
    28
    29 private static $_pool = null;
    30
    31 private function __construct() {
    32 $this->_pool = array();
    33 }
    34
    35 public function factory($cacheSavePath) {
    36
    37 $cache = null;
    38 if (array_key_exists($cacheSavePath, $this->_pool)) {
    39 $cache = $this->_pool[$cacheSavePath];
    40 } else {
    41 $cache = new Cache($cacheSavePath);
    42 $this->_pool[$cacheSavePath] = $cache;
    43 }
    44 return $cache;
    45 }
    46
    47 public static function getInstance() {
    48 if (self::$_instance === null) {
    49 $_instance = new CacheFactory();
    50 }
    51 return $_instance;
    52 }
    53 }
    54
    55 /**
    56 * キャッシュを表現するクラスです。
    57 * このオブジェクトのファクトリクラスCacheFactoryを用いてインスタンス生成するべきです。
    58 */
    59 class Cache {
    60
    61 /**
    62 * @var このオブジェクトが表現するキャッシュのパス
    63 */
    64 private $_cachePath;
    65
    66 /**
    67 *
    68 * @var int キャッシュの有効期限
    69 */
    70 private $_expire = -1;
    71
    72 /**
    73 * キャッシュ有効期限の更新単位。デフォルトは1時間
    74 * @var int
    75 */
    76 private static $_limit = 3600;
    77
    78 /**
    79 * @var string キャッシュデータ。バイナリもおk
    80 */
    81 private $_data = null;
    82
    83 /**
    84 * @throws Exception 既存のキャッシュファイルのオープンに失敗した場合
    85 */
    86 public function __construct($cachePath) {
    87 $this->_cachePath = $cachePath;
    88 if (file_exists($cachePath)) {
    89 $this->_expire = filemtime($this->_cachePath);
    90 if ($data = file_get_contents($this->_cachePath)) {
    91 $this->_data = $data;
    92 } else {
    93 throw new Exception("キャッシュファイルのオープンに失敗しました。");
    94 }
    95 } else {
    96 $this->_expire = -1;
    97 }
    98
    99
    100 }
    101
    102 /**
    103 * @param int limit
    104 */
    105 public static function setExpireLimit($limit) {
    106 self::$_limit = $limit;
    107 }
    108
    109 /**
    110 * @return int
    111 */
    112 public static function getExpireLimit() {
    113 return self::$_limit;
    114 }
    115
    116 public function getExpire() {
    117 return $this->_expire;
    118 }
    119
    120 /**
    121 * 指定されたデータをキャッシュデータとしてセットします。
    122 * @param string $data
    123 */
    124 public function setData($data) {
    125 $this->_data = $data;
    126 }
    127
    128 /**
    129 * 指定されたデータを現在のキャッシュデータ末尾に追加します。
    130 * @param string $data
    131 */
    132 public function addData($data) {
    133 $this->_data .= $data;
    134 }
    135
    136 /**
    137 * キャッシュデータを返します。
    138 * @return string
    139 */
    140 public function getData() {
    141 return $this->_data;
    142 }
    143
    144 /**
    145 * キャッシュファイルにデータを保存します。
    146 */
    147 public function save() {
    148 $fh = fopen($this->_cachePath, "w");
    149 if ($fh === false) {
    150 throw new Exception("キャッシュファイルを開けませんでした。");
    151 }
    152 if (!fwrite($fh, $this->_data)) {
    153 throw new Exception("キャッシュを保存できませんでした。");
    154 }
    155 fclose($fh);
    156 $expire = time() + self::$_limit;
    157 if (!touch($this->_cachePath, $expire, $expire)) {
    158 throw new Exception("有効期限の更新に失敗しました。($expire)");
    159 }
    160 }
    161
    162 /**
    163 * @return boolean このキャッシュの有効期限が切れている場合にtrueを返します。
    164 */
    165 public function isExpired() {
    166 return $this->_expire <= time();
    167 }
    168 }
    169
    170
    171


    ダウンロード



    追伸



    テストスクリプト(7行目〜20行目)の例外補足・処理がしょぼいのは目をつむってください。


    参考サイト