Completed
Push — master ( fb6693...12c1f4 )
by Lars
02:01
created

AdapterFile::validateDataFromCache()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4.25

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 14
ccs 6
cts 8
cp 0.75
rs 9.2
cc 4
eloc 7
nc 4
nop 1
crap 4.25
1
<?php
2
3
namespace voku\cache;
4
5
/**
6
 * AdapterFile: File-adapter
7
 *
8
 * @package   voku\cache
9
 */
10
class AdapterFile implements iAdapter
11
{
12
  const CACHE_FILE_PREFIX = '__';
13
  const CACHE_FILE_SUBFIX = '.php.cache';
14
15
  /**
16
   * @var bool
17
   */
18
  public $installed = false;
19
20
  /**
21
   * @var string
22
   */
23
  protected $cacheDir;
24
25
  /**
26
   * @var iSerializer
27
   */
28
  protected $serializer;
29
30
  /**
31
   * @var string
32
   */
33
  protected $fileMode = '0755';
34
  
35
  /**
36
   * @var string
37
   */
38
  protected $internalFileValueCache = array();
39
40
  /**
41
   * @param string $cacheDir
42
   */
43 9
  public function __construct($cacheDir = null)
44
  {
45 9
    $this->serializer = new SerializerIgbinary();
46
47 9
    if (!$cacheDir) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cacheDir of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
48 9
      $cacheDir = realpath(sys_get_temp_dir()) . '/simple_php_cache';
49 9
    }
50
51 9
    $this->cacheDir = (string)$cacheDir;
52
53 9
    if ($this->createCacheDirectory($cacheDir) === true) {
54 9
      $this->installed = true;
55 9
    }
56 9
  }
57
58
  /**
59
   * Recursively creates & chmod directories.
60
   *
61
   * @param string $path
62
   *
63
   * @return bool
64
   */
65 9
  protected function createCacheDirectory($path)
66
  {
67
    if (
68
        !$path
69 9
        ||
70
        $path === '/'
71 9
        ||
72
        $path === '.'
73 9
        ||
74
        $path === '\\'
75 9
    ) {
76
      return false;
77
    }
78
79
    // if the directory already exists, just return true
80 9
    if (is_dir($path) && is_writable($path)) {
81 9
      return true;
82
    }
83
84
    // if more than one level, try parent first
85 1
    if (dirname($path) !== '.') {
86 1
      $return = $this->createCacheDirectory(dirname($path));
87
      // if creating parent fails, we can abort immediately
88 1
      if (!$return) {
89
        return false;
90
      }
91 1
    }
92
93 1
    $mode_dec = intval($this->fileMode, 8);
94 1
    $oldumask = umask(0);
95
96
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
97 1
    if (!@mkdir($path, $mode_dec) && !is_dir($path)) {
98
      $return = false;
99
    } else {
100 1
      $return = true;
101
    }
102
103 1
    if (is_dir($path) && !is_writable($path)) {
104
      $return = chmod($path, $mode_dec);
105
    }
106
107 1
    umask($oldumask);
108
109 1
    return $return;
110
  }
111
112
  /**
113
   * @param $cacheFile
114
   *
115
   * @return bool
116
   */
117 1
  protected function deleteFile($cacheFile)
118
  {
119 1
    if (is_file($cacheFile)) {
120 1
      return unlink($cacheFile);
121
    }
122
123
    return false;
124
  }
125
  
126
  /**
127
   * @param string $key
128
   *
129
   * @return mixed <p>Will return null if the was no value, otherwise it will return the cache value.</p>
130
   */
131 5
  protected function getInternalFileValueCache($key)
132
  {
133 5
    if (isset($this->internalFileValueCache[$key]) === true) {
134
      return $this->internalFileValueCache[$key];
135
    }
136
    
137 5
    return null;
138
  }
139
  
140
  /**
141
   * @param string $key
142
   * @param mixed  $value
143
   */
144 1
  protected function setInternalFileValueCache($key, $value)
145
  {
146 1
    $this->internalFileValueCache[$key] = $value;
147 1
  }
148
  
149
  /**
150
   * @param string $key
151
   */
152 1
  protected function removeInternalFileValueCache($key)
153
  {
154 1
    unset($this->internalFileValueCache[$key]);
155 1
  }
156
157
  /**
158
   * @inheritdoc
159
   */
160 1
  public function exists($key)
161
  {
162 1
    $value = $this->get($key);
163
    
164 1
    $this->setInternalFileValueCache($key, $value);
165
    
166 1
    return null !== $value;
167
  }
168
169
  /**
170
   * @inheritdoc
171
   */
172 5
  public function get($key)
