Passed
Push — 8.0 ( dede3d...c48da2 )
by liu
11:17 queued 13s
created

File::get()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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

299
        /** @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...
300
301 3
        return true;
302
    }
303
}
304