へぼいいいわけ このページをアンテナに追加 RSSフィード Twitter

2011年03月29日

PHPの出力をキャッシュして、ついでに圧縮してみる

コンストラクタキャッシュの準備を行って、「cacheメソッドが呼ばれたら実際にキャッシュするようにフラグを立てて、デストラクタでキャッシュするクラスを作ったら結構便利だったので公開します。

ソース

<?php
class Output
{
	const CLEVEL_DEFAULT = 5;
	private $cache_path = '';
	private $cache_timeout = 0;
	private $now_time = 0;
	private $compress_level = Output::CLEVEL_DEFAULT;

	public function __construct($match_path, $path = './cache', $clevel = Output::CLEVEL_DEFAULT)
	{
		$this->now_time = time();
		$this->cache_path = $path.'/'.substr(md5($match_path), 0, 10).'.cache';
		if((is_int($clevel) === false) || ($clevel < 0) || ($clevel > 9)){
			throw new Exception('圧縮レベルがおかしい', 1);
		}
		$this->compress_level = $clevel;

		if($this->cache_check() === true){
			// キャッシュ出力
			$this->cache_output();
			// コンストラクタ終了
			throw new Exception('キャッシュを出力したよ', 0);
		}
		// 出力のバッファリング開始
		ob_start();
	}

	public function __destruct()
	{
		if($this->cache_timeout > 0){
			// キャッシュ時間が設定されていた場合
			$this->cache_write();
		}
		// 表示
		ob_end_flush();
	}

	public function cache($timeout = 0)
	{
		$ret = false;
		if($timeout > 0){
			$this->cache_timeout = $timeout;
			$ret = true;
		}
		return $ret;
	}

	private function cache_check()
	{
		$ret = false;
		if(file_exists($this->cache_path)){
			if(filemtime($this->cache_path) > $this->now_time){
				// キャッシュファイルのタイムスタンプが未来だった場合
				$ret = true;
			}
		}
		return $ret;
	}

	private function cache_write()
	{
		$ret = false;
		// バッファに溜まったデータを取得
		$data = ob_get_contents();
		if($data !== false){
			$gzfp = gzopen($this->cache_path, 'wb'.$this->compress_level);
			gzwrite($gzfp, $data);
			gzclose($gzfp);
			$time = $this->now_time + $this->cache_timeout;
			touch($this->cache_path, $time, $time);
			$ret = true;
		}
		return $ret;
	}

	private function cache_output()
	{
		$ret = false;
		if(file_exists($this->cache_path)){
			readgzfile($this->cache_path);
			$ret = true;
		}
		return $ret;
	}
}
?>

適当に標準出力に垂れ流したデータを「Output::cacheメソッドを呼ぶ事で、指定した秒数だけファイルに圧縮してキャッシュすることが出来ます。


使い方

基本的な使い方としては、インスタンスを生成して、キャッシュしたくなったら「Output::cacheメソッドを呼ぶだけ。あとはデストラクタが勝手にキャッシュ処理を実行します。

キャッシュがすでにある場合はコンストラクタキャッシュを出力して例外を投げるので、適当に処理すること。


その1

※Outputクラスは読み込んであるものとします。

<?php
$out = null;
try {
	$out = new Output('hogehoge');
} catch(Exception $e){
	exit(0);
}
for($i = 0; $i < 10000; $i++){
	echo 'hogehogehogehoge', "\n";
}
$out->cache(60);
?>

その2
<?php
function hoge()
{
	$out = null;
	try {
		$out = new Output('hogefunc', '/hoge/huga/web/data', 9);
	} catch(Exception $e){
		return;
	}
	echo 'unkounkounko';
	$data = file_get_contents('http://d.hatena.ne.jp/heiwaboke/');
	if($data !== false){
		echo $data;
		$out->cache(60);
	}
	echo 'unkounkounko';
}

hoge();
hoge();
hoge();
?>

こんなものを作った理由

3週間くらい前の話ですが、Copipe2datというWEBサービスを作りました。その時に、今までフレームワークMFCくらいしか使った事がなかったので、有名どころのフレームワークを覚えてWEBサービスを量産できるようにしようと思い、CodeIgniterというPHPフレームワークを勉強してみました。

そのCodeIgniterですが、びっくりするくらい良く出来ていて複雑な決まりも無く、Copipe2datのようなものはリファレンスを一通り読むだけで簡単に作れてしまいました。コントローラの中で「$this->output->cache(3600);」とか呼ぶだけで全ての出力をキャッシュする機能が標準で付いていたり、フレームワークすげぇと感動してました。

でも、CodeIgniterにはパス中に「/」を含むパスをひとつの引数として受け取れない(必ず「/」で分解されてしまう)という、個人的に気に入らない点がありまして*1、その点を解消するためだけにCodeIgniterのクラス名&メソッド名を丸パクリしたクラス群を作成し、そこにCopipe2datを移植するという面倒な事をしました。

ただCodeIgniterで先に作ってしまったCopipe2datを少しの修正で動くようにするためだけに作ったため、中身スカスカのとてもフレームワークと呼べるようなものではないです。そんな中身スカスカの中でもOutputクラスだけは個人的に気に入ったので、公開してみました。おわり。


参考

*1:設定を弄れば対応できるかもしれないです。

Connection: close