Passed
Push — 8.0 ( 1ef6ec...a598c3 )
by yun
02:26
created

File::inc()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.2098

Importance

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

306
        /** @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...
307
308 3
        return true;
309
    }
310
}
311