PageRenderTime 26ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/cakephp/cakephp/src/Cache/Engine/FileEngine.php

https://gitlab.com/minotnepal/www
PHP | 486 lines | 318 code | 43 blank | 125 comment | 43 complexity | e9a5c1a5bbb7585d2965471677088dae MD5 | raw file
Possible License(s): BSD-3-Clause, MIT
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 1.2.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Cache\Engine;
  16. use Cake\Cache\CacheEngine;
  17. use Cake\Core\Configure;
  18. use Cake\Utility\Inflector;
  19. use Exception;
  20. use LogicException;
  21. /**
  22. * File Storage engine for cache. Filestorage is the slowest cache storage
  23. * to read and write. However, it is good for servers that don't have other storage
  24. * engine available, or have content which is not performance sensitive.
  25. *
  26. * You can configure a FileEngine cache, using Cache::config()
  27. *
  28. */
  29. class FileEngine extends CacheEngine
  30. {
  31. /**
  32. * Instance of SplFileObject class
  33. *
  34. * @var \SplFileObject
  35. */
  36. protected $_File = null;
  37. /**
  38. * The default config used unless overriden by runtime configuration
  39. *
  40. * - `duration` Specify how long items in this cache configuration last.
  41. * - `groups` List of groups or 'tags' associated to every key stored in this config.
  42. * handy for deleting a complete group from cache.
  43. * - `isWindows` Automatically populated with whether the host is windows or not
  44. * - `lock` Used by FileCache. Should files be locked before writing to them?
  45. * - `mask` The mask used for created files
  46. * - `path` Path to where cachefiles should be saved. Defaults to system's temp dir.
  47. * - `prefix` Prepended to all entries. Good for when you need to share a keyspace
  48. * with either another cache config or another application.
  49. * - `probability` Probability of hitting a cache gc cleanup. Setting to 0 will disable
  50. * cache::gc from ever being called automatically.
  51. * - `serialize` Should cache objects be serialized first.
  52. *
  53. * @var array
  54. */
  55. protected $_defaultConfig = [
  56. 'duration' => 3600,
  57. 'groups' => [],
  58. 'isWindows' => false,
  59. 'lock' => true,
  60. 'mask' => 0664,
  61. 'path' => null,
  62. 'prefix' => 'cake_',
  63. 'probability' => 100,
  64. 'serialize' => true
  65. ];
  66. /**
  67. * True unless FileEngine::__active(); fails
  68. *
  69. * @var bool
  70. */
  71. protected $_init = true;
  72. /**
  73. * Initialize File Cache Engine
  74. *
  75. * Called automatically by the cache frontend.
  76. *
  77. * @param array $config array of setting for the engine
  78. * @return bool True if the engine has been successfully initialized, false if not
  79. */
  80. public function init(array $config = [])
  81. {
  82. parent::init($config);
  83. if ($this->_config['path'] === null) {
  84. $this->_config['path'] = sys_get_temp_dir();
  85. }
  86. if (DS === '\\') {
  87. $this->_config['isWindows'] = true;
  88. }
  89. if (substr($this->_config['path'], -1) !== DS) {
  90. $this->_config['path'] .= DS;
  91. }
  92. if (!empty($this->_groupPrefix)) {
  93. $this->_groupPrefix = str_replace('_', DS, $this->_groupPrefix);
  94. }
  95. return $this->_active();
  96. }
  97. /**
  98. * Garbage collection. Permanently remove all expired and deleted data
  99. *
  100. * @param int|null $expires [optional] An expires timestamp, invalidating all data before.
  101. * @return bool True if garbage collection was successful, false on failure
  102. */
  103. public function gc($expires = null)
  104. {
  105. return $this->clear(true);
  106. }
  107. /**
  108. * Write data for key into cache
  109. *
  110. * @param string $key Identifier for the data
  111. * @param mixed $data Data to be cached
  112. * @return bool True if the data was successfully cached, false on failure
  113. */
  114. public function write($key, $data)
  115. {
  116. if ($data === '' || !$this->_init) {
  117. return false;
  118. }
  119. $key = $this->_key($key);
  120. if ($this->_setKey($key, true) === false) {
  121. return false;
  122. }
  123. $lineBreak = "\n";
  124. if ($this->_config['isWindows']) {
  125. $lineBreak = "\r\n";
  126. }
  127. if (!empty($this->_config['serialize'])) {
  128. if ($this->_config['isWindows']) {
  129. $data = str_replace('\\', '\\\\\\\\', serialize($data));
  130. } else {
  131. $data = serialize($data);
  132. }
  133. }
  134. $duration = $this->_config['duration'];
  135. $expires = time() + $duration;
  136. $contents = $expires . $lineBreak . $data . $lineBreak;
  137. if ($this->_config['lock']) {
  138. $this->_File->flock(LOCK_EX);
  139. }
  140. $this->_File->rewind();
  141. $success = $this->_File->ftruncate(0) &&
  142. $this->_File->fwrite($contents) &&
  143. $this->_File->fflush();
  144. if ($this->_config['lock']) {
  145. $this->_File->flock(LOCK_UN);
  146. }
  147. $this->_File = null;
  148. return $success;
  149. }
  150. /**
  151. * Read a key from the cache
  152. *
  153. * @param string $key Identifier for the data
  154. * @return mixed The cached data, or false if the data doesn't exist, has
  155. * expired, or if there was an error fetching it
  156. */
  157. public function read($key)
  158. {
  159. $key = $this->_key($key);
  160. if (!$this->_init || $this->_setKey($key) === false) {
  161. return false;
  162. }
  163. if ($this->_config['lock']) {
  164. $this->_File->flock(LOCK_SH);
  165. }
  166. $this->_File->rewind();
  167. $time = time();
  168. $cachetime = (int)$this->_File->current();
  169. if ($cachetime !== false &&
  170. ($cachetime < $time || ($time + $this->_config['duration']) < $cachetime)
  171. ) {
  172. if ($this->_config['lock']) {
  173. $this->_File->flock(LOCK_UN);
  174. }
  175. return false;
  176. }
  177. $data = '';
  178. $this->_File->next();
  179. while ($this->_File->valid()) {
  180. $data .= $this->_File->current();
  181. $this->_File->next();
  182. }
  183. if ($this->_config['lock']) {
  184. $this->_File->flock(LOCK_UN);
  185. }
  186. $data = trim($data);
  187. if ($data !== '' && !empty($this->_config['serialize'])) {
  188. if ($this->_config['isWindows']) {
  189. $data = str_replace('\\\\\\\\', '\\', $data);
  190. }
  191. $data = unserialize((string)$data);
  192. }
  193. return $data;
  194. }
  195. /**
  196. * Delete a key from the cache
  197. *
  198. * @param string $key Identifier for the data
  199. * @return bool True if the value was successfully deleted, false if it didn't
  200. * exist or couldn't be removed
  201. */
  202. public function delete($key)
  203. {
  204. $key = $this->_key($key);
  205. if ($this->_setKey($key) === false || !$this->_init) {
  206. return false;
  207. }
  208. $path = $this->_File->getRealPath();
  209. $this->_File = null;
  210. //@codingStandardsIgnoreStart
  211. return @unlink($path);
  212. //@codingStandardsIgnoreEnd
  213. }
  214. /**
  215. * Delete all values from the cache
  216. *
  217. * @param bool $check Optional - only delete expired cache items
  218. * @return bool True if the cache was successfully cleared, false otherwise
  219. */
  220. public function clear($check)
  221. {
  222. if (!$this->_init) {
  223. return false;
  224. }
  225. $this->_File = null;
  226. $threshold = $now = false;
  227. if ($check) {
  228. $now = time();
  229. $threshold = $now - $this->_config['duration'];
  230. }
  231. $this->_clearDirectory($this->_config['path'], $now, $threshold);
  232. $directory = new \RecursiveDirectoryIterator($this->_config['path']);
  233. $contents = new \RecursiveIteratorIterator(
  234. $directory,
  235. \RecursiveIteratorIterator::SELF_FIRST
  236. );
  237. $cleared = [];
  238. foreach ($contents as $path) {
  239. if ($path->isFile()) {
  240. continue;
  241. }
  242. $path = $path->getRealPath() . DS;
  243. if (!in_array($path, $cleared)) {
  244. $this->_clearDirectory($path, $now, $threshold);
  245. $cleared[] = $path;
  246. }
  247. }
  248. return true;
  249. }
  250. /**
  251. * Used to clear a directory of matching files.
  252. *
  253. * @param string $path The path to search.
  254. * @param int $now The current timestamp
  255. * @param int $threshold Any file not modified after this value will be deleted.
  256. * @return void
  257. */
  258. protected function _clearDirectory($path, $now, $threshold)
  259. {
  260. $prefixLength = strlen($this->_config['prefix']);
  261. if (!is_dir($path)) {
  262. return;
  263. }
  264. $dir = dir($path);
  265. while (($entry = $dir->read()) !== false) {
  266. if (substr($entry, 0, $prefixLength) !== $this->_config['prefix']) {
  267. continue;
  268. }
  269. try {
  270. $file = new \SplFileObject($path . $entry, 'r');
  271. } catch (Exception $e) {
  272. continue;
  273. }
  274. if ($threshold) {
  275. $mtime = $file->getMTime();
  276. if ($mtime > $threshold) {
  277. continue;
  278. }
  279. $expires = (int)$file->current();
  280. if ($expires > $now) {
  281. continue;
  282. }
  283. }
  284. if ($file->isFile()) {
  285. $filePath = $file->getRealPath();
  286. $file = null;
  287. //@codingStandardsIgnoreStart
  288. @unlink($filePath);
  289. //@codingStandardsIgnoreEnd
  290. }
  291. }
  292. }
  293. /**
  294. * Not implemented
  295. *
  296. * @param string $key The key to decrement
  297. * @param int $offset The number to offset
  298. * @return void
  299. * @throws \LogicException
  300. */
  301. public function decrement($key, $offset = 1)
  302. {
  303. throw new LogicException('Files cannot be atomically decremented.');
  304. }
  305. /**
  306. * Not implemented
  307. *
  308. * @param string $key The key to decrement
  309. * @param int $offset The number to offset
  310. * @return void
  311. * @throws \LogicException
  312. */
  313. public function increment($key, $offset = 1)
  314. {
  315. throw new LogicException('Files cannot be atomically incremented.');
  316. }
  317. /**
  318. * Sets the current cache key this class is managing, and creates a writable SplFileObject
  319. * for the cache file the key is referring to.
  320. *
  321. * @param string $key The key
  322. * @param bool $createKey Whether the key should be created if it doesn't exists, or not
  323. * @return bool true if the cache key could be set, false otherwise
  324. */
  325. protected function _setKey($key, $createKey = false)
  326. {
  327. $groups = null;
  328. if (!empty($this->_groupPrefix)) {
  329. $groups = vsprintf($this->_groupPrefix, $this->groups());
  330. }
  331. $dir = $this->_config['path'] . $groups;
  332. if (!is_dir($dir)) {
  333. mkdir($dir, 0775, true);
  334. }
  335. $path = new \SplFileInfo($dir . $key);
  336. if (!$createKey && !$path->isFile()) {
  337. return false;
  338. }
  339. if (empty($this->_File) || $this->_File->getBaseName() !== $key) {
  340. $exists = file_exists($path->getPathname());
  341. try {
  342. $this->_File = $path->openFile('c+');
  343. } catch (Exception $e) {
  344. trigger_error($e->getMessage(), E_USER_WARNING);
  345. return false;
  346. }
  347. unset($path);
  348. if (!$exists &&
  349. !chmod($this->_File->getPathname(), (int)$this->_config['mask'])
  350. ) {
  351. trigger_error(sprintf(
  352. 'Could not apply permission mask "%s" on cache file "%s"',
  353. $this->_File->getPathname(),
  354. $this->_config['mask']
  355. ), E_USER_WARNING);
  356. }
  357. }
  358. return true;
  359. }
  360. /**
  361. * Determine is cache directory is writable
  362. *
  363. * @return bool
  364. */
  365. protected function _active()
  366. {
  367. $dir = new \SplFileInfo($this->_config['path']);
  368. $path = $dir->getPathname();
  369. if (!is_dir($path)) {
  370. mkdir($path, 0775, true);
  371. }
  372. if ($this->_init && !($dir->isDir() && $dir->isWritable())) {
  373. $this->_init = false;
  374. trigger_error(sprintf(
  375. '%s is not writable',
  376. $this->_config['path']
  377. ), E_USER_WARNING);
  378. return false;
  379. }
  380. return true;
  381. }
  382. /**
  383. * Generates a safe key for use with cache engine storage engines.
  384. *
  385. * @param string $key the key passed over
  386. * @return mixed string $key or false
  387. */
  388. public function key($key)
  389. {
  390. if (empty($key)) {
  391. return false;
  392. }
  393. $key = Inflector::underscore(str_replace(
  394. [DS, '/', '.', '<', '>', '?', ':', '|', '*', '"'],
  395. '_',
  396. strval($key)
  397. ));
  398. return $key;
  399. }
  400. /**
  401. * Recursively deletes all files under any directory named as $group
  402. *
  403. * @param string $group The group to clear.
  404. * @return bool success
  405. */
  406. public function clearGroup($group)
  407. {
  408. $this->_File = null;
  409. $directoryIterator = new \RecursiveDirectoryIterator($this->_config['path']);
  410. $contents = new \RecursiveIteratorIterator(
  411. $directoryIterator,
  412. \RecursiveIteratorIterator::CHILD_FIRST
  413. );
  414. foreach ($contents as $object) {
  415. $containsGroup = strpos($object->getPathName(), DS . $group . DS) !== false;
  416. $hasPrefix = true;
  417. if (strlen($this->_config['prefix']) !== 0) {
  418. $hasPrefix = strpos($object->getBaseName(), $this->_config['prefix']) === 0;
  419. }
  420. if ($object->isFile() && $containsGroup && $hasPrefix) {
  421. $path = $object->getPathName();
  422. $object = null;
  423. //@codingStandardsIgnoreStart
  424. @unlink($path);
  425. //@codingStandardsIgnoreEnd
  426. }
  427. }
  428. return true;
  429. }
  430. }