Kohana v3.3.0

This commit is contained in:
Deon George
2013-04-22 14:09:50 +10:00
commit f96694b18f
1280 changed files with 145034 additions and 0 deletions

3
modules/cache/classes/Cache.php vendored Normal file
View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') or die('No direct script access.');
abstract class Cache extends Kohana_Cache {}

3
modules/cache/classes/Cache/Apc.php vendored Normal file
View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') or die('No direct script access.');
class Cache_Apc extends Kohana_Cache_Apc {}

View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') or die('No direct script access.');
interface Cache_Arithmetic extends Kohana_Cache_Arithmetic {}

View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') or die('No direct script access.');
class Cache_Exception extends Kohana_Cache_Exception {}

3
modules/cache/classes/Cache/File.php vendored Normal file
View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') or die('No direct script access.');
class Cache_File extends Kohana_Cache_File {}

View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') or die('No direct script access.');
interface Cache_GarbageCollect extends Kohana_Cache_GarbageCollect {}

View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') or die('No direct script access.');
class Cache_Memcache extends Kohana_Cache_Memcache {}

View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') or die('No direct script access.');
class Cache_MemcacheTag extends Kohana_Cache_MemcacheTag {}

View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') or die('No direct script access.');
class Cache_Sqlite extends Kohana_Cache_Sqlite {}

View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') or die('No direct script access.');
interface Cache_Tagging extends Kohana_Cache_Tagging {}

View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') or die('No direct script access.');
class Cache_Wincache extends Kohana_Cache_Wincache {}

3
modules/cache/classes/HTTP/Cache.php vendored Normal file
View File

@@ -0,0 +1,3 @@
<?php defined('SYSPATH') or die('No direct script access.');
class HTTP_Cache extends Kohana_HTTP_Cache {}

300
modules/cache/classes/Kohana/Cache.php vendored Normal file
View File

