Files   B
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 248
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 41
lcom 1
cbo 1
dl 0
loc 248
ccs 118
cts 118
cp 1
rs 8.2769
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 3
B loadKey() 0 31 6
B loadTag() 0 26 6
B readFile() 0 23 5
B save() 0 22 4
A delete() 0 10 2
A clean() 0 21 4
A getTtl() 0 16 4
B flush() 0 16 7

How to fix   Complexity   

Complex Class

Complex classes like Files often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Files, and based on these observations, apply Extract Interface, too.

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