Passed
Push — 8.0 ( 7db137...8b2282 )
by liu
02:57
created

File   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 283
Duplicated Lines 0 %

Test Coverage

Coverage 85.06%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 91
dl 0
loc 283
ccs 74
cts 87
cp 0.8506
rs 8.8798
c 1
b 0
f 0
wmc 44

13 Methods

Rating   Name   Duplication   Size   Complexity  
A delete() 0 3 1
B set() 0 41 9
A inc() 0 11 3
A dec() 0 3 1
A rmdir() 0 19 5
A clearTag() 0 4 2
A clear() 0 7 1
A unlink() 0 6 3
A __construct() 0 12 4
B getRaw() 0 26 8
A has() 0 3 1
A getCacheKey() 0 14 3
A get() 0 8 3

How to fix   Complexity   

Complex Class

Complex classes like File 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.

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 File, and based on these observations, apply Extract Interface, too.

1
<?php
2
// +----------------------------------------------------------------------
3
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
6
// +----------------------------------------------------------------------
7
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
// +----------------------------------------------------------------------
9
// | Author: liu21st <[email protected]>
10
// +----------------------------------------------------------------------
11
declare (strict_types = 1);
12
13
namespace think\cache\driver;
14
15
use DateTimeInterface;
16
use FilesystemIterator;
17
use think\App;
18
use think\cache\Driver;
19
use think\exception\InvalidCacheException;
20
21
/**
22
 * 文件缓存类
23
 */
