vendor/gregwar/cache/Gregwar/Cache/Cache.php line 362

Open in your IDE?
  1. <?php
  2. namespace Gregwar\Cache;
  3. /**
  4. * A cache system based on files
  5. *
  6. * @author Gregwar <g.passault@gmail.com>
  7. */
  8. class Cache implements CacheInterface
  9. {
  10. /**
  11. * Cache directory
  12. * @var string
  13. */
  14. protected $cacheDirectory;
  15. /**
  16. * Use a different directory as actual cache
  17. * @var string|null
  18. */
  19. protected $actualCacheDirectory;
  20. /**
  21. * Prefix directories size
  22. *
  23. * For instance, if the file is helloworld.txt and the prefix size is
  24. * 5, the cache file will be: h/e/l/l/o/helloworld.txt
  25. *
  26. * This is useful to avoid reaching a too large number of files into the
  27. * cache system directories
  28. * @var int
  29. */
  30. protected $prefixSize = 5;
  31. /**
  32. * Directory mode
  33. *
  34. * Allows setting of the access mode for the directories created.
  35. * @var int
  36. */
  37. protected $directoryMode = 0755;
  38. /**
  39. * Constructs the cache system
  40. *
  41. * @param string $cacheDirectory the cache directory
  42. */
  43. public function __construct($cacheDirectory = 'cache')
  44. {
  45. $this->cacheDirectory = $cacheDirectory;
  46. }
  47. /**
  48. * Sets the cache directory
  49. *
  50. * @param string $cacheDirectory the cache directory
  51. * @return self
  52. */
  53. public function setCacheDirectory($cacheDirectory)
  54. {
  55. $this->cacheDirectory = $cacheDirectory;
  56. return $this;
  57. }
  58. /**
  59. * Gets the cache directory
  60. *
  61. * @return string the cache directory
  62. */
  63. public function getCacheDirectory()
  64. {
  65. return $this->cacheDirectory;
  66. }
  67. /**
  68. * Sets the actual cache directory
  69. *
  70. * @param string|null $actualCacheDirectory the actual cache directory
  71. * @return self
  72. */
  73. public function setActualCacheDirectory($actualCacheDirectory = null)
  74. {
  75. $this->actualCacheDirectory = $actualCacheDirectory;
  76. return $this;
  77. }
  78. /**
  79. * Returns the actual cache directory
  80. */
  81. public function getActualCacheDirectory()
  82. {
  83. return $this->actualCacheDirectory ?: $this->cacheDirectory;
  84. }
  85. /**
  86. * Change the prefix size
  87. *
  88. * @param int $prefixSize the size of the prefix directories
  89. * @return self
  90. */
  91. public function setPrefixSize($prefixSize)
  92. {
  93. $this->prefixSize = $prefixSize;
  94. return $this;
  95. }
  96. /**
  97. * Change the directory mode
  98. *
  99. * @param int $directoryMode the directory mode to use
  100. * @return self
  101. */
  102. public function setDirectoryMode($directoryMode)
  103. {
  104. if (!$directoryMode) {
  105. $directoryMode = 0755;
  106. }
  107. $this->directoryMode = $directoryMode;
  108. return $this;
  109. }
  110. /**
  111. * Creates a directory
  112. *
  113. * @param string $directory the target directory
  114. */
  115. protected function mkdir($directory)
  116. {
  117. if (!is_dir($directory)) {
  118. @mkdir($directory, $this->directoryMode, true);
  119. }
  120. }
  121. /**
  122. * Gets the cache file name
  123. *
  124. * @param string $filename the name of the cache file
  125. * @param bool $actual get the actual file or the public file
  126. * @param bool $mkdir a boolean to enable/disable the construction of the
  127. * cache file directory
  128. * @return string
  129. */
  130. public function getCacheFile($filename, $actual = false, $mkdir = false)
  131. {
  132. $path = array();
  133. // Getting the length of the filename before the extension
  134. $parts = explode('.', $filename);
  135. $len = strlen($parts[0]);
  136. for ($i=0; $i<min($len, $this->prefixSize); $i++) {
  137. $path[] = $filename[$i];
  138. }
  139. $path = implode('/', $path);
  140. if ($mkdir) {
  141. $actualDir = $this->getActualCacheDirectory() . '/' . $path;
  142. $this->mkdir($actualDir);
  143. }
  144. $path .= '/' . $filename;
  145. if ($actual) {
  146. return $this->getActualCacheDirectory() . '/' . $path;
  147. } else {
  148. return $this->getCacheDirectory() . '/' . $path;
  149. }
  150. }
  151. /**
  152. * Checks that the cache conditions are respected
  153. *
  154. * @param string $cacheFile the cache file
  155. * @param array $conditions an array of conditions to check
  156. * @return bool
  157. * @throws \Exception
  158. */
  159. protected function checkConditions($cacheFile, array $conditions = array())
  160. {
  161. // Implicit condition: the cache file should exist
  162. if (!file_exists($cacheFile)) {
  163. return false;
  164. }
  165. foreach ($conditions as $type => $value) {
  166. switch ($type) {
  167. case 'maxage':
  168. case 'max-age':
  169. // Return false if the file is older than $value
  170. $age = time() - filemtime($cacheFile);
  171. if ($age > $value) {
  172. return false;
  173. }
  174. break;
  175. case 'younger-than':
  176. case 'youngerthan':
  177. // Return false if the file is older than the file $value, or the files $value
  178. $check = function($filename) use ($cacheFile) {
  179. return !file_exists($filename) || filemtime($cacheFile) < filemtime($filename);
  180. };
  181. if (!is_array($value)) {
  182. if (!$this->isRemote($value) && $check($value)) {
  183. return false;
  184. }
  185. } else {
  186. foreach ($value as $file) {
  187. if (!$this->isRemote($file) && $check($file)) {
  188. return false;
  189. }
  190. }
  191. }
  192. break;
  193. default:
  194. throw new \Exception('Cache condition '.$type.' not supported');
  195. }
  196. }
  197. return true;
  198. }
  199. /**
  200. * Checks if the target filename exists in the cache and if the conditions
  201. * are respected
  202. *
  203. * @param string $filename the filename
  204. * @param array $conditions the conditions to respect
  205. * @return bool
  206. */
  207. public function exists($filename, array $conditions = array())
  208. {
  209. $cacheFile = $this->getCacheFile($filename, true);
  210. return $this->checkConditions($cacheFile, $conditions);
  211. }
  212. /**
  213. * Alias for exists
  214. *
  215. * @param string $filename the filename
  216. * @param array $conditions the conditions to respect
  217. * @return bool
  218. */
  219. public function check($filename, array $conditions = array())
  220. {
  221. return $this->exists($filename, $conditions);
  222. }
  223. /**
  224. * Write data in the cache
  225. *
  226. * @param string $filename the name of the cache file
  227. * @param string $contents the contents to store
  228. * @return self
  229. */
  230. public function set($filename, $contents = '')
  231. {
  232. $cacheFile = $this->getCacheFile($filename, true, true);
  233. file_put_contents($cacheFile, $contents, \LOCK_EX);
  234. return $this;
  235. }
  236. /**
  237. * Alias for set()
  238. *
  239. * @param string $filename the name of the cache file
  240. * @param string $contents the contents to store
  241. * @return self
  242. */
  243. public function write($filename, $contents = '')
  244. {
  245. return $this->set($filename, $contents);
  246. }
  247. /**
  248. * Get data from the cache
  249. *
  250. * @param string $filename the cache file name
  251. * @param array $conditions
  252. * @return null|string
  253. */
  254. public function get($filename, array $conditions = array())
  255. {
  256. if ($this->exists($filename, $conditions)) {
  257. return file_get_contents($this->getCacheFile($filename, true));
  258. } else {
  259. return null;
  260. }
  261. }
  262. /**
  263. * Is this URL remote?
  264. *
  265. * @param string $file
  266. * @return bool
  267. */
  268. protected function isRemote($file)
  269. {
  270. if (preg_match('/^([a-z]+):\/\//', $file, $match)) {
  271. return ($match[1] != 'file');
  272. }
  273. return false;
  274. }
  275. /**
  276. * Get or create the cache entry
  277. *
  278. * @param string $filename the cache file name
  279. * @param array $conditions an array of conditions about expiration
  280. * @param \Closure $function the closure to call if the file does not exist
  281. * @param bool $file returns the cache file or the file contents
  282. * @param bool $actual returns the actual cache file
  283. * @return string
  284. * @throws \InvalidArgumentException
  285. */
  286. public function getOrCreate($filename, array $conditions, $function, $file = false, $actual = false)
  287. {
  288. if (!is_callable($function)) {
  289. throw new \InvalidArgumentException('The argument $function should be callable');
  290. }
  291. $cacheFile = $this->getCacheFile($filename, true, true);
  292. $data = null;
  293. if (!$this->check($filename, $conditions)) {
  294. if(file_exists($cacheFile)) {
  295. unlink($cacheFile);
  296. }
  297. $data = call_user_func($function, $cacheFile);
  298. // Test if the closure wrote the file or if it returned the data
  299. if (!file_exists($cacheFile)) {
  300. $this->set($filename, $data);
  301. } else {
  302. $data = file_get_contents($cacheFile);
  303. }
  304. }
  305. return $file ? $this->getCacheFile($filename, $actual) : file_get_contents($cacheFile);
  306. }
  307. /**
  308. * Alias to getOrCreate with $file = true
  309. *
  310. * @param string $filename the cache file name
  311. * @param array $conditions an array of conditions about expiration
  312. * @param \Closure $function the closure to call if the file does not exist
  313. * @param bool $actual returns the actual cache file
  314. * @return string
  315. * @throws \InvalidArgumentException
  316. */
  317. public function getOrCreateFile($filename, array $conditions, $function, $actual = false)
  318. {
  319. return $this->getOrCreate($filename, $conditions, $function, true, $actual);
  320. }
  321. }