Completed
Pull Request — master (#8)
by
unknown
18:26
created

Files::loadKey()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 5

Importance

Changes 2
Bugs 0 Features 2
Metric Value
c 2
b 0
f 2
dl 0
loc 25
ccs 15
cts 15
cp 1
rs 8.439
cc 5
eloc 16
nc 4
nop 1
crap 5
1
<?php
2
3
namespace Apix\Cache;
4
5
/**
6
 * Class Files
7
 * In files cache wrapper.
8
 * Expiration time and tags are stored in the cache file
9
 *
10
 * @package Apix\Cache
11
 * @author  MacFJA
12
 */
13
class Files extends AbstractCache
14
{
15
    /**
16
     * Constructor.
17
     *
18
     * @param array  $options Array of options.
19
     */
20 272
    public function __construct(array $options=null)
21
    {
22
        $options += array(
23 272
            'directory' => sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'apix-cache',
24 272
            'locking' => true
25 272
        );
26 272
        parent::__construct(null, $options);
27 272
        if (!file_exists($this->getOption('directory')) || !is_dir($this->getOption('directory'))) {
28 272
            mkdir($this->getOption('directory'), 0755, true);
29 272
        }
30
    }
31
32
    /**
33
     * Retrieves the cache content for the given key.
34
     *
35
     * @param  string $key The cache key to retrieve.
36
     * @return mixed|null Returns the cached data or null.
37 153
     */
38
    public function loadKey($key)
39 153
    {
40 153
        $key = $this->mapKey($key);
41
        $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . base64_encode($key);
42 153
43 85
        if (!file_exists($path) || !is_file($path)) {
44
            return null;
45
        }
46 102
47 102
        $data = $this->readFile($path);
48 17
49 17
        if ('' === $data) {
50
            unlink($path);
51 102
            return null;
52 102
        }
53 102
        $pos = strpos($data, PHP_EOL, 0);
54 17
        $pos = strpos($data, PHP_EOL, $pos+1);
55 17
        if (false === $pos) {// Un-complete file
56
            unlink($path);
57
            return null;
58 85
        }
59 85
60
        $serialized = substr($data, $pos+1);
61
        return unserialize($serialized);
62
    }
63
64
    /**
65
     * Retrieves the cache keys for the given tag.
66
     *
67
     * @param  string $tag The cache tag to retrieve.
68 119
     * @return array|null Returns an array of cache keys or null.
69
     */
70 119
    public function loadTag($tag)
71 17
    {
72
        if (!$this->getOption('tag_enable')) {
73
            return null;
74 102
        }
75 102
76 102
        $encoded = base64_encode($this->mapTag($tag));
77 102
        $found = array();
78 102
        $files = scandir($this->getOption('directory'));
79 102
        foreach ($files as $file) {
80
            if (substr($file, 0, 1) === '.') {
81 85
                continue;
82 85
            }
83 85
            $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . $file;
84 85
            $fileTags = explode(' ', $this->readFile($path, 1));
85 85
86 85
            if (in_array($encoded, $fileTags, true)) {
87 85
                $found[] = base64_decode($file);
88 102
            }
89
        }
90 102
91 51
        if (0 === count($found)) {
92
            return null;
93 85
        }
94
        return $found;
95
    }
96
97
    /**
98
     * Get the file data.
99
     * If enable, lock file to preserve atomicity
100
     *
101
     * @param string $path The file path
102
     * @param int $line The line to read. If -1 read the whole file
103
     * @return string
104
     */
105
    protected function readFile($path, $line = -1)
106 221
    {
107
        $handle = fopen($path, 'r');
108 221
        if ($this->getOption('locking')) {
109 221
            flock($handle, LOCK_SH);
110
        }
111 221
112 221
        if (-1 === $line) {
113 102
            $data = stream_get_contents($handle);
114 102
        } else {
115 102
            for ($read = 1; $read < $line; $read++) {
116 102
                fgets($handle);
117 102
            }
118 102
            $data = rtrim(fgets($handle), PHP_EOL);
119
        }
120 221
121 221
        if ($this->getOption('locking')) {
122 221
            flock($handle, LOCK_UN);
123
        }
124
        fclose($handle);
125
126
        return $data;
127
    }
128
129
    /**
130
     * Saves data to the cache.
131 68
     *
132
     * @param  mixed $data The data to cache.
133 68
     * @param  string $key The cache id to save.
134 68
     * @param  array $tags The cache tags for this cache entry.
135 68
     * @param  int $ttl The time-to-live in seconds, if set to null the
136 34
     *                       cache is valid forever.
137
     * @return boolean Returns True on success or False on failure.
138
     */
139 51
    public function save($data, $key, array $tags = null, $ttl = null)
140
    {
141
        $key = $this->mapKey($key);
142
        $expire = (null === $ttl) ? 0 : time() + $ttl;
143
144
        $tag = '';
145
        if (null !== $tags) {
146
            $baseTags = $tags;
147
            array_walk($baseTags, function (&$item, $key, Files $cache) {
148 17
                $item = base64_encode($cache->mapTag($item));
149
            }, $this);
150 17
            $tag = implode(' ', $baseTags);
151 17
        }
152 17
153 17
        $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . base64_encode($key);
154 17
        file_put_contents(
155
            $path,
156 17
            $tag . PHP_EOL . $expire . PHP_EOL . serialize($data),
157 17
            $this->getOption('locking') ? LOCK_EX : null
158 17
        );
159
        return true;
160 17
    }
161 17
162 17
    /**
163
     * Deletes the specified cache record.
164 17
     *
165
     * @param  string $key The cache id to remove.
166
     * @return boolean Returns True on success or False on failure.
167
     */
168
    public function delete($key)
169
    {
170
        $key = $this->mapKey($key);
171
        $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . base64_encode($key);
172
        if (!file_exists($path)) {
173
            return false;
174 272
        }
175
176 272
        return unlink($path);
177 272
    }
178 272
179 272
    /**
180
     * Removes all the cached entries associated with the given tag names.
181 204
     *
182 204
     * @param  array $tags The array of tags to remove.
183 204
     * @return boolean Returns True on success or False on failure.
184
     */
185 204
    public function clean(array $tags)
186 204
    {
187 204
        $toRemove = array();
188 272
        foreach ($tags as $tag) {
189 272
            $keys = $this->loadTag($tag);
190
            if (null === $keys) {
191
                return false;
192
            }
193
            $toRemove = array_merge($toRemove, $keys);
194
        }
195
        $toRemove = array_unique($toRemove);
196
197
        foreach ($toRemove as $key) {
198 34
            $this->delete($this->removePrefixKey($key));
199
        }
200 34
201 34
        return true;
202 34
    }
203 34
204
    /**
205
     * Flush all the cached entries.
206 34
     *
207 34
     * @param  boolean $all Wether to flush the whole database, or (preferably)
208 34
     *                      the entries prefixed with prefix_key and prefix_tag.
209 34
     * @return boolean Returns True on success or False on failure.
210
     */
211 34
    public function flush($all = false)
212 17
    {
213
        $files = scandir($this->getOption('directory'));
214
        foreach ($files as $file) {
215 34
            if ('.' === substr($file, 0, 1)) {
216
                continue;
217
            }
218
            $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . $file;
219
            $fullKey = base64_decode($file);
220
            $key = $this->removePrefixKey($fullKey);
221
222
            if (!$all && ($key !== $fullKey || '' === $this->options['prefix_key'])) {
223
                unlink($path);
224
            }
225
        }
226
    }
227
228
    /**
229
     * Returns the time-to-live (in seconds) for the given key.
230
     *
231
     * @param  string $key The name of the key.
232
     * @return int|false Returns the number of seconds left, 0 if valid
233
     *                       forever or False if the key is non-existant.
234
     */
235
    public function getTtl($key)
236
    {
237
        $key = $this->mapKey($key);
238
        $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . base64_encode($key);
239
        if (!file_exists($path) || !is_file($path)) {
240
            return false;
241
        }
242
243
        $expire = $this->readFile($path, 2);
244
245
        if (0 === (int) $expire) {
246
            return 0;
247
        }
248
249
        return $expire - time();
0 ignored issues
show
Bug Compatibility introduced by
The expression $expire - time(); of type integer|double adds the type double to the return on line 249 which is incompatible with the return type declared by the interface Apix\Cache\Adapter::getTtl of type integer|false.
Loading history...
250
    }
251
}