OSDN Git Service

Remove PHP closing tag
[ethna/ethna.git] / class / Plugin / Cachemanager / Memcache.php
1 <?php
2 // vim: foldmethod=marker tabstop=4 shiftwidth=4 autoindent
3 /**
4  *  Memcache.php
5  *
6  *  - Point Cutしたいと思った!
7  *  - キャッシュキーには250文字までしか使用できないので注意して下さい
8  *
9  *  @todo   ネームスペース/キャッシュキー長のエラーハンドリング
10  *
11  *  @author     Masaki Fujimoto <fujimoto@php.net>
12  *  @package    Ethna
13  *  @version    $Id$
14  */
15
16 /**
17  *  キャッシュマネージャクラス(memcache版)
18  *
19  *  @author     Masaki Fujimoto <fujimoto@php.net>
20  *  @access     public
21  *  @package    Ethna
22  */
23 class Ethna_Plugin_Cachemanager_Memcache extends Ethna_Plugin_Cachemanager
24 {
25     /**#@+  @access private */
26
27     /** @var    object  MemCache    MemCacheオブジェクト */
28     var $memcache = null;
29
30     /** @var bool 圧縮フラグ */
31     var $compress = true;
32
33     /** @var    array   plugin configure */
34     var $config_default = array(
35         'host' => 'localhost',
36         'port' => '11211',
37         'retry' => 3,
38         'timeout' => 3,
39     );
40
41     /**#@-*/
42
43     /**
44      *  _load
45      *
46      *  @access protected
47      */
48     function _load()
49     {
50         parent::_load();
51         $this->memcache_pool = array();
52     }
53
54     /**
55      *  memcacheキャッシュオブジェクトを生成、取得する
56      *
57      *  @access protected
58      */
59     function _getMemcache($cache_key, $namespace = null)
60     {
61         $retry = $this->config['retry'];
62         $timeout = $this->config['timeout'];
63
64         $r = false;
65
66         list($host, $port) = $this->_getMemcacheInfo($cache_key, $namespace);
67         if (isset($this->memcache_pool["$host:$port"])) {
68             // activate
69             $this->memcache = $this->memcache_pool["$host:$port"];
70             return $this->memcache;
71         }
72         $this->memcache_pool["$host:$port"] =& new MemCache();
73
74         while ($retry > 0) {
75             if ($this->config['use_pconnect']) {
76                 $r = $this->memcache_pool["$host:$port"]->pconnect($host, $port, $timeout);
77             } else {
78                 $r = $this->memcache_pool["$host:$port"]->connect($host, $port, $timeout);
79             }
80             if ($r) {
81                 break;
82             }
83             sleep(1);
84             $retry--;
85         }
86         if ($r == false) {
87             trigger_error("memcache: connection failed");
88             $this->memcache_pool["$host:$port"] = null;
89         }
90
91         $this->memcache = $this->memcache_pool["$host:$port"];
92         return $this->memcache;
93     }
94
95     /**
96      *  memcache接続情報を取得する
97      *
98      *  @access protected
99      *  @return array   array(host, port)
100      *  @todo   $cache_keyから$indexを決める方法を変更できるようにする
101      */
102     function _getMemcacheInfo($cache_key, $namespace)
103     {
104         $namespace = $this->getNamespace($namespace);
105
106         $memcache_info = $this->config['info'];
107         $default_memcache_host = $this->config['host'];
108         $default_memcache_port = $this->config['port'];
109
110         if ($memcache_info == null || isset($memcache_info[$namespace]) == false) {
111             return array($default_memcache_host, $default_memcache_port);
112         }
113
114         // namespace/cache_keyで接続先を決定
115         $n = count($memcache_info[$namespace]);
116
117         $index = $cache_key % $n;
118         return array(
119             isset($memcache_info[$namespace][$index]['host']) ?
120                 $memcache_info[$namespace][$index]['host'] :
121                 $this->config['host'],
122             isset($memcache_info[$namespace][$index]['port']) ?
123                 $memcache_info[$namespace][$index]['port'] :
124                 $this->config['port'],
125         );
126
127         // for safe
128         return array($default_memcache_host, $default_memcache_port);
129     }
130
131     /**
132      *  キャッシュに設定された値を取得する
133      *
134      *  キャッシュに値が設定されている場合はキャッシュ値
135      *  が戻り値となる。キャッシュに値が無い場合やlifetime
136      *  を過ぎている場合、エラーが発生した場合はEthna_Error
137      *  オブジェクトが戻り値となる。
138      *
139      *  @access public
140      *  @param  string  $key        キャッシュキー
141      *  @param  int     $lifetime   キャッシュ有効期間
142      *  @param  string  $namespace  キャッシュネームスペース
143      *  @return array   キャッシュ値
144      */
145     function get($key, $lifetime = null, $namespace = null)
146     {
147         $this->_getMemcache($key, $namespace);
148         if ($this->memcache == null) {
149             return Ethna::raiseError('memcache server not available', E_CACHE_NO_VALUE);
150         }
151
152         $namespace = $this->getNamespace($namespace);
153
154         $cache_key = $this->_getCacheKey($namespace, $key);
155         if ($cache_key == null) {
156             return Ethna::raiseError('invalid cache key (too long?)', E_CACHE_NO_VALUE);
157         }
158
159         $value = $this->memcache->get($cache_key);
160         if ($value == null) {
161             return Ethna::raiseError('no such cache', E_CACHE_NO_VALUE);
162         }
163         $time = $value['time'];
164         $data = $value['data'];
165
166         // ライフタイムチェック
167         if (is_null($lifetime) == false) {
168             if (($time+$lifetime) < time()) {
169                 return Ethna::raiseError('lifetime expired', E_CACHE_EXPIRED);
170             }
171         }
172
173         return $data;
174     }
175
176     /**
177      *  キャッシュの最終更新日時を取得する
178      *
179      *  @access public
180      *  @param  string  $key        キャッシュキー
181      *  @param  string  $namespace  キャッシュネームスペース
182      *  @return int     最終更新日時(unixtime)
183      */
184     function getLastModified($key, $namespace = null)
185     {
186         $this->_getMemcache($key, $namespace);
187         if ($this->memcache == null) {
188             return Ethna::raiseError('memcache server not available', E_CACHE_NO_VALUE);
189         }
190
191         $namespace = $this->getNamespace($namespace);
192
193         $cache_key = $this->_getCacheKey($namespace, $key);
194         if ($cache_key == null) {
195             return Ethna::raiseError('invalid cache key (too long?)', E_CACHE_NO_VALUE);
196         }
197
198         $value = $this->memcache->get($cache_key);
199
200         return $value['time'];
201     }
202
203     /**
204      *  値がキャッシュされているかどうかを取得する
205      *
206      *  @access public
207      *  @param  string  $key        キャッシュキー
208      *  @param  int     $lifetime   キャッシュ有効期間
209      *  @param  string  $namespace  キャッシュネームスペース
210      */
211     function isCached($key, $lifetime = null, $namespace = null)
212     {
213         $r = $this->get($key, $lifetime, $namespace);
214
215         return Ethna::isError($r) ? false: true;
216     }
217
218     /**
219      *  キャッシュに値を設定する
220      *
221      *  @access public
222      *  @param  string  $key        キャッシュキー
223      *  @param  mixed   $value      キャッシュ値
224      *  @param  int     $timestamp  キャッシュ最終更新時刻(unixtime)
225      *  @param  string  $namespace  キャッシュネームスペース
226      */
227     function set($key, $value, $timestamp = null, $namespace = null)
228     {
229         $this->_getMemcache($key, $namespace);
230         if ($this->memcache == null) {
231             return Ethna::raiseError('memcache server not available', E_CACHE_NO_VALUE);
232         }
233
234         $namespace = $this->getNamespace($namespace);
235
236         $cache_key = $this->_getCacheKey($namespace, $key);
237         if ($cache_key == null) {
238             return Ethna::raiseError('invalid cache key (too long?)', E_CACHE_NO_VALUE);
239         }
240
241         $time = $timestamp ? $timestamp : time();
242         $this->memcache->set($cache_key, array('time' => $time, 'data' => $value), $this->compress ? MEMCACHE_COMPRESSED : null);
243     }
244
245     /**
246      *  キャッシュ値を削除する
247      *
248      *  @access public
249      *  @param  string  $key        キャッシュキー
250      *  @param  string  $namespace  キャッシュネームスペース
251      */
252     function clear($key, $namespace = null)
253     {
254         $this->_getMemcache($key, $namespace);
255         if ($this->memcache == null) {
256             return Ethna::raiseError('memcache server not available', E_CACHE_NO_VALUE);
257         }
258
259         $namespace = $this->getNamespace($namespace);
260
261         $cache_key = $this->_getCacheKey($namespace, $key);
262         if ($cache_key == null) {
263             return Ethna::raiseError('invalid cache key (too long?)', E_CACHE_NO_VALUE);
264         }
265
266         $this->memcache->delete($cache_key, -1);
267     }
268
269     /**
270      *  キャッシュデータをロックする
271      *
272      *  @access public
273      *  @param  string  $key        キャッシュキー
274      *  @param  int     $timeout    ロックタイムアウト
275      *  @param  string  $namespace  キャッシュネームスペース
276      *  @return bool    true:成功 false:失敗
277      */
278     function lock($key, $timeout = 5, $namespace = null)
279     {
280         $this->_getMemcache($key, $namespace);
281         if ($this->memcache == null) {
282             return Ethna::raiseError('memcache server not available', E_CACHE_LOCK_ERROR);
283         }
284
285         // ロック用キャッシュデータを利用する
286         $namespace = $this->getNamespace($namespace);
287         $cache_key = "lock::" . $this->_getCacheKey($namespace, $key);
288         $lock_lifetime = 30;
289
290         do {
291             $r = $this->memcache->add($cache_key, true, false, $lock_lifetime);
292             if ($r != false) {
293                 break;
294             }
295             sleep(1);
296             $timeout--;
297         } while ($timeout > 0);
298
299         if ($r == false) {
300             return Ethna::raiseError('lock timeout', E_CACHE_LOCK_TIMEOUT);
301         }
302
303         return true;
304     }
305
306     /**
307      *  キャッシュデータのロックを解除する
308      *
309      *  @access public
310      *  @param  string  $key        キャッシュキー
311      *  @param  string  $namespace  キャッシュネームスペース
312      *  @return bool    true:成功 false:失敗
313      */
314     function unlock($key, $namespace = null)
315     {
316         $this->_getMemcache($key, $namespace);
317         if ($this->memcache == null) {
318             return Ethna::raiseError('memcache server not available', E_CACHE_LOCK_ERROR);
319         }
320
321         $namespace = $this->getNamespace($namespace);
322         $cache_key = "lock::" . $this->_getCacheKey($namespace, $key);
323
324         $this->memcache->delete($cache_key, -1);
325     }
326
327     /**
328      *  ネームスペースからキャッシュキーを生成する
329      *
330      *  @access private
331      */
332     function _getCacheKey($namespace, $key)
333     {
334         // 少し乱暴だけど...
335         $key = str_replace(":", "_", $key);
336         $cache_key = $namespace . "::" . $key;
337         if (strlen($cache_key) > 250) {
338             return null;
339         }
340         return $cache_key;
341     }
342
343     /**
344      * 圧縮フラグを立てる
345      *
346      * MySQLなどいくつかの子クラスで有効
347      * 
348      * @access public
349      * @param bool $flag フラグ
350      */
351     function setCompress($flag) {
352         $this->compress = $flag;
353     }
354 }