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 require_once 'Cache/Error.php';
23 * Common base class of all cache storage container.
25 * To speed up things we do a preload you should know about, otherwise it might
26 * play you a trick. The Cache controller classes (Cache/Cache, Cache/Output, ...)
27 * usually do something like is (isCached($id) && !isExpired($id)) return $container->load($id).
28 * if you implement isCached(), isExpired() and load() straight ahead, each of this
29 * functions will result in a storage medium (db, file,...) access. This generates too much load.
30 * Now, a simple speculative preload should saves time in most cases. Whenever
31 * one of the mentioned methods is invoked we preload the cached dataset into class variables.
32 * That means that we have only one storage medium access for the sequence
33 * (isCached($id) && !isExpired($id)) return $container->load($id).
34 * The bad thing is that the preloaded data might be outdated meanwhile, which is
35 * unlikely but for you power users, be warned. If you do not want the preload
36 * you should switch it off by setting the class variable $preload to false. Anyway, this is
39 * @author Ulf Wendel <ulf.wendel@phpdoc.de>
45 class Cache_Container {
48 * Flag indicating wheter to preload datasets.
50 * See the class description for more details.
57 * ID of a preloaded dataset
64 * Cache group of a preloaded dataset
71 * Expiration timestamp of a preloaded dataset.
73 * @var integer 0 means never, endless
78 * Value of a preloaded dataset.
85 * Preloaded userdata field.
92 * Flag indicating that the dataset requested for preloading is unknown.
99 * Encoding mode for cache data: base64 or addslashes() (slash).
101 * @var string base64 or slash
103 var $encoding_mode = 'base64';
106 * Highwater mark - maximum space required by all cache entries.
108 * Whenever the garbage collection runs it checks the amount of space
109 * required by all cache entries. If it's more than n (highwater) bytes
110 * the garbage collection deletes as many entries as necessary to reach the
116 var $highwater = 2048000;
125 var $lowwater = 1536000;
129 * Options that can be set in every derived class using it's constructor.
133 var $allowed_options = array('encoding_mode', 'highwater', 'lowwater');
136 * Loads a dataset from the cache.
138 * @param string dataset ID
139 * @param string cache group
140 * @return mixed dataset value or NULL on failure
143 function load($id, $group) {
144 if ($this->preload) {
145 if ($this->id != $id || $this->group != $group)
146 $this->preload($id, $group);
148 return $this->cachedata;
150 list( , $data, ) = $this->fetch($id, $group);
156 * Returns the userdata field of a cached data set.
158 * @param string dataset ID
159 * @param string cache group
160 * @return string userdata
163 function getUserdata($id, $group) {
164 if ($this->preload) {
165 if ($this->id != $id || $this->group != $group)
166 $this->preload($id, $group);
168 return $this->userdata;
170 list( , , $userdata) = $this->fetch($id, $group);
173 } // end func getUserdata
176 * Checks if a dataset is expired.
178 * @param string dataset ID
179 * @param string cache group
180 * @param integer maximum age timestamp
184 function isExpired($id, $group, $max_age) {
185 if ($this->preload) {
186 if ($this->id != $id || $this->group != $group)
187 $this->preload($id, $group);
192 // check if at all it is cached
193 if (!$this->isCached($id, $group))
197 list($this->expires, , ) = $this->fetch($id, $group);
201 if (0 == $this->expires)
204 // you feel fine, Ulf?
205 if ($expired = ($this->expires <= time() || ($max_age && ($this->expires <= $max_age))) ) {
207 $this->remove($id, $group);
208 $this->flushPreload();
211 } // end func isExpired
214 * Checks if a dataset is cached.
216 * @param string dataset ID
217 * @param string cache group
220 function isCached($id, $group) {
221 if ($this->preload) {
222 if ($this->id != $id || $this->group != $group)
223 $this->preload($id, $group);
225 return !($this->unknown);
227 return $this->idExists($id, $group);
229 } // end func isCached
236 * Fetches a dataset from the storage medium.
238 * @param string dataset ID
239 * @param string cache group
240 * @return array format: [expire date, cached data, user data]
241 * @throws Cache_Error
244 function fetch($id, $group) {
245 return array(NULL, NULL, NULL);
251 * @param string dataset ID
252 * @param mixed data to store
253 * @param mixed userdefined expire date
254 * @param string cache group
255 * @param string additional userdefined data
257 * @throws Cache_Error
261 function save($id, $data, $expire, $group, $userdata) {
262 // QUESTION: Should we update the preload buffer instead?
263 // Don't think so as the sequence save()/load() is unlikely.
264 $this->flushPreload($id, $group);
272 * @param string dataset ID
273 * @param string cache group
278 function remove($id, $group) {
279 $this->flushPreload($id, $group);
284 * Flushes the cache - removes all caches datasets from the cache.
286 * @param string If a cache group is given only the group will be flushed
287 * @return integer Number of removed datasets, -1 on failure
291 function flush($group) {
292 $this->flushPreload();
297 * Checks if a dataset exists.
299 * @param string dataset ID
300 * @param string cache group
305 function idExists($id, $group) {
307 } // end func idExists
310 * Starts the garbage collection.
315 function garbageCollection() {
316 $this->flushPreload();
317 } // end func garbageCollection
320 * Does a speculative preload of a dataset
322 * @param string dataset ID
323 * @param string cache group
326 function preload($id, $group) {
327 // whatever happens, remember the preloaded ID
329 $this->group = $group;
331 list($this->expires, $this->cachedata, $this->userdata) = $this->fetch($id, $group);
333 if (NULL === $this->expires) {
335 $this->flushPreload();
340 $this->unknown = false;
343 } // end func preload
346 * Flushes the internal preload buffer.
348 * save(), remove() and flush() must call this method
349 * to preevent differences between the preloaded values and
350 * the real cache contents.
352 * @param string dataset ID, if left out the preloaded values will be flushed.
353 * If given the preloaded values will only be flushed if they are
354 * equal to the given id and group
355 * @param string cache group
358 function flushPreload($id = '', $group = 'default') {
359 if (!$id || ($this->id == $id && $this->group == $group)) {
360 // clear the internal preload values
363 $this->cachedata = '';
364 $this->userdata = '';
366 $this->unknown = true;
368 } // end func flushPreload
371 * Imports the requested datafields as object variables if allowed
373 * @param array List of fields to be imported as object variables
374 * @param array List of allowed datafields
376 function setOptions($requested, $allowed) {
377 foreach ($allowed as $k => $field)
378 if (isset($requested[$field]))
379 $this->$field = $requested[$field];
381 } // end func setOptions
384 * Encodes the data for the storage container.
386 * @var mixed data to encode
388 function encode($data) {
389 if ('base64' == $this->encoding_mode)
390 return base64_encode(serialize($data));
392 return serialize($data);
396 * Decodes the data from the storage container.
400 function decode($data) {
401 if ('base64' == $this->encoding_mode)
402 return unserialize(base64_decode($data));
404 return unserialize($data);
408 * Translates human readable/relative times in unixtime
410 * @param mixed can be in the following formats:
411 * human readable : yyyymmddhhmm[ss]] eg: 20010308095100
412 * relative in seconds (1) : +xx eg: +10
413 * relative in seconds (2) : x < 946681200 eg: 10
414 * absolute unixtime : x < 2147483648 eg: 2147483648
415 * see comments in code for details
416 * @return integer unix timestamp
418 function getExpiresAbsolute($expires)
422 //for api-compatibility, one has not to provide a "+",
423 // if integer is < 946681200 (= Jan 01 2000 00:00:00)
424 if ('+' == $expires[0] || $expires < 946681200)
426 return(time() + $expires);
428 //if integer is < 100000000000 (= in 3140 years),
429 // it must be an absolut unixtime
430 // (since the "human readable" definition asks for a higher number)
431 elseif ($expires < 100000000000)
435 // else it's "human readable";
438 $year = substr($expires, 0, 4);
439 $month = substr($expires, 4, 2);
440 $day = substr($expires, 6, 2);
441 $hour = substr($expires, 8, 2);
442 $minute = substr($expires, 10, 2);
443 $second = substr($expires, 12, 2);
444 return mktime($hour, $minute, $second, $month, $day, $year);
447 } // end func getExpireAbsolute
449 } // end class Container