Passed
Push — master ( 679316...9474ab )
by
unknown
01:25
created

TempFileCache::checkAndCreateDir()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5

Importance

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