Passed
Push — 8.0 ( 6989f2...c99583 )
by liu
09:37 queued 07:22
created

File::rmdir()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5.025

Importance

Changes 0
Metric Value
cc 5
eloc 10
c 0
b 0
f 0
nc 4
nop 1
dl 0
loc 19
ccs 9
cts 10
cp 0.9
crap 5.025
rs 9.6111
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
        $this->readTimes++;
138
139 3
        $raw = $this->getRaw($name);
140
141 3
        return is_null($raw) ? $default : $this->unserialize($raw['content']);
142
    }
143
144
    /**
145
     * 写入缓存
146
     * @access public
147
     * @param string                 $name   缓存变量名
148
     * @param mixed                  $value  存储数据
149
     * @param int|\DateInterval|DateTimeInterface|null $expire 有效时间 0为永久
150
     * @return bool
151
     */
152 3
    public function set(string $name, mixed $value, int|DateInterval|DateTimeInterface $expire = null): bool
153
    {
154 3
        $this->writeTimes++;
155
156 3
        if (is_null($expire)) {
157 3
            $expire = $this->options['expire'];
158
        }
159
160 3
        $expire   = $this->getExpireTime($expire);
161 3
        $filename = $this->getCacheKey($name);
162
163 3
        $dir = dirname($filename);
164
165 3
        if (!is_dir($dir)) {
166
            try {
167 3
                mkdir($dir, 0755, true);
168
            } catch (\Exception $e) {
169
                // 创建失败
170
            }
171
        }
172
173 3
        $data = $this->serialize($value);
174
175 3
        if ($this->options['data_compress'] && function_exists('gzcompress')) {
176
            //数据压缩
177
            $data = gzcompress($data, 3);
178
        }
179
180 3
        $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
181
182 3
        if (str_contains($filename, '://') && !str_starts_with($filename, 'file://')) {
183
            //虚拟文件不加锁
184 3
            $result = file_put_contents($filename, $data);
185
        } else {
186
            $result = file_put_contents($filename, $data, LOCK_EX);
187
        }
188
189 3
        if ($result) {
190 3
            clearstatcache();
191 3
            return true;
192
        }
193
194
        return false;
195
    }
196
197
    /**
198
     * 自增缓存(针对数值缓存)
199
     * @access public
200
     * @param string $name 缓存变量名
201
     * @param int    $step 步长
202
     * @return false|int
203
     */
204 3
    public function inc(string $name, int $step = 1)
205
    {
206 3
        if ($raw = $this->getRaw($name)) {
207 3
            $value  = $this->unserialize($raw['content']) + $step;
208 3
            $expire = $raw['expire'];
209
        } else {
210
            $value  = $step;
211
            $expire = 0;
212
        }
213
214 3
        return $this->set($name, $value, $expire) ? $value : false;
215
    }
216
217
    /**
218
     * 自减缓存(针对数值缓存)
219
     * @access public
220
     * @param string $name 缓存变量名
221
     * @param int    $step 步长
222
     * @return false|int
223
     */
224 3
    public function dec(string $name, int $step = 1)
225
    {
226 3
        return $this->inc($name, -$step);
227
    }
228
229
    /**
230
     * 删除缓存
231
     * @access public
232
     * @param string $name 缓存变量名
233
     * @return bool
234
     */
235 3
    public function delete(string $name): bool
236
    {
237 3
        $this->writeTimes++;
238
239 3
        return $this->unlink($this->getCacheKey($name));
240
    }
241
242
    /**
243
     * 清除缓存
244
     * @access public
245
     * @return bool
246
     */
247 3
    public function clear(): bool
248
    {
249 3
        $this->writeTimes++;
250
251 3
        $dirname = $this->options['path'] . $this->options['prefix'];
252
253 3
        $this->rmdir($dirname);
254
255 3
        return true;
256
    }
257
258
    /**
259
     * 删除缓存标签
260
     * @access public
261
     * @param array $keys 缓存标识列表
262
     * @return void
263
     */
264 3
    public function clearTag(array $keys): void
265
    {
266 3
        foreach ($keys as $key) {
267 3
            $this->unlink($key);
268
        }
269
    }
270
271
    /**
272
     * 判断文件是否存在后,删除
273
     * @access private
274
     * @param string $path
275
     * @return bool
276
     */
277 3
    private function unlink(string $path): bool
278
    {
279
        try {
280 3
            return is_file($path) && unlink($path);
281
        } catch (\Exception $e) {
282
            return false;
283
        }
284
    }
285
286
    /**
287
     * 删除文件夹
288
     * @param $dirname
289
     * @return bool
290
     */
291 3
    private function rmdir($dirname)
292
    {
293 3
        if (!is_dir($dirname)) {
294
            return false;
295
        }
296
297 3
        $items = new FilesystemIterator($dirname);
298
299 3
        foreach ($items as $item) {
300 3
            if ($item->isDir() && !$item->isLink()) {
301 3
                $this->rmdir($item->getPathname());
302
            } else {
303 3
                $this->unlink($item->getPathname());
304
            }
305
        }
306
307 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

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