on-demand release 3.8dev+
[moodle.git] / lib / scssphp / Cache.php
blob1cf496f51493f7451fb67c168014645f473c1a59
1 <?php
2 /**
3 * SCSSPHP
5 * @copyright 2012-2019 Leaf Corcoran
7 * @license http://opensource.org/licenses/MIT MIT
9 * @link http://scssphp.github.io/scssphp
12 namespace ScssPhp\ScssPhp;
14 use Exception;
16 /**
17 * The scss cache manager.
19 * In short:
21 * allow to put in cache/get from cache a generic result from a known operation on a generic dataset,
22 * taking in account options that affects the result
24 * The cache manager is agnostic about data format and only the operation is expected to be described by string
28 /**
29 * SCSS cache
31 * @author Cedric Morin
33 class Cache
35 const CACHE_VERSION = 1;
37 // directory used for storing data
38 public static $cacheDir = false;
40 // prefix for the storing data
41 public static $prefix = 'scssphp_';
43 // force a refresh : 'once' for refreshing the first hit on a cache only, true to never use the cache in this hit
44 public static $forceRefresh = false;
46 // specifies the number of seconds after which data cached will be seen as 'garbage' and potentially cleaned up
47 public static $gcLifetime = 604800;
49 // array of already refreshed cache if $forceRefresh==='once'
50 protected static $refreshed = [];
52 /**
53 * Constructor
55 * @param array $options
57 public function __construct($options)
59 // check $cacheDir
60 if (isset($options['cache_dir'])) {
61 self::$cacheDir = $options['cache_dir'];
64 if (empty(self::$cacheDir)) {
65 throw new Exception('cache_dir not set');
68 if (isset($options['prefix'])) {
69 self::$prefix = $options['prefix'];
72 if (empty(self::$prefix)) {
73 throw new Exception('prefix not set');
76 if (isset($options['forceRefresh'])) {
77 self::$forceRefresh = $options['force_refresh'];
80 self::checkCacheDir();
83 /**
84 * Get the cached result of $operation on $what,
85 * which is known as dependant from the content of $options
87 * @param string $operation parse, compile...
88 * @param mixed $what content key (e.g., filename to be treated)
89 * @param array $options any option that affect the operation result on the content
90 * @param integer $lastModified last modified timestamp
92 * @return mixed
94 * @throws \Exception
96 public function getCache($operation, $what, $options = [], $lastModified = null)
98 $fileCache = self::$cacheDir . self::cacheName($operation, $what, $options);
100 if ((! self::$forceRefresh || (self::$forceRefresh === 'once' &&
101 isset(self::$refreshed[$fileCache]))) && file_exists($fileCache)
103 $cacheTime = filemtime($fileCache);
105 if ((is_null($lastModified) || $cacheTime > $lastModified) &&
106 $cacheTime + self::$gcLifetime > time()
108 $c = file_get_contents($fileCache);
109 $c = unserialize($c);
111 if (is_array($c) && isset($c['value'])) {
112 return $c['value'];
117 return null;
121 * Put in cache the result of $operation on $what,
122 * which is known as dependant from the content of $options
124 * @param string $operation
125 * @param mixed $what
126 * @param mixed $value
127 * @param array $options
129 public function setCache($operation, $what, $value, $options = [])
131 $fileCache = self::$cacheDir . self::cacheName($operation, $what, $options);
133 $c = ['value' => $value];
134 $c = serialize($c);
135 file_put_contents($fileCache, $c);
137 if (self::$forceRefresh === 'once') {
138 self::$refreshed[$fileCache] = true;
143 * Get the cache name for the caching of $operation on $what,
144 * which is known as dependant from the content of $options
146 * @param string $operation
147 * @param mixed $what
148 * @param array $options
150 * @return string
152 private static function cacheName($operation, $what, $options = [])
154 $t = [
155 'version' => self::CACHE_VERSION,
156 'operation' => $operation,
157 'what' => $what,
158 'options' => $options
161 $t = self::$prefix
162 . sha1(json_encode($t))
163 . ".$operation"
164 . ".scsscache";
166 return $t;
170 * Check that the cache dir exists and is writeable
172 * @throws \Exception
174 public static function checkCacheDir()
176 self::$cacheDir = str_replace('\\', '/', self::$cacheDir);
177 self::$cacheDir = rtrim(self::$cacheDir, '/') . '/';
179 if (! file_exists(self::$cacheDir)) {
180 if (! mkdir(self::$cacheDir)) {
181 throw new Exception('Cache directory couldn\'t be created: ' . self::$cacheDir);
183 } elseif (! is_dir(self::$cacheDir)) {
184 throw new Exception('Cache directory doesn\'t exist: ' . self::$cacheDir);
185 } elseif (! is_writable(self::$cacheDir)) {
186 throw new Exception('Cache directory isn\'t writable: ' . self::$cacheDir);
191 * Delete unused cached files
193 public static function cleanCache()
195 static $clean = false;
197 if ($clean || empty(self::$cacheDir)) {
198 return;
201 $clean = true;
203 // only remove files with extensions created by SCSSPHP Cache
204 // css files removed based on the list files
205 $removeTypes = ['scsscache' => 1];
207 $files = scandir(self::$cacheDir);
209 if (! $files) {
210 return;
213 $checkTime = time() - self::$gcLifetime;
215 foreach ($files as $file) {
216 // don't delete if the file wasn't created with SCSSPHP Cache
217 if (strpos($file, self::$prefix) !== 0) {
218 continue;
221 $parts = explode('.', $file);
222 $type = array_pop($parts);
224 if (! isset($removeTypes[$type])) {
225 continue;
228 $fullPath = self::$cacheDir . $file;
229 $mtime = filemtime($fullPath);
231 // don't delete if it's a relatively new file
232 if ($mtime > $checkTime) {
233 continue;
236 unlink($fullPath);