Completed
Push — tmp_tooltest ( b199b4...3ffa99 )
by Jonathan
01:43
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 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
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
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
        $this->directory = realpath($this->directory); /* Get rid of extraneous symlinks, ..'s, etc. */
0 ignored issues
show
Documentation Bug introduced by
It seems like realpath($this->directory) can also be of type false. However, the property $directory is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
43 22
        if (!$this->directory) {
44 1
            throw new \Exception("Could not get directory realpath");
45
        }
46
47 22
        $this->extension = empty($extension) ? "" : (string)$extension;
48 22
    }
49
50
    /**
51
     * Check for a directory's existence and writability, and create otherwise
52
     *
53
     * @param string $directory
54
     * @throws \Exception
55
     */
56 22
    private function checkAndCreateDir($directory)
57
    {
58 22
        if (!file_exists($directory)) {
59 22
            if (!@mkdir($directory, 0700, true)) {
60 22
                throw new \Exception("Directory does not exist, and could not be created: {$directory}");
61
            }
62 3
        } elseif (is_dir($directory)) {
63 3
            if (!is_writable($directory)) {
64 3
                throw new \Exception("Directory is not writable: {$directory}");
65
            }
66
        } else {
67 1
            throw new \Exception("Not a directory: {$directory}");
68
        }
69 22
    }
70
71
    /**
72
     * Generate a consistent temporary directory based on a requested directory name.
73
     *
74
     * @param string $directory The name or path of a temporary directory.
75
     * @return string The directory name, resolved to a full path.
76
     */
77 22
    private function getTempDir($directory)
78
    {
79 22
        if (empty($directory) || !is_string($directory)) {
80 22
            $classParts = explode("\\", static::class);
81 22
            return sys_get_temp_dir()  . '/' . end($classParts);
82 2
        } elseif (strpos($directory, '/') !== 0) {
83 1
            return sys_get_temp_dir() . '/' . $directory;
84
        } else {
85 1
            return $directory;
86
        }
87
    }
88
89
    /**
90
     * @inheritDoc Vectorface\Cache\Cache
91
     */
92 20
    public function get($key, $default = null)
93
    {
94 20
        $file = $this->makePath($this->key($key));
95 19
        $data = @file_get_contents($file);
96 19
        if (!$data) {
97 16
            return $default;
98
        }
99
100 9
        $data = @unserialize($data);
101 9
        if (!$data) {
102 1
            $this->delete($key); /* Delete corrupted. */
103 1
            return $default;
104
        }
105
106 8
        list($expiry, $value) = $data;
107 8
        if ($expiry !== false && ($expiry < microtime(true))) {
108 1
            $this->delete($key);
109 1
            return $default;
110
        }
111
112 8
        return $value;
113
    }
114
115
    /**
116
     * @inheritDoc Vectorface\Cache\Cache
117
     */
118 20
    public function set($key, $value, $ttl = null)
119
    {
120 20
        $ttl = $this->ttl($ttl);
121 20
        $data = [$ttl ? microtime(true) + $ttl : false, $value];
122 20
        return @file_put_contents($this->makePath($this->key($key)), serialize($data)) !== false;
123
    }
124
125
    /**
126
     * Delete an entry in the cache by key regardless of TTL
127
     *
128
     * @param string $key A key to delete from the cache.
129
     * @return bool True if the cache entry was successfully deleted, false otherwise.
130
     */
131 7
    public function delete($key)
132
    {
133 7
        return @unlink($this->makePath($this->key($key)));
134
    }
135
136
    /**
137
     * Manually clean out entries older than their TTL
138
     *
139
     * @return bool Returns true if the cache directory was cleaned.
140
     */
141 2
    public function clean()
142
    {
143 2
        if (!($files = $this->getCacheFiles())) {
144 1
            return false;
145
        }
146
147 1
        foreach ($files as $file) {
148 1
            $key = basename($file, $this->extension);
149 1
            $this->get($key); // Automatically deletes if expired
150
        }
151 1
        return true;
152
    }
153
154
    /**
155
     * Clear the cache
156
     *
157
     * @return bool Returns true if all files were flushed.
158
     */
159 22
    public function flush()
160
    {
161 22
        if (($files = $this->getCacheFiles()) === false) {
162 1
            return false;
163
        }
164
165 22
        $result = true;
166 22
        foreach ($files as $file) {
167 12
            $result = $result && @unlink($file);
168
        }
169 22
        return $result;
170
    }
171
172
    /**
173
     * @inheritDoc \Psr\SimpleCache\CacheInterface
174
     */
175 3
    public function clear()
176
    {
177 3
        return $this->flush();
178
    }
179
180
    /**
181
     * @inheritDoc \Psr\SimpleCache\CacheInterface
182
     */
183 1
    public function has($key)
184
    {
185 1
        return $this->get($key, null) !== null;
186
    }
187
188
    /**
189
     * Creates a file path in the form directory/key.extension
190
     *
191
     * @param  String $key the key of the cached element
192
     * @return String The file path to the cached element's enclosing file.
193
     */
194 19
    private function makePath($key)
195
    {
196 19
        return $this->directory . "/" . hash("sha224", $key) . $this->extension;
197
    }
198
199
    /**
200
     * Finds all files with the cache extension in the cache directory
201
     *
202
     * @return array|false Returns an array of filenames that represent cached entries.
203
     */
204 22
    private function getCacheFiles()
205
    {
206 22
        if (!($files = @scandir($this->directory, 1))) {
207 1
            return false;
208
        }
209
210 22
        $negExtLen = -1 * strlen($this->extension);
211 22
        $return = [];
212 22
        foreach ($files as $file) {
213 22
            if (substr($file, $negExtLen) === $this->extension) {
214 12
                $return[] = $this->directory . '/' . $file;
215
            }
216
        }
217 22
        return $return;
218
    }
219
220
    /**
221
     * Destroy this cache; Clear everything.
222
     *
223
     * Any operations on the cache after this operation are invalid, and their behavior will be undefined.
224
     *
225
     * @return bool True if the cache was flushed and the directory deleted.
226
     */
227 22
    public function destroy()
228
    {
229 22
        return $this->flush() && @rmdir($this->directory);
230
    }
231
}
232