Implements: Kohana_Cache_GarbageCollect | Cache_GarbageCollect
Kohana Cache File driver. Provides a file based driver for the Kohana Cache library. This is one of the slowest caching methods.
Below is an example of a file server configuration.
return [
// File driver group
'file' => [
// Using File driver
'driver' => 'file',
// Cache location
'cache_dir' => APPPATH . 'cache/.kohana_cache',
],
];
In cases where only one cache group is required, if the group is named default
there is
no need to pass the group name when instantiating a cache instance.
Below are the settings available to all types of cache driver.
Name | Required | Description |
---|---|---|
driver | YES | (string) The driver type to use |
cache_dir | NO | (string) The cache directory to use for this cache instance |
Class declared in MODPATH/cache/classes/Cache/File.php on line 3.
integer 3600
string
$defaultdefault driver to use
string(4) "file"
Kohana_Cache
$instancesinstances
array(0)
string
$_cache_dirthe caching directory
NULL
Config
$_configarray(0)
Delete a cache entry based on id
// Delete 'foo' entry from the file group
Cache::instance('file')->delete('foo');
string
$id
required - Id to remove from cache boolean
public function delete($id)
{
$filename = Cache_File::filename($this->_sanitize_id($id));
$directory = $this->_resolve_directory($filename);
return $this->_delete_file(new SplFileInfo($directory . $filename), false, true);
}
Delete all cache entries.
Beware of using this method when using shared memory cache systems, as it will wipe every entry within the system for all clients.
// Delete all cache entries in the file group
Cache::instance('file')->delete_all();
boolean
public function delete_all()
{
return $this->_delete_file($this->_cache_dir, true);
}
Garbage collection method that cleans any expired cache entries from the cache.
void
public function garbage_collect()
{
$this->_delete_file($this->_cache_dir, true, false, true);
return;
}
Retrieve a cached value entry by id.
// Retrieve cache entry from file group
$data = Cache::instance('file')->get('foo');
// Retrieve cache entry from file group and return 'bar' if miss
$data = Cache::instance('file')->get('foo', 'bar');
string
$id
required - Id of cache to entry string
$default
= NULL - Default value to return if cache miss mixed
public function get($id, $default = null)
{
$filename = Cache_File::filename($this->_sanitize_id($id));
$directory = $this->_resolve_directory($filename);
// Wrap operations in try/catch to handle notices
try {
// Open file
$file = new SplFileInfo($directory . $filename);
// If file does not exist
if (!$file->isFile()) {
// Return default value
return $default;
} else {
// Test the expiry
if ($this->_is_expired($file)) {
// Delete the file
$this->_delete_file($file, false, true);
return $default;
}
// open the file to read data
$data = $file->openFile();
// Run first fgets(). Cache data starts from the second line
// as the first contains the lifetime timestamp
$data->fgets();
$cache = '';
while ($data->eof() === false) {
$cache .= $data->fgets();
}
return unserialize($cache);
}
} catch (ErrorException $e) {
// Handle ErrorException caused by failed unserialization
if ($e->getCode() === E_NOTICE) {
throw new Cache_Exception(__METHOD__ . ' failed to unserialize cached object with message : ' . $e->getMessage());
}
// Otherwise throw the exception
throw $e;
} catch (RuntimeException $e) {
// Catch RuntimeException when cache file deleted between isFile and openFile
return $default;
}
}
Set a value to cache with id and lifetime
$data = 'bar';
// Set 'bar' to 'foo' in file group, using default expiry
Cache::instance('file')->set('foo', $data);
// Set 'bar' to 'foo' in file group for 30 seconds
Cache::instance('file')->set('foo', $data, 30);
string
$id
required - Id of cache entry string
$data
required - Data to set to cache integer
$lifetime
= NULL - Lifetime in seconds boolean
public function set($id, $data, $lifetime = null)
{
$filename = Cache_File::filename($this->_sanitize_id($id));
$directory = $this->_resolve_directory($filename);
// If lifetime is null
if ($lifetime === null) {
// Set to the default expiry
$lifetime = Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE);
}
// Open directory
$dir = new SplFileInfo($directory);
// If the directory path is not a directory
if (!$dir->isDir()) {
$this->_make_directory($directory, 0777, true);
}
// Open file to inspect
$resouce = new SplFileInfo($directory . $filename);
$file = $resouce->openFile('w');
try {
$data = $lifetime . "\n" . serialize($data);
$file->fwrite($data, strlen($data));
return (bool) $file->fflush();
} catch (ErrorException $e) {
// If serialize through an error exception
if ($e->getCode() === E_NOTICE) {
// Throw a caching error
throw new Cache_Exception(__METHOD__ . ' failed to serialize data for caching with message : ' . $e->getMessage());
}
// Else rethrow the error exception
throw $e;
}
}
Overload the __clone() method to prevent cloning
void
final public function __clone()
{
throw new Cache_Exception('Cloning of Kohana_Cache objects is forbidden');
}
Getter and setter for the configuration. If no argument provided, the current configuration is returned. Otherwise the configuration is set to this class.
// Overwrite all configuration
$cache->config(['driver' => 'memcache', '...']);
// Set a new configuration setting
$cache->config('servers', ['foo' => 'bar', '...']);
// Get a configuration setting
$servers = $cache->config('servers);
mixed
$key
= NULL - Key to set to array, either array or config path mixed
$value
= NULL - Value to associate with key mixed
public function config($key = null, $value = null)
{
if ($key === null)
return $this->_config;
if (is_array($key)) {
$this->_config = $key;
} else {
if ($value === null)
return Arr::get($this->_config, $key);
$this->_config[$key] = $value;
}
return $this;
}
Creates a singleton of a Kohana Cache group. If no group is supplied the default cache group is used.
// Create an instance of the default group
$default_group = Cache::instance();
// Create an instance of a group
$foo_group = Cache::instance('foo');
// Access an instantiated group directly
$foo_group = Cache::$instances['default'];
string
$group
= NULL - The name of the cache group to use [Optional] Cache
public static function instance($group = null)
{
// If there is no group supplied
if ($group === null) {
// Use the default setting
$group = Cache::$default;
}
if (isset(Cache::$instances[$group])) {
// Return the current group if initiated already
return Cache::$instances[$group];
}
$config = Kohana::$config->load('cache');
if (!$config->offsetExists($group)) {
throw new Cache_Exception('Failed to load Kohana Cache group: :group', [':group' => $group]);
}
$config = $config->get($group);
// Create a new cache type instance
$cache_class = 'Cache_' . ucfirst($config['driver']);
Cache::$instances[$group] = new $cache_class($config);
// Return the instance
return Cache::$instances[$group];
}
Constructs the file cache driver. This method cannot be invoked externally. The file cache driver must
be instantiated using the Cache::instance()
method.
array
$config
required - Config protected function __construct(array $config)
{
// Setup parent
parent::__construct($config);
try {
$directory = Arr::get($this->_config, 'cache_dir', Kohana::$cache_dir);
$this->_cache_dir = new SplFileInfo($directory);
}
// PHP < 5.3 exception handle
catch (ErrorException $e) {
$this->_cache_dir = $this->_make_directory($directory, 0777, true);
}
// PHP >= 5.3 exception handle
catch (UnexpectedValueException $e) {
$this->_cache_dir = $this->_make_directory($directory, 0777, true);
}
// If the defined directory is a file, get outta here
if ($this->_cache_dir->isFile()) {
throw new Cache_Exception('Unable to create cache directory as a file already exists : :resource', [':resource' => $this->_cache_dir->getRealPath()]);
}
// Check the read status of the directory
if (!$this->_cache_dir->isReadable()) {
throw new Cache_Exception('Unable to read from the cache directory :resource', [':resource' => $this->_cache_dir->getRealPath()]);
}
// Check the write status of the directory
if (!$this->_cache_dir->isWritable()) {
throw new Cache_Exception('Unable to write to the cache directory :resource', [':resource' => $this->_cache_dir->getRealPath()]);
}
}
Deletes files recursively and returns false on any errors
// Delete a file or folder whilst retaining parent directory and ignore all errors
$this->_delete_file($folder, true, true);
SplFileInfo
$file
required - File boolean
$retain_parent_directory
= bool FALSE - Retain the parent directory boolean
$ignore_errors
= bool FALSE - Ignore_errors to prevent all exceptions interrupting exec boolean
$only_expired
= bool FALSE - Only expired files boolean
protected function _delete_file(SplFileInfo $file, $retain_parent_directory = false, $ignore_errors = false, $only_expired = false)
{
// Allow graceful error handling
try {
// If is file
if ($file->isFile()) {
try {
// Handle ignore files
if (in_array($file->getFilename(), $this->config('ignore_on_delete'))) {
$delete = false;
}
// If only expired is not set
elseif ($only_expired === false) {
// We want to delete the file
$delete = true;
}
// Otherwise...
else {
// Assess the file expiry to flag it for deletion
$delete = $this->_is_expired($file);
}
// If the delete flag is set delete file
if ($delete === true)
return unlink($file->getRealPath());
else
return false;
} catch (ErrorException $e) {
// Catch any delete file warnings
if ($e->getCode() === E_WARNING) {
throw new Cache_Exception(__METHOD__ . ' failed to delete file : :file', [':file' => $file->getRealPath()]);
}
}
}
// Else, is directory
elseif ($file->isDir()) {
// Create new DirectoryIterator
$files = new DirectoryIterator($file->getPathname());
// Iterate over each entry
while ($files->valid()) {
// Extract the entry name
$name = $files->getFilename();
// If the name is not a dot
if ($name != '.' AND $name != '..') {
// Create new file resource
$fp = new SplFileInfo($files->getRealPath());
// Delete the file
$this->_delete_file($fp, $retain_parent_directory, $ignore_errors, $only_expired);
}
// Move the file pointer on
$files->next();
}
// If set to retain parent directory, return now
if ($retain_parent_directory) {
return true;
}
try {
// Remove the files iterator
// (fixes Windows PHP which has permission issues with open iterators)
unset($files);
// Try to remove the parent directory
return rmdir($file->getRealPath());
} catch (ErrorException $e) {
// Catch any delete directory warnings
if ($e->getCode() === E_WARNING) {
throw new Cache_Exception(__METHOD__ . ' failed to delete directory : :directory', [':directory' => $file->getRealPath()]);
}
throw $e;
}
} else {
// We get here if a file has already been deleted
return false;
}
}
// Catch all exceptions
catch (Exception $e) {
// If ignore_errors is on
if ($ignore_errors === true) {
// Return
return false;
}
// Throw exception
throw $e;
}
}
Test if cache file is expired
SplFileInfo
$file
required - The cache file boolean
- True if expired false otherwiseprotected function _is_expired(SplFileInfo $file)
{
// Open the file and parse data
$created = $file->getMTime();
$data = $file->openFile("r");
$lifetime = (int) $data->fgets();
// If we're at the EOF at this point, corrupted!
if ($data->eof()) {
throw new Cache_Exception(__METHOD__ . ' corrupted cache file!');
}
//close file
$data = null;
// test for expiry and return
return (($lifetime !== 0) AND ( ($created + $lifetime) < time()));
}
Makes the cache directory if it doesn't exist. Simply a wrapper for
mkdir
to ensure DRY principles
string
$directory
required - Directory path integer
$mode
= integer 511 - Chmod mode boolean
$recursive
= bool FALSE - Allows nested directories creation resource
$context
= NULL - A stream context SplFileInfo
protected function _make_directory($directory, $mode = 0777, $recursive = false, $context = null)
{
// call mkdir according to the availability of a passed $context param
$mkdir_result = $context ?
mkdir($directory, $mode, $recursive, $context) :
mkdir($directory, $mode, $recursive);
// throw an exception if unsuccessful
if (!$mkdir_result) {
throw new Cache_Exception('Failed to create the defined cache directory : :directory', [':directory' => $directory]);
}
// chmod to solve potential umask issues
chmod($directory, $mode);
return new SplFileInfo($directory);
}
Resolves the cache directory real path from the filename
// Get the realpath of the cache folder
$realpath = $this->_resolve_directory($filename);
string
$filename
required - Filename to resolve string
protected function _resolve_directory($filename)
{
return $this->_cache_dir->getRealPath() . DIRECTORY_SEPARATOR . $filename[0] . $filename[1] . DIRECTORY_SEPARATOR;
}
Creates a hashed filename based on the string. This is used to create shorter unique IDs for each cache filename.
// Create the cache filename
$filename = Cache_File::filename($this->_sanitize_id($id));
string
$string
required - String to hash into filename string
protected static function filename($string)
{
return sha1($string) . '.cache';
}
Replaces troublesome characters with underscores.
// Sanitize a cache id
$id = $this->_sanitize_id($id);
string
$id
required - Id of cache to sanitize string
protected function _sanitize_id($id)
{
// Change slashes and spaces to underscores
return str_replace(['/', '\\', ' '], '_', $id);
}