1<?php 2// +----------------------------------------------------------------------+ 3// | PEAR :: Cache | 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// +----------------------------------------------------------------------+ 19// 20// $Id: Container.php 293863 2010-01-23 03:46:52Z clockwerx $ 21 22require_once 'Cache/Error.php'; 23 24/** 25* Common base class of all cache storage container. 26* 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 39* not recommended! 40* 41* @author Ulf Wendel <ulf.wendel@phpdoc.de> 42* @version $Id: Container.php 293863 2010-01-23 03:46:52Z clockwerx $ 43* @package Cache 44* @access public 45* @abstract 46*/ 47class Cache_Container 48{ 49 50 /** 51 * Flag indicating wheter to preload datasets. 52 * 53 * See the class description for more details. 54 * 55 * @var boolean 56 */ 57 var $preload = true; 58 59 /** 60 * ID of a preloaded dataset 61 * 62 * @var string 63 */ 64 var $id = ''; 65 66 /** 67 * Cache group of a preloaded dataset 68 * 69 * @var string 70 */ 71 var $group = ''; 72 73 /** 74 * Expiration timestamp of a preloaded dataset. 75 * 76 * @var integer 0 means never, endless 77 */ 78 var $expires = 0; 79 80 /** 81 * Value of a preloaded dataset. 82 * 83 * @var string 84 */ 85 var $cachedata = ''; 86 87 /** 88 * Preloaded userdata field. 89 * 90 * @var string 91 */ 92 var $userdata = ''; 93 94 /** 95 * Flag indicating that the dataset requested for preloading is unknown. 96 * 97 * @var boolean 98 */ 99 var $unknown = true; 100 101 /** 102 * Encoding mode for cache data: base64 or addslashes() (slash). 103 * 104 * @var string base64 or slash 105 */ 106 var $encoding_mode = 'base64'; 107 108 /** 109 * Highwater mark - maximum space required by all cache entries. 110 * 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 114 * lowwater mark. 115 * 116 * @var int 117 * @see lowwater 118 */ 119 var $highwater = 2048000; 120 121 122 /** 123 * Lowwater mark 124 * 125 * @var int 126 * @see highwater 127 */ 128 var $lowwater = 1536000; 129 130 131 /** 132 * Options that can be set in every derived class using it's constructor. 133 * 134 * @var array 135 */ 136 var $allowed_options = array('encoding_mode', 'highwater', 'lowwater'); 137 138 139 /** 140 * Loads a dataset from the cache. 141 * 142 * @param string dataset ID 143 * @param string cache group 144 * @return mixed dataset value or null on failure 145 * @access public 146 */ 147 function load($id, $group) 148 { 149 if ($this->preload) { 150 if ($this->id != $id || $this->group != $group) { 151 $this->preload($id, $group); 152 } 153 return $this->cachedata; 154 } 155 156 $ret = $this->fetch($id, $group); 157 if (PEAR::isError($ret)) { 158 return $ret; 159 } 160 161 list( , $data, ) = $ret; 162 return $data; 163 } // end func load 164 165 /** 166 * Returns the userdata field of a cached data set. 167 * 168 * @param string dataset ID 169 * @param string cache group 170 * @return string userdata 171 * @access public 172 */ 173 function getUserdata($id, $group) 174 { 175 if ($this->preload) { 176 if ($this->id != $id || $this->group != $group) { 177 $this->preload($id, $group); 178 } 179 return $this->userdata; 180 } 181 182 $ret = $this->fetch($id, $group); 183 if (PEAR::isError($ret)) { 184 return $ret; 185 } 186 187 list( , , $userdata) = $ret; 188 return $userdata; 189 } // end func getUserdata 190 191 /** 192 * Checks if a dataset is expired. 193 * 194 * @param string dataset ID 195 * @param string cache group 196 * @param integer maximum age timestamp 197 * @return boolean 198 * @access public 199 */ 200 function isExpired($id, $group, $max_age) 201 { 202 if ($this->preload) { 203 if ($this->id != $id || $this->group != $group) { 204 $this->preload($id, $group); 205 } 206 if ($this->unknown) { 207 return false; 208 } 209 } else { 210 // check if at all it is cached 211 if (!$this->isCached($id, $group)) { 212 return false; 213 } 214 // I'm lazy... 215 $ret = $this->fetch($id, $group); 216 if (PEAR::isError($ret)) { 217 return $ret; 218 } 219 220 list($this->expires, , ) = $ret; 221 } 222 223 // endless 224 if (0 == $this->expires) { 225 return false; 226 } 227 // you feel fine, Ulf? 228 if ($expired = ($this->expires <= time() || ($max_age && ($this->expires <= $max_age))) ) { 229 230 $this->remove($id, $group); 231 $this->flushPreload(); 232 } 233 return $expired; 234 } // end func isExpired 235 236 /** 237 * Checks if a dataset is cached. 238 * 239 * @param string dataset ID 240 * @param string cache group 241 * @return boolean 242 */ 243 function isCached($id, $group) 244 { 245 if ($this->preload) { 246 if ($this->id != $id || $this->group != $group) { 247 $this->preload($id, $group); 248 } 249 return !($this->unknown); 250 } 251 return $this->idExists($id, $group); 252 } // end func isCached 253 254 // 255 // abstract methods 256 // 257 258 /** 259 * Fetches a dataset from the storage medium. 260 * 261 * @param string dataset ID 262 * @param string cache group 263 * @return array format: [expire date, cached data, user data] 264 * @throws Cache_Error 265 * @abstract 266 */ 267 function fetch($id, $group) 268 { 269 return array(null, null, null); 270 } // end func fetch 271 272 /** 273 * Stores a dataset. 274 * 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 280 * @return boolean 281 * @throws Cache_Error 282 * @access public 283 * @abstract 284 */ 285 function save($id, $data, $expire, $group, $userdata) 286 { 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); 290 return null; 291 } // end func save 292 293 /** 294 * Removes a dataset. 295 * 296 * @param string dataset ID 297 * @param string cache group 298 * @return boolean 299 * @access public 300 * @abstract 301 */ 302 function remove($id, $group) 303 { 304 $this->flushPreload($id, $group); 305 return null; 306 } // end func remove 307 308 /** 309 * Flushes the cache - removes all caches datasets from the cache. 310 * 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 313 * @access public 314 * @abstract 315 */ 316 function flush($group) 317 { 318 $this->flushPreload(); 319 return null; 320 } // end func flush 321 322 /** 323 * Checks if a dataset exists. 324 * 325 * @param string dataset ID 326 * @param string cache group 327 * @return boolean 328 * @access public 329 * @abstract 330 */ 331 function idExists($id, $group) 332 { 333 return null; 334 } // end func idExists 335 336 /** 337 * Starts the garbage collection. 338 * 339 * @param int $gc_maxlifetime The maximum lifetime (seconds) for a cache 340 * entry. Implemented by containers, 341 * 342 * @access public 343 * @abstract 344 */ 345 function garbageCollection($gc_maxlifetime) 346 { 347 $this->flushPreload(); 348 } // end func garbageCollection 349 350 /** 351 * Does a speculative preload of a dataset 352 * 353 * @param string dataset ID 354 * @param string cache group 355 * @return boolean 356 */ 357 function preload($id, $group) 358 { 359 // whatever happens, remember the preloaded ID 360 $this->id = $id; 361 $this->group = $group; 362 363 $ret = $this->fetch($id, $group); 364 if (PEAR::isError($ret)) { 365 return $ret; 366 } 367 368 list($this->expires, $this->cachedata, $this->userdata) = $ret; 369 if ($this->expires === null) { 370 // Uuups, unknown ID 371 $this->flushPreload(); 372 return false; 373 } 374 375 $this->unknown = false; 376 377 return true; 378 } // end func preload 379 380 /** 381 * Flushes the internal preload buffer. 382 * 383 * save(), remove() and flush() must call this method 384 * to preevent differences between the preloaded values and 385 * the real cache contents. 386 * 387 * @param string dataset ID, if left out the preloaded values will be flushed. 388 * If given the preloaded values will only be flushed if they are 389 * equal to the given id and group 390 * @param string cache group 391 * @see preload() 392 */ 393 function flushPreload($id = '', $group = 'default') 394 { 395 if (!$id || ($this->id == $id && $this->group == $group)) { 396 // clear the internal preload values 397 $this->id = ''; 398 $this->group = ''; 399 $this->cachedata = ''; 400 $this->userdata = ''; 401 $this->expires = -1; 402 $this->unknown = true; 403 } 404 } // end func flushPreload 405 406 /** 407 * Imports the requested datafields as object variables if allowed 408 * 409 * @param array List of fields to be imported as object variables 410 * @param array List of allowed datafields 411 */ 412 function setOptions($requested, $allowed) 413 { 414 foreach ($allowed as $k => $field) { 415 if (isset($requested[$field])) { 416 $this->$field = $requested[$field]; 417 } 418 } 419 } // end func setOptions 420 421 /** 422 * Encodes the data for the storage container. 423 * 424 * @var mixed data to encode 425 */ 426 function encode($data) 427 { 428 if ($this->encoding_mode == 'base64') { 429 return base64_encode(serialize($data)); 430 } else { 431 return serialize($data); 432 } 433 } // end func encode 434 435 436 /** 437 * Decodes the data from the storage container. 438 * 439 * @var mixed 440 */ 441 function decode($data) 442 { 443 if ($this->encoding_mode == 'base64') { 444 return unserialize(base64_decode($data)); 445 } else { 446 return unserialize($data); 447 } 448 } // end func decode 449 450 451 /** 452 * Translates human readable/relative times in unixtime 453 * 454 * @param mixed can be in the following formats: 455 * human readable : yyyymmddhhmm[ss]] eg: 20010308095100 456 * relative in seconds (1) : +xx eg: +10 457 * relative in seconds (2) : x < 946681200 eg: 10 458 * absolute unixtime : x < 2147483648 eg: 2147483648 459 * see comments in code for details 460 * @return integer unix timestamp 461 */ 462 function getExpiresAbsolute($expires) 463 { 464 if (!$expires) { 465 return 0; 466 } 467 //for api-compatibility, one has not to provide a "+", 468 // if integer is < 946681200 (= Jan 01 2000 00:00:00) 469 if ($expires[0] == '+' || $expires < 946681200) { 470 return(time() + $expires); 471 } elseif ($expires < 100000000000) { 472 //if integer is < 100000000000 (= in 3140 years), 473 // it must be an absolut unixtime 474 // (since the "human readable" definition asks for a higher number) 475 return $expires; 476 } else { 477 // else it's "human readable"; 478 $year = substr($expires, 0, 4); 479 $month = substr($expires, 4, 2); 480 $day = substr($expires, 6, 2); 481 $hour = substr($expires, 8, 2); 482 $minute = substr($expires, 10, 2); 483 $second = substr($expires, 12, 2); 484 return mktime($hour, $minute, $second, $month, $day, $year); 485 } 486 487 } // end func getExpireAbsolute 488 489} // end class Container 490?> 491