@@ -0,0 +1,300 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Kohana Cache provides a common interface to a variety of caching engines. Tags are
* supported where available natively to the cache system. Kohana Cache supports multiple
* instances of cache engines through a grouped singleton pattern.
*
* ### Supported cache engines
*
* * [APC](http://php.net/manual/en/book.apc.php)
* * [eAccelerator](http://eaccelerator.net/)
* * File
* * [Memcache](http://memcached.org/)
* * [Memcached-tags](http://code.google.com/p/memcached-tags/)
* * [SQLite](http://www.sqlite.org/)
* * [Xcache](http://xcache.lighttpd.net/)
*
* ### Introduction to caching
*
* Caching should be implemented with consideration. Generally, caching the result of resources
* is faster than reprocessing them. Choosing what, how and when to cache is vital. PHP APC is
* presently one of the fastest caching systems available, closely followed by Memcache. SQLite
* and File caching are two of the slowest cache methods, however usually faster than reprocessing
* a complex set of instructions.
*
* Caching engines that use memory are considerably faster than the file based alternatives. But
* memory is limited whereas disk space is plentiful. If caching large datasets it is best to use
* file caching.
*
* ### Configuration settings
*
* Kohana Cache uses configuration groups to create cache instances. A configuration group can
* use any supported driver, with successive groups using the same driver type if required.
*
* #### Configuration example
*
* Below is an example of a _memcache_ server configuration.
*
* return array(
* 'default' => array( // Default group
* 'driver' => 'memcache', // using Memcache driver
* 'servers' => array( // Available server definitions
* array(
* 'host' => 'localhost',
* 'port' => 11211,
* 'persistent' => FALSE
* )
* ),
* 'compression' => FALSE, // Use compression?
* ),
* )
*
* 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.
*
* #### General cache group configuration settings
*
* Below are the settings available to all types of cache driver.
*
* Name | Required | Description
* -------------- | -------- | ---------------------------------------------------------------
* driver | __YES__ | (_string_) The driver type to use
*
* Details of the settings specific to each driver are available within the drivers documentation.
*
* ### System requirements
*
* * Kohana 3.0.x
* * PHP 5.2.4 or greater
*
* @package Kohana/Cache
* @category Base
* @version 2.0
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
abstract class Kohana_Cache {
const DEFAULT_EXPIRE = 3600;
/**
* @var string default driver to use
*/
public static $default = 'file';
/**
* @var Kohana_Cache instances
*/
public static $instances = array();
/**
* 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'];
*
* @param string $group the name of the cache group to use [Optional]
* @return Cache
* @throws Cache_Exception
*/
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',
array(':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];
}
/**
* @var Config
*/
protected $_config = array();
/**
* Ensures singleton pattern is observed, loads the default expiry
*
* @param array $config configuration
*/
protected function __construct(array $config)
{
$this->config($config);
}
/**
* 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(array('driver' => 'memcache', '...'));
*
* // Set a new configuration setting
* $cache->config('servers', array(
* 'foo' => 'bar',
* '...'
* ));
*
* // Get a configuration setting
* $servers = $cache->config('servers);
*
* @param mixed key to set to array, either array or config path
* @param mixed value to associate with key
* @return 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;
}
/**
* Overload the __clone() method to prevent cloning
*
* @return void
* @throws Cache_Exception
*/
final public function __clone()
{
throw new Cache_Exception('Cloning of Kohana_Cache objects is forbidden');
}
/**
* Retrieve a cached value entry by id.
*
* // Retrieve cache entry from default group
* $data = Cache::instance()->get('foo');
*
* // Retrieve cache entry from default group and return 'bar' if miss
* $data = Cache::instance()->get('foo', 'bar');
*
* // Retrieve cache entry from memcache group
* $data = Cache::instance('memcache')->get('foo');
*
* @param string $id id of cache to entry
* @param string $default default value to return if cache miss
* @return mixed
* @throws Cache_Exception
*/
abstract public function get($id, $default = NULL);
/**
* Set a value to cache with id and lifetime
*
* $data = 'bar';
*
* // Set 'bar' to 'foo' in default group, using default expiry
* Cache::instance()->set('foo', $data);
*
* // Set 'bar' to 'foo' in default group for 30 seconds
* Cache::instance()->set('foo', $data, 30);
*
* // Set 'bar' to 'foo' in memcache group for 10 minutes
* if (Cache::instance('memcache')->set('foo', $data, 600))
* {
* // Cache was set successfully
* return
* }
*
* @param string $id id of cache entry
* @param string $data data to set to cache
* @param integer $lifetime lifetime in seconds
* @return boolean
*/
abstract public function set($id, $data, $lifetime = 3600);
/**
* Delete a cache entry based on id
*
* // Delete 'foo' entry from the default group
* Cache::instance()->delete('foo');
*
* // Delete 'foo' entry from the memcache group
* Cache::instance('memcache')->delete('foo')
*
* @param string $id id to remove from cache
* @return boolean
*/
abstract public function delete($id);
/**
* 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 default group
* Cache::instance()->delete_all();
*
* // Delete all cache entries in the memcache group
* Cache::instance('memcache')->delete_all();
*
* @return boolean
*/
abstract public function delete_all();
/**
* Replaces troublesome characters with underscores.
*
* // Sanitize a cache id
* $id = $this->_sanitize_id($id);
*
* @param string $id id of cache to sanitize
* @return string
*/
protected function _sanitize_id($id)
{
// Change slashes and spaces to underscores
return str_replace(array('/', '\\', ' '), '_', $id);
}
}
// End Kohana_Cache

View File

@@ -0,0 +1,166 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* [Kohana Cache](api/Kohana_Cache) APC driver. Provides an opcode based
* driver for the Kohana Cache library.
*
* ### Configuration example
*
* Below is an example of an _apc_ server configuration.
*
* return array(
* 'apc' => array( // Driver group
* 'driver' => 'apc', // using APC driver
* ),
* )
*
* 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.
*
* #### General cache group configuration settings
*
* Below are the settings available to all types of cache driver.
*
* Name | Required | Description
* -------------- | -------- | ---------------------------------------------------------------
* driver | __YES__ | (_string_) The driver type to use
*
* ### System requirements
*
* * Kohana 3.0.x
* * PHP 5.2.4 or greater
* * APC PHP extension
*
* @package Kohana/Cache
* @category Base
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_Cache_Apc extends Cache implements Cache_Arithmetic {
/**
* Check for existence of the APC extension This method cannot be invoked externally. The driver must
* be instantiated using the `Cache::instance()` method.
*
* @param array $config configuration
* @throws Cache_Exception
*/
protected function __construct(array $config)
{
if ( ! extension_loaded('apc'))
{
throw new Cache_Exception('PHP APC extension is not available.');
}
parent::__construct($config);
}
/**
* Retrieve a cached value entry by id.
*
* // Retrieve cache entry from apc group
* $data = Cache::instance('apc')->get('foo');
*
* // Retrieve cache entry from apc group and return 'bar' if miss
* $data = Cache::instance('apc')->get('foo', 'bar');
*
* @param string $id id of cache to entry
* @param string $default default value to return if cache miss
* @return mixed
* @throws Cache_Exception
*/
public function get($id, $default = NULL)
{
$data = apc_fetch($this->_sanitize_id($id), $success);
return $success ? $data : $default;
}
/**
* Set a value to cache with id and lifetime
*
* $data = 'bar';
*
* // Set 'bar' to 'foo' in apc group, using default expiry
* Cache::instance('apc')->set('foo', $data);
*
* // Set 'bar' to 'foo' in apc group for 30 seconds
* Cache::instance('apc')->set('foo', $data, 30);
*
* @param string $id id of cache entry
* @param string $data data to set to cache
* @param integer $lifetime lifetime in seconds
* @return boolean
*/
public function set($id, $data, $lifetime = NULL)
{
if ($lifetime === NULL)
{
$lifetime = Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE);
}
return apc_store($this->_sanitize_id($id), $data, $lifetime);
}
/**
* Delete a cache entry based on id
*
* // Delete 'foo' entry from the apc group
* Cache::instance('apc')->delete('foo');
*
* @param string $id id to remove from cache
* @return boolean
*/
public function delete($id)
{
return apc_delete($this->_sanitize_id($id));
}
/**
* 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 apc group
* Cache::instance('apc')->delete_all();
*
* @return boolean
*/
public function delete_all()
{
return apc_clear_cache('user');
}
/**
* Increments a given value by the step value supplied.
* Useful for shared counters and other persistent integer based
* tracking.
*
* @param string id of cache entry to increment
* @param int step value to increment by
* @return integer
* @return boolean
*/
public function increment($id, $step = 1)
{
return apc_inc($id, $step);
}
/**
* Decrements a given value by the step value supplied.
* Useful for shared counters and other persistent integer based
* tracking.
*
* @param string id of cache entry to decrement
* @param int step value to decrement by
* @return integer
* @return boolean
*/
public function decrement($id, $step = 1)
{
return apc_dec($id, $step);
}
} // End Kohana_Cache_Apc

View File

@@ -0,0 +1,39 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Kohana Cache Arithmetic Interface, for basic cache integer based
* arithmetic, addition and subtraction
*
* @package Kohana/Cache
* @category Base
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaphp.com/license
* @since 3.2.0
*/
interface Kohana_Cache_Arithmetic {
/**
* Increments a given value by the step value supplied.
* Useful for shared counters and other persistent integer based
* tracking.
*
* @param string id of cache entry to increment
* @param int step value to increment by
* @return integer
* @return boolean
*/
public function increment($id, $step = 1);
/**
* Decrements a given value by the step value supplied.
* Useful for shared counters and other persistent integer based
* tracking.
*
* @param string id of cache entry to decrement
* @param int step value to decrement by
* @return integer
* @return boolean
*/
public function decrement($id, $step = 1);
} // End Kohana_Cache_Arithmetic

View File

@@ -0,0 +1,11 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Kohana Cache Exception
*
* @package Kohana/Cache
* @category Base
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_Cache_Exception extends Kohana_Exception {}

View File

@@ -0,0 +1,466 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* [Kohana Cache](api/Kohana_Cache) File driver. Provides a file based
* driver for the Kohana Cache library. This is one of the slowest
* caching methods.
*
* ### Configuration example
*
* Below is an example of a _file_ server configuration.
*
* return array(
* 'file' => array( // File driver group
* 'driver' => 'file', // using File driver
* 'cache_dir' => APPPATH.'cache/.kohana_cache', // Cache location
* ),
* )
*
* 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.
*
* #### General cache group configuration settings
*
* 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
*
* ### System requirements
*
* * Kohana 3.0.x
* * PHP 5.2.4 or greater
*
* @package Kohana/Cache
* @category Base
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_Cache_File extends Cache implements Cache_GarbageCollect {
/**
* 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));
*
* @param string $string string to hash into filename
* @return string
*/
protected static function filename($string)
{
return sha1($string).'.cache';
}
/**
* @var string the caching directory
*/
protected $_cache_dir;
/**
* Constructs the file cache driver. This method cannot be invoked externally. The file cache driver must
* be instantiated using the `Cache::instance()` method.
*
* @param array $config config
* @throws Cache_Exception
*/
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', array(':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', array(':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', array(':resource' => $this->_cache_dir->getRealPath()));
}
}
/**
* 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');
*
* @param string $id id of cache to entry
* @param string $default default value to return if cache miss
* @return mixed
* @throws Cache_Exception
*/
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
{
// Open the file and parse data
$created = $file->getMTime();
$data = $file->openFile();
$lifetime = $data->fgets();
// If we're at the EOF at this point, corrupted!
if ($data->eof())
{
throw new Cache_Exception(__METHOD__.' corrupted cache file!');
}
$cache = '';
while ($data->eof() === FALSE)
{
$cache .= $data->fgets();
}
// Test the expiry
if (($created + (int) $lifetime) < time())
{
// Delete the file
$this->_delete_file($file, NULL, TRUE);
return $default;
}
else
{
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;
}
}
/**
* 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);
*
* @param string $id id of cache entry
* @param string $data data to set to cache
* @param integer $lifetime lifetime in seconds
* @return 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())
{
// Create the directory
if ( ! mkdir($directory, 0777, TRUE))
{
throw new Cache_Exception(__METHOD__.' unable to create directory : :directory', array(':directory' => $directory));
}
// chmod to solve potential umask issues
chmod($directory, 0777);
}
// 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;
}
}
/**
* Delete a cache entry based on id
*
* // Delete 'foo' entry from the file group
* Cache::instance('file')->delete('foo');
*
* @param string $id id to remove from cache
* @return 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), NULL, 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();
*
* @return 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.
*
* @return void
*/
public function garbage_collect()
{
$this->_delete_file($this->_cache_dir, TRUE, FALSE, TRUE);
return;
}
/**
* 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);
*
* @param SplFileInfo $file file
* @param boolean $retain_parent_directory retain the parent directory
* @param boolean $ignore_errors ignore_errors to prevent all exceptions interrupting exec
* @param boolean $only_expired only expired files
* @return boolean
* @throws Cache_Exception
*/
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
$json = $file->openFile('r')->current();
$data = json_decode($json);
$delete = $data->expiry < time();
}
// 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', array(':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);
}
// 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', array(':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;
}
}
/**
* Resolves the cache directory real path from the filename
*
* // Get the realpath of the cache folder
* $realpath = $this->_resolve_directory($filename);
*
* @param string $filename filename to resolve
* @return string
*/
protected function _resolve_directory($filename)
{
return $this->_cache_dir->getRealPath().DIRECTORY_SEPARATOR.$filename[0].$filename[1].DIRECTORY_SEPARATOR;
}
/**
* Makes the cache directory if it doesn't exist. Simply a wrapper for
* `mkdir` to ensure DRY principles
*
* @link http://php.net/manual/en/function.mkdir.php
* @param string $directory
* @param integer $mode
* @param boolean $recursive
* @param resource $context
* @return SplFileInfo
* @throws Cache_Exception
*/
protected function _make_directory($directory, $mode = 0777, $recursive = FALSE, $context = NULL)
{
if ( ! mkdir($directory, $mode, $recursive, $context))
{
throw new Cache_Exception('Failed to create the defined cache directory : :directory', array(':directory' => $directory));
}
chmod($directory, $mode);
return new SplFileInfo($directory);
}
}

