FileCache.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. <?php
  2. namespace ForkBB\Core\Cache;
  3. use RecursiveDirectoryIterator;
  4. use RecursiveIteratorIterator;
  5. use RegexIterator;
  6. use RuntimeException;
  7. use InvalidArgumentException;
  8. class FileCache implements ProviderCacheInterface
  9. {
  10. /**
  11. * Директория кэша
  12. * @var string
  13. */
  14. protected $cacheDir;
  15. /**
  16. * Конструктор
  17. *
  18. * @param string $dir
  19. *
  20. * @throws InvalidArgumentException
  21. * @throws RuntimeException
  22. */
  23. public function __construct($dir)
  24. {
  25. if (
  26. empty($dir)
  27. || ! \is_string($dir)
  28. ) {
  29. throw new InvalidArgumentException('Cache directory must be set to a string');
  30. } elseif (! \is_dir($dir)) {
  31. throw new RuntimeException("`$dir`: Not a directory");
  32. } elseif (! \is_writable($dir)) {
  33. throw new RuntimeException("No write access to `$dir` directory");
  34. }
  35. $this->cacheDir = $dir;
  36. }
  37. /**
  38. * Получение данных из кэша по ключу
  39. *
  40. * @param string $key
  41. * @param mixed $default
  42. *
  43. * @return mixed
  44. */
  45. public function get(string $key, $default = null)
  46. {
  47. $file = $this->file($key);
  48. if (\is_file($file)) {
  49. require $file;
  50. if (
  51. isset($expire, $data)
  52. && (
  53. $expire < 1
  54. || $expire > \time()
  55. )
  56. ) {
  57. return $data;
  58. }
  59. }
  60. return $default;
  61. }
  62. /**
  63. * Установка данных в кэш по ключу
  64. *
  65. * @param string $key
  66. * @param mixed $value
  67. * @param int $ttl
  68. *
  69. * @throws RuntimeException
  70. *
  71. * @return bool
  72. */
  73. public function set(string $key, $value, int $ttl = null): bool
  74. {
  75. $file = $this->file($key);
  76. $expire = null === $ttl || $ttl < 1 ? 0 : \time() + $ttl;
  77. $content = "<?php\n\n" . '$expire = ' . $expire . ";\n\n" . '$data = ' . \var_export($value, true) . ";\n";
  78. if (false === \file_put_contents($file, $content, \LOCK_EX)) {
  79. throw new RuntimeException("The key '$key' can not be saved");
  80. } else {
  81. $this->invalidate($file);
  82. return true;
  83. }
  84. }
  85. /**
  86. * Удаление данных по ключу
  87. *
  88. * @param string $key
  89. *
  90. * @throws RuntimeException
  91. *
  92. * @return bool
  93. */
  94. public function delete(string $key): bool
  95. {
  96. $file = $this->file($key);
  97. if (\is_file($file)) {
  98. if (\unlink($file)) {
  99. $this->invalidate($file);
  100. return true;
  101. } else {
  102. throw new RuntimeException("The key `$key` could not be removed");
  103. }
  104. } else {
  105. return true;
  106. }
  107. }
  108. /**
  109. * Очистка кэша
  110. *
  111. * @return bool
  112. */
  113. public function clear(): bool
  114. {
  115. $dir = new RecursiveDirectoryIterator($this->cacheDir, RecursiveDirectoryIterator::SKIP_DOTS);
  116. $iterator = new RecursiveIteratorIterator($dir);
  117. $files = new RegexIterator($iterator, '%\.php$%i', RegexIterator::MATCH);
  118. $result = true;
  119. foreach ($files as $file) {
  120. $result = \unlink($file->getPathname()) && $result;
  121. }
  122. return $result;
  123. }
  124. /**
  125. * Проверка наличия ключа
  126. *
  127. * @param string $key
  128. *
  129. * @return bool
  130. */
  131. public function has(string $key): bool
  132. {
  133. return null !== $this->get($key);
  134. }
  135. /**
  136. * Генерация имени файла по ключу
  137. *
  138. * @param string $key
  139. *
  140. * @throws InvalidArgumentException
  141. *
  142. * @return string
  143. */
  144. protected function file(string $key): string
  145. {
  146. if (
  147. \is_string($key)
  148. && \preg_match('%^[a-z0-9_-]+$%Di', $key)
  149. ) {
  150. return $this->cacheDir . '/cache_' . $key . '.php';
  151. }
  152. throw new InvalidArgumentException("Key '$key' contains invalid characters.");
  153. }
  154. /**
  155. * Очистка opcache и apc от закэшированного файла
  156. *
  157. * @param string $file
  158. */
  159. protected function invalidate(string $file): void
  160. {
  161. if (\function_exists('\\opcache_invalidate')) {
  162. \opcache_invalidate($file, true);
  163. } elseif (\function_exists('\\apc_delete_file')) {
  164. \apc_delete_file($file);
  165. }
  166. }
  167. }