Completed
Push — master ( 832a42...a69dc8 )
by Franck
06:59
created

Files::readFile()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 23
ccs 17
cts 17
cp 1
rs 8.5907
cc 5
eloc 14
nc 8
nop 2
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
            '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 272
    }
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
     */
38 153
    public function loadKey($key)
39
    {
40 153
        $key = $this->mapKey($key);
41 153
        $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . base64_encode($key);
42
43 153
        if (!file_exists($path) || !is_file($path)) {
44 85
            return null;
45
        }
46
47 102
        $data = $this->readFile($path);
48
49 102
        if ('' === $data) {
50 17
            unlink($path);
51 17
            return null;
52
        }
53 102
        $pos = strpos($data, PHP_EOL, 0);
54 102
        $pos = strpos($data, PHP_EOL, $pos+1);
55 102
        if (false === $pos) {// Un-complete file
56 17
            unlink($path);
57 17
            return null;
58
        }
59
60 85
        $serialized = substr($data, $pos+1);
61 85
        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
     * @return array|null Returns an array of cache keys or null.
69
     */
70 119
    public function loadTag($tag)
71
    {
72 119
        if (!$this->getOption('tag_enable')) {
73 17
            return null;
74
        }
75
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 102
            if (substr($file, 0, 1) === '.') {
81 102
                continue;
82
            }
83 85
            $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . $file;
84 85
            $fileTags = explode(' ', $this->readFile($path, 1));
85
86 85
            if (in_array($encoded, $fileTags, true)) {
87 85
                $found[] = base64_decode($file);
88 85
            }
89 102
        }
90
91 102
        if (0 === count($found)) {
92 51
            return null;
93
        }
94 85
        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 187
    protected function readFile($path, $line = -1)
106
    {
107 187
        $handle = fopen($path, 'r');
108 187
        if ($this->getOption('locking')) {
109 187
            flock($handle, LOCK_SH);
110 187
        }
111
112 187
        if (-1 === $line) {
113 102
            $data = stream_get_contents($handle);
114 102
        } else {
115 119
            for ($read = 1; $read < $line; $read++) {
116 34
                fgets($handle);
117 34
            }
118 119
            $data = rtrim(fgets($handle), PHP_EOL);
119
        }
120
121 187
        if ($this->getOption('locking')) {
122 187
            flock($handle, LOCK_UN);
123 187
        }
124 187
        fclose($handle);
125
126 187
        return $data;
127
    }
128
129
    /**
130
     * Saves data to the cache.
131
     *
132
     * @param  mixed $data The data to cache.
133
     * @param  string $key The cache id to save.
134
     * @param  array $tags The cache tags for this cache entry.
135
     * @param  int $ttl The time-to-live in seconds, if set to null the
136
     *                       cache is valid forever.
137
     * @return boolean Returns True on success or False on failure.
138
     */
139 221
    public function save($data, $key, array $tags = null, $ttl = null)
140
    {
141 221
        $key = $this->mapKey($key);
142 221
        $expire = (null === $ttl) ? 0 : time() + $ttl;
143
144 221
        $tag = '';
145 221
        if (null !== $tags) {
146 102
            $baseTags = $tags;
147 102
            array_walk($baseTags, function (&$item, $key, Files $cache) {
148 102
                $item = base64_encode($cache->mapTag($item));
149 102
            }, $this);
150 102
            $tag = implode(' ', $baseTags);
151 102
        }
152
153 221
        $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . base64_encode($key);
154 221
        file_put_contents(
155 221
            $path,
156 221
            $tag . PHP_EOL . $expire . PHP_EOL . serialize($data),
157 221
            $this->getOption('locking') ? LOCK_EX : null
158 221
        );
159 221
        return true;
160
    }
161
162
    /**
163
     * Deletes the specified cache record.
164
     *
165
     * @param  string $key The cache id to remove.
166
     * @return boolean Returns True on success or False on failure.
167
     */
168 68
    public function delete($key)
169
    {
170 68
        $key = $this->mapKey($key);
171 68
        $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . base64_encode($key);
172 68
        if (!file_exists($path)) {
173 34
            return false;
174
        }
175
176 51
        return unlink($path);
177
    }
178
179
    /**
180
     * Removes all the cached entries associated with the given tag names.
181
     *
182
     * @param  array $tags The array of tags to remove.
183
     * @return boolean Returns True on success or False on failure.
184
     */
185 17
    public function clean(array $tags)
186
    {
187 17
        $toRemove = array();
188 17
        foreach ($tags as $tag) {
189 17
            $keys = $this->loadTag($tag);
190 17
            if (null === $keys) {
191 17
                return false;
192
            }
193 17
            $toRemove = array_merge($toRemove, $keys);
194 17
        }
195 17
        $toRemove = array_unique($toRemove);
196
197 17
        foreach ($toRemove as $key) {
198 17
            $this->delete($this->removePrefixKey($key));
199 17
        }
200
201 17
        return true;
202
    }
203
204
    /**
205
     * Flush all the cached entries.
206
     *
207
     * @param  boolean $all Wether to flush the whole database, or (preferably)
208
     *                      the entries prefixed with prefix_key and prefix_tag.
209
     * @return boolean Returns True on success or False on failure.
210
     */
211 272
    public function flush($all = false)
212
    {
213 272
        $files = scandir($this->getOption('directory'));
214 272
        foreach ($files as $file) {
215 272
            if ('.' === substr($file, 0, 1)) {
216 272
                continue;
217
            }
218 204
            $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . $file;
219 204
            $fullKey = base64_decode($file);
220 204
            $key = $this->removePrefixKey($fullKey);
221
222 204
            if (!$all && ($key !== $fullKey || '' === $this->options['prefix_key'])) {
223 204
                unlink($path);
224 204
            }
225 272
        }
226 272
    }
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 34
    public function getTtl($key)
236
    {
237 34
        $key = $this->mapKey($key);
238 34
        $path = $this->getOption('directory') . DIRECTORY_SEPARATOR . base64_encode($key);
239 34
        if (!file_exists($path) || !is_file($path)) {
240 34
            return false;
241
        }
242
243 34
        $expire = $this->readFile($path, 2);
244
245 34
        if (0 === (int) $expire) {
246 17
            return 0;
247
        }
248
249 34
        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
}