173
  {
174 5
    $cachedValue = $this->getInternalFileValueCache($key);
175 5
    if ($cachedValue !== null) {
176
      return $cachedValue;
177
    }
178
    
179 5
    $path = $this->getFileName($key);
180
181
    if (
182 5
        file_exists($path) === false
183 5
        ||
184 5
        filesize($path) === 0
185 5
    ) {
186 1
      return null;
187
    }
188
189
    // init
190 5
    $string = '';
191
192
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
193 5
    $fp = @fopen($path, 'rb');
194 5
    if ($fp && flock($fp, LOCK_SH | LOCK_NB)) {
195 5
      while (!feof($fp)) {
196 5
        $line = fgets($fp);
197 5
        $string .= $line;
198 5
      }
199 5
      flock($fp, LOCK_UN);
200 5
    }
201 5
    fclose($fp);
202
203 5
    if (!$string) {
204
      return null;
205
    }
206
207 5
    $data = $this->serializer->unserialize(file_get_contents($path));
208
209 5
    if (!$data || !$this->validateDataFromCache($data)) {
210
      return null;
211
    }
212
213 5
    if ($this->ttlHasExpired($data['ttl']) === true) {
214
      $this->remove($key);
215
216
      return null;
217
    }
218
219 5
    return $data['value'];
220
  }
221
222
  /**
223
   * @inheritdoc
224
   */
225
  public function installed()
226
  {
227
    return $this->installed;
228
  }
229
230
  /**
231
   * @inheritdoc
232
   */
233 1
  public function remove($key)
234
  {
235 1
    $this->removeInternalFileValueCache($key);
236
    
237 1
    $cacheFile = $this->getFileName($key);
238
239 1
    return $this->deleteFile($cacheFile);
240
  }
241
242
  /**
243
   * @inheritdoc
244
   */
245 1
  public function removeAll()
246
  {
247 1
    $this->internalFileValueCache = array();
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type string of property $internalFileValueCache.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
248
    
249 1
    if (!$this->cacheDir) {
250
      return false;
251
    }
252
253 1
    $return = array();
254 1
    foreach (new \DirectoryIterator($this->cacheDir) as $fileInfo) {
255 1
      if (!$fileInfo->isDot()) {
256 1
        $return[] = unlink($fileInfo->getPathname());
257 1
      }
258 1
    }
259
260 1
    return in_array(false, $return, true) === false;
261
  }
262
263
  /**
264
   * @inheritdoc
265
   */
266 3
  public function set($key, $value)
267
  {
268 3
    return $this->setExpired($key, $value);
269
  }
270
271
  /**
272
   * @inheritdoc
273
   */
274 4
  public function setExpired($key, $value, $ttl = 0)
275
  {
276 4
    $item = $this->serializer->serialize(
277
        array(
278 4
            'value' => $value,
279 4
            'ttl'   => $ttl ? (int)$ttl + time() : 0,
280
        )
281 4
    );
282
283 4
    $octetWritten = false;
284 4
    $cacheFile = $this->getFileName($key);
285
286
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
287 4
    $fp = @fopen($cacheFile, 'ab');
288 4
    if ($fp && flock($fp, LOCK_EX | LOCK_NB)) {
289 4
      ftruncate($fp, 0);
290 4
      $octetWritten = fwrite($fp, $item);
291 4
      fflush($fp);
292 4
      flock($fp, LOCK_UN);
293 4
    }
294 4
    fclose($fp);
295
296 4
    return $octetWritten !== false;
297
  }
298
299
  /**
300
   * @param string $key
301
   *
302
   * @return string
303
   */
304 6
  protected function getFileName($key)
305
  {
306 6
    return $this->cacheDir . DIRECTORY_SEPARATOR . self::CACHE_FILE_PREFIX . $key . self::CACHE_FILE_SUBFIX;
307
  }
308
309
  /**
310
   * Set the file-mode for new cache-files.
311
   *
312
   * e.g. '0777', or '0755' ...
313
   *
314
   * @param $fileMode
315
   */
316
  public function setFileMode($fileMode)
317
  {
318
    $this->fileMode = $fileMode;
319
  }
320
321
  /**
322
   * @param $ttl
323
   *
324
   * @return bool
325
   */
326 5
  protected function ttlHasExpired($ttl)
327
  {
328 5
    if ($ttl === 0) {
329 4
      return false;
330
    }
331
332 1
    return (time() > $ttl);
333
  }
334
335
  /**
336
   * @param $data
337
   *
338
   * @return bool
339
   */
340 5
  protected function validateDataFromCache($data)
341
  {
342 5
    if (!is_array($data)) {
343
      return false;
344
    }
345
346 5
    foreach (array('value', 'ttl') as $missing) {
347 5
      if (!array_key_exists($missing, $data)) {
348
        return false;
349
      }
350 5
    }
351
352 5
    return true;
353
  }
354
}
355