View File

@@ -0,0 +1,23 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Garbage Collection interface for caches that have no GC methods
* of their own, such as [Cache_File] and [Cache_Sqlite]. Memory based
* cache systems clean their own caches periodically.
*
* @package Kohana/Cache
* @category Base
* @version 2.0
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaphp.com/license
* @since 3.0.8
*/
interface Kohana_Cache_GarbageCollect {
/**
* Garbage collection method that cleans any expired
* cache entries from the cache.
*
* @return void
*/
public function garbage_collect();
}

View File

@@ -0,0 +1,354 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* [Kohana Cache](api/Kohana_Cache) Memcache driver,
*
* ### Supported cache engines
*
* * [Memcache](http://www.php.net/manual/en/book.memcache.php)
* * [Memcached-tags](http://code.google.com/p/memcached-tags/)
*
* ### Configuration example
*
* Below is an example of a _memcache_ server configuration.
*
* return array(
* 'default' => array( // Default group
* 'driver' => 'memcache', // using Memcache driver
* 'servers' => array( // Available server definitions
* // First memcache server server
* array(
* 'host' => 'localhost',
* 'port' => 11211,
* 'persistent' => FALSE
* 'weight' => 1,
* 'timeout' => 1,
* 'retry_interval' => 15,
* 'status' => TRUE,
* 'instant_death' => TRUE,
* 'failure_callback' => array('className', 'classMethod')
* ),
* // Second memcache server
* array(
* 'host' => '192.168.1.5',
* 'port' => 22122,
* 'persistent' => TRUE
* )
* ),
* 'compression' => FALSE, // Use compression?
* ),
* )
*
* 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.
*
* #### General cache group configuration settings
*
* Below are the settings available to all types of cache driver.
*
* Name | Required | Description
* -------------- | -------- | ---------------------------------------------------------------
* driver | __YES__ | (_string_) The driver type to use
* servers | __YES__ | (_array_) Associative array of server details, must include a __host__ key. (see _Memcache server configuration_ below)
* compression | __NO__ | (_boolean_) Use data compression when caching
*
* #### Memcache server configuration
*
* The following settings should be used when defining each memcache server
*
* Name | Required | Description
* ---------------- | -------- | ---------------------------------------------------------------
* host | __YES__ | (_string_) The host of the memcache server, i.e. __localhost__; or __127.0.0.1__; or __memcache.domain.tld__
* port | __NO__ | (_integer_) Point to the port where memcached is listening for connections. Set this parameter to 0 when using UNIX domain sockets. Default __11211__
* persistent | __NO__ | (_boolean_) Controls the use of a persistent connection. Default __TRUE__
* weight | __NO__ | (_integer_) Number of buckets to create for this server which in turn control its probability of it being selected. The probability is relative to the total weight of all servers. Default __1__
* timeout | __NO__ | (_integer_) Value in seconds which will be used for connecting to the daemon. Think twice before changing the default value of 1 second - you can lose all the advantages of caching if your connection is too slow. Default __1__
* retry_interval | __NO__ | (_integer_) Controls how often a failed server will be retried, the default value is 15 seconds. Setting this parameter to -1 disables automatic retry. Default __15__
* status | __NO__ | (_boolean_) Controls if the server should be flagged as online. Default __TRUE__
* failure_callback | __NO__ | (_[callback](http://www.php.net/manual/en/language.pseudo-types.php#language.types.callback)_) Allows the user to specify a callback function to run upon encountering an error. The callback is run before failover is attempted. The function takes two parameters, the hostname and port of the failed server. Default __NULL__
*
* ### System requirements
*
* * Kohana 3.0.x
* * PHP 5.2.4 or greater
* * Memcache (plus Memcached-tags for native tagging support)
* * Zlib
*
* @package Kohana/Cache
* @category Base
* @version 2.0
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_Cache_Memcache extends Cache implements Cache_Arithmetic {
// Memcache has a maximum cache lifetime of 30 days
const CACHE_CEILING = 2592000;
/**
* Memcache resource
*
* @var Memcache
*/
protected $_memcache;
/**
* Flags to use when storing values
*
* @var string
*/
protected $_flags;
/**
* The default configuration for the memcached server
*
* @var array
*/
protected $_default_config = array();
/**
* Constructs the memcache Kohana_Cache object
*
* @param array $config configuration
* @throws Cache_Exception
*/
protected function __construct(array $config)
{
// Check for the memcache extention
if ( ! extension_loaded('memcache'))
{
throw new Cache_Exception('Memcache PHP extention not loaded');
}
parent::__construct($config);
// Setup Memcache
$this->_memcache = new Memcache;
// Load servers from configuration
$servers = Arr::get($this->_config, 'servers', NULL);
if ( ! $servers)
{
// Throw an exception if no server found
throw new Cache_Exception('No Memcache servers defined in configuration');
}
// Setup default server configuration
$this->_default_config = array(
'host' => 'localhost',
'port' => 11211,
'persistent' => FALSE,
'weight' => 1,
'timeout' => 1,
'retry_interval' => 15,
'status' => TRUE,
'instant_death' => TRUE,
'failure_callback' => array($this, '_failed_request'),
);
// Add the memcache servers to the pool
foreach ($servers as $server)
{
// Merge the defined config with defaults
$server += $this->_default_config;
if ( ! $this->_memcache->addServer($server['host'], $server['port'], $server['persistent'], $server['weight'], $server['timeout'], $server['retry_interval'], $server['status'], $server['failure_callback']))
{
throw new Cache_Exception('Memcache could not connect to host \':host\' using port \':port\'', array(':host' => $server['host'], ':port' => $server['port']));
}
}
// Setup the flags
$this->_flags = Arr::get($this->_config, 'compression', FALSE) ? MEMCACHE_COMPRESSED : FALSE;
}
/**
* Retrieve a cached value entry by id.
*
* // Retrieve cache entry from memcache group
* $data = Cache::instance('memcache')->get('foo');
*
* // Retrieve cache entry from memcache group and return 'bar' if miss
* $data = Cache::instance('memcache')->get('foo', 'bar');
*
* @param string $id id of cache to entry
* @param string $default default value to return if cache miss
* @return mixed
* @throws Cache_Exception
*/
public function get($id, $default = NULL)
{
// Get the value from Memcache
$value = $this->_memcache->get($this->_sanitize_id($id));
// If the value wasn't found, normalise it
if ($value === FALSE)
{
$value = (NULL === $default) ? NULL : $default;
}
// Return the value
return $value;
}
/**
* Set a value to cache with id and lifetime
*
* $data = 'bar';
*
* // Set 'bar' to 'foo' in memcache group for 10 minutes
* if (Cache::instance('memcache')->set('foo', $data, 600))
* {
* // Cache was set successfully
* return
* }
*
* @param string $id id of cache entry
* @param mixed $data data to set to cache
* @param integer $lifetime lifetime in seconds, maximum value 2592000
* @return boolean
*/
public function set($id, $data, $lifetime = 3600)
{
// If the lifetime is greater than the ceiling
if ($lifetime > Cache_Memcache::CACHE_CEILING)
{
// Set the lifetime to maximum cache time
$lifetime = Cache_Memcache::CACHE_CEILING + time();
}
// Else if the lifetime is greater than zero
elseif ($lifetime > 0)
{
$lifetime += time();
}
// Else
else
{
// Normalise the lifetime
$lifetime = 0;
}
// Set the data to memcache
return $this->_memcache->set($this->_sanitize_id($id), $data, $this->_flags, $lifetime);
}
/**
* Delete a cache entry based on id
*
* // Delete the 'foo' cache entry immediately
* Cache::instance('memcache')->delete('foo');
*
* // Delete the 'bar' cache entry after 30 seconds
* Cache::instance('memcache')->delete('bar', 30);
*
* @param string $id id of entry to delete
* @param integer $timeout timeout of entry, if zero item is deleted immediately, otherwise the item will delete after the specified value in seconds
* @return boolean
*/
public function delete($id, $timeout = 0)
{
// Delete the id
return $this->_memcache->delete($this->_sanitize_id($id), $timeout);
}
/**
* 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 default group
* Cache::instance('memcache')->delete_all();
*
* @return boolean
*/
public function delete_all()
{
$result = $this->_memcache->flush();
// We must sleep after flushing, or overwriting will not work!
// @see http://php.net/manual/en/function.memcache-flush.php#81420
sleep(1);
return $result;
}
/**
* Callback method for Memcache::failure_callback to use if any Memcache call
* on a particular server fails. This method switches off that instance of the
* server if the configuration setting `instant_death` is set to `TRUE`.
*
* @param string $hostname
* @param integer $port
* @return void|boolean
* @since 3.0.8
*/
public function _failed_request($hostname, $port)
{
if ( ! $this->_config['instant_death'])
return;
// Setup non-existent host
$host = FALSE;
// Get host settings from configuration
foreach ($this->_config['servers'] as $server)
{
// Merge the defaults, since they won't always be set
$server += $this->_default_config;
// We're looking at the failed server
if ($hostname == $server['host'] and $port == $server['port'])
{
// Server to disable, since it failed
$host = $server;
continue;
}
}
if ( ! $host)
return;
else
{
return $this->_memcache->setServerParams(
$host['host'],
$host['port'],
$host['timeout'],
$host['retry_interval'],
FALSE, // Server is offline
array($this, '_failed_request'
));
}
}
/**
* Increments a given value by the step value supplied.
* Useful for shared counters and other persistent integer based
* tracking.
*
* @param string id of cache entry to increment
* @param int step value to increment by
* @return integer
* @return boolean
*/
public function increment($id, $step = 1)
{
return $this->_memcache->increment($id, $step);
}
/**
* Decrements a given value by the step value supplied.
* Useful for shared counters and other persistent integer based
* tracking.
*
* @param string id of cache entry to decrement
* @param int step value to decrement by
* @return integer
* @return boolean
*/
public function decrement($id, $step = 1)
{
return $this->_memcache->decrement($id, $step);
}
}

View File

@@ -0,0 +1,78 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* See [Kohana_Cache_Memcache]
*
* @package Kohana/Cache
* @category Base
* @version 2.0
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_Cache_MemcacheTag extends Cache_Memcache implements Cache_Tagging {
/**
* Constructs the memcache object
*
* @param array $config configuration
* @throws Cache_Exception
*/
protected function __construct(array $config)
{
parent::__construct($config);
if ( ! method_exists($this->_memcache, 'tag_add'))
{
throw new Cache_Exception('Memcached-tags PHP plugin not present. Please see http://code.google.com/p/memcached-tags/ for more information');
}
}
/**
* Set a value based on an id with tags
*
* @param string $id id
* @param mixed $data data
* @param integer $lifetime lifetime [Optional]
* @param array $tags tags [Optional]
* @return boolean
*/
public function set_with_tags($id, $data, $lifetime = NULL, array $tags = NULL)
{
$id = $this->_sanitize_id($id);
$result = $this->set($id, $data, $lifetime);
if ($result and $tags)
{
foreach ($tags as $tag)
{
$this->_memcache->tag_add($tag, $id);
}
}
return $result;
}
/**
* Delete cache entries based on a tag
*
* @param string $tag tag
* @return boolean
*/
public function delete_tag($tag)
{
return $this->_memcache->tag_delete($tag);
}
/**
* Find cache entries based on a tag
*
* @param string $tag tag
* @return void
* @throws Cache_Exception
*/
public function find($tag)
{
throw new Cache_Exception('Memcached-tags does not support finding by tag');
}
}

View File

@@ -0,0 +1,334 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Kohana Cache Sqlite Driver
*
* Requires SQLite3 and PDO
*
* @package Kohana/Cache
* @category Base
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_Cache_Sqlite extends Cache implements Cache_Tagging, Cache_GarbageCollect {
/**
* Database resource
*
* @var PDO
*/
protected $_db;
/**
* Sets up the PDO SQLite table and
* initialises the PDO connection
*
* @param array $config configuration
* @throws Cache_Exception
*/
protected function __construct(array $config)
{
parent::__construct($config);
$database = Arr::get($this->_config, 'database', NULL);
if ($database === NULL)
{
throw new Cache_Exception('Database path not available in Kohana Cache configuration');
}
// Load new Sqlite DB
$this->_db = new PDO('sqlite:'.$database);
// Test for existing DB
$result = $this->_db->query("SELECT * FROM sqlite_master WHERE name = 'caches' AND type = 'table'")->fetchAll();
// If there is no table, create a new one
if (0 == count($result))
{
$database_schema = Arr::get($this->_config, 'schema', NULL);
if ($database_schema === NULL)
{
throw new Cache_Exception('Database schema not found in Kohana Cache configuration');
}
try
{
// Create the caches table
$this->_db->query(Arr::get($this->_config, 'schema', NULL));
}
catch (PDOException $e)
{
throw new Cache_Exception('Failed to create new SQLite caches table with the following error : :error', array(':error' => $e->getMessage()));
}
}
}
/**
* Retrieve a value based on an id
*
* @param string $id id
* @param string $default default [Optional] Default value to return if id not found
* @return mixed
* @throws Cache_Exception
*/
public function get($id, $default = NULL)
{
// Prepare statement
$statement = $this->_db->prepare('SELECT id, expiration, cache FROM caches WHERE id = :id LIMIT 0, 1');
// Try and load the cache based on id
try
{
$statement->execute(array(':id' => $this->_sanitize_id($id)));
}
catch (PDOException $e)
{
throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage()));
}
if ( ! $result = $statement->fetch(PDO::FETCH_OBJ))
{
return $default;
}
// If the cache has expired
if ($result->expiration != 0 and $result->expiration <= time())
{
// Delete it and return default value
$this->delete($id);
return $default;
}
// Otherwise return cached object
else
{
// Disable notices for unserializing
$ER = error_reporting(~E_NOTICE);
// Return the valid cache data
$data = unserialize($result->cache);
// Turn notices back on
error_reporting($ER);
// Return the resulting data
return $data;
}
}
/**
* Set a value based on an id. Optionally add tags.
*
* @param string $id id
* @param mixed $data data
* @param integer $lifetime lifetime [Optional]
* @return boolean
*/
public function set($id, $data, $lifetime = NULL)
{
return (bool) $this->set_with_tags($id, $data, $lifetime);
}
/**
* Delete a cache entry based on id
*
* @param string $id id
* @return boolean
* @throws Cache_Exception
*/
public function delete($id)
{
// Prepare statement
$statement = $this->_db->prepare('DELETE FROM caches WHERE id = :id');
// Remove the entry
try
{
$statement->execute(array(':id' => $this->_sanitize_id($id)));
}
catch (PDOException $e)
{
throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage()));
}
return (bool) $statement->rowCount();
}
/**
* Delete all cache entries
*
* @return boolean
*/
public function delete_all()
{
// Prepare statement
$statement = $this->_db->prepare('DELETE FROM caches');
// Remove the entry
try
{
$statement->execute();
}
catch (PDOException $e)
{
throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage()));
}
return (bool) $statement->rowCount();
}
/**
* Set a value based on an id. Optionally add tags.
*
* @param string $id id
* @param mixed $data data
* @param integer $lifetime lifetime [Optional]
* @param array $tags tags [Optional]
* @return boolean
* @throws Cache_Exception
*/
public function set_with_tags($id, $data, $lifetime = NULL, array $tags = NULL)
{
// Serialize the data
$data = serialize($data);
// Normalise tags
$tags = (NULL === $tags) ? NULL : ('<'.implode('>,<', $tags).'>');
// Setup lifetime
if ($lifetime === NULL)
{
$lifetime = (0 === Arr::get($this->_config, 'default_expire', NULL)) ? 0 : (Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE) + time());
}
else
{
$lifetime = (0 === $lifetime) ? 0 : ($lifetime + time());
}
// Prepare statement
// $this->exists() may throw Cache_Exception, no need to catch/rethrow
$statement = $this->exists($id) ? $this->_db->prepare('UPDATE caches SET expiration = :expiration, cache = :cache, tags = :tags WHERE id = :id') : $this->_db->prepare('INSERT INTO caches (id, cache, expiration, tags) VALUES (:id, :cache, :expiration, :tags)');
// Try to insert
try
{
$statement->execute(array(':id' => $this->_sanitize_id($id), ':cache' => $data, ':expiration' => $lifetime, ':tags' => $tags));
}
catch (PDOException $e)
{
throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage()));
}
return (bool) $statement->rowCount();
}
/**
* Delete cache entries based on a tag
*
* @param string $tag tag
* @return boolean
* @throws Cache_Exception
*/
public function delete_tag($tag)
{
// Prepare the statement
$statement = $this->_db->prepare('DELETE FROM caches WHERE tags LIKE :tag');
// Try to delete
try
{
$statement->execute(array(':tag' => "%<{$tag}>%"));
}
catch (PDOException $e)
{
throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage()));
}
return (bool) $statement->rowCount();
}
/**
* Find cache entries based on a tag
*
* @param string $tag tag
* @return array
* @throws Cache_Exception
*/
public function find($tag)
{
// Prepare the statement
$statement = $this->_db->prepare('SELECT id, cache FROM caches WHERE tags LIKE :tag');
// Try to find
try
{
if ( ! $statement->execute(array(':tag' => "%<{$tag}>%")))
{
return array();
}
}
catch (PDOException $e)
{
throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage()));
}
$result = array();
while ($row = $statement->fetchObject())
{
// Disable notices for unserializing
$ER = error_reporting(~E_NOTICE);
$result[$row->id] = unserialize($row->cache);
// Turn notices back on
error_reporting($ER);
}
return $result;
}
/**
* Garbage collection method that cleans any expired
* cache entries from the cache.
*
* @return void
*/
public function garbage_collect()
{
// Create the sequel statement
$statement = $this->_db->prepare('DELETE FROM caches WHERE expiration < :expiration');
try
{
$statement->execute(array(':expiration' => time()));
}
catch (PDOException $e)
{
throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage()));
}
}
/**
* Tests whether an id exists or not
*
* @param string $id id
* @return boolean
* @throws Cache_Exception
*/
protected function exists($id)
{
$statement = $this->_db->prepare('SELECT id FROM caches WHERE id = :id');
try
{
$statement->execute(array(':id' => $this->_sanitize_id($id)));
}
catch (PDOExeption $e)
{
throw new Cache_Exception('There was a problem querying the local SQLite3 cache. :error', array(':error' => $e->getMessage()));
}
return (bool) $statement->fetchAll();
}
}

