Completed
Push — master ( bb41dd...0c5dc2 )
by Lars
01:41
created

AdapterFile::get()   C

Complexity

Conditions 11
Paths 17

Size

Total Lines 46
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 11.0795

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 46
ccs 21
cts 23
cp 0.913
rs 5.2653
cc 11
eloc 25
nc 17
nop 1
crap 11.0795

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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