Passed
Push — develop ( d108c3...2e70b5 )
by Jonathan
01:33
created

TempFileCache::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 7
c 3
b 0
f 0
dl 0
loc 12
ccs 8
cts 8
cp 1
rs 10
cc 3
nc 3
nop 2
crap 3
1
<?php
2
3
namespace Vectorface\Cache;
4
5
use Vectorface\Cache\Common\MultipleTrait;
6
use Vectorface\Cache\Common\PSR16Util;
7
8
/**
9
 * Represents a cache whose entries are stored in temporary files.
10
 */
11
class TempFileCache implements Cache
12
{
13
    use MultipleTrait, PSR16Util;
14
15
    /**
16
     * @var string
17
     */
18
    private $directory;
19
20
    /**
21
     * @var string
22
     */
23
    private $extension;
24
25
    /**
26
     * Create a temporary file cache.
27
     *
28
     * @param string $directory The directory in which cache files should be stored.
29
     * @param string $extension The extension to be used after the filename to uniquely identify cache files.
30
     *
31
     * Note:
32
     *  - Without a directory argument, the system tempdir will be used (e.g. /tmp/TempFileCache/)
33
     *  - If given a relative path, it will create that directory within the system tempdir.
34
     *  - If given an absolute path, it will attempt to use that path as-is. Not recommended.
35
     * @throws \Exception
36
     */
37 22
    public function __construct($directory = null, $extension = '.tempcache')
38
    {
39 22
        $this->directory = $this->getTempDir($directory);
40 22
        $this->checkAndCreateDir($this->directory);
41
42 22
        $realpath = realpath($this->directory); /* Get rid of extraneous symlinks, ..'s, etc. */
43 22
        if (!$realpath) {
44 1
            throw new \Exception("Could not get directory realpath");
45
        }
46 22
        $this->directory = $realpath;
47
48 22
        $this->extension = empty($extension) ? "" : (string)$extension;
49 22
    }
50
51
    /**
52
     * Check for a directory's existence and writability, and create otherwise
53
     *
54
     * @param string $directory
55
     * @throws \Exception
56
     */
57 22
    private function checkAndCreateDir($directory)
58
    {
59 22
        if (!file_exists($directory)) {
60 22
            if (!@mkdir($directory, 0700, true)) {
61 22
                throw new \Exception("Directory does not exist, and could not be created: {$directory}");
62
            }
63 3
        } elseif (is_dir($directory)) {
64 3
            if (!is_writable($directory)) {
65 3
                throw new \Exception("Directory is not writable: {$directory}");
66
            }
67
        } else {
68 1
            throw new \Exception("Not a directory: {$directory}");
69
        }
70 22
    }
71
72
    /**
73
     * Generate a consistent temporary directory based on a requested directory name.
74
     *
75
     * @param string $directory The name or path of a temporary directory.
76
     * @return string The directory name, resolved to a full path.
77
     */
78 22
    private function getTempDir($directory)
79
    {
80 22
        if (empty($directory) || !is_string($directory)) {
81 22
            $classParts = explode("\\", static::class);
82 22
            return sys_get_temp_dir()  . '/' . end($classParts);
83 2
        } elseif (strpos($directory, '/') !== 0) {
84 1
            return sys_get_temp_dir() . '/' . $directory;
85
        } else {
86 1
            return $directory;
87
        }
88
    }
89
90
    /**
91
     * @inheritDoc Vectorface\Cache\Cache
92
     */
93 20
    public function get($key, $default = null)
94
    {
95 20
        $file = $this->makePath($this->key($key));
96 19
        $data = @file_get_contents($file);
97 19
        if (!$data) {
98 16
            return $default;
99
        }
100
101 9
        $data = @unserialize($data);
102 9
        if (!$data) {
103 1
            $this->delete($key); /* Delete corrupted. */
104 1
            return $default;
105
        }
106
107 8
        list($expiry, $value) = $data;
108 8
        if ($expiry !== false && ($expiry < microtime(true))) {
109 1
            $this->delete($key);
110 1
            return $default;
111
        }
112
113 8
        return $value;
114
    }
115
116
    /**
117
     * @inheritDoc Vectorface\Cache\Cache
118
     */
119 20
    public function set($key, $value, $ttl = null)
120
    {
121 20
        $ttl = $this->ttl($ttl);
122 20
        $data = [$ttl ? microtime(true) + $ttl : false, $value];
123 20
        return @file_put_contents($this->makePath($this->key($key)), serialize($data)) !== false;
124
    }
125
126
    /**
127
     * Delete an entry in the cache by key regardless of TTL
128
     *
129
     * @param string $key A key to delete from the cache.
130
     * @return bool True if the cache entry was successfully deleted, false otherwise.
131
     */
132 7
    public function delete($key)
133
    {
134 7
        return @unlink($this->makePath($this->key($key)));
135
    }
136
137
    /**
138
     * Manually clean out entries older than their TTL
139
     *
140
     * @return bool Returns true if the cache directory was cleaned.
141
     */
142 2
    public function clean()
143
    {
144 2
        if (!($files = $this->getCacheFiles())) {
145 1
            return false;
146
        }
147
148 1
        foreach ($files as $file) {
149 1
            $key = basename($file, $this->extension);
150 1
            $this->get($key); // Automatically deletes if expired
151
        }
152 1
        return true;
153
    }
154
155
    /**
156
     * Clear the cache
157
     *
158
     * @return bool Returns true if all files were flushed.
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 \Psr\SimpleCache\CacheInterface
175
     */
176 3
    public function clear()
177
    {
178 3
        return $this->flush();
179
    }
180
181
    /**
182
     * @inheritDoc \Psr\SimpleCache\CacheInterface
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