PageRenderTime 27ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/nette/caching/src/Caching/Storages/FileStorage.php

https://gitlab.com/paveltizek/shop
PHP | 405 lines | 252 code | 70 blank | 83 comment | 57 complexity | 7119316828c01e8ded2fc434b40b5849 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the Nette Framework (https://nette.org)
  4. * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  5. */
  6. namespace Nette\Caching\Storages;
  7. use Nette;
  8. use Nette\Caching\Cache;
  9. /**
  10. * Cache file storage.
  11. */
  12. class FileStorage extends Nette\Object implements Nette\Caching\IStorage
  13. {
  14. /**
  15. * Atomic thread safe logic:
  16. *
  17. * 1) reading: open(r+b), lock(SH), read
  18. * - delete?: delete*, close
  19. * 2) deleting: delete*
  20. * 3) writing: open(r+b || wb), lock(EX), truncate*, write data, write meta, close
  21. *
  22. * delete* = try unlink, if fails (on NTFS) { lock(EX), truncate, close, unlink } else close (on ext3)
  23. */
  24. /** @internal cache file structure */
  25. const META_HEADER_LEN = 28, // 22b signature + 6b meta-struct size + serialized meta-struct + data
  26. // meta structure: array of
  27. META_TIME = 'time', // timestamp
  28. META_SERIALIZED = 'serialized', // is content serialized?
  29. META_EXPIRE = 'expire', // expiration timestamp
  30. META_DELTA = 'delta', // relative (sliding) expiration
  31. META_ITEMS = 'di', // array of dependent items (file => timestamp)
  32. META_CALLBACKS = 'callbacks'; // array of callbacks (function, args)
  33. /** additional cache structure */
  34. const FILE = 'file',
  35. HANDLE = 'handle';
  36. /** @var float probability that the clean() routine is started */
  37. public static $gcProbability = 0.001;
  38. /** @var bool */
  39. public static $useDirectories = TRUE;
  40. /** @var string */
  41. private $dir;
  42. /** @var bool */
  43. private $useDirs;
  44. /** @var IJournal */
  45. private $journal;
  46. /** @var array */
  47. private $locks;
  48. public function __construct($dir, IJournal $journal = NULL)
  49. {
  50. if (!is_dir($dir)) {
  51. throw new Nette\DirectoryNotFoundException("Directory '$dir' not found.");
  52. }
  53. $this->dir = $dir;
  54. $this->useDirs = (bool) static::$useDirectories;
  55. $this->journal = $journal;
  56. if (mt_rand() / mt_getrandmax() < static::$gcProbability) {
  57. $this->clean(array());
  58. }
  59. }
  60. /**
  61. * Read from cache.
  62. * @param string key
  63. * @return mixed|NULL
  64. */
  65. public function read($key)
  66. {
  67. $meta = $this->readMetaAndLock($this->getCacheFile($key), LOCK_SH);
  68. if ($meta && $this->verify($meta)) {
  69. return $this->readData($meta); // calls fclose()
  70. } else {
  71. return NULL;
  72. }
  73. }
  74. /**
  75. * Verifies dependencies.
  76. * @param array
  77. * @return bool
  78. */
  79. private function verify($meta)
  80. {
  81. do {
  82. if (!empty($meta[self::META_DELTA])) {
  83. // meta[file] was added by readMetaAndLock()
  84. if (filemtime($meta[self::FILE]) + $meta[self::META_DELTA] < time()) {
  85. break;
  86. }
  87. touch($meta[self::FILE]);
  88. } elseif (!empty($meta[self::META_EXPIRE]) && $meta[self::META_EXPIRE] < time()) {
  89. break;
  90. }
  91. if (!empty($meta[self::META_CALLBACKS]) && !Cache::checkCallbacks($meta[self::META_CALLBACKS])) {
  92. break;
  93. }
  94. if (!empty($meta[self::META_ITEMS])) {
  95. foreach ($meta[self::META_ITEMS] as $depFile => $time) {
  96. $m = $this->readMetaAndLock($depFile, LOCK_SH);
  97. if ($m[self::META_TIME] !== $time || ($m && !$this->verify($m))) {
  98. break 2;
  99. }
  100. }
  101. }
  102. return TRUE;
  103. } while (FALSE);
  104. $this->delete($meta[self::FILE], $meta[self::HANDLE]); // meta[handle] & meta[file] was added by readMetaAndLock()
  105. return FALSE;
  106. }
  107. /**
  108. * Prevents item reading and writing. Lock is released by write() or remove().
  109. * @param string key
  110. * @return void
  111. */
  112. public function lock($key)
  113. {
  114. $cacheFile = $this->getCacheFile($key);
  115. if ($this->useDirs && !is_dir($dir = dirname($cacheFile))) {
  116. @mkdir($dir); // @ - directory may already exist
  117. }
  118. $handle = fopen($cacheFile, 'c+b');
  119. if ($handle) {
  120. $this->locks[$key] = $handle;
  121. flock($handle, LOCK_EX);
  122. }
  123. }
  124. /**
  125. * Writes item into the cache.
  126. * @param string key
  127. * @param mixed data
  128. * @param array dependencies
  129. * @return void
  130. */
  131. public function write($key, $data, array $dp)
  132. {
  133. $meta = array(
  134. self::META_TIME => microtime(),
  135. );
  136. if (isset($dp[Cache::EXPIRATION])) {
  137. if (empty($dp[Cache::SLIDING])) {
  138. $meta[self::META_EXPIRE] = $dp[Cache::EXPIRATION] + time(); // absolute time
  139. } else {
  140. $meta[self::META_DELTA] = (int) $dp[Cache::EXPIRATION]; // sliding time
  141. }
  142. }
  143. if (isset($dp[Cache::ITEMS])) {
  144. foreach ((array) $dp[Cache::ITEMS] as $item) {
  145. $depFile = $this->getCacheFile($item);
  146. $m = $this->readMetaAndLock($depFile, LOCK_SH);
  147. $meta[self::META_ITEMS][$depFile] = $m[self::META_TIME]; // may be NULL
  148. unset($m);
  149. }
  150. }
  151. if (isset($dp[Cache::CALLBACKS])) {
  152. $meta[self::META_CALLBACKS] = $dp[Cache::CALLBACKS];
  153. }
  154. if (!isset($this->locks[$key])) {
  155. $this->lock($key);
  156. if (!isset($this->locks[$key])) {
  157. return;
  158. }
  159. }
  160. $handle = $this->locks[$key];
  161. unset($this->locks[$key]);
  162. $cacheFile = $this->getCacheFile($key);
  163. if (isset($dp[Cache::TAGS]) || isset($dp[Cache::PRIORITY])) {
  164. if (!$this->journal) {
  165. throw new Nette\InvalidStateException('CacheJournal has not been provided.');
  166. }
  167. $this->journal->write($cacheFile, $dp);
  168. }
  169. ftruncate($handle, 0);
  170. if (!is_string($data)) {
  171. $data = serialize($data);
  172. $meta[self::META_SERIALIZED] = TRUE;
  173. }
  174. $head = serialize($meta) . '?>';
  175. $head = '<?php //netteCache[01]' . str_pad((string) strlen($head), 6, '0', STR_PAD_LEFT) . $head;
  176. $headLen = strlen($head);
  177. $dataLen = strlen($data);
  178. do {
  179. if (fwrite($handle, str_repeat("\x00", $headLen), $headLen) !== $headLen) {
  180. break;
  181. }
  182. if (fwrite($handle, $data, $dataLen) !== $dataLen) {
  183. break;
  184. }
  185. fseek($handle, 0);
  186. if (fwrite($handle, $head, $headLen) !== $headLen) {
  187. break;
  188. }
  189. flock($handle, LOCK_UN);
  190. fclose($handle);
  191. return;
  192. } while (FALSE);
  193. $this->delete($cacheFile, $handle);
  194. }
  195. /**
  196. * Removes item from the cache.
  197. * @param string key
  198. * @return void
  199. */
  200. public function remove($key)
  201. {
  202. unset($this->locks[$key]);
  203. $this->delete($this->getCacheFile($key));
  204. }
  205. /**
  206. * Removes items from the cache by conditions & garbage collector.
  207. * @param array conditions
  208. * @return void
  209. */
  210. public function clean(array $conditions)
  211. {
  212. $all = !empty($conditions[Cache::ALL]);
  213. $collector = empty($conditions);
  214. // cleaning using file iterator
  215. if ($all || $collector) {
  216. $now = time();
  217. foreach (Nette\Utils\Finder::find('_*')->from($this->dir)->childFirst() as $entry) {
  218. $path = (string) $entry;
  219. if ($entry->isDir()) { // collector: remove empty dirs
  220. @rmdir($path); // @ - removing dirs is not necessary
  221. continue;
  222. }
  223. if ($all) {
  224. $this->delete($path);
  225. } else { // collector
  226. $meta = $this->readMetaAndLock($path, LOCK_SH);
  227. if (!$meta) {
  228. continue;
  229. }
  230. if ((!empty($meta[self::META_DELTA]) && filemtime($meta[self::FILE]) + $meta[self::META_DELTA] < $now)
  231. || (!empty($meta[self::META_EXPIRE]) && $meta[self::META_EXPIRE] < $now)
  232. ) {
  233. $this->delete($path, $meta[self::HANDLE]);
  234. continue;
  235. }
  236. flock($meta[self::HANDLE], LOCK_UN);
  237. fclose($meta[self::HANDLE]);
  238. }
  239. }
  240. if ($this->journal) {
  241. $this->journal->clean($conditions);
  242. }
  243. return;
  244. }
  245. // cleaning using journal
  246. if ($this->journal) {
  247. foreach ($this->journal->clean($conditions) as $file) {
  248. $this->delete($file);
  249. }
  250. }
  251. }
  252. /**
  253. * Reads cache data from disk.
  254. * @param string file path
  255. * @param int lock mode
  256. * @return array|NULL
  257. */
  258. protected function readMetaAndLock($file, $lock)
  259. {
  260. $handle = @fopen($file, 'r+b'); // @ - file may not exist
  261. if (!$handle) {
  262. return NULL;
  263. }
  264. flock($handle, $lock);
  265. $head = stream_get_contents($handle, self::META_HEADER_LEN);
  266. if ($head && strlen($head) === self::META_HEADER_LEN) {
  267. $size = (int) substr($head, -6);
  268. $meta = stream_get_contents($handle, $size, self::META_HEADER_LEN);
  269. $meta = @unserialize($meta); // intentionally @
  270. if (is_array($meta)) {
  271. $meta[self::FILE] = $file;
  272. $meta[self::HANDLE] = $handle;
  273. return $meta;
  274. }
  275. }
  276. flock($handle, LOCK_UN);
  277. fclose($handle);
  278. return NULL;
  279. }
  280. /**
  281. * Reads cache data from disk and closes cache file handle.
  282. * @param array
  283. * @return mixed
  284. */
  285. protected function readData($meta)
  286. {
  287. $data = stream_get_contents($meta[self::HANDLE]);
  288. flock($meta[self::HANDLE], LOCK_UN);
  289. fclose($meta[self::HANDLE]);
  290. if (empty($meta[self::META_SERIALIZED])) {
  291. return $data;
  292. } else {
  293. return @unserialize($data); // intentionally @
  294. }
  295. }
  296. /**
  297. * Returns file name.
  298. * @param string
  299. * @return string
  300. */
  301. protected function getCacheFile($key)
  302. {
  303. $file = urlencode($key);
  304. if ($this->useDirs && $a = strrpos($file, '%00')) { // %00 = urlencode(Nette\Caching\Cache::NAMESPACE_SEPARATOR)
  305. $file = substr_replace($file, '/_', $a, 3);
  306. }
  307. return $this->dir . '/_' . $file;
  308. }
  309. /**
  310. * Deletes and closes file.
  311. * @param string
  312. * @param resource
  313. * @return void
  314. */
  315. private static function delete($file, $handle = NULL)
  316. {
  317. if (@unlink($file)) { // @ - file may not already exist
  318. if ($handle) {
  319. flock($handle, LOCK_UN);
  320. fclose($handle);
  321. }
  322. return;
  323. }
  324. if (!$handle) {
  325. $handle = @fopen($file, 'r+'); // @ - file may not exist
  326. }
  327. if ($handle) {
  328. flock($handle, LOCK_EX);
  329. ftruncate($handle, 0);
  330. flock($handle, LOCK_UN);
  331. fclose($handle);
  332. @unlink($file); // @ - file may not already exist
  333. }
  334. }
  335. }