Completed
Push — master ( e099bf...2f0857 )
by Lars
04:09
created

AdapterFile::getFileName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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