Completed
Push — master ( 6c7192...fb6693 )
by Lars
02:12
created

AdapterFile::installed()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 2
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
   * @param string $cacheDir
37
   */
38 9
  public function __construct($cacheDir = null)
39
  {
40 9
    $this->serializer = new SerializerIgbinary();
41
42 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...
43 9
      $cacheDir = realpath(sys_get_temp_dir()) . '/simple_php_cache';
44 9
    }
45
46 9
    $this->cacheDir = (string)$cacheDir;
47
48 9
    if ($this->createCacheDirectory($cacheDir) === true) {
49 9
      $this->installed = true;
50 9
    }
51 9
  }
52
53
  /**
54
   * Recursively creates & chmod directories.
55
   *
56
   * @param string $path
57
   *
58
   * @return bool
59
   */
60 9
  protected function createCacheDirectory($path)
61
  {
62
    if (
63
        !$path
64 9
        ||
65
        $path === '/'
66 9
        ||
67
        $path === '.'
68 9
        ||
69
        $path === '\\'
70 9
    ) {
71
      return false;
72
    }
73
74
    // if the directory already exists, just return true
75 9
    if (is_dir($path) && is_writable($path)) {
76 9
      return true;
77
    }
78
79
    // if more than one level, try parent first
80 1
    if (dirname($path) !== '.') {
81 1
      $return = $this->createCacheDirectory(dirname($path));
82
      // if creating parent fails, we can abort immediately
83 1
      if (!$return) {
84
        return false;
85
      }
86 1
    }
87
88 1
    $mode_dec = intval($this->fileMode, 8);
89 1
    $oldumask = umask(0);
90
91
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
92 1
    if (!@mkdir($path, $mode_dec) && !is_dir($path)) {
93
      $return = false;
94
    } else {
95 1
      $return = true;
96
    }
97
98 1
    if (is_dir($path) && !is_writable($path)) {
99
      $return = chmod($path, $mode_dec);
100
    }
101
102 1
    umask($oldumask);
103
104 1
    return $return;
105
  }
106
107
  /**
108
   * @param $cacheFile
109
   *
110
   * @return bool
111
   */
112 1
  protected function deleteFile($cacheFile)
113
  {
114 1
    if (is_file($cacheFile)) {
115 1
      return unlink($cacheFile);
116
    }
117
118
    return false;
119
  }
120
121
  /**
122
   * @inheritdoc
123
   */
124 1
  public function exists($key)
125
  {
126 1
    return null !== $this->get($key);
127
  }
128
129
  /**
130
   * @inheritdoc
131
   */
132 5
  public function get($key)
133
  {
134 5
    $path = $this->getFileName($key);
135
136
    if (
137 5
        file_exists($path) === false
138 5
        ||
139 5
        filesize($path) === 0
140 5
    ) {
141 1
      return null;
142
    }
143
144
    // init
145 5
    $string = '';
146
147
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
148 5
    $fp = @fopen($path, 'rb');
149 5
    if ($fp) {
150 5
      $locked = flock($fp, LOCK_SH);
151 5
      if ($locked) {
152 5
        while (!feof($fp)) {
153 5
          $line = fgets($fp);
154 5
          $string .= $line;
155 5
        }
156 5
      }
157 5
      flock($fp, LOCK_UN);
158 5
    }
159 5
    fclose($fp);
160
161 5
    if (!$string) {
162
      return null;
163
    }
164
165 5
    $data = $this->serializer->unserialize(file_get_contents($path));
166
167 5
    if (!$data || !$this->validateDataFromCache($data)) {
168
      return null;
169
    }
170
171 5
    if ($this->ttlHasExpired($data['ttl']) === true) {
172
      $this->remove($key);
173
174
      return null;
175
    }
176
177 5
    return $data['value'];
178
  }
179
180
  /**
181
   * @inheritdoc
182
   */
183
  public function installed()
184
  {
185
    return $this->installed;
186
  }
187
188
  /**
189
   * @inheritdoc
190
   */
191 1
  public function remove($key)
192
  {
193 1
    $cacheFile = $this->getFileName($key);
194
195 1
    return $this->deleteFile($cacheFile);
196
  }
197
198
  /**
199
   * @inheritdoc
200
   */
201 1
  public function removeAll()
202
  {
203 1
    if (!$this->cacheDir) {
204
      return false;
205
    }
206
207 1
    $return = array();
208 1
    foreach (new \DirectoryIterator($this->cacheDir) as $fileInfo) {
209 1
      if (!$fileInfo->isDot()) {
210 1
        $return[] = unlink($fileInfo->getPathname());
211 1
      }
212 1
    }
213
214 1
    return in_array(false, $return, true) === false;
215
  }
216
217
  /**
218
   * @inheritdoc
219
   */
220 3
  public function set($key, $value)
221
  {
222 3
    return $this->setExpired($key, $value);
223
  }
224
225
  /**
226
   * @inheritdoc
227
   */
228 4
  public function setExpired($key, $value, $ttl = 0)
229
  {
230 4
    $item = $this->serializer->serialize(
231
        array(
232 4
            'value' => $value,
233 4
            'ttl'   => $ttl ? (int)$ttl + time() : 0,
234
        )
235 4
    );
236
237 4
    $octetWritten = false;
238 4
    $cacheFile = $this->getFileName($key);
239
240
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
241 4
    $fp = @fopen($cacheFile, 'ab');
242 4
    if ($fp && flock($fp, LOCK_EX)) {
243 4
      ftruncate($fp, 0);
244 4
      $octetWritten = fwrite($fp, $item);
245 4
      fflush($fp);
246 4
      flock($fp, LOCK_UN);
247 4
    }
248 4
    fclose($fp);
249
250 4
    return $octetWritten !== false;
251
  }
252
253
  /**
254
   * @param string $key
255
   *
256
   * @return string
257
   */
258 6
  protected function getFileName($key)
259
  {
260 6
    return $this->cacheDir . DIRECTORY_SEPARATOR . self::CACHE_FILE_PREFIX . $key . self::CACHE_FILE_SUBFIX;
261
  }
262
263
  /**
264
   * Set the file-mode for new cache-files.
265
   *
266
   * e.g. '0777', or '0755' ...
267
   *
268
   * @param $fileMode
269
   */
270
  public function setFileMode($fileMode)
271
  {
272
    $this->fileMode = $fileMode;
273
  }
274
275
  /**
276
   * @param $ttl
277
   *
278
   * @return bool
279
   */
280 5
  protected function ttlHasExpired($ttl)
281
  {
282 5
    if ($ttl === 0) {
283 4
      return false;
284
    }
285
286 1
    return (time() > $ttl);
287
  }
288
289
  /**
290
   * @param $data
291
   *
292
   * @return bool
293
   */
294 5
  protected function validateDataFromCache($data)
295
  {
296 5
    if (!is_array($data)) {
297
      return false;
298
    }
299
300 5
    foreach (array('value', 'ttl') as $missing) {
301 5
      if (!array_key_exists($missing, $data)) {
302
        return false;
303
      }
304 5
    }
305
306 5
    return true;
307
  }
308
}
309