24
class File extends Driver
25
{
26
    /**
27
     * 配置参数
28
     * @var array
29
     */
30
    protected $options = [
31
        'expire'        => 0,
32
        'cache_subdir'  => true,
33
        'prefix'        => '',
34
        'path'          => '',
35
        'hash_type'     => 'md5',
36
        'data_compress' => false,
37
        'tag_prefix'    => 'tag:',
38
        'serialize'     => [],
39
        'fail_delete'   => false,
40
    ];
41
42
    /**
43
     * 架构函数
44
     * @param App   $app
45
     * @param array $options 参数
46
     */
47 6
    public function __construct(App $app, array $options = [])
48
    {
49 6
        if (!empty($options)) {
50 6
            $this->options = array_merge($this->options, $options);
51
        }
52
53 6
        if (empty($this->options['path'])) {
54 3
            $this->options['path'] = $app->getRuntimePath() . 'cache';
55
        }
56
57 6
        if (!str_ends_with($this->options['path'], DIRECTORY_SEPARATOR)) {
58 6
            $this->options['path'] .= DIRECTORY_SEPARATOR;
59
        }
60
    }
61
62
    /**
63
     * 取得变量的存储文件名
64
     * @access public
65
     * @param string $name 缓存变量名
66
     * @return string
67
     */
68 3
    public function getCacheKey(string $name): string
69
    {
70 3
        $name = hash($this->options['hash_type'], $name);
71
72 3
        if ($this->options['cache_subdir']) {
73
            // 使用子目录
74 3
            $name = substr($name, 0, 2) . DIRECTORY_SEPARATOR . substr($name, 2);
75
        }
76
77 3
        if ($this->options['prefix']) {
78
            $name = $this->options['prefix'] . DIRECTORY_SEPARATOR . $name;
79
        }
80
81 3
        return $this->options['path'] . $name . '.php';
82
    }
83
84
    /**
85
     * 获取缓存数据
86
     * @param string $name 缓存标识名
87
     * @return array|null
88
     */
89 3
    protected function getRaw(string $name)
90
    {
91 3
        $filename = $this->getCacheKey($name);
92
93 3
        if (!is_file($filename)) {
94 3
            return;
95
        }
96
97 3
        $content = @file_get_contents($filename);
98
99 3
        if (false !== $content) {
100 3
            $expire = (int) substr($content, 8, 12);
101 3
            if (0 != $expire && time() - $expire > filemtime($filename)) {
102
                //缓存过期删除缓存文件
103
                $this->unlink($filename);
104
                return;
105
            }
106
107 3
            $content = substr($content, 32);
108
109 3
            if ($this->options['data_compress'] && function_exists('gzcompress')) {
110
                //启用数据压缩
111
                $content = gzuncompress($content);
112
            }
113
114 3
            return is_string($content) ? ['content' => (string) $content, 'expire' => $expire] : null;
0 ignored issues
show
introduced by
The condition is_string($content) is always true.
Loading history...
115
        }
116
    }
117
118
    /**
119
     * 判断缓存是否存在
120
     * @access public
121
     * @param string $name 缓存变量名
122
     * @return bool
123
     */
124 3
    public function has($name): bool
125
    {
126 3
        return $this->getRaw($name) !== null;
127
    }
128
129
    /**
130
     * 读取缓存
131
     * @access public
132
     * @param string $name    缓存变量名
133
     * @param mixed  $default 默认值
134
     * @return mixed
135
     */
136 3
    public function get($name, $default = null): mixed
137
    {
138 3
        $raw = $this->getRaw($name);
139
140
        try {
141 3
            return is_null($raw) ? $this->getDefaultValue($name, $default) : $this->unserialize($raw['content']);
142 3
        } catch (InvalidCacheException $e) {
143 3
            return $this->getDefaultValue($name, $default, true);
144
        }
145
    }
146
147
    /**
148
     * 写入缓存
149
     * @access public
150
     * @param string                                   $name   缓存变量名
151
     * @param mixed                                    $value  存储数据
152
     * @param int|\DateInterval|DateTimeInterface|null $expire 有效时间 0为永久
153
     * @return bool
154
     */
155 3
    public function set($name, $value, $expire = null): bool
156
    {
157 3
        if (is_null($expire)) {
158 3
            $expire = $this->options['expire'];
159
        }
160
161 3
        $expire   = $this->getExpireTime($expire);
162 3
        $filename = $this->getCacheKey($name);
163
164 3
        $dir = dirname($filename);
165
166 3
        if (!is_dir($dir)) {
167
            try {
168 3
                mkdir($dir, 0755, true);
169
            } catch (\Exception $e) {
170
                // 创建失败
171
            }
172
        }
173
174 3
        $data = $this->serialize($value);
175
176 3
        if ($this->options['data_compress'] && function_exists('gzcompress')) {
177
            //数据压缩
178
            $data = gzcompress($data, 3);
179
        }
180
181 3
        $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
182
183 3
        if (str_contains($filename, '://') && !str_starts_with($filename, 'file://')) {
184
            //虚拟文件不加锁
185 3
            $result = file_put_contents($filename, $data);
186
        } else {
187
            $result = file_put_contents($filename, $data, LOCK_EX);
188
        }
189
190 3
        if ($result) {
191 3
            clearstatcache();
192 3
            return true;
193
        }
194
195
        return false;
196
    }
197
198
    /**
199
     * 自增缓存(针对数值缓存)
200
     * @access public
201
     * @param string $name 缓存变量名
202
     * @param int    $step 步长
203
     * @return false|int
204
     */
205 3
    public function inc($name, $step = 1)
206
    {
207 3
        if ($raw = $this->getRaw($name)) {
208 3
            $value  = $this->unserialize($raw['content']) + $step;
209 3
            $expire = $raw['expire'];
210
        } else {
211
            $value  = $step;
212
            $expire = 0;
213
        }
214
215 3
        return $this->set($name, $value, $expire) ? $value : false;
216
    }
217
218
    /**
219
     * 自减缓存(针对数值缓存)
220
     * @access public
221
     * @param string $name 缓存变量名
222
     * @param int    $step 步长
223
     * @return false|int
224
     */
225 3
    public function dec($name, $step = 1)
226
    {
227 3
        return $this->inc($name, -$step);
228
    }
229
230
    /**
231
     * 删除缓存
232
     * @access public
233
     * @param string $name 缓存变量名
234
     * @return bool
235
     */
236 3
    public function delete($name): bool
237
    {
238 3
        return $this->unlink($this->getCacheKey($name));
239
    }
240
241
    /**
242
     * 清除缓存
243
     * @access public
244
     * @return bool
245
     */
246 3
    public function clear(): bool
247
    {
248 3
        $dirname = $this->options['path'] . $this->options['prefix'];
249
250 3
        $this->rmdir($dirname);
251
252 3
        return true;
253
    }
254
255
    /**
256
     * 删除缓存标签
257
     * @access public
258
     * @param array $keys 缓存标识列表
259
     * @return void
260
     */
261 3
    public function clearTag($keys): void
262
    {
263 3
        foreach ($keys as $key) {
264 3
            $this->unlink($key);
265
        }
266
    }
267
268
    /**
269
     * 判断文件是否存在后,删除
270
     * @access private
271
     * @param string $path
272
     * @return bool
273
     */
274 3
    private function unlink(string $path): bool
275
    {
276
        try {
277 3
            return is_file($path) && unlink($path);
278
        } catch (\Exception $e) {
279
            return false;
280
        }
281
    }
282
283
    /**
284
     * 删除文件夹
285
     * @param $dirname
286
     * @return bool
287
     */
288 3
    private function rmdir($dirname)
289
    {
290 3
        if (!is_dir($dirname)) {
291
            return false;
292
        }
293
294 3
        $items = new FilesystemIterator($dirname);
295
296 3
        foreach ($items as $item) {
297 3
            if ($item->isDir() && !$item->isLink()) {
298 3
                $this->rmdir($item->getPathname());
299
            } else {
300 3
                $this->unlink($item->getPathname());
301
            }
302
        }
303
304 3
        @rmdir($dirname);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for rmdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

304
        /** @scrutinizer ignore-unhandled */ @rmdir($dirname);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
305
306 3
        return true;
307
    }
308
}
309