See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401]
1 <?php 2 3 /** 4 * SCSSPHP 5 * 6 * @copyright 2012-2020 Leaf Corcoran 7 * 8 * @license http://opensource.org/licenses/MIT MIT 9 * 10 * @link http://scssphp.github.io/scssphp 11 */ 12 13 namespace ScssPhp\ScssPhp; 14 15 use Exception; 16 use ScssPhp\ScssPhp\Version; 17 18 /** 19 * The scss cache manager. 20 * 21 * In short: 22 * 23 * allow to put in cache/get from cache a generic result from a known operation on a generic dataset, 24 * taking in account options that affects the result 25 * 26 * The cache manager is agnostic about data format and only the operation is expected to be described by string 27 */ 28 29 /** 30 * SCSS cache 31 * 32 * @author Cedric Morin <cedric@yterium.com> 33 * 34 * @internal 35 */ 36 class Cache 37 { 38 const CACHE_VERSION = 1; 39 40 /** 41 * directory used for storing data 42 * 43 * @var string|false 44 */ 45 public static $cacheDir = false; 46 47 /** 48 * prefix for the storing data 49 * 50 * @var string 51 */ 52 public static $prefix = 'scssphp_'; 53 54 /** 55 * force a refresh : 'once' for refreshing the first hit on a cache only, true to never use the cache in this hit 56 * 57 * @var bool|string 58 */ 59 public static $forceRefresh = false; 60 61 /** 62 * specifies the number of seconds after which data cached will be seen as 'garbage' and potentially cleaned up 63 * 64 * @var int 65 */ 66 public static $gcLifetime = 604800; 67 68 /** 69 * array of already refreshed cache if $forceRefresh==='once' 70 * 71 * @var array<string, bool> 72 */ 73 protected static $refreshed = []; 74 75 /** 76 * Constructor 77 * 78 * @param array $options 79 * 80 * @phpstan-param array{cacheDir?: string, prefix?: string, forceRefresh?: string} $options 81 */ 82 public function __construct($options) 83 { 84 // check $cacheDir 85 if (isset($options['cacheDir'])) { 86 self::$cacheDir = $options['cacheDir']; 87 } 88 89 if (empty(self::$cacheDir)) { 90 throw new Exception('cacheDir not set'); 91 } 92 93 if (isset($options['prefix'])) { 94 self::$prefix = $options['prefix']; 95 } 96 97 if (empty(self::$prefix)) { 98 throw new Exception('prefix not set'); 99 } 100 101 if (isset($options['forceRefresh'])) { 102 self::$forceRefresh = $options['forceRefresh']; 103 } 104 105 self::checkCacheDir(); 106 } 107 108 /** 109 * Get the cached result of $operation on $what, 110 * which is known as dependant from the content of $options 111 * 112 * @param string $operation parse, compile... 113 * @param mixed $what content key (e.g., filename to be treated) 114 * @param array $options any option that affect the operation result on the content 115 * @param int|null $lastModified last modified timestamp 116 * 117 * @return mixed 118 * 119 * @throws \Exception 120 */ 121 public function getCache($operation, $what, $options = [], $lastModified = null) 122 { 123 $fileCache = self::$cacheDir . self::cacheName($operation, $what, $options); 124 125 if ( 126 ((self::$forceRefresh === false) || (self::$forceRefresh === 'once' && 127 isset(self::$refreshed[$fileCache]))) && file_exists($fileCache) 128 ) { 129 $cacheTime = filemtime($fileCache); 130 131 if ( 132 (\is_null($lastModified) || $cacheTime > $lastModified) && 133 $cacheTime + self::$gcLifetime > time() 134 ) { 135 $c = file_get_contents($fileCache); 136 $c = unserialize($c); 137 138 if (\is_array($c) && isset($c['value'])) { 139 return $c['value']; 140 } 141 } 142 } 143 144 return null; 145 } 146 147 /** 148 * Put in cache the result of $operation on $what, 149 * which is known as dependant from the content of $options 150 * 151 * @param string $operation 152 * @param mixed $what 153 * @param mixed $value 154 * @param array $options 155 * 156 * @return void 157 */ 158 public function setCache($operation, $what, $value, $options = []) 159 { 160 $fileCache = self::$cacheDir . self::cacheName($operation, $what, $options); 161 162 $c = ['value' => $value]; 163 $c = serialize($c); 164 165 file_put_contents($fileCache, $c); 166 167 if (self::$forceRefresh === 'once') { 168 self::$refreshed[$fileCache] = true; 169 } 170 } 171 172 /** 173 * Get the cache name for the caching of $operation on $what, 174 * which is known as dependant from the content of $options 175 * 176 * @param string $operation 177 * @param mixed $what 178 * @param array $options 179 * 180 * @return string 181 */ 182 private static function cacheName($operation, $what, $options = []) 183 { 184 $t = [ 185 'version' => self::CACHE_VERSION, 186 'scssphpVersion' => Version::VERSION, 187 'operation' => $operation, 188 'what' => $what, 189 'options' => $options 190 ]; 191 192 $t = self::$prefix 193 . sha1(json_encode($t)) 194 . ".$operation" 195 . ".scsscache"; 196 197 return $t; 198 } 199 200 /** 201 * Check that the cache dir exists and is writeable 202 * 203 * @return void 204 * 205 * @throws \Exception 206 */ 207 public static function checkCacheDir() 208 { 209 self::$cacheDir = str_replace('\\', '/', self::$cacheDir); 210 self::$cacheDir = rtrim(self::$cacheDir, '/') . '/'; 211 212 if (! is_dir(self::$cacheDir)) { 213 throw new Exception('Cache directory doesn\'t exist: ' . self::$cacheDir); 214 } 215 216 if (! is_writable(self::$cacheDir)) { 217 throw new Exception('Cache directory isn\'t writable: ' . self::$cacheDir); 218 } 219 } 220 221 /** 222 * Delete unused cached files 223 * 224 * @return void 225 */ 226 public static function cleanCache() 227 { 228 static $clean = false; 229 230 if ($clean || empty(self::$cacheDir)) { 231 return; 232 } 233 234 $clean = true; 235 236 // only remove files with extensions created by SCSSPHP Cache 237 // css files removed based on the list files 238 $removeTypes = ['scsscache' => 1]; 239 240 $files = scandir(self::$cacheDir); 241 242 if (! $files) { 243 return; 244 } 245 246 $checkTime = time() - self::$gcLifetime; 247 248 foreach ($files as $file) { 249 // don't delete if the file wasn't created with SCSSPHP Cache 250 if (strpos($file, self::$prefix) !== 0) { 251 continue; 252 } 253 254 $parts = explode('.', $file); 255 $type = array_pop($parts); 256 257 if (! isset($removeTypes[$type])) { 258 continue; 259 } 260 261 $fullPath = self::$cacheDir . $file; 262 $mtime = filemtime($fullPath); 263 264 // don't delete if it's a relatively new file 265 if ($mtime > $checkTime) { 266 continue; 267 } 268 269 unlink($fullPath); 270 } 271 } 272 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body