2 // +----------------------------------------------------------------------+
4 // +----------------------------------------------------------------------+
5 // | Copyright (c) 1997-2003 The PHP Group |
6 // +----------------------------------------------------------------------+
7 // | This source file is subject to version 2.0 of the PHP license, |
8 // | that is bundled with this package in the file LICENSE, and is |
9 // | available at through the world-wide-web at |
10 // | http://www.php.net/license/2_02.txt. |
11 // | If you did not receive a copy of the PHP license and are unable to |
12 // | obtain it through the world-wide-web, please send a note to |
13 // | license@php.net so we can mail you a copy immediately. |
14 // +----------------------------------------------------------------------+
15 // | Authors: Ulf Wendel <ulf.wendel@phpdoc.de> |
16 // | Sebastian Bergmann <sb@sebastian-bergmann.de> |
17 // | Christian Stocker <chregu@phant.ch> |
18 // +----------------------------------------------------------------------+
20 // $Id: Container.php,v 1.9 2008/10/07 09:04:49 dufuz Exp $
22 require_once 'Cache/Error.php';
25 * Common base class of all cache storage container.
27 * To speed up things we do a preload you should know about, otherwise it might
28 * play you a trick. The Cache controller classes (Cache/Cache, Cache/Output, ...)
29 * usually do something like is (isCached($id) && !isExpired($id)) return $container->load($id).
30 * if you implement isCached(), isExpired() and load() straight ahead, each of this
31 * functions will result in a storage medium (db, file,...) access. This generates too much load.
32 * Now, a simple speculative preload should saves time in most cases. Whenever
33 * one of the mentioned methods is invoked we preload the cached dataset into class variables.
34 * That means that we have only one storage medium access for the sequence
35 * (isCached($id) && !isExpired($id)) return $container->load($id).
36 * The bad thing is that the preloaded data might be outdated meanwhile, which is
37 * unlikely but for you power users, be warned. If you do not want the preload
38 * you should switch it off by setting the class variable $preload to false. Anyway, this is
41 * @author Ulf Wendel <ulf.wendel@phpdoc.de>
42 * @version $Id: Container.php,v 1.9 2008/10/07 09:04:49 dufuz Exp $
51 * Flag indicating wheter to preload datasets.
53 * See the class description for more details.
60 * ID of a preloaded dataset
67 * Cache group of a preloaded dataset
74 * Expiration timestamp of a preloaded dataset.
76 * @var integer 0 means never, endless
81 * Value of a preloaded dataset.
88 * Preloaded userdata field.
95 * Flag indicating that the dataset requested for preloading is unknown.
102 * Encoding mode for cache data: base64 or addslashes() (slash).
104 * @var string base64 or slash
106 var $encoding_mode = 'base64';
109 * Highwater mark - maximum space required by all cache entries.
111 * Whenever the garbage collection runs it checks the amount of space
112 * required by all cache entries. If it's more than n (highwater) bytes
113 * the garbage collection deletes as many entries as necessary to reach the
119 var $highwater = 2048000;
128 var $lowwater = 1536000;
132 * Options that can be set in every derived class using it's constructor.
136 var $allowed_options = array('encoding_mode', 'highwater', 'lowwater');
140 * Loads a dataset from the cache.
142 * @param string dataset ID
143 * @param string cache group
144 * @return mixed dataset value or null on failure
147 function load($id, $group)
149 if ($this->preload) {
150 if ($this->id != $id || $this->group != $group) {
151 $this->preload($id, $group);
153 return $this->cachedata;
156 $ret = $this->fetch($id, $group);
157 if (PEAR::isError($ret)) {
161 list( , $data, ) = $ret;
166 * Returns the userdata field of a cached data set.
168 * @param string dataset ID
169 * @param string cache group
170 * @return string userdata
173 function getUserdata($id, $group)
175 if ($this->preload) {
176 if ($this->id != $id || $this->group != $group) {
177 $this->preload($id, $group);
179 return $this->userdata;
182 $ret = $this->fetch($id, $group);
183 if (PEAR::isError($ret)) {
187 list( , , $userdata) = $ret;
189 } // end func getUserdata
192 * Checks if a dataset is expired.
194 * @param string dataset ID
195 * @param string cache group
196 * @param integer maximum age timestamp
200 function isExpired($id, $group, $max_age)
202 if ($this->preload) {
203 if ($this->id != $id || $this->group != $group) {
204 $this->preload($id, $group);
206 if ($this->unknown) {
210 // check if at all it is cached
211 if (!$this->isCached($id, $group)) {
215 $ret = $this->fetch($id, $group);
216 if (PEAR::isError($ret)) {
220 list($this->expires, , ) = $ret;
224 if (0 == $this->expires) {
227 // you feel fine, Ulf?
228 if ($expired = ($this->expires <= time() || ($max_age && ($this->expires <= $max_age))) ) {
230 $this->remove($id, $group);
231 $this->flushPreload();
234 } // end func isExpired
237 * Checks if a dataset is cached.
239 * @param string dataset ID
240 * @param string cache group
243 function isCached($id, $group)
245 if ($this->preload) {
246 if ($this->id != $id || $this->group != $group) {
247 $this->preload($id, $group);
249 return !($this->unknown);
251 return $this->idExists($id, $group);
252 } // end func isCached
259 * Fetches a dataset from the storage medium.
261 * @param string dataset ID
262 * @param string cache group
263 * @return array format: [expire date, cached data, user data]
264 * @throws Cache_Error
267 function fetch($id, $group)
269 return array(null, null, null);
275 * @param string dataset ID
276 * @param mixed data to store
277 * @param mixed userdefined expire date
278 * @param string cache group
279 * @param string additional userdefined data
281 * @throws Cache_Error
285 function save($id, $data, $expire, $group, $userdata)
287 // QUESTION: Should we update the preload buffer instead?
288 // Don't think so as the sequence save()/load() is unlikely.
289 $this->flushPreload($id, $group);
296 * @param string dataset ID
297 * @param string cache group
302 function remove($id, $group)
304 $this->flushPreload($id, $group);
309 * Flushes the cache - removes all caches datasets from the cache.
311 * @param string If a cache group is given only the group will be flushed
312 * @return integer Number of removed datasets, -1 on failure
316 function flush($group)
318 $this->flushPreload();
323 * Checks if a dataset exists.
325 * @param string dataset ID
326 * @param string cache group
331 function idExists($id, $group)
334 } // end func idExists
337 * Starts the garbage collection.
342 function garbageCollection()
344 $this->flushPreload();
345 } // end func garbageCollection
348 * Does a speculative preload of a dataset
350 * @param string dataset ID
351 * @param string cache group
354 function preload($id, $group)
356 // whatever happens, remember the preloaded ID
358 $this->group = $group;
360 $ret = $this->fetch($id, $group);
361 if (PEAR::isError($ret)) {
365 list($this->expires, $this->cachedata, $this->userdata) = $ret;
366 if ($this->expires === null) {
368 $this->flushPreload();
372 $this->unknown = false;
375 } // end func preload
378 * Flushes the internal preload buffer.
380 * save(), remove() and flush() must call this method
381 * to preevent differences between the preloaded values and
382 * the real cache contents.
384 * @param string dataset ID, if left out the preloaded values will be flushed.
385 * If given the preloaded values will only be flushed if they are
386 * equal to the given id and group
387 * @param string cache group
390 function flushPreload($id = '', $group = 'default')
392 if (!$id || ($this->id == $id && $this->group == $group)) {
393 // clear the internal preload values
396 $this->cachedata = '';
397 $this->userdata = '';
399 $this->unknown = true;
401 } // end func flushPreload
404 * Imports the requested datafields as object variables if allowed
406 * @param array List of fields to be imported as object variables
407 * @param array List of allowed datafields
409 function setOptions($requested, $allowed)
411 foreach ($allowed as $k => $field) {
412 if (isset($requested[$field])) {
413 $this->$field = $requested[$field];
416 } // end func setOptions
419 * Encodes the data for the storage container.
421 * @var mixed data to encode
423 function encode($data)
425 if ($this->encoding_mode == 'base64') {
426 return base64_encode(serialize($data));
428 return serialize($data);
434 * Decodes the data from the storage container.
438 function decode($data)
440 if ($this->encoding_mode == 'base64') {
441 return unserialize(base64_decode($data));
443 return unserialize($data);
449 * Translates human readable/relative times in unixtime
451 * @param mixed can be in the following formats:
452 * human readable : yyyymmddhhmm[ss]] eg: 20010308095100
453 * relative in seconds (1) : +xx eg: +10
454 * relative in seconds (2) : x < 946681200 eg: 10
455 * absolute unixtime : x < 2147483648 eg: 2147483648
456 * see comments in code for details
457 * @return integer unix timestamp
459 function getExpiresAbsolute($expires)
464 //for api-compatibility, one has not to provide a "+",
465 // if integer is < 946681200 (= Jan 01 2000 00:00:00)
466 if ($expires[0] == '+' || $expires < 946681200) {
467 return(time() + $expires);
468 } elseif ($expires < 100000000000) {
469 //if integer is < 100000000000 (= in 3140 years),
470 // it must be an absolut unixtime
471 // (since the "human readable" definition asks for a higher number)
474 // else it's "human readable";
475 $year = substr($expires, 0, 4);
476 $month = substr($expires, 4, 2);
477 $day = substr($expires, 6, 2);
478 $hour = substr($expires, 8, 2);
479 $minute = substr($expires, 10, 2);
480 $second = substr($expires, 12, 2);
481 return mktime($hour, $minute, $second, $month, $day, $year);
484 } // end func getExpireAbsolute
486 } // end class Container