upload
[pear] / Cache / Container.php
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,v 1.9 2008/10/07 09:04:49 dufuz Exp $
21
22 require_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,v 1.9 2008/10/07 09:04:49 dufuz Exp $
43 * @package  Cache
44 * @access   public
45 * @abstract
46 */
47 class 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     * @access   public
340     * @abstract
341     */
342     function garbageCollection()
343     {
344         $this->flushPreload();
345     } // end func garbageCollection
346
347     /**
348     * Does a speculative preload of a dataset
349     *
350     * @param    string  dataset ID
351     * @param    string  cache group
352     * @return   boolean
353     */
354     function preload($id, $group)
355     {
356         // whatever happens, remember the preloaded ID
357         $this->id = $id;
358         $this->group = $group;
359
360         $ret = $this->fetch($id, $group);
361         if (PEAR::isError($ret)) {
362             return $ret;
363         }
364
365         list($this->expires, $this->cachedata, $this->userdata) = $ret;
366         if ($this->expires === null) {
367             // Uuups, unknown ID
368             $this->flushPreload();
369             return false;
370         }
371
372         $this->unknown = false;
373
374         return true;
375     } // end func preload
376
377     /**
378     * Flushes the internal preload buffer.
379     *
380     * save(), remove() and flush() must call this method
381     * to preevent differences between the preloaded values and
382     * the real cache contents.
383     *
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
388     * @see  preload()
389     */
390     function flushPreload($id = '', $group = 'default')
391     {
392         if (!$id || ($this->id == $id && $this->group == $group)) {
393             // clear the internal preload values
394             $this->id = '';
395             $this->group = '';
396             $this->cachedata = '';
397             $this->userdata = '';
398             $this->expires = -1;
399             $this->unknown = true;
400         }
401     } // end func flushPreload
402
403     /**
404     * Imports the requested datafields as object variables if allowed
405     *
406     * @param    array   List of fields to be imported as object variables
407     * @param    array   List of allowed datafields
408     */
409     function setOptions($requested, $allowed)
410     {
411         foreach ($allowed as $k => $field) {
412             if (isset($requested[$field])) {
413                 $this->$field = $requested[$field];
414             }
415         }
416     } // end func setOptions
417
418     /**
419     * Encodes the data for the storage container.
420     *
421     * @var  mixed data to encode
422     */
423     function encode($data)
424     {
425         if ($this->encoding_mode == 'base64') {
426             return base64_encode(serialize($data));
427         } else {
428             return serialize($data);
429         }
430     } // end func encode
431
432
433     /**
434     * Decodes the data from the storage container.
435     *
436     * @var  mixed
437     */
438     function decode($data)
439     {
440         if ($this->encoding_mode == 'base64') {
441             return unserialize(base64_decode($data));
442         } else {
443             return unserialize($data);
444         }
445     } // end func decode
446
447
448     /**
449     * Translates human readable/relative times in unixtime
450     *
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
458     */
459     function getExpiresAbsolute($expires)
460     {
461         if (!$expires) {
462             return 0;
463         }
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)
472             return $expires;
473         } else {
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);
482         }
483
484     } // end func getExpireAbsolute
485
486 } // end class Container
487 ?>