While Kohana has an internal caching function that works quite well, I wanted a bit more robust handling. Namely, I wanted to be able to set a cache file that never expired - something Kohana 3's cache function doesn't handle. Also, I found it awkward to have to pass the lifespan of the cache into read function. I found some sample code where you can play with the touch() function to set an m time in the future. When the m and c times are equal, we have an indefinite cache file
There are only a few situations where you'd want an indefinite cache, and it leaves handling expiration up to the function doing the caching. In my current case, this is reading ATOM feeds from Picasa. The feed headers allow the feed to be queried to see if the feed has been updated.
- Attempt to get cached feed to get the etag field.
- Retrieve the feed using the If-None-Match:
header - Evaluate response:
- If response is HTTP 304 - content is not modified, use cached feed. In this case the entire feed is not sent back in the response.
- If response is HTTP 200 - content is modified, take feed and update cache
Code
Much of this is based on Kohana's caching function with some file locking tossed in.This is an early version - not tested much, beware!
/** * Cache helper * * @package Cache * @author Levi Baker */ class Cache { /** * @var directory permissions for the cache directories. */ private static $cache_dir_permissions = 0700; /** * Cache a resource * @param string $name - resource name * @param mixed $data - data to be serialized and cached. * @param int $lifetime - ttl of the data in seconds. */ public static function write($name, $data, $lifetime = null) { $file = self::name($name); $dir = self::dir($name); try { // Ensure that the directory has been created. if ( ! is_dir($dir)) { // Create the cache directory mkdir($dir, self::$cache_dir_permissions, TRUE); // Set permissions (must be manually set to fix umask issues) chmod($dir, self::$cache_dir_permissions); } // Open the file. $f = fopen($dir.$file, 'w'); // Get an exlusive lock on the file while writing flock($f, LOCK_EX); // Write to cach if (fwrite($f, serialize($data)) === false) throw new Exception('Could not write to cache file.'); fclose($f); // close and release the lock. // Set lifetime if ( ! touch($dir.$file, time() + (int) $lifetime)) throw new Exception('Could not touch cache file'); return true; } catch (Exception $e) { throw $e; } } /** * Read from a cache resource * @param string $name - resource name * @return mixed - unserialized resource, or null if not found. */ public static function read($name) { try { $path = self::dir($name).self::name($name); if (is_file($path)) { // Check if a lifetime was set. if (filemtime($path) > filectime($path)) { // cache file is expired. if (time() > filemtime($path)) { unlink ($path); return null; } } // We need a file pointer to do a lock $f = fopen($path,'r'); if (!$f) return null; // Get a shared lock flock($f, LOCK_SH); $data = file_get_contents($path); fclose($f); return unserialize($data); } // Cache not found. return null; } catch (Exception $e) { throw $e; } } /** * Delete a cached resource * @param string $name - resource name */ public static function delete($name) { $path = self::dir($name).self::name($name); if (is_file($path)) { return unlink ($path); } return false; } /** * Name - returns the name of the cache file * @param string $name */ private static function name($name) { return sha1($name).'.txt'; } /** * Directory - returns the full path of the cache file * @param string $name */ private static function dir($name) { $file = self::name($name); return Kohana::$cache_dir.DIRECTORY_SEPARATOR.$file[0].$file[1].DIRECTORY_SEPARATOR; } }
While this is setup to run with Kohana 3, it could quite easily be made standalone by modifying the function dir($name) to set the cache directory elsewhere.
Using this class is pretty simple:
// Write to a cache with no expiration cache::write('resource.name', $my_cool_data); // Write to a cache with a lifespan of 5 minutes cache::write('resource.expires', $temporary_data, 300); // Read data $data = cache::read('resource.name'); // Check that the resource didn't expire though... if ($data !== null) { // Do things! }
Notes
This uses serialize on the data coming in, so be sure that the data being saved is serializable. Some php classes have issues with serialization. Look into the __sleep() and __wakeup() magic functions to extend and enable serialization on objects.
No comments:
Post a Comment