PageRenderTime 45ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/cache/classes/kohana/cache/file.php

https://github.com/d4rky-pl/kohana-api-lookup
PHP | 466 lines | 224 code | 39 blank | 203 comment | 24 complexity | 6e22a16a7d13cf17890e40c759fef464 MD5 | raw file
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. /**
  3. * [Kohana Cache](api/Kohana_Cache) File driver. Provides a file based
  4. * driver for the Kohana Cache library. This is one of the slowest
  5. * caching methods.
  6. *
  7. * ### Configuration example
  8. *
  9. * Below is an example of a _file_ server configuration.
  10. *
  11. * return array(
  12. * 'file' => array( // File driver group
  13. * 'driver' => 'file', // using File driver
  14. * 'cache_dir' => APPPATH.'cache/.kohana_cache', // Cache location
  15. * ),
  16. * )
  17. *
  18. * In cases where only one cache group is required, if the group is named `default` there is
  19. * no need to pass the group name when instantiating a cache instance.
  20. *
  21. * #### General cache group configuration settings
  22. *
  23. * Below are the settings available to all types of cache driver.
  24. *
  25. * Name | Required | Description
  26. * -------------- | -------- | ---------------------------------------------------------------
  27. * driver | __YES__ | (_string_) The driver type to use
  28. * cache_dir | __NO__ | (_string_) The cache directory to use for this cache instance
  29. *
  30. * ### System requirements
  31. *
  32. * * Kohana 3.0.x
  33. * * PHP 5.2.4 or greater
  34. *
  35. * @package Kohana/Cache
  36. * @category Base
  37. * @author Kohana Team
  38. * @copyright (c) 2009-2010 Kohana Team
  39. * @license http://kohanaphp.com/license
  40. */
  41. class Kohana_Cache_File extends Cache implements Cache_GarbageCollect {
  42. /**
  43. * Creates a hashed filename based on the string. This is used
  44. * to create shorter unique IDs for each cache filename.
  45. *
  46. * // Create the cache filename
  47. * $filename = Cache_File::filename($this->_sanitize_id($id));
  48. *
  49. * @param string string to hash into filename
  50. * @return string
  51. */
  52. protected static function filename($string)
  53. {
  54. return sha1($string).'.cache';
  55. }
  56. /**
  57. * @var string the caching directory
  58. */
  59. protected $_cache_dir;
  60. /**
  61. * Constructs the file cache driver. This method cannot be invoked externally. The file cache driver must
  62. * be instantiated using the `Cache::instance()` method.
  63. *
  64. * @param array config
  65. * @throws Cache_Exception
  66. */
  67. protected function __construct(array $config)
  68. {
  69. // Setup parent
  70. parent::__construct($config);
  71. try
  72. {
  73. $directory = Arr::get($this->_config, 'cache_dir', Kohana::$cache_dir);
  74. $this->_cache_dir = new SplFileInfo($directory);
  75. }
  76. // PHP < 5.3 exception handle
  77. catch (ErrorException $e)
  78. {
  79. $this->_cache_dir = $this->_make_directory($directory, 0777, TRUE);
  80. }
  81. // PHP >= 5.3 exception handle
  82. catch (UnexpectedValueException $e)
  83. {
  84. $this->_cache_dir = $this->_make_directory($directory, 0777, TRUE);
  85. }
  86. // If the defined directory is a file, get outta here
  87. if ($this->_cache_dir->isFile())
  88. {
  89. throw new Cache_Exception('Unable to create cache directory as a file already exists : :resource', array(':resource' => $this->_cache_dir->getRealPath()));
  90. }
  91. // Check the read status of the directory
  92. if ( ! $this->_cache_dir->isReadable())
  93. {
  94. throw new Cache_Exception('Unable to read from the cache directory :resource', array(':resource' => $this->_cache_dir->getRealPath()));
  95. }
  96. // Check the write status of the directory
  97. if ( ! $this->_cache_dir->isWritable())
  98. {
  99. throw new Cache_Exception('Unable to write to the cache directory :resource', array(':resource' => $this->_cache_dir->getRealPath()));
  100. }
  101. }
  102. /**
  103. * Retrieve a cached value entry by id.
  104. *
  105. * // Retrieve cache entry from file group
  106. * $data = Cache::instance('file')->get('foo');
  107. *
  108. * // Retrieve cache entry from file group and return 'bar' if miss
  109. * $data = Cache::instance('file')->get('foo', 'bar');
  110. *
  111. * @param string id of cache to entry
  112. * @param string default value to return if cache miss
  113. * @return mixed
  114. * @throws Cache_Exception
  115. */
  116. public function get($id, $default = NULL)
  117. {
  118. $filename = Cache_File::filename($this->_sanitize_id($id));
  119. $directory = $this->_resolve_directory($filename);
  120. // Wrap operations in try/catch to handle notices
  121. try
  122. {
  123. // Open file
  124. $file = new SplFileInfo($directory.$filename);
  125. // If file does not exist
  126. if ( ! $file->isFile())
  127. {
  128. // Return default value
  129. return $default;
  130. }
  131. else
  132. {
  133. // Open the file and parse data
  134. $created = $file->getMTime();
  135. $data = $file->openFile();
  136. $lifetime = $data->fgets();
  137. // If we're at the EOF at this point, corrupted!
  138. if ($data->eof())
  139. {
  140. throw new Cache_Exception(__METHOD__.' corrupted cache file!');
  141. }
  142. $cache = '';
  143. while ($data->eof() === FALSE)
  144. {
  145. $cache .= $data->fgets();
  146. }
  147. // Test the expiry
  148. if (($created + (int) $lifetime) < time())
  149. {
  150. // Delete the file
  151. $this->_delete_file($file, NULL, TRUE);
  152. return $default;
  153. }
  154. else
  155. {
  156. return unserialize($cache);
  157. }
  158. }
  159. }
  160. catch (ErrorException $e)
  161. {
  162. // Handle ErrorException caused by failed unserialization
  163. if ($e->getCode() === E_NOTICE)
  164. {
  165. throw new Cache_Exception(__METHOD__.' failed to unserialize cached object with message : '.$e->getMessage());
  166. }
  167. // Otherwise throw the exception
  168. throw $e;
  169. }
  170. }
  171. /**
  172. * Set a value to cache with id and lifetime
  173. *
  174. * $data = 'bar';
  175. *
  176. * // Set 'bar' to 'foo' in file group, using default expiry
  177. * Cache::instance('file')->set('foo', $data);
  178. *
  179. * // Set 'bar' to 'foo' in file group for 30 seconds
  180. * Cache::instance('file')->set('foo', $data, 30);
  181. *
  182. * @param string id of cache entry
  183. * @param string data to set to cache
  184. * @param integer lifetime in seconds
  185. * @return boolean
  186. */
  187. public function set($id, $data, $lifetime = NULL)
  188. {
  189. $filename = Cache_File::filename($this->_sanitize_id($id));
  190. $directory = $this->_resolve_directory($filename);
  191. // If lifetime is NULL
  192. if ($lifetime === NULL)
  193. {
  194. // Set to the default expiry
  195. $lifetime = Arr::get($this->_config, 'default_expire', Cache::DEFAULT_EXPIRE);
  196. }
  197. // Open directory
  198. $dir = new SplFileInfo($directory);
  199. // If the directory path is not a directory
  200. if ( ! $dir->isDir())
  201. {
  202. // Create the directory
  203. if ( ! mkdir($directory, 0777, TRUE))
  204. {
  205. throw new Cache_Exception(__METHOD__.' unable to create directory : :directory', array(':directory' => $directory));
  206. }
  207. // chmod to solve potential umask issues
  208. chmod($directory, 0777);
  209. }
  210. // Open file to inspect
  211. $resouce = new SplFileInfo($directory.$filename);
  212. $file = $resouce->openFile('w');
  213. try
  214. {
  215. $data = $lifetime."\n".serialize($data);
  216. $file->fwrite($data, strlen($data));
  217. return (bool) $file->fflush();
  218. }
  219. catch (ErrorException $e)
  220. {
  221. // If serialize through an error exception
  222. if ($e->getCode() === E_NOTICE)
  223. {
  224. // Throw a caching error
  225. throw new Cache_Exception(__METHOD__.' failed to serialize data for caching with message : '.$e->getMessage());
  226. }
  227. // Else rethrow the error exception
  228. throw $e;
  229. }
  230. }
  231. /**
  232. * Delete a cache entry based on id
  233. *
  234. * // Delete 'foo' entry from the file group
  235. * Cache::instance('file')->delete('foo');
  236. *
  237. * @param string id to remove from cache
  238. * @return boolean
  239. */
  240. public function delete($id)
  241. {
  242. $filename = Cache_File::filename($this->_sanitize_id($id));
  243. $directory = $this->_resolve_directory($filename);
  244. return $this->_delete_file(new SplFileInfo($directory.$filename), NULL, TRUE);
  245. }
  246. /**
  247. * Delete all cache entries.
  248. *
  249. * Beware of using this method when
  250. * using shared memory cache systems, as it will wipe every
  251. * entry within the system for all clients.
  252. *
  253. * // Delete all cache entries in the file group
  254. * Cache::instance('file')->delete_all();
  255. *
  256. * @return boolean
  257. */
  258. public function delete_all()
  259. {
  260. return $this->_delete_file($this->_cache_dir, TRUE);
  261. }
  262. /**
  263. * Garbage collection method that cleans any expired
  264. * cache entries from the cache.
  265. *
  266. * @return void
  267. */
  268. public function garbage_collect()
  269. {
  270. $this->_delete_file($this->_cache_dir, TRUE, FALSE, TRUE);
  271. return;
  272. }
  273. /**
  274. * Deletes files recursively and returns FALSE on any errors
  275. *
  276. * // Delete a file or folder whilst retaining parent directory and ignore all errors
  277. * $this->_delete_file($folder, TRUE, TRUE);
  278. *
  279. * @param SplFileInfo file
  280. * @param boolean retain the parent directory
  281. * @param boolean ignore_errors to prevent all exceptions interrupting exec
  282. * @param boolean only expired files
  283. * @return boolean
  284. * @throws Cache_Exception
  285. */
  286. protected function _delete_file(SplFileInfo $file, $retain_parent_directory = FALSE, $ignore_errors = FALSE, $only_expired = FALSE)
  287. {
  288. // Allow graceful error handling
  289. try
  290. {
  291. // If is file
  292. if ($file->isFile())
  293. {
  294. try
  295. {
  296. // Handle ignore files
  297. if (in_array($file->getFilename(), $this->config('ignore_on_delete')))
  298. {
  299. $delete = FALSE;
  300. }
  301. // If only expired is not set
  302. elseif ($only_expired === FALSE)
  303. {
  304. // We want to delete the file
  305. $delete = TRUE;
  306. }
  307. // Otherwise...
  308. else
  309. {
  310. // Assess the file expiry to flag it for deletion
  311. $json = $file->openFile('r')->current();
  312. $data = json_decode($json);
  313. $delete = $data->expiry < time();
  314. }
  315. // If the delete flag is set delete file
  316. if ($delete === TRUE)
  317. return unlink($file->getRealPath());
  318. else
  319. return FALSE;
  320. }
  321. catch (ErrorException $e)
  322. {
  323. // Catch any delete file warnings
  324. if ($e->getCode() === E_WARNING)
  325. {
  326. throw new Cache_Exception(__METHOD__.' failed to delete file : :file', array(':file' => $file->getRealPath()));
  327. }
  328. }
  329. }
  330. // Else, is directory
  331. elseif ($file->isDir())
  332. {
  333. // Create new DirectoryIterator
  334. $files = new DirectoryIterator($file->getPathname());
  335. // Iterate over each entry
  336. while ($files->valid())
  337. {
  338. // Extract the entry name
  339. $name = $files->getFilename();
  340. // If the name is not a dot
  341. if ($name != '.' AND $name != '..')
  342. {
  343. // Create new file resource
  344. $fp = new SplFileInfo($files->getRealPath());
  345. // Delete the file
  346. $this->_delete_file($fp);
  347. }
  348. // Move the file pointer on
  349. $files->next();
  350. }
  351. // If set to retain parent directory, return now
  352. if ($retain_parent_directory)
  353. {
  354. return TRUE;
  355. }
  356. try
  357. {
  358. // Remove the files iterator
  359. // (fixes Windows PHP which has permission issues with open iterators)
  360. unset($files);
  361. // Try to remove the parent directory
  362. return rmdir($file->getRealPath());
  363. }
  364. catch (ErrorException $e)
  365. {
  366. // Catch any delete directory warnings
  367. if ($e->getCode() === E_WARNING)
  368. {
  369. throw new Cache_Exception(__METHOD__.' failed to delete directory : :directory', array(':directory' => $file->getRealPath()));
  370. }
  371. throw $e;
  372. }
  373. }
  374. else
  375. {
  376. // We get here if a file has already been deleted
  377. return FALSE;
  378. }
  379. }
  380. // Catch all exceptions
  381. catch (Exception $e)
  382. {
  383. // If ignore_errors is on
  384. if ($ignore_errors === TRUE)
  385. {
  386. // Return
  387. return FALSE;
  388. }
  389. // Throw exception
  390. throw $e;
  391. }
  392. }
  393. /**
  394. * Resolves the cache directory real path from the filename
  395. *
  396. * // Get the realpath of the cache folder
  397. * $realpath = $this->_resolve_directory($filename);
  398. *
  399. * @param string filename to resolve
  400. * @return string
  401. */
  402. protected function _resolve_directory($filename)
  403. {
  404. return $this->_cache_dir->getRealPath().DIRECTORY_SEPARATOR.$filename[0].$filename[1].DIRECTORY_SEPARATOR;
  405. }
  406. /**
  407. * Makes the cache directory if it doesn't exist. Simply a wrapper for
  408. * `mkdir` to ensure DRY principles
  409. *
  410. * @see http://php.net/manual/en/function.mkdir.php
  411. * @param string directory
  412. * @param string mode
  413. * @param string recursive
  414. * @param string context
  415. * @return SplFileInfo
  416. * @throws Cache_Exception
  417. */
  418. protected function _make_directory($directory, $mode = 0777, $recursive = FALSE, $context = NULL)
  419. {
  420. if ( ! mkdir($directory, $mode, $recursive, $context))
  421. {
  422. throw new Cache_Exception('Failed to create the defined cache directory : :directory', array(':directory' => $directory));
  423. }
  424. chmod($directory, $mode);
  425. return new SplFileInfo($directory);;
  426. }
  427. }