1<?php 2 3namespace App\Utils; 4 5/** 6 * A simple cache service for storing data in file(s). 7 */ 8class Cache 9{ 10 /** 11 * Pool of items. 12 * 13 * @var array 14 */ 15 private $pool = []; 16 17 /** 18 * Temporary cache directory where data is stored. 19 * 20 * @var string 21 */ 22 private $dir; 23 24 /** 25 * Default time after cache file is considered expired in seconds. 26 */ 27 public const TTL = 3600; 28 29 /** 30 * Class constructor. 31 */ 32 public function __construct(string $dir) 33 { 34 $this->dir = $dir; 35 36 // Create cache directory if it doesn't exist. 37 if (!file_exists($this->dir)) { 38 mkdir($this->dir, 0777, true); 39 chmod($this->dir, 0777); 40 } 41 42 // Validate cache directory 43 if (!is_dir($this->dir)) { 44 throw new \Exception($this->dir.' is not a valid directory.'); 45 } 46 } 47 48 /** 49 * Write data to cache file. 50 */ 51 public function set(string $key, $data, int $ttl = self::TTL): void 52 { 53 if (!$this->validateKey($key)) { 54 throw new Exception('Key name '.$key.' is invalid.'); 55 } 56 57 $item = [time() + $ttl, serialize($data)]; 58 $this->pool[$key] = $data; 59 60 $string = '<?php return '.var_export($item, true).";\n"; 61 62 file_put_contents($this->dir.'/'.$key.'.php', $string); 63 } 64 65 /** 66 * Check if item has been cached and is available. 67 */ 68 public function has(string $key): bool 69 { 70 if (isset($this->pool[$key])) { 71 return true; 72 } 73 74 $file = $this->dir.'/'.$key.'.php'; 75 76 if (!is_file($file)) { 77 return false; 78 } 79 80 $data = require $file; 81 82 if (is_array($data) && isset($data[0]) && is_int($data[0]) && time() < $data[0]) { 83 return true; 84 } 85 86 return false; 87 } 88 89 /** 90 * Get data from the cache pool. 91 */ 92 public function get(string $key): ?array 93 { 94 if (isset($this->pool[$key])) { 95 return $this->pool[$key]; 96 } 97 98 $file = $this->dir.'/'.$key.'.php'; 99 100 if (is_file($file)) { 101 $data = require $file; 102 $this->pool[$key] = unserialize($data[1]); 103 104 return $this->pool[$key]; 105 } 106 107 return null; 108 } 109 110 /** 111 * Wipes entire cache. 112 */ 113 public function clear(): bool 114 { 115 $success = true; 116 117 $this->pool = []; 118 119 foreach (new \DirectoryIterator($this->dir) as $fileInfo) { 120 if ($fileInfo->isDot()) { 121 continue; 122 } 123 124 if (!unlink($fileInfo->getRealPath())) { 125 $success = false; 126 } 127 } 128 129 return $success; 130 } 131 132 /** 133 * Delete item from the cache. 134 */ 135 public function delete(string $key): bool 136 { 137 $success = true; 138 139 unset($this->pool[$key]); 140 141 $file = $this->dir.'/'.$key.'.php'; 142 143 if (is_file($file) && !unlink($file)) { 144 $success = false; 145 } 146 147 return $success; 148 } 149 150 /** 151 * Validate key. 152 */ 153 private function validateKey(string $key): bool 154 { 155 return (bool) preg_match('/[a-z\_\-0-9]/i', $key); 156 } 157} 158