Add an API to handle caching.
[platal.git] / classes / plcache.php
CommitLineData
e3c13162
FB
1<?php
2/***************************************************************************
3 * Copyright (C) 2003-2010 Polytechnique.org *
4 * http://opensource.polytechnique.org/ *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the Free Software *
18 * Foundation, Inc., *
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
20 ***************************************************************************/
21
22/** This class provide a common API for caching data.
23 */
24class PlCache
25{
26 /* Data types
27 */
28 const SCRIPT = 0x0001; /* The value expires after the execution of the script */
29 const SESSION = 0x0002; /* The value is session specific */
30 const TIMER = 0x0004; /* The value expires after some timeout */
31
32 private static $backends = array();
33
34 private static function getBackend($type)
35 {
36 if (isset(self::$backends[$type])) {
37 return self::$backends[$type];
38 }
39 $globals = Platal::globals();
40 if (($globals->debug & DEBUG_NOCACHE) != 0) {
41 $storage = 'none';
42 } else if (($globals->debug & DEBUG_SCRIPTCACHE) != 0
43 || php_sapi_name() == 'cli') {
44 $storage = 'static';
45 } else {
46 $storage = 'static';
47 switch ($type) {
48 case self::TIMER:
49 if ($globals->core->memcache) {
50 $storage = 'memcache';
51 break;
52 }
53
54 case self::SESSION:
55 $storage = 'session';
56 break;
57 }
58 }
59 if (!isset(self::$backends[$storage])) {
60 switch ($storage) {
61 case 'none':
62 self::$backends['none'] = new PlDummyCache();
63 break;
64
65 case 'static':
66 self::$backends['static'] = new PlStaticCache();
67 break;
68
69 case 'session':
70 self::$backends['session'] = new PlSessionCache();
71 break;
72
73 case 'memcache':
74 $servers = preg_split('/[, ]+/', $globals->core->memcache);
75 self::$backends['memcache'] = new PlMemcacheCache($servers);
76 break;
77 }
78 }
79 self::$backends[$type] = self::$backends[$storage];
80 return self::$backends[$type];
81 }
82
83
84 /** Get the value associated with the key in the cache.
85 *
86 * If the value does not exists, and a callback is provided,
87 * the value is built by calling the callback with the given
88 * expiration time.
89 *
90 * @throw PlNotFoundInCacheException if the value is not in the
91 * cache and $callback is null.
92 */
93 private static function get($key, $type, $callback, $cbargs, $expire)
94 {
95 $backend = self::getBackend($type);
96 return $backend->get($key, $type, $callback, $cbargs, $expire);
97 }
98
99 /** Invalidate the entry of the cache with the given name.
100 */
101 private static function invalidate($key, $type)
102 {
103 $backend = self::getBackend($type);
104 return $backend->invalidate($key, $type);
105 }
106
107 /** Set the value associated with the key in the cache.
108 */
109 private static function set($key, $type, $var, $expire)
110 {
111 $backend = self::getBackend($type);
112 return $backend->set($key, $type, $var, $expire);
113 }
114
115 /** Check if the key exists in the cache.
116 */
117 private static function has($key, $type)
118 {
119 $backend = self::getBackend($type);
120 return $backend->has($key, $type);
121 }
122
123
124 /** Global data storage. Global data is independent from
125 * the current session and can thus be shared by several
126 * PHP instances (for example using memcache if enabled).
127 *
128 * Global data can expire. The expire argument follow the
129 * semantic of the Memcache:: API:
130 * - 0 mean no timeout
131 * - <= 2592000 mean expires in $expire seconds
132 * - else $expire is an unix timestamp
133 */
134
135 public static function getGlobal($key, $callback = null, $cbargs = null,
136 $expire = 0)
137 {
138 return self::get($key, self::TIMER, $callback, $cbargs, $expire);
139 }
140
141 public static function invalidateGlobal($key)
142 {
143 return self::invalidate($key, self::TIMER);
144 }
145
146 public static function setGlobal($key, $var, $expire = 0)
147 {
148 return self::set($key, self::TIMER, $var, $expire);
149 }
150
151 public static function hasGlobal($key)
152 {
153 return self::has($key, self::TIMER);
154 }
155
156
157 /** Session data storage. Session data is session-dependent
158 * and thus must not be shared between sessions but can
159 * be stored in the $_SESSION php variable.
160 */
161
162 public static function getSession($key, $callback = null, $cbargs = null)
163 {
164 return self::get($key, self::SESSION, $callback, $cbargs, 0);
165 }
166
167 public static function invalidateSession($key)
168 {
169 return self::invalidate($key, self::SESSION);
170 }
171
172 public static function setSession($key, $var)
173 {
174 return self::set($key, self::SESSION, $var, 0);
175 }
176
177 public static function hasSession($key)
178 {
179 return self::has($key, self::SESSION);
180 }
181
182
183 /** Script local data storage. This stores data that
184 * expires at the end of the execution of the current
185 * script (or page).
186 */
187
188 public static function getLocal($key, $callback = null, $cbargs = null)
189 {
190 return self::get($key, self::SCRIPT, $callback, $cbargs, 0);
191 }
192
193 public static function invalidateLocal($key)
194 {
195 return self::invalidate($key, self::SCRIPT);
196 }
197
198 public static function setLocal($key, $var)
199 {
200 return self::set($key, self::SCRIPT, $var, 0);
201 }
202
203 public static function hasLocal($key)
204 {
205 return self::has($key, self::SCRIPT);
206 }
207}
208
209
210/** Exception thrown when trying to get the value associated
211 * with a missing key.
212 */
213class PlNotFoundInCacheException extends PlException
214{
215 public function __construct($key, $type)
216 {
217 parent::__construct('Erreur lors de l\'accès aux données',
218 "Key '$key' not found in cache");
219 }
220}
221
222
223/** Interface for the storage backend.
224 */
225interface PlCacheBackend
226{
227 /** Return true if the backend contains the given key
228 * for the given storage type.
229 */
230 public function has($key, $type);
231
232 /** Set the value for the given key and type.
233 */
234 public function set($key, $type, $var, $expire);
235
236 /** Get the value for the given key and type.
237 *
238 * If the value is not found and a $callback is provided,
239 * call the function, pass $cbargs as arguments and use
240 * its output as the new value of the entry.
241 */
242 public function get($key, $type, $callback, $cbargs, $expire);
243
244 /** Remove the entry from the cache.
245 */
246 public function invalidate($key, $type);
247}
248
249class PlDummyCache implements PlCacheBackend
250{
251 public function has($key, $type)
252 {
253 return false;
254 }
255
256 public function set($key, $type, $var, $expire)
257 {
258 }
259
260 public function get($key, $type, $callback, $cbargs, $expire)
261 {
262 if (!is_null($callback)) {
263 return call_user_func_array($callback, $cbargs);
264 } else {
265 throw new PlNotFoundInCacheException($key, $type);
266 }
267 }
268
269 public function invalidate($key, $type)
270 {
271 }
272}
273
274abstract class PlArrayCache implements PlCacheBackend
275{
276 protected function getData(array $data, $key, $type)
277 {
278 $key = $this->arrayKey($key, $type);
279 if (!isset($data[$key])) {
280 throw new PlNotFoundInCacheException($key, $type);
281 }
282 if ($type == PlCache::TIMER) {
283 $entry = $data[$key];
284 $timeout = $entry['timeout'];
285 if (time() > $timeout) {
286 throw new PlNotFoundInCacheException($key, $type);
287 }
288 return $entry['data'];
289 }
290 return $data[$key];
291 }
292
293 protected function buildData($key, $type, $var, $expire)
294 {
295 if ($type == PlCache::TIMER) {
296 if ($expire == 0) {
297 $expire = 2592000;
298 }
299 if ($expire <= 2592000) {
300 $expire = time() + $expire;
301 }
302 return array('timeout' => $expire,
303 'data' => $var);
304 }
305 return $var;
306 }
307
308 protected function getAndSetData(array $data, $key, $type,
309 $callback, $cbargs, $expire)
310 {
311 if (is_null($callback)) {
312 return $this->getData($data, $key, $type);
313 } else {
314 try {
315 $value = $this->getData($data, $key, $type);
316 } catch (PlNotFoundInCacheException $e) {
317 $value = call_user_func_array($callback, $cbargs);
318 $this->set($key, $type, $value, $expire);
319 }
320 return $value;
321 }
322 }
323
324 protected abstract function arrayKey($key, $type);
325
326 public function has($key, $type)
327 {
328 try {
329 $this->get($key, $type, null, null, 0);
330 return true;
331 } catch (PlNotFoundInCacheException $e) {
332 return false;
333 }
334 }
335}
336
337class PlStaticCache extends PlArrayCache
338{
339 private $data = array();
340
341 protected function arrayKey($key, $type)
342 {
343 return $key;
344 }
345
346 public function get($key, $type, $callback, $cbargs, $expire)
347 {
348 return $this->getAndSetData($this->data, $key, $type,
349 $callback, $cbargs, $expire);
350 }
351
352 public function set($key, $type, $var, $expire)
353 {
354 $this->data[$this->arrayKey($key, $type)]
355 = $this->buildData($key, $type, $var, $expire);
356 }
357
358 public function invalidate($key, $type)
359 {
360 unset($this->data[$key]);
361 }
362}
363
364class PlSessionCache extends PlArrayCache
365{
366 public function __construct()
367 {
368 }
369
370 protected function arrayKey($key, $type)
371 {
372 return '__cache_' . $key;
373 }
374
375 public function get($key, $type, $callback, $cbargs, $expire)
376 {
377 return $this->getAndSetData($_SESSION, $key, $type,
378 $callback, $cbargs, $expire);
379 }
380
381 public function set($key, $type, $var, $expire)
382 {
383 S::set($this->arrayKey($key, $type),
384 $this->buildData($key, $type, $var, $expire));
385 }
386
387 public function invalidate($key, $type)
388 {
389 S::kill($this->arrayKey($key, $type));
390 }
391}
392
393class PlMemcacheCache implements PlCacheBackend
394{
395 private $context;
396
397 public function __construct(array $servers)
398 {
399 $this->context = new Memcache();
400 foreach ($servers as $address) {
401 /* XXX: Not IPv6 ready.
402 */
403 if (strpos($address, ':') !== false) {
404 list($addr, $port) = explode(':', $address, 2);
405 $this->context->addServer($addr, $port);
406 } else {
407 $this->context->addServer($address);
408 }
409 }
410 }
411
412 public function has($key, $type)
413 {
414 return $this->context->get($key) !== false;
415 }
416
417 public function get($key, $type, $callbac, $cbargs, $expire)
418 {
419 $value = $this->context->get($key);
420 if ($value === false) {
421 if (is_null($callback)) {
422 throw new PlNotFoundInCacheException($key);
423 }
424 $value = call_user_func_array($callback, $cbargs);
425 $this->set($key, $type, $value, $expire);
426 }
427 return $value;
428 }
429
430 public function set($key, $type, $var, $expire)
431 {
432 return $this->context->set($key, $var, 0, $expire);
433 }
434
435 public function invalidate($key, $type)
436 {
437 return $this->context->delete($key);
438 }
439}
440
441// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
442?>