1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Vectorface\Cache; |
4
|
|
|
|
5
|
|
|
use Vectorface\Cache\Common\MultipleTrait; |
6
|
|
|
use Vectorface\Cache\Common\PSR16Util; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* Represents a cache whose entries are stored in temporary files. |
10
|
|
|
*/ |
11
|
|
|
class TempFileCache implements Cache |
12
|
|
|
{ |
13
|
|
|
use MultipleTrait, PSR16Util; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* @var string |
17
|
|
|
*/ |
18
|
|
|
private $directory; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* @var string |
22
|
|
|
*/ |
23
|
|
|
private $extension; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Create a temporary file cache. |
27
|
|
|
* |
28
|
|
|
* @param string $directory The directory in which cache files should be stored. |
29
|
|
|
* @param string $extension The extension to be used after the filename to uniquely identify cache files. |
30
|
|
|
* |
31
|
|
|
* Note: |
32
|
|
|
* - Without a directory argument, the system tempdir will be used (e.g. /tmp/TempFileCache/) |
33
|
|
|
* - If given a relative path, it will create that directory within the system tempdir. |
34
|
|
|
* - If given an absolute path, it will attempt to use that path as-is. Not recommended. |
35
|
|
|
* @throws \Exception |
36
|
|
|
*/ |
37
|
22 |
|
public function __construct($directory = null, $extension = '.tempcache') |
38
|
|
|
{ |
39
|
22 |
|
$this->directory = $this->getTempDir($directory); |
40
|
22 |
|
$this->checkAndCreateDir($this->directory); |
41
|
|
|
|
42
|
22 |
|
$this->directory = realpath($this->directory); /* Get rid of extraneous symlinks, ..'s, etc. */ |
|
|
|
|
43
|
22 |
|
if (!$this->directory) { |
44
|
1 |
|
throw new \Exception("Could not get directory realpath"); |
45
|
|
|
} |
46
|
|
|
|
47
|
22 |
|
$this->extension = empty($extension) ? "" : (string)$extension; |
48
|
22 |
|
} |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Check for a directory's existence and writability, and create otherwise |
52
|
|
|
* |
53
|
|
|
* @param string $directory |
54
|
|
|
* @throws \Exception |
55
|
|
|
*/ |
56
|
22 |
|
private function checkAndCreateDir($directory) |
57
|
|
|
{ |
58
|
22 |
|
if (!file_exists($directory)) { |
59
|
22 |
|
if (!@mkdir($directory, 0700, true)) { |
60
|
22 |
|
throw new \Exception("Directory does not exist, and could not be created: {$directory}"); |
61
|
|
|
} |
62
|
3 |
|
} elseif (is_dir($directory)) { |
63
|
3 |
|
if (!is_writable($directory)) { |
64
|
3 |
|
throw new \Exception("Directory is not writable: {$directory}"); |
65
|
|
|
} |
66
|
|
|
} else { |
67
|
1 |
|
throw new \Exception("Not a directory: {$directory}"); |
68
|
|
|
} |
69
|
22 |
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Generate a consistent temporary directory based on a requested directory name. |
73
|
|
|
* |
74
|
|
|
* @param string $directory The name or path of a temporary directory. |
75
|
|
|
* @return string The directory name, resolved to a full path. |
76
|
|
|
*/ |
77
|
22 |
|
private function getTempDir($directory) |
78
|
|
|
{ |
79
|
22 |
|
if (empty($directory) || !is_string($directory)) { |
80
|
22 |
|
$classParts = explode("\\", static::class); |
81
|
22 |
|
return sys_get_temp_dir() . '/' . end($classParts); |
82
|
2 |
|
} elseif (strpos($directory, '/') !== 0) { |
83
|
1 |
|
return sys_get_temp_dir() . '/' . $directory; |
84
|
|
|
} else { |
85
|
1 |
|
return $directory; |
86
|
|
|
} |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* @inheritDoc Vectorface\Cache\Cache |
91
|
|
|
*/ |
92
|
20 |
|
public function get($key, $default = null) |
93
|
|
|
{ |
94
|
20 |
|
$file = $this->makePath($this->key($key)); |
95
|
19 |
|
$data = @file_get_contents($file); |
96
|
19 |
|
if (!$data) { |
97
|
16 |
|
return $default; |
98
|
|
|
} |
99
|
|
|
|
100
|
9 |
|
$data = @unserialize($data); |
101
|
9 |
|
if (!$data) { |
102
|
1 |
|
$this->delete($key); /* Delete corrupted. */ |
103
|
1 |
|
return $default; |
104
|
|
|
} |
105
|
|
|
|
106
|
8 |
|
list($expiry, $value) = $data; |
107
|
8 |
|
if ($expiry !== false && ($expiry < microtime(true))) { |
108
|
1 |
|
$this->delete($key); |
109
|
1 |
|
return $default; |
110
|
|
|
} |
111
|
|
|
|
112
|
8 |
|
return $value; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* @inheritDoc Vectorface\Cache\Cache |
117
|
|
|
*/ |
118
|
20 |
|
public function set($key, $value, $ttl = null) |
119
|
|
|
{ |
120
|
20 |
|
$ttl = $this->ttl($ttl); |
121
|
20 |
|
$data = [$ttl ? microtime(true) + $ttl : false, $value]; |
122
|
20 |
|
return @file_put_contents($this->makePath($this->key($key)), serialize($data)) !== false; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* Delete an entry in the cache by key regardless of TTL |
127
|
|
|
* |
128
|
|
|
* @param string $key A key to delete from the cache. |
129
|
|
|
* @return bool True if the cache entry was successfully deleted, false otherwise. |
130
|
|
|
*/ |
131
|
7 |
|
public function delete($key) |
132
|
|
|
{ |
133
|
7 |
|
return @unlink($this->makePath($this->key($key))); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* Manually clean out entries older than their TTL |
138
|
|
|
* |
139
|
|
|
* @return bool Returns true if the cache directory was cleaned. |
140
|
|
|
*/ |
141
|
2 |
|
public function clean() |
142
|
|
|
{ |
143
|
2 |
|
if (!($files = $this->getCacheFiles())) { |
144
|
1 |
|
return false; |
145
|
|
|
} |
146
|
|
|
|
147
|
1 |
|
foreach ($files as $file) { |
148
|
1 |
|
$key = basename($file, $this->extension); |
149
|
1 |
|
$this->get($key); // Automatically deletes if expired |
150
|
|
|
} |
151
|
1 |
|
return true; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Clear the cache |
156
|
|
|
* |
157
|
|
|
* @return bool Returns true if all files were flushed. |
158
|
|
|
*/ |
159
|
22 |
|
public function flush() |
160
|
|
|
{ |
161
|
22 |
|
if (($files = $this->getCacheFiles()) === false) { |
162
|
1 |
|
return false; |
163
|
|
|
} |
164
|
|
|
|
165
|
22 |
|
$result = true; |
166
|
22 |
|
foreach ($files as $file) { |
167
|
12 |
|
$result = $result && @unlink($file); |
168
|
|
|
} |
169
|
22 |
|
return $result; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* @inheritDoc \Psr\SimpleCache\CacheInterface |
174
|
|
|
*/ |
175
|
3 |
|
public function clear() |
176
|
|
|
{ |
177
|
3 |
|
return $this->flush(); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* @inheritDoc \Psr\SimpleCache\CacheInterface |
182
|
|
|
*/ |
183
|
1 |
|
public function has($key) |
184
|
|
|
{ |
185
|
1 |
|
return $this->get($key, null) !== null; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Creates a file path in the form directory/key.extension |
190
|
|
|
* |
191
|
|
|
* @param String $key the key of the cached element |
192
|
|
|
* @return String The file path to the cached element's enclosing file. |
193
|
|
|
*/ |
194
|
19 |
|
private function makePath($key) |
195
|
|
|
{ |
196
|
19 |
|
return $this->directory . "/" . hash("sha224", $key) . $this->extension; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* Finds all files with the cache extension in the cache directory |
201
|
|
|
* |
202
|
|
|
* @return array|false Returns an array of filenames that represent cached entries. |
203
|
|
|
*/ |
204
|
22 |
|
private function getCacheFiles() |
205
|
|
|
{ |
206
|
22 |
|
if (!($files = @scandir($this->directory, 1))) { |
207
|
1 |
|
return false; |
208
|
|
|
} |
209
|
|
|
|
210
|
22 |
|
$negExtLen = -1 * strlen($this->extension); |
211
|
22 |
|
$return = []; |
212
|
22 |
|
foreach ($files as $file) { |
213
|
22 |
|
if (substr($file, $negExtLen) === $this->extension) { |
214
|
12 |
|
$return[] = $this->directory . '/' . $file; |
215
|
|
|
} |
216
|
|
|
} |
217
|
22 |
|
return $return; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* Destroy this cache; Clear everything. |
222
|
|
|
* |
223
|
|
|
* Any operations on the cache after this operation are invalid, and their behavior will be undefined. |
224
|
|
|
* |
225
|
|
|
* @return bool True if the cache was flushed and the directory deleted. |
226
|
|
|
*/ |
227
|
22 |
|
public function destroy() |
228
|
|
|
{ |
229
|
22 |
|
return $this->flush() && @rmdir($this->directory); |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.