View File

@@ -0,0 +1,41 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Kohana Cache Tagging Interface
*
* @package Kohana/Cache
* @category Base
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
interface Kohana_Cache_Tagging {
/**
* Set a value based on an id. Optionally add tags.
*
* Note : Some caching engines do not support
* tagging
*
* @param string $id id
* @param mixed $data data
* @param integer $lifetime lifetime [Optional]
* @param array $tags tags [Optional]
* @return boolean
*/
public function set_with_tags($id, $data, $lifetime = NULL, array $tags = NULL);
/**
* Delete cache entries based on a tag
*
* @param string $tag tag
*/
public function delete_tag($tag);
/**
* Find cache entries based on a tag
*
* @param string $tag tag
* @return array
*/
public function find($tag);
}

View File

@@ -0,0 +1,140 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* [Kohana Cache](api/Kohana_Cache) Wincache driver. Provides an opcode based
* driver for the Kohana Cache library.
*
* ### Configuration example
*
* Below is an example of an _wincache_ server configuration.
*
* return array(
* 'wincache' => array( // Driver group
* 'driver' => 'wincache', // using wincache driver
* ),
* )
*
* 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.
*
* #### General cache group configuration settings
*
* Below are the settings available to all types of cache driver.
*
* Name | Required | Description
* -------------- | -------- | ---------------------------------------------------------------
* driver | __YES__ | (_string_) The driver type to use
*
* ### System requirements
*
* * Windows XP SP3 with IIS 5.1 and » FastCGI Extension
* * Windows Server 2003 with IIS 6.0 and » FastCGI Extension
* * Windows Vista SP1 with IIS 7.0 and FastCGI Module
* * Windows Server 2008 with IIS 7.0 and FastCGI Module
* * Windows 7 with IIS 7.5 and FastCGI Module
* * Windows Server 2008 R2 with IIS 7.5 and FastCGI Module
* * PHP 5.2.X, Non-thread-safe build
* * PHP 5.3 X86, Non-thread-safe VC9 build
*
* @package Kohana/Cache
* @category Base
* @author Kohana Team
* @copyright (c) 2009-2012 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_Cache_Wincache extends Cache {
/**
* Check for existence of the wincache extension This method cannot be invoked externally. The driver must
* be instantiated using the `Cache::instance()` method.
*
* @param array $config configuration
* @throws Cache_Exception
*/
protected function __construct(array $config)
{
if ( ! extension_loaded('wincache'))
{
throw new Cache_Exception('PHP wincache extension is not available.');
}
parent::__construct($config);
}
/**
* Retrieve a cached value entry by id.
*
* // Retrieve cache entry from wincache group
* $data = Cache::instance('wincache')->get('foo');
*
* // Retrieve cache entry from wincache group and return 'bar' if miss
* $data = Cache::instance('wincache')->get('foo', 'bar');
*
* @param string $id id of cache to entry
* @param string $default default value to return if cache miss
* @return mixed
* @throws Cache_Exception
*/
public function get($id, $default = NULL)
{
$data = wincache_ucache_get($this->_sanitize_id($id), $success);
return $success ? $data : $default;
}
/**
* Set a value to cache with id and lifetime
*
* $data = 'bar';
*
* // Set 'bar' to 'foo' in wincache group, using default expiry
* Cache::instance('wincache')->set('foo', $data);
*
* // Set 'bar' to 'foo' in wincache group for 30 seconds
* Cache::instance('wincache')->set('foo', $data, 30);
*
* @param string $id id of cache entry
* @param string $data data to set to cache
* @param integer $lifetime lifetime in seconds
* @return boolean
*/
public function set($id, $data, $lifetime = NULL)
{
if ($lifetime === NULL)
{
$lifetime = Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE);
}
return wincache_ucache_set($this->_sanitize_id($id), $data, $lifetime);
}
/**
* Delete a cache entry based on id
*
* // Delete 'foo' entry from the wincache group
* Cache::instance('wincache')->delete('foo');
*
* @param string $id id to remove from cache
* @return boolean
*/
public function delete($id)
{
return wincache_ucache_delete($this->_sanitize_id($id));
}
/**
* 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 wincache group
* Cache::instance('wincache')->delete_all();
*
* @return boolean
*/
public function delete_all()
{
return wincache_ucache_clear();
}
}

