OSDN Git Service

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