root/fCache.php

Revision 577, 9.4 kB (checked in by wbond, 10 months ago)

Updated documentation to indicate when methods will throw the exception in an @throws tag, changed some magic methods ( methods) to @internal

LineHide Line Numbers
1 <?php
2 /**
3  * A simple interface to cache data using different backends
4  *
5  * @copyright  Copyright (c) 2009 Will Bond
6  * @author     Will Bond [wb] <will@flourishlib.com>
7  * @license    http://flourishlib.com/license
8  *
9  * @package    Flourish
10  * @link       http://flourishlib.com/fCache
11  *
12  * @version    1.0.0b
13  * @changes    1.0.0b   The initial implementation [wb, 2009-04-28]
14  */
15 class fCache
16 {
17     /**
18     * The data cache, only used for file caches
19     *
20     * The array structure is:
21     * {{{
22     * array(
23     *     (string) {key} => array(
24     *         'value'  => (mixed) {the key's value},
25     *         'expire' => (integer) {the timestamp to expire at, 0 for none}
26     *     )
27     * )
28     * }}}
29     *
30     * @var array
31     */
32     protected $cache;
33    
34     /**
35     * The data store to use - the file path for a file cache, Memcache object for memcache
36     *
37     * @var mixed
38     */
39     protected $data_store;
40    
41     /**
42     * The data state, only used for file caches
43     *
44     * The valid values are:
45     *  - `'clean'`
46     *  - `'dirty'`
47     *
48     * @var string
49     */
50     protected $state;
51    
52     /**
53     * The type of cache
54     *
55     * The valid values are:
56     *  - `'apc'`
57     *  - `'file'`
58     *  - `'memcache'`
59     *  - `'xcache'`
60     *
61     * @var string
62     */
63     protected $type;
64    
65     /**
66     * Set the type and master key for the cache
67     *
68     * A `file` cache uses a single file to store values in an associative
69     * array and is probably not suitable for a large number of keys.
70     *
71     * Using an `apc` or `xcache` cache will have far better performance
72     * than a file or directory, however please remember that keys are shared
73     * server-wide.
74     *
75     * @param  string $type        The type of caching to use: `'apc'`, `'file'`, `'memcache'`, `'xcache'`
76     * @param  mixed  $data_store  The path for a `file` cache, or an `Memcache` object for a `memcache` cache - not used for `apc` or `xcache`
77     * @return fCache
78     */
79     public function __construct($type, $data_store=NULL)
80     {
81         switch ($type) {
82             case 'file':
83                 $exists = file_exists($data_store);
84                 if (!$exists && !is_writable(dirname($data_store))) {
85                     throw new fEnvironmentException(
86                         'The file specified, %s, does not exist and the directory it in inside of is not writable',
87                         $data_store
88                     );       
89                 }
90                 if ($exists && !is_writable($data_store)) {
91                     throw new fEnvironmentException(
92                         'The file specified, %s, is not writable',
93                         $data_store
94                     );
95                 }
96                 $this->data_store = $data_store;
97                 if ($exists) {
98                     $this->cache = unserialize(file_get_contents($data_store));
99                 } else {
100                     $this->cache = array();   
101                 }
102                 $this->state = 'clean';
103                 break;
104            
105             case 'apc':
106             case 'xcache':
107             case 'memcache':
108                 if (!extension_loaded($type)) {
109                     throw new fEnvironmentException(
110                         'The %s extension does not appear to be installed',
111                         $type
112                     );   
113                 }
114                 if ($type == 'memcache') {
115                     if (!$data_store instanceof Memcache) {
116                         throw new fProgrammerException(
117                             'The data store provided is not a valid %s object',
118                             'Memcache'
119                         );
120                     }
121                     $this->data_store = $data_store;   
122                 }
123                 break;
124                
125             default:
126                 throw new fProgrammerException(
127                     'The type specified, %s, is not a valid cache type. Must be one of: %s.',
128                     $type,
129                     join(', ', array('apc', 'directory', 'file', 'memcache', 'xcache'))
130                 );   
131         }
132        
133         $this->type = $type;               
134     }
135    
136    
137     /**
138     * Cleans up after the cache object
139     *
140     * @internal
141      *
142     * @return void
143     */
144     public function __destruct()
145     {
146         $this->save();
147         if ($this->type == 'memcache') {
148             $this->data_store->close();   
149         }
150     }
151    
152    
153     /**
154     * Tries to set a value to the cache, but stops if a value already exists
155     *
156     * @param  string  $key    The key to store as, this should not exceed 250 characters
157     * @param  mixed   $value  The value to store, this will be serialized
158     * @param  integer $ttl    The number of seconds to keep the cache valid for, 0 for no limit
159     * @return boolean  If the key/value pair were added successfully
160     */
161     public function add($key, $value, $ttl=0)
162     {
163         switch ($this->type) {
164             case 'apc':
165                 return apc_add($key, serialize($value), $ttl);
166                
167             case 'file':
168                 if (isset($this->cache[$key]) && $this->cache[$key]['expire'] && $this->cache[$key]['expire'] >= time()) {
169                     return FALSE;   
170                 }
171                 $this->cache[$key] = array(
172                     'value'  => $value,
173                     'expire' => (!$ttl) ? 0 : time() + $ttl
174                 );
175                 $this->state = 'dirty';
176                 return TRUE;
177            
178             case 'memcache':
179                 if ($ttl > 2592000) {
180                     $ttl = time() + 2592000;       
181                 }
182                 return $this->data_store->add($key, serialize($value), $ttl);
183            
184             case 'xcache':
185                 if (xcache_isset($key)) {
186                     return FALSE;   
187                 }
188                 xcache_set($key, serialize($value), $ttl);
189                 return TRUE;
190         }       
191     }
192    
193    
194     /**
195     * Clears the WHOLE cache of every key, use with caution!
196     *
197     * xcache may require a login or password depending on your ini settings.
198     *
199     * @return void
200     */
201     public function clear()
202     {
203         switch ($this->type) {
204             case 'apc':
205                 apc_clear_cache('user');
206                 return;
207                
208             case 'file':
209                 $this->cache = array();
210                 $this->state = 'dirty';
211                 return;
212            
213             case 'memcache':
214                 $this->data_store->flush();
215                 return;
216            
217             case 'xcache':
218                 xcache_clear_cache(XC_TYPE_VAR, 0);
219                 return;
220         }           
221     }
222    
223    
224     /**
225     * Deletes a value from the cache
226     *
227     * @param  string $key  The key to delete
228     * @return void
229     */
230     public function delete($key)
231     {
232         switch ($this->type) {
233             case 'apc':
234                 apc_delete($key);
235                 return;
236                
237             case 'file':
238                 if (isset($this->cache[$key])) {
239                     unset($this->cache[$key]);
240                     $this->state = 'dirty';   
241                 }
242                 return;
243            
244             case 'memcache':
245                 $this->data_store->delete($key);
246                 return;
247            
248             case 'xcache':
249                 xcache_unset($key);
250                 return;
251         }       
252     }
253    
254    
255     /**
256     * Returns a value from the cache
257     *
258     * @param  string $key      The key to return the value for
259     * @param  mixed  $default  The value to return if the key did not exist
260     * @return mixed  The cached value or the default value if no cached value was found
261     */
262     public function get($key, $default=NULL)
263     {
264         switch ($this->type) {
265             case 'apc':
266                 $value = apc_fetch($key);
267                 if ($value === FALSE) { return $default; }
268                 return unserialize($value);
269                
270             case 'file':
271                 if (isset($this->cache[$key])) {
272                     $expire = $this->cache[$key]['expire'];
273                     if (!$expire || $expire >= time()) {
274                         return $this->cache[$key]['value'];   
275                     } elseif ($expire) {
276                         unset($this->cache[$key]);
277                         $this->state = 'dirty';   
278                     }
279                 }
280                 return $default;
281            
282             case 'memcache':
283                 $value = $this->data_store->get($key);
284                 if ($value === FALSE) { return $default; }
285                 return unserialize($value);
286            
287             case 'xcache':
288                 $value = xcache_get($key);
289                 if ($value === FALSE) { return $default; }
290                 return unserialize($value);
291         }       
292     }
293    
294    
295     /**
296     * Only valid for `file` caches, saves the file to disk and will randomly clean up expired values
297     *
298     * @return void
299     */
300     public function save()
301     {
302         if ($this->type != 'file') {
303             return;
304         }           
305        
306         // Randomly clean the cache out
307         if (rand(0, 99) == 50) {
308             $clear_before = time();
309            
310             foreach ($this->cache as $key => $value) {
311                 if ($value['expire'] && $value['expire'] < $clear_before) {
312                     unset($this->cache[$key]);   
313                     $this->state = 'dirty';
314                 }   
315             }
316         }
317        
318         if ($this->state == 'clean') { return; }
319        
320         file_put_contents($this->data_store, serialize($this->cache));
321         $this->state = 'clean';   
322     }
323    
324    
325     /**
326     * Sets a value to the cache, overriding any previous value
327     *
328     * @param  string  $key    The key to store as, this should not exceed 250 characters
329     * @param  mixed   $value  The value to store, this will be serialized
330     * @param  integer $ttl    The number of seconds to keep the cache valid for, 0 for no limit
331     * @return void
332     */
333     public function set($key, $value, $ttl=0)
334     {
335         switch ($this->type) {
336             case 'apc':
337                 apc_store($key, serialize($value), $ttl);
338                 return;
339                
340             case 'file':
341                 $this->cache[$key] = array(
342                     'value'  => $value,
343                     'expire' => (!$ttl) ? 0 : time() + $ttl
344                 );
345                 $this->state = 'dirty';
346                 return;
347            
348             case 'memcache':
349                 if ($ttl > 2592000) {
350                     $ttl = time() + 2592000;       
351                 }
352                 $value = serialize($value);
353                 if (!$this->data_store->replace($key, $value, $ttl)) {
354                     $this->data_store->set($key, $value, $ttl);
355                 }
356                 return;
357            
358             case 'xcache':
359                 xcache_set($key, serialize($value), $ttl);
360                 return;
361         }               
362     }
363 }
364  
365  
366  
367 /**
368  * Copyright (c) 2009 Will Bond <will@flourishlib.com>
369  *
370  * Permission is hereby granted, free of charge, to any person obtaining a copy
371  * of this software and associated documentation files (the "Software"), to deal
372  * in the Software without restriction, including without limitation the rights
373  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
374  * copies of the Software, and to permit persons to whom the Software is
375  * furnished to do so, subject to the following conditions:
376  *
377  * The above copyright notice and this permission notice shall be included in
378  * all copies or substantial portions of the Software.
379  *
380  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
381  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
382  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
383  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
384  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
385  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
386  * THE SOFTWARE.
387  */