PageRenderTime 51ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/php/FileCache.php

https://gitlab.com/Blueprint-Marketing/cli
PHP | 365 lines | 305 code | 11 blank | 49 comment | 0 complexity | 4bdddc320f21d8c3fb63cfec1ff9a860 MD5 | raw file
  1. <?php
  2. /*
  3. * This file is heavily inspired and use code from Composer(getcomposer.org),
  4. * in particular Composer/Cache and Composer/Util/FileSystem from 1.0.0-alpha7
  5. *
  6. * The original code and this file are both released under MIT license.
  7. *
  8. * The copyright holders of the original code are:
  9. * (c) Nils Adermann <naderman@naderman.de>
  10. * Jordi Boggiano <j.boggiano@seld.be>
  11. */
  12. namespace Terminus;
  13. use Symfony\Component\Finder\Finder;
  14. /**
  15. * Reads/writes to a filesystem cache
  16. */
  17. class FileCache {
  18. /**
  19. * @var string cache path
  20. */
  21. protected $root;
  22. /**
  23. * @var bool
  24. */
  25. protected $enabled = true;
  26. /**
  27. * @var int files time to live
  28. */
  29. protected $ttl = 36000;
  30. /**
  31. * @var int max total size
  32. */
  33. protected $maxSize;
  34. /**
  35. * @var string key allowed chars (regex class)
  36. */
  37. protected $whitelist;
  38. /**
  39. * @param string $cacheDir location of the cache
  40. * @param int $ttl cache files default time to live (expiration)
  41. * @param int $maxSize max total cache size
  42. * @param string $whitelist List of characters that are allowed in path names (used in a regex character class)
  43. */
  44. public function __construct( $cacheDir, $ttl, $maxSize, $whitelist = 'a-z0-9._-' ) {
  45. $this->root = rtrim( $cacheDir, '/\\' ) . '/';
  46. $this->ttl = (int) $ttl;
  47. $this->maxSize = (int) $maxSize;
  48. $this->whitelist = $whitelist;
  49. if ( !$this->ensure_dir_exists( $this->root ) ) {
  50. $this->enabled = false;
  51. }
  52. }
  53. /**
  54. * Cache is enabled
  55. *
  56. * @return bool
  57. */
  58. public function is_enabled() {
  59. return $this->enabled;
  60. }
  61. /**
  62. * Cache root
  63. *
  64. * @return string
  65. */
  66. public function get_root() {
  67. return $this->root;
  68. }
  69. /**
  70. * Check if a file is in cache and return its filename
  71. *
  72. * @param string $key cache key
  73. * @param int $ttl time to live
  74. * @return bool|string filename or false
  75. */
  76. public function has( $key, $ttl = null ) {
  77. if ( !$this->enabled ) {
  78. return false;
  79. }
  80. $filename = $this->filename( $key );
  81. if ( !file_exists( $filename ) ) {
  82. return false;
  83. }
  84. // use ttl param or global ttl
  85. if ( $ttl === null ) {
  86. $ttl = $this->ttl;
  87. } elseif ( $this->ttl > 0 ) {
  88. $ttl = min( (int) $ttl, $this->ttl );
  89. } else {
  90. $ttl = (int) $ttl;
  91. }
  92. //
  93. if ( $ttl > 0 && filemtime( $filename ) + $ttl < time() ) {
  94. if ( $this->ttl > 0 && $ttl >= $this->ttl ) {
  95. unlink( $filename );
  96. }
  97. return false;
  98. }
  99. return $filename;
  100. }
  101. /**
  102. * Write to cache file
  103. *
  104. * @param string $key cache key
  105. * @param string $contents file contents
  106. * @return bool
  107. */
  108. public function write( $key, $contents ) {
  109. $filename = $this->prepare_write( $key );
  110. if ( $filename ) {
  111. return file_put_contents( $filename, $contents ) && touch( $filename );
  112. } else {
  113. return false;
  114. }
  115. }
  116. public function put_data( $key, $array ) {
  117. $json = json_encode( $array );
  118. $result = $this->write( $key, $json );
  119. return $result;
  120. }
  121. /**
  122. * Read from cache file
  123. *
  124. * @param string $key cache key
  125. * @param int $ttl time to live
  126. * @return bool|string file contents or false
  127. */
  128. public function read( $key, $ttl = null ) {
  129. $filename = $this->has( $key, $ttl );
  130. if ( $filename ) {
  131. return file_get_contents( $filename );
  132. } else {
  133. return false;
  134. }
  135. }
  136. public function get_data($key, $options = array()) {
  137. $defaults = array(
  138. 'decode_array' => false,
  139. 'ttl' => null
  140. );
  141. $options = array_merge($defaults, $options);
  142. $contents = $this->read($key, $options['ttl']);
  143. if ($contents) {
  144. return json_decode($contents, $options['decode_array']);
  145. }
  146. else {
  147. return false;
  148. }
  149. }
  150. /**
  151. * Copy a file into the cache
  152. *
  153. * @param string $key cache key
  154. * @param string $source source filename
  155. * @return bool
  156. */
  157. public function import( $key, $source ) {
  158. $filename = $this->prepare_write( $key );
  159. if ( $filename ) {
  160. return copy( $source, $filename ) && touch( $filename );
  161. } else {
  162. return false;
  163. }
  164. }
  165. /**
  166. * Copy a file out of the cache
  167. *
  168. * @param string $key cache key
  169. * @param string $target target filename
  170. * @param int $ttl time to live
  171. * @return bool
  172. */
  173. public function export( $key, $target, $ttl = null ) {
  174. $filename = $this->has( $key, $ttl );
  175. if ( $filename ) {
  176. return copy( $filename, $target );
  177. } else {
  178. return false;
  179. }
  180. }
  181. /**
  182. * Remove file from cache
  183. *
  184. * @param string $key cache key
  185. * @return bool
  186. */
  187. public function remove( $key ) {
  188. if ( !$this->enabled ) {
  189. return false;
  190. }
  191. $filename = $this->filename( $key );
  192. if ( file_exists( $filename ) ) {
  193. return unlink( $filename );
  194. } else {
  195. return false;
  196. }
  197. }
  198. /**
  199. * Clean cache based on time to live and max size
  200. *
  201. * @return bool
  202. */
  203. public function clean() {
  204. if ( !$this->enabled ) {
  205. return false;
  206. }
  207. $ttl = $this->ttl;
  208. $maxSize = $this->maxSize;
  209. // unlink expired files
  210. if ( $ttl > 0 ) {
  211. $expire = new \DateTime();
  212. $expire->modify( '-' . $ttl . ' seconds' );
  213. $finder = $this->get_finder()->date( 'until ' . $expire->format( 'Y-m-d H:i:s' ) );
  214. foreach ( $finder as $file ) {
  215. unlink( $file->getRealPath() );
  216. }
  217. }
  218. // unlink older files if max cache size is exceeded
  219. if ( $maxSize > 0 ) {
  220. $files = array_reverse( iterator_to_array( $this->get_finder()->sortByAccessedTime()->getIterator() ) );
  221. $total = 0;
  222. foreach ( $files as $file ) {
  223. if ( $total + $file->getSize() <= $maxSize ) {
  224. $total += $file->getSize();
  225. } else {
  226. unlink( $file->getRealPath() );
  227. }
  228. }
  229. }
  230. return true;
  231. }
  232. /**
  233. * Ensure directory exists
  234. *
  235. * @param string $dir directory
  236. * @return bool
  237. */
  238. protected function ensure_dir_exists( $dir ) {
  239. if ( !is_dir( $dir ) ) {
  240. if ( file_exists( $dir ) ) {
  241. // exists and not a dir
  242. return false;
  243. }
  244. if ( !@mkdir( $dir, 0777, true ) ) {
  245. return false;
  246. }
  247. }
  248. return true;
  249. }
  250. /**
  251. * Prepare cache write
  252. *
  253. * @param string $key cache key
  254. * @return bool|string filename or false
  255. */
  256. protected function prepare_write( $key ) {
  257. if ( !$this->enabled ) {
  258. return false;
  259. }
  260. $filename = $this->filename( $key );
  261. if ( !$this->ensure_dir_exists( dirname( $filename ) ) ) {
  262. return false;
  263. }
  264. return $filename;
  265. }
  266. /**
  267. * Validate cache key
  268. *
  269. * @param string $key cache key
  270. * @return string relative filename
  271. */
  272. protected function validate_key( $key ) {
  273. $url_parts = parse_url( $key );
  274. if ( ! empty($url_parts['scheme']) ) { // is url
  275. $parts = array('misc');
  276. $parts[] = $url_parts['scheme'] . '-' . @$url_parts['host'] .
  277. ( empty( $url_parts['port'] ) ? '' : '-' . $url_parts['port'] );
  278. $parts[] = substr($url_parts['path'], 1) .
  279. ( empty( $url_parts['query'] ) ? '' : '-' . $url_parts['query'] );
  280. } else {
  281. $key = str_replace( '\\', '/', $key );
  282. $parts = explode( '/', ltrim( $key ) );
  283. }
  284. $parts = preg_replace( "#[^{$this->whitelist}]#i", '-', $parts );
  285. return implode( '/', $parts );
  286. }
  287. /**
  288. * Filename from key
  289. *
  290. * @param string $key
  291. * @return string filename
  292. */
  293. protected function filename( $key ){
  294. return $this->root . $this->validate_key( $key );
  295. }
  296. /**
  297. * Get a Finder that iterates in cache root only the files
  298. *
  299. * @return Finder
  300. */
  301. public function get_finder() {
  302. return Finder::create()->in( $this->root )->files();
  303. }
  304. /**
  305. * Flushes all caches
  306. *
  307. */
  308. public function flush($key = null) {
  309. $finder = $this->get_finder();
  310. foreach($finder as $file) {
  311. unlink($file->getRealPath());
  312. }
  313. }
  314. }