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// +----------------------------------------------------------------------+
18//
19// $Id: Cache.php 267047 2008-10-07 08:58:46Z dufuz $
20
21require_once 'PEAR.php';
22require_once 'Cache/Error.php';
23
24/**
25* Cache is a base class for cache implementations.
26*
27* The pear cache module is a generic data cache which can be used to
28* cache script runs. The idea behind the cache is quite simple. If you have
29* the same input parameters for whatever tasks/algorithm you use you'll
30* usually get the same output. So why not caching templates, functions calls,
31* graphic generation etc. Caching certain actions e.g. XSLT tranformations
32* saves you lots of time.
33*
34* The design of the cache reminds of PHPLibs session implementation. A
35* (PHPLib: session) controller uses storage container (PHPLib: ct_*.inc) to save
36* certain data (PHPLib: session data). In contrast to the session stuff it's up to
37* you to generate an ID for the data to cache. If you're using the output cache
38* you might use the script name as a seed for cache::generateID(), if your using the
39* function cache you'd use an array with all function parameters.
40*
41* Usage example of the generic data cache:
42*
43* require_once('Cache.php');
44*
45* $cache = new Cache('file', array('cache_dir' => 'cache/') );
46* $id = $cache->generateID('testentry');
47*
48* if ($data = $cache->get($id)) {
49*    print "Cache hit.<br>Data: $data";
50*
51* } else {
52*   $data = 'data of any kind';
53*   $cache->save($id, $data);
54*   print 'Cache miss.<br>';
55* }
56*
57* WARNING: No File/DB-Table-Row locking is implemented yet,
58*          it's possible, that you get corrupted data-entries under
59*          bad circumstances  (especially with the file container)
60*
61* @author   Ulf Wendel <ulf.wendel@phpdoc.de>
62* @version  $Id: Cache.php 267047 2008-10-07 08:58:46Z dufuz $
63* @package  Cache
64* @access   public
65*/
66class Cache extends PEAR
67{
68
69    /**
70    * Enables / disables caching.
71    *
72    * TODO: Add explanation what this is good for.
73    *
74    * @var      boolean
75    * @access   private
76    */
77    var $caching = true;
78
79    /**
80    * Garbage collection: probability in seconds
81    *
82    * If set to a value above 0 a garbage collection will
83    * flush all cache entries older than the specified number
84    * of seconds.
85    *
86    * @var      integer
87    * @see      $gc_probability, $gc_maxlifetime
88    * @access   public
89    */
90    var $gc_time  = 1;
91
92    /**
93    * Garbage collection: probability in percent
94    *
95    * TODO: Add an explanation.
96    *
97    * @var      integer     0 => never
98    * @see      $gc_time, $gc_maxlifetime
99    * @access   public
100    */
101    var $gc_probability = 1;
102
103    /**
104    * Garbage collection: delete all entries not use for n seconds.
105    *
106    * Default is one day, 60 * 60 * 24 = 86400 seconds.
107    *
108    * @var  integer
109    * @see  $gc_probability, $gc_time
110    */
111    var $gc_maxlifetime = 86400;
112
113    /**
114    * Storage container object.
115    *
116    * @var  object Cache_Container
117    */
118    var $container;
119
120    //
121    // public methods
122    //
123
124    /**
125    *
126    * @param    string  Name of container class
127    * @param    array   Array with container class options
128    */
129    function Cache($container, $container_options = '')
130    {
131        $this->PEAR();
132        $container = strtolower($container);
133        $container_class = 'Cache_Container_' . $container;
134        $container_classfile = 'Cache/Container/' . $container . '.php';
135
136        include_once $container_classfile;
137        $this->container = new $container_class($container_options);
138    }
139
140    //deconstructor
141    function _Cache()
142    {
143        $this->garbageCollection();
144    }
145
146    /**
147     * Returns the current caching state.
148     *
149     * @return  boolean     The current caching state.
150     * @access  public
151     */
152    function getCaching()
153    {
154        return $this->caching;
155    }
156
157    /**
158     * Enables or disables caching.
159     *
160     * @param   boolean     The new caching state.
161     * @access  public
162     */
163    function setCaching($state)
164    {
165        $this->caching = $state;
166    }
167
168    /**
169    * Returns the requested dataset it if exists and is not expired
170    *
171    * @param    string  dataset ID
172    * @param    string  cache group
173    * @return   mixed   cached data or null on failure
174    * @access   public
175    */
176    function get($id, $group = 'default')
177    {
178        if (!$this->caching) {
179            return '';
180        }
181
182        if ($this->isCached($id, $group) && !$this->isExpired($id, $group)) {
183            return $this->load($id, $group);
184        }
185        return null;
186    } // end func get
187
188    /**
189    * Stores the given data in the cache.
190    *
191    * @param    string  dataset ID used as cache identifier
192    * @param    mixed   data to cache
193    * @param    integer lifetime of the cached data in seconds - 0 for endless
194    * @param    string  cache group
195    * @return   boolean
196    * @access   public
197    */
198    function save($id, $data, $expires = 0, $group = 'default')
199    {
200        if (!$this->caching) {
201            return true;
202        }
203        return $this->extSave($id, $data, '',$expires, $group);
204    } // end func save
205
206    /**
207    * Stores a dataset with additional userdefined data.
208    *
209    * @param    string  dataset ID
210    * @param    mixed   data to store
211    * @param    string  additional userdefined data
212    * @param    mixed   userdefined expire date
213    * @param    string  cache group
214    * @return   boolean
215    * @throws   Cache_Error
216    * @access   public
217    * @see      getUserdata()
218    */
219    function extSave($id, $cachedata, $userdata, $expires = 0, $group = 'default')
220    {
221        if (!$this->caching) {
222            return true;
223        }
224        return $this->container->save($id, $cachedata, $expires, $group, $userdata);
225    } // end func extSave
226
227    /**
228    * Loads the given ID from the cache.
229    *
230    * @param    string  dataset ID
231    * @param    string  cache group
232    * @return   mixed   cached data or null on failure
233    * @access   public
234    */
235    function load($id, $group = 'default')
236    {
237        if (!$this->caching) {
238            return '';
239        }
240        return $this->container->load($id, $group);
241    } // end func load
242
243    /**
244    * Returns the userdata field of a cached data set.
245    *
246    * @param    string  dataset ID
247    * @param    string  cache group
248    * @return   string  userdata
249    * @access   public
250    * @see      extSave()
251    */
252    function getUserdata($id, $group = 'default')
253    {
254        if (!$this->caching) {
255            return '';
256        }
257        return $this->container->getUserdata($id, $group);
258    } // end func getUserdata
259
260    /**
261    * Removes the specified dataset from the cache.
262    *
263    * @param    string  dataset ID
264    * @param    string  cache group
265    * @return   boolean
266    * @access   public
267    */
268    function remove($id, $group = 'default')
269    {
270        if (!$this->caching) {
271            return true;
272        }
273        return $this->container->remove($id, $group);
274    } // end func remove
275
276    /**
277    * Flushes the cache - removes all data from it
278    *
279    * @param    string  cache group, if empty all groups will be flashed
280    * @return   integer number of removed datasets
281    */
282    function flush($group = 'default')
283    {
284        if (!$this->caching) {
285            return true;
286        }
287        return $this->container->flush($group);
288    } // end func flush
289
290    /**
291    * Checks if a dataset exists.
292    *
293    * Note: this does not say that the cached data is not expired!
294    *
295    * @param    string  dataset ID
296    * @param    string  cache group
297    * @return   boolean
298    * @access   public
299    */
300    function isCached($id, $group = 'default')
301    {
302        if (!$this->caching) {
303            return false;
304        }
305        return $this->container->isCached($id, $group);
306    } // end func isCached
307
308    /**
309    * Checks if a dataset is expired
310    *
311    * @param    string  dataset ID
312    * @param    string  cache group
313    * @param    integer maximum age for the cached data in seconds - 0 for endless
314    *                   If the cached data is older but the given lifetime it will
315    *                   be removed from the cache. You don't have to provide this
316    *                   argument if you call isExpired(). Every dataset knows
317    *                   it's expire date and will be removed automatically. Use
318    *                   this only if you know what you're doing...
319    * @return   boolean
320    * @access   public
321    */
322    function isExpired($id, $group = 'default', $max_age = 0)
323    {
324        if (!$this->caching) {
325            return true;
326        }
327        return $this->container->isExpired($id, $group, $max_age);
328    } // end func isExpired
329
330    /**
331    * Generates a "unique" ID for the given value
332    *
333    * This is a quick but dirty hack to get a "unique" ID for a any kind of variable.
334    * ID clashes might occur from time to time although they are extreme unlikely!
335    *
336    * @param    mixed   variable to generate a ID for
337    * @return   string  "unique" ID
338    * @access   public
339    */
340    function generateID($variable)
341    {
342        // WARNING: ID clashes are possible although unlikely
343        return md5(serialize($variable));
344    }
345
346    /**
347    * Calls the garbage collector of the storage object with a certain probability
348    *
349    * @param    boolean Force a garbage collection run?
350    * @see  $gc_probability, $gc_time
351    */
352    function garbageCollection($force = false)
353    {
354        static $last_run = 0;
355
356        if (!$this->caching) {
357            return;
358        }
359
360        // time and probability based
361        if (($force) || ($last_run && $last_run < time() + $this->gc_time) || (rand(1, 100) < $this->gc_probability)) {
362            $this->container->garbageCollection($this->gc_maxlifetime);
363            $last_run = time();
364        }
365    } // end func garbageCollection
366
367} // end class cache
368?>
369