View File

@@ -0,0 +1,503 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* HTTT Caching adaptor class that provides caching services to the
* [Request_Client] class, using HTTP cache control logic as defined in
* RFC 2616.
*
* @package Kohana
* @category Base
* @author Kohana Team
* @copyright (c) 2008-2012 Kohana Team
* @license http://kohanaframework.org/license
* @since 3.2.0
*/
class Kohana_HTTP_Cache {
const CACHE_STATUS_KEY = 'x-cache-status';
const CACHE_STATUS_SAVED = 'SAVED';
const CACHE_STATUS_HIT = 'HIT';
const CACHE_STATUS_MISS = 'MISS';
const CACHE_HIT_KEY = 'x-cache-hits';
/**
* Factory method for HTTP_Cache that provides a convenient dependency
* injector for the Cache library.
*
* // Create HTTP_Cache with named cache engine
* $http_cache = HTTP_Cache::factory('memcache', array(
* 'allow_private_cache' => FALSE
* )
* );
*
* // Create HTTP_Cache with supplied cache engine
* $http_cache = HTTP_Cache::factory(Cache::instance('memcache'),
* array(
* 'allow_private_cache' => FALSE
* )
* );
*
* @uses [Cache]
* @param mixed $cache cache engine to use
* @param array $options options to set to this class
* @return HTTP_Cache
*/
public static function factory($cache, array $options = array())
{
if ( ! $cache instanceof Cache)
{
$cache = Cache::instance($cache);
}
$options['cache'] = $cache;
return new HTTP_Cache($options);
}
/**
* Basic cache key generator that hashes the entire request and returns
* it. This is fine for static content, or dynamic content where user
* specific information is encoded into the request.
*
* // Generate cache key
* $cache_key = HTTP_Cache::basic_cache_key_generator($request);
*
* @param Request $request
* @return string
*/
public static function basic_cache_key_generator(Request $request)
{
$uri = $request->uri();
$query = $request->query();
$headers = $request->headers()->getArrayCopy();
$body = $request->body();
return sha1($uri.'?'.http_build_query($query, NULL, '&').'~'.implode('~', $headers).'~'.$body);
}
/**
* @var Cache cache driver to use for HTTP caching
*/
protected $_cache;
/**
* @var callback Cache key generator callback
*/
protected $_cache_key_callback;
/**
* @var boolean Defines whether this client should cache `private` cache directives
* @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
*/
protected $_allow_private_cache = FALSE;
/**
* @var int The timestamp of the request
*/
protected $_request_time;
/**
* @var int The timestamp of the response
*/
protected $_response_time;
/**
* Constructor method for this class. Allows dependency injection of the
* required components such as `Cache` and the cache key generator.
*
* @param array $options
*/
public function __construct(array $options = array())
{
foreach ($options as $key => $value)
{
if (method_exists($this, $key))
{
$this->$key($value);
}
}
if ($this->_cache_key_callback === NULL)
{
$this->cache_key_callback('HTTP_Cache::basic_cache_key_generator');
}
}
/**
* Executes the supplied [Request] with the supplied [Request_Client].
* Before execution, the HTTP_Cache adapter checks the request type,
* destructive requests such as `POST`, `PUT` and `DELETE` will bypass
* cache completely and ensure the response is not cached. All other
* Request methods will allow caching, if the rules are met.
*
* @param Request_Client $client client to execute with Cache-Control
* @param Request $request request to execute with client
* @return [Response]
*/
public function execute(Request_Client $client, Request $request, Response $response)
{
if ( ! $this->_cache instanceof Cache)
return $client->execute_request($request, $response);
// If this is a destructive request, by-pass cache completely
if (in_array($request->method(), array(
HTTP_Request::POST,
HTTP_Request::PUT,
HTTP_Request::DELETE)))
{
// Kill existing caches for this request
$this->invalidate_cache($request);
$response = $client->execute_request($request, $response);
$cache_control = HTTP_Header::create_cache_control(array(
'no-cache',
'must-revalidate'
));
// Ensure client respects destructive action
return $response->headers('cache-control', $cache_control);
}
// Create the cache key
$cache_key = $this->create_cache_key($request, $this->_cache_key_callback);
// Try and return cached version
if (($cached_response = $this->cache_response($cache_key, $request)) instanceof Response)
return $cached_response;
// Start request time
$this->_request_time = time();
// Execute the request with the Request client
$response = $client->execute_request($request, $response);
// Stop response time
$this->_response_time = (time() - $this->_request_time);
// Cache the response
$this->cache_response($cache_key, $request, $response);
$response->headers(HTTP_Cache::CACHE_STATUS_KEY,
HTTP_Cache::CACHE_STATUS_MISS);
return $response;
}
/**
* Invalidate a cached response for the [Request] supplied.
* This has the effect of deleting the response from the
* [Cache] entry.
*
* @param Request $request Response to remove from cache
* @return void
*/
public function invalidate_cache(Request $request)
{
if (($cache = $this->cache()) instanceof Cache)
{
$cache->delete($this->create_cache_key($request, $this->_cache_key_callback));
}
return;
}
/**
* Getter and setter for the internal caching engine,
* used to cache responses if available and valid.
*
* @param Kohana_Cache $cache engine to use for caching
* @return Kohana_Cache
* @return Kohana_Request_Client
*/
public function cache(Cache $cache = NULL)
{
if ($cache === NULL)
return $this->_cache;
$this->_cache = $cache;
return $this;
}
/**
* Gets or sets the [Request_Client::allow_private_cache] setting.
* If set to `TRUE`, the client will also cache cache-control directives
* that have the `private` setting.
*
* @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
* @param boolean $setting allow caching of privately marked responses
* @return boolean
* @return [Request_Client]
*/
public function allow_private_cache($setting = NULL)
{
if ($setting === NULL)
return $this->_allow_private_cache;
$this->_allow_private_cache = (bool) $setting;
return $this;
}
/**
* Sets or gets the cache key generator callback for this caching
* class. The cache key generator provides a unique hash based on the
* `Request` object passed to it.
*
* The default generator is [HTTP_Cache::basic_cache_key_generator()], which
* serializes the entire `HTTP_Request` into a unique sha1 hash. This will
* provide basic caching for static and simple dynamic pages. More complex
* algorithms can be defined and then passed into `HTTP_Cache` using this
* method.
*
* // Get the cache key callback
* $callback = $http_cache->cache_key_callback();
*
* // Set the cache key callback
* $http_cache->cache_key_callback('Foo::cache_key');
*
* // Alternatively, in PHP 5.3 use a closure
* $http_cache->cache_key_callback(function (Request $request) {
* return sha1($request->render());
* });
*
* @param callback $callback
* @return mixed
* @throws HTTP_Exception
*/
public function cache_key_callback($callback = NULL)
{
if ($callback === NULL)
return $this->_cache_key_callback;
if ( ! is_callable($callback))
throw new Kohana_Exception('cache_key_callback must be callable!');
$this->_cache_key_callback = $callback;
return $this;
}
/**
* Creates a cache key for the request to use for caching
* [Kohana_Response] returned by [Request::execute].
*
* This is the default cache key generating logic, but can be overridden
* by setting [HTTP_Cache::cache_key_callback()].
*
* @param Request $request request to create key for
* @param callback $callback optional callback to use instead of built-in method
* @return string
*/
public function create_cache_key(Request $request, $callback = FALSE)
{
if (is_callable($callback))
return call_user_func($callback, $request);
else
return HTTP_Cache::basic_cache_key_generator($request);
}
/**
* Controls whether the response can be cached. Uses HTTP
* protocol to determine whether the response can be cached.
*
* @link RFC 2616 http://www.w3.org/Protocols/rfc2616/
* @param Response $response The Response
* @return boolean
*/
public function set_cache(Response $response)
{
$headers = $response->headers()->getArrayCopy();
if ($cache_control = Arr::get($headers, 'cache-control'))
{
// Parse the cache control
$cache_control = HTTP_Header::parse_cache_control($cache_control);
// If the no-cache or no-store directive is set, return
if (array_intersect($cache_control, array('no-cache', 'no-store')))
return FALSE;
// Check for private cache and get out of here if invalid
if ( ! $this->_allow_private_cache AND in_array('private', $cache_control))
{
if ( ! isset($cache_control['s-maxage']))
return FALSE;
// If there is a s-maxage directive we can use that
$cache_control['max-age'] = $cache_control['s-maxage'];
}
// Check that max-age has been set and if it is valid for caching
if (isset($cache_control['max-age']) AND $cache_control['max-age'] < 1)
return FALSE;
}
if ($expires = Arr::get($headers, 'expires') AND ! isset($cache_control['max-age']))
{
// Can't cache things that have expired already
if (strtotime($expires) <= time())
return FALSE;
}
return TRUE;
}
/**
* Caches a [Response] using the supplied [Cache]
* and the key generated by [Request_Client::_create_cache_key].
*
* If not response is supplied, the cache will be checked for an existing
* one that is available.
*
* @param string $key the cache key to use
* @param Request $request the HTTP Request
* @param Response $response the HTTP Response
* @return mixed
*/
public function cache_response($key, Request $request, Response $response = NULL)
{
if ( ! $this->_cache instanceof Cache)
return FALSE;
// Check for Pragma: no-cache
if ($pragma = $request->headers('pragma'))
{
if ($pragma == 'no-cache')
return FALSE;
elseif (is_array($pragma) AND in_array('no-cache', $pragma))
return FALSE;
}
// If there is no response, lookup an existing cached response
if ($response === NULL)
{
$response = $this->_cache->get($key);
if ( ! $response instanceof Response)
return FALSE;
// Do cache hit arithmetic, using fast arithmetic if available
if ($this->_cache instanceof Cache_Arithmetic)
{
$hit_count = $this->_cache->increment(HTTP_Cache::CACHE_HIT_KEY.$key);
}
else
{
$hit_count = $this->_cache->get(HTTP_Cache::CACHE_HIT_KEY.$key);
$this->_cache->set(HTTP_Cache::CACHE_HIT_KEY.$key, ++$hit_count);
}
// Update the header to have correct HIT status and count
$response->headers(HTTP_Cache::CACHE_STATUS_KEY,
HTTP_Cache::CACHE_STATUS_HIT)
->headers(HTTP_Cache::CACHE_HIT_KEY, $hit_count);
return $response;
}
else
{
if (($ttl = $this->cache_lifetime($response)) === FALSE)
return FALSE;
$response->headers(HTTP_Cache::CACHE_STATUS_KEY,
HTTP_Cache::CACHE_STATUS_SAVED);
// Set the hit count to zero
$this->_cache->set(HTTP_Cache::CACHE_HIT_KEY.$key, 0);
return $this->_cache->set($key, $response, $ttl);
}
}
/**
* Calculates the total Time To Live based on the specification
* RFC 2616 cache lifetime rules.
*
* @param Response $response Response to evaluate
* @return mixed TTL value or false if the response should not be cached
*/
public function cache_lifetime(Response $response)
{
// Get out of here if this cannot be cached
if ( ! $this->set_cache($response))
return FALSE;
// Calculate apparent age
if ($date = $response->headers('date'))
{
$apparent_age = max(0, $this->_response_time - strtotime($date));
}
else
{
$apparent_age = max(0, $this->_response_time);
}
// Calculate corrected received age
if ($age = $response->headers('age'))
{
$corrected_received_age = max($apparent_age, intval($age));
}
else
{
$corrected_received_age = $apparent_age;
}
// Corrected initial age
$corrected_initial_age = $corrected_received_age + $this->request_execution_time();
// Resident time
$resident_time = time() - $this->_response_time;
// Current age
$current_age = $corrected_initial_age + $resident_time;
// Prepare the cache freshness lifetime
$ttl = NULL;
// Cache control overrides
if ($cache_control = $response->headers('cache-control'))
{
// Parse the cache control header
$cache_control = HTTP_Header::parse_cache_control($cache_control);
if (isset($cache_control['max-age']))
{
$ttl = $cache_control['max-age'];
}
if (isset($cache_control['s-maxage']) AND isset($cache_control['private']) AND $this->_allow_private_cache)
{
$ttl = $cache_control['s-maxage'];
}
if (isset($cache_control['max-stale']) AND ! isset($cache_control['must-revalidate']))
{
$ttl = $current_age + $cache_control['max-stale'];
}
}
// If we have a TTL at this point, return
if ($ttl !== NULL)
return $ttl;
if ($expires = $response->headers('expires'))
return strtotime($expires) - $current_age;
return FALSE;
}
/**
* Returns the duration of the last request execution.
* Either returns the time of completed requests or
* `FALSE` if the request hasn't finished executing, or
* is yet to be run.
*
* @return mixed
*/
public function request_execution_time()
{
if ($this->_request_time === NULL OR $this->_response_time === NULL)
return FALSE;
return $this->_response_time - $this->_request_time;
}
} // End Kohana_HTTP_Cache