123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492 |
- <?php
- /**
- * [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) Kohana Team
- * @license https://koseven.ga/LICENSE.md
- */
- 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;
- /**
- * @var boolean does the cache directory exists and writeable
- */
- protected $_cache_dir_usable = FALSE;
- /**
- * Check that the cache directory exists and writeable. Attempts to create
- * it if not exists.
- *
- * @throws Cache_Exception
- */
- protected function _check_cache_dir()
- {
- try
- {
- $directory = Arr::get($this->_config, 'cache_dir', Kohana::$cache_dir);
- $this->_cache_dir = new SplFileInfo($directory);
- }
- 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()]);
- }
- $this->_cache_dir_usable = TRUE;
- }
- /**
- * 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)
- {
- $this->_cache_dir_usable or $this->_check_cache_dir();
- $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;
- }
- }
- /**
- * 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)
- {
- $this->_cache_dir_usable or $this->_check_cache_dir();
- $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;
- }
- }
- /**
- * 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)
- {
- $this->_cache_dir_usable or $this->_check_cache_dir();
- $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();
- *
- * @return boolean
- */
- public function delete_all()
- {
- $this->_cache_dir_usable or $this->_check_cache_dir();
- 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->_cache_dir_usable or $this->_check_cache_dir();
- $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
- $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;
- }
- }
- /**
- * 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 directory path
- * @param integer $mode chmod mode
- * @param boolean $recursive allows nested directories creation
- * @param resource $context a stream context
- * @return SplFileInfo
- * @throws Cache_Exception
- */
- 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);
- }
- /**
- * Test if cache file is expired
- *
- * @param SplFileInfo $file the cache file
- * @return boolean TRUE if expired false otherwise
- */
- protected 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()));
- }
- }
|