2 // vim: foldmethod=marker tabstop=4 shiftwidth=4 autoindent
4 * Ethna_Plugin_Cachemanager_Memcache.php
7 * - キャッシュキーには250文字までしか使用できないので注意して下さい
9 * @todo ネームスペース/キャッシュキー長のエラーハンドリング
11 * @author Masaki Fujimoto <fujimoto@php.net>
17 * キャッシュマネージャクラス(memcache版)
19 * @author Masaki Fujimoto <fujimoto@php.net>
23 class Ethna_Plugin_Cachemanager_Memcache extends Ethna_Plugin_Cachemanager
25 /**#@+ @access private */
27 /** @var object MemCache MemCacheオブジェクト */
30 /** @var bool 圧縮フラグ */
36 * Ethna_Plugin_Cachemanager_Memcacheクラスのコンストラクタ
40 function Ethna_Plugin_Cachemanager_Memcache(&$controller)
42 parent::Ethna_Plugin_Cachemanager($controller);
43 $this->memcache_pool = array();
47 * memcacheキャッシュオブジェクトを生成、取得する
51 function _getMemcache($cache_key, $namespace = null)
53 $retry = $this->config->get('memcache_retry');
57 $timeout = $this->config->get('memcache_timeout');
63 list($host, $port) = $this->_getMemcacheInfo($cache_key, $namespace);
64 if (isset($this->memcache_pool["$host:$port"])) {
66 $this->memcache = $this->memcache_pool["$host:$port"];
67 return $this->memcache;
69 $this->memcache_pool["$host:$port"] =& new MemCache();
72 if ($this->config->get('memcache_use_pconnect')) {
73 $r = $this->memcache_pool["$host:$port"]->pconnect($host, $port, $timeout);
75 $r = $this->memcache_pool["$host:$port"]->connect($host, $port, $timeout);
84 trigger_error("memcache: connection failed");
85 $this->memcache_pool["$host:$port"] = null;
88 $this->memcache = $this->memcache_pool["$host:$port"];
89 return $this->memcache;
96 * @todo $cache_keyから$indexを決める方法を変更できるようにする
98 function _getMemcacheInfo($cache_key, $namespace)
100 $namespace = is_null($namespace) ? $this->namespace : $namespace;
102 $memcache_info = $this->config->get('memcache');
103 $default_memcache_host = $this->config->get('memcache_host');
104 if ($default_memcache_host == "") {
105 $default_memcache_host = "localhost";
107 $default_memcache_port = $this->config->get('memcache_port');
108 if ($default_memcache_port == "") {
109 $default_memcache_port = 11211;
111 if ($memcache_info == null || isset($memcache_info[$namespace]) == false) {
112 return array($default_memcache_host, $default_memcache_port);
115 // namespace/cache_keyで接続先を決定
116 $n = count($memcache_info[$namespace]);
118 $index = $cache_key % $n;
120 isset($memcache_info[$namespace][$index]['memcache_host']) ?
121 $memcache_info[$namespace][$index]['memcache_host'] :
123 isset($memcache_info[$namespace][$index]['memcache_port']) ?
124 $memcache_info[$namespace][$index]['memcache_port'] :
129 return array($default_memcache_host, $default_memcache_port);
135 * キャッシュに値が設定されている場合はキャッシュ値
136 * が戻り値となる。キャッシュに値が無い場合やlifetime
137 * を過ぎている場合、エラーが発生した場合はEthna_Error
141 * @param string $key キャッシュキー
142 * @param int $lifetime キャッシュ有効期間
143 * @param string $namespace キャッシュネームスペース
144 * @return array キャッシュ値
146 function get($key, $lifetime = null, $namespace = null)
148 $this->_getMemcache($key, $namespace);
149 if ($this->memcache == null) {
150 return Ethna::raiseError('memcache server not available', E_CACHE_NO_VALUE);
153 $namespace = is_null($namespace) ? $this->namespace : $namespace;
155 $cache_key = $this->_getCacheKey($namespace, $key);
156 if ($cache_key == null) {
157 return Ethna::raiseError('invalid cache key (too long?)', E_CACHE_NO_VALUE);
160 $value = $this->memcache->get($cache_key);
161 if ($value == null) {
162 return Ethna::raiseError('no such cache', E_CACHE_NO_VALUE);
164 $time = $value['time'];
165 $data = $value['data'];
168 if (is_null($lifetime) == false) {
169 if (($time+$lifetime) < time()) {
170 return Ethna::raiseError('lifetime expired', E_CACHE_EXPIRED);
181 * @param string $key キャッシュキー
182 * @param string $namespace キャッシュネームスペース
183 * @return int 最終更新日時(unixtime)
185 function getLastModified($key, $namespace = null)
187 $this->_getMemcache($key, $namespace);
188 if ($this->memcache == null) {
189 return Ethna::raiseError('memcache server not available', E_CACHE_NO_VALUE);
192 $namespace = is_null($namespace) ? $this->namespace : $namespace;
194 $cache_key = $this->_getCacheKey($namespace, $key);
195 if ($cache_key == null) {
196 return Ethna::raiseError('invalid cache key (too long?)', E_CACHE_NO_VALUE);
199 $value = $this->memcache->get($cache_key);
201 return $value['time'];
205 * 値がキャッシュされているかどうかを取得する
208 * @param string $key キャッシュキー
209 * @param int $lifetime キャッシュ有効期間
210 * @param string $namespace キャッシュネームスペース
212 function isCached($key, $lifetime = null, $namespace = null)
214 $r = $this->get($key, $lifetime, $namespace);
216 return Ethna::isError($r) ? false: true;
223 * @param string $key キャッシュキー
224 * @param mixed $value キャッシュ値
225 * @param int $timestamp キャッシュ最終更新時刻(unixtime)
226 * @param string $namespace キャッシュネームスペース
228 function set($key, $value, $timestamp = null, $namespace = null)
230 $this->_getMemcache($key, $namespace);
231 if ($this->memcache == null) {
232 return Ethna::raiseError('memcache server not available', E_CACHE_NO_VALUE);
235 $namespace = is_null($namespace) ? $this->namespace : $namespace;
237 $cache_key = $this->_getCacheKey($namespace, $key);
238 if ($cache_key == null) {
239 return Ethna::raiseError('invalid cache key (too long?)', E_CACHE_NO_VALUE);
242 $time = $timestamp ? $timestamp : time();
243 $this->memcache->set($cache_key, array('time' => $time, 'data' => $value), $this->compress ? MEMCACHE_COMPRESSED : null);
250 * @param string $key キャッシュキー
251 * @param string $namespace キャッシュネームスペース
253 function clear($key, $namespace = null)
255 $this->_getMemcache($key, $namespace);
256 if ($this->memcache == null) {
257 return Ethna::raiseError('memcache server not available', E_CACHE_NO_VALUE);
260 $namespace = is_null($namespace) ? $this->namespace : $namespace;
262 $cache_key = $this->_getCacheKey($namespace, $key);
263 if ($cache_key == null) {
264 return Ethna::raiseError('invalid cache key (too long?)', E_CACHE_NO_VALUE);
267 $this->memcache->delete($cache_key, -1);
274 * @param string $key キャッシュキー
275 * @param int $timeout ロックタイムアウト
276 * @param string $namespace キャッシュネームスペース
277 * @return bool true:成功 false:失敗
279 function lock($key, $timeout = 5, $namespace = null)
281 $this->_getMemcache($key, $namespace);
282 if ($this->memcache == null) {
283 return Ethna::raiseError('memcache server not available', E_CACHE_LOCK_ERROR);
287 $namespace = is_null($namespace) ? $this->namespace : $namespace;
288 $cache_key = "lock::" . $this->_getCacheKey($namespace, $key);
292 $r = $this->memcache->add($cache_key, true, false, $lock_lifetime);
298 } while ($timeout > 0);
301 return Ethna::raiseError('lock timeout', E_CACHE_LOCK_TIMEOUT);
311 * @param string $key キャッシュキー
312 * @param string $namespace キャッシュネームスペース
313 * @return bool true:成功 false:失敗
315 function unlock($key, $namespace = null)
317 $this->_getMemcache($key, $namespace);
318 if ($this->memcache == null) {
319 return Ethna::raiseError('memcache server not available', E_CACHE_LOCK_ERROR);
322 $namespace = is_null($namespace) ? $this->namespace : $namespace;
323 $cache_key = "lock::" . $this->_getCacheKey($namespace, $key);
325 $this->memcache->delete($cache_key, -1);
329 * ネームスペースからキャッシュキーを生成する
333 function _getCacheKey($namespace, $key)
336 $key = str_replace(":", "_", $key);
337 $cache_key = $namespace . "::" . $key;
338 if (strlen($cache_key) > 250) {
347 * MySQLなどいくつかの子クラスで有効
350 * @param bool $flag フラグ
352 function setCompress($flag) {
353 $this->compress = $flag;