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 // +----------------------------------------------------------------------+
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>
47 class Cache_Container {
50 * Flag indicating wheter to preload datasets.
52 * See the class description for more details.
59 * ID of a preloaded dataset
66 * Cache group of a preloaded dataset
73 * Expiration timestamp of a preloaded dataset.
75 * @var integer 0 means never, endless
80 * Value of a preloaded dataset.
87 * Preloaded userdata field.
94 * Flag indicating that the dataset requested for preloading is unknown.
101 * Encoding mode for cache data: base64 or addslashes() (slash).
103 * @var string base64 or slash
105 var $encoding_mode = 'base64';
108 * Highwater mark - maximum space required by all cache entries.
110 * Whenever the garbage collection runs it checks the amount of space
111 * required by all cache entries. If it's more than n (highwater) bytes
112 * the garbage collection deletes as many entries as necessary to reach the
118 var $highwater = 2048000;
127 var $lowwater = 1536000;
131 * Options that can be set in every derived class using it's constructor.
135 var $allowed_options = array('encoding_mode', 'highwater', 'lowwater');
139 * Loads a dataset from the cache.
141 * @param string dataset ID
142 * @param string cache group
143 * @return mixed dataset value or NULL on failure
146 function load($id, $group) {
147 if ($this->preload) {
148 if ($this->id != $id || $this->group != $group)
149 $this->preload($id, $group);
151 return $this->cachedata;
153 list( , $data, ) = $this->fetch($id, $group);
159 * Returns the userdata field of a cached data set.
161 * @param string dataset ID
162 * @param string cache group
163 * @return string userdata
166 function getUserdata($id, $group) {
167 if ($this->preload) {
168 if ($this->id != $id || $this->group != $group)
169 $this->preload($id, $group);
171 return $this->userdata;
173 list( , , $userdata) = $this->fetch($id, $group);
176 } // end func getUserdata
179 * Checks if a dataset is expired.
181 * @param string dataset ID
182 * @param string cache group
183 * @param integer maximum age timestamp
187 function isExpired($id, $group, $max_age) {
188 if ($this->preload) {
189 if ($this->id != $id || $this->group != $group)
190 $this->preload($id, $group);
195 // check if at all it is cached
196 if (!$this->isCached($id, $group))
200 list($this->expires, , ) = $this->fetch($id, $group);
204 if (0 == $this->expires)
207 // you feel fine, Ulf?
208 if ($expired = ($this->expires <= time() || ($max_age && ($this->expires <= $max_age))) ) {
210 $this->remove($id, $group);
211 $this->flushPreload();
214 } // end func isExpired
217 * Checks if a dataset is cached.
219 * @param string dataset ID
220 * @param string cache group
223 function isCached($id, $group) {
224 if ($this->preload) {
225 if ($this->id != $id || $this->group != $group)
226 $this->preload($id, $group);
228 return !($this->unknown);
230 return $this->idExists($id, $group);
232 } // end func isCached
239 * Fetches a dataset from the storage medium.
241 * @param string dataset ID
242 * @param string cache group
243 * @return array format: [expire date, cached data, user data]
244 * @throws Cache_Error
247 function fetch($id, $group) {
248 return array(NULL, NULL, NULL);
254 * @param string dataset ID
255 * @param mixed data to store
256 * @param mixed userdefined expire date
257 * @param string cache group
258 * @param string additional userdefined data
260 * @throws Cache_Error
264 function save($id, $data, $expire, $group, $userdata) {
265 // QUESTION: Should we update the preload buffer instead?
266 // Don't think so as the sequence save()/load() is unlikely.
267 $this->flushPreload($id, $group);
275 * @param string dataset ID
276 * @param string cache group
281 function remove($id, $group) {
282 $this->flushPreload($id, $group);
287 * Flushes the cache - removes all caches datasets from the cache.
289 * @param string If a cache group is given only the group will be flushed
290 * @return integer Number of removed datasets, -1 on failure
294 function flush($group) {
295 $this->flushPreload();
300 * Checks if a dataset exists.
302 * @param string dataset ID
303 * @param string cache group
308 function idExists($id, $group) {
310 } // end func idExists
313 * Starts the garbage collection.
318 function garbageCollection() {
319 $this->flushPreload();
320 } // end func garbageCollection
323 * Does a speculative preload of a dataset
325 * @param string dataset ID
326 * @param string cache group
329 function preload($id, $group) {
330 // whatever happens, remember the preloaded ID
332 $this->group = $group;
334 list($this->expires, $this->cachedata, $this->userdata) = $this->fetch($id, $group);
336 if (NULL === $this->expires) {
338 $this->flushPreload();
343 $this->unknown = false;
346 } // end func preload
349 * Flushes the internal preload buffer.
351 * save(), remove() and flush() must call this method
352 * to preevent differences between the preloaded values and
353 * the real cache contents.
355 * @param string dataset ID, if left out the preloaded values will be flushed.
356 * If given the preloaded values will only be flushed if they are
357 * equal to the given id and group
358 * @param string cache group
361 function flushPreload($id = '', $group = 'default') {
362 if (!$id || ($this->id == $id && $this->group == $group)) {
363 // clear the internal preload values
366 $this->cachedata = '';
367 $this->userdata = '';
369 $this->unknown = true;
371 } // end func flushPreload
374 * Imports the requested datafields as object variables if allowed
376 * @param array List of fields to be imported as object variables
377 * @param array List of allowed datafields
379 function setOptions($requested, $allowed) {
380 foreach ($allowed as $k => $field)
381 if (isset($requested[$field]))
382 $this->$field = $requested[$field];
384 } // end func setOptions
387 * Encodes the data for the storage container.
389 * @var mixed data to encode
391 function encode($data) {
392 if ('base64' == $this->encoding_mode)
393 return base64_encode(serialize($data));
395 return serialize($data);
400 * Decodes the data from the storage container.
404 function decode($data) {
405 if ('base64' == $this->encoding_mode)
406 return unserialize(base64_decode($data));
408 return unserialize($data);
413 * Translates human readable/relative times in unixtime
415 * @param mixed can be in the following formats:
416 * human readable : yyyymmddhhmm[ss]] eg: 20010308095100
417 * relative in seconds (1) : +xx eg: +10
418 * relative in seconds (2) : x < 946681200 eg: 10
419 * absolute unixtime : x < 2147483648 eg: 2147483648
420 * see comments in code for details
421 * @return integer unix timestamp
423 function getExpiresAbsolute($expires)
427 //for api-compatibility, one has not to provide a "+",
428 // if integer is < 946681200 (= Jan 01 2000 00:00:00)
429 if ('+' == $expires[0] || $expires < 946681200)
431 return(time() + $expires);
433 //if integer is < 100000000000 (= in 3140 years),
434 // it must be an absolut unixtime
435 // (since the "human readable" definition asks for a higher number)
436 elseif ($expires < 100000000000)
440 // else it's "human readable";
443 $year = substr($expires, 0, 4);
444 $month = substr($expires, 4, 2);
445 $day = substr($expires, 6, 2);
446 $hour = substr($expires, 8, 2);
447 $minute = substr($expires, 10, 2);
448 $second = substr($expires, 12, 2);
449 return mktime($hour, $minute, $second, $month, $day, $year);
452 } // end func getExpireAbsolute
454 } // end class Container