Completed
Branch 6.0 (d30585)
by yun
04:17
created

File::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 3
Ratio 25 %

Code Coverage

Tests 7
CRAP Score 3.0175

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 2
dl 3
loc 12
rs 9.8666
c 0
b 0
f 0
ccs 7
cts 8
cp 0.875
crap 3.0175
1
<?php
2
// +----------------------------------------------------------------------
3
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006~2019 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\session\driver;
14
15
use Closure;
16
use Exception;
17
use FilesystemIterator;
18
use Generator;
19
use SplFileInfo;
20
use think\App;
21
use think\contract\SessionHandlerInterface;
22
23
/**
24
 * Session 文件驱动
25
 */
26
class File implements SessionHandlerInterface
27
{
28
    protected $config = [
29
        'path'           => '',
30
        'expire'         => 1440,
31
        'prefix'         => '',
32
        'data_compress'  => false,
33
        'gc_probability' => 1,
34
        'gc_divisor'     => 100,
35
    ];
36
37 3
    public function __construct(App $app, array $config = [])
38
    {
39 3
        $this->config = array_merge($this->config, $config);
40
41 3
        if (empty($this->config['path'])) {
42
            $this->config['path'] = $app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . 'session' . DIRECTORY_SEPARATOR;
43 3 View Code Duplication
        } elseif (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
44 3
            $this->config['path'] .= DIRECTORY_SEPARATOR;
45
        }
46
47 3
        $this->init();
48 3
    }
49
50
    /**
51
     * 打开Session
52
     * @access protected
53
     * @throws Exception
54
     */
55 3
    protected function init(): void
56
    {
57
        try {
58 3
            !is_dir($this->config['path']) && mkdir($this->config['path'], 0755, true);
59
        } catch (\Exception $e) {
60
            // 写入失败
61
        }
62
63
        // 垃圾回收
64 3
        if (random_int(1, $this->config['gc_divisor']) <= $this->config['gc_probability']) {
65 3
            $this->gc();
66
        }
67 3
    }
68
69
    /**
70
     * Session 垃圾回收
71
     * @access public
72
     * @return void
73
     */
74 3
    public function gc(): void
75
    {
76 3
        $lifetime = $this->config['expire'];
77 3
        $now      = time();
78
79
        $files = $this->findFiles($this->config['path'], function (SplFileInfo $item) use ($lifetime, $now) {
80 3
            return $now - $lifetime > $item->getMTime();
81 3
        });
82
83 3
        foreach ($files as $file) {
84 3
            $this->unlink($file->getPathname());
85
        }
86 3
    }
87
88
    /**
89
     * 查找文件
90
     * @param string  $root
91
     * @param Closure $filter
92
     * @return Generator
93
     */
94 3
    protected function findFiles(string $root, Closure $filter)
95
    {
96 3
        $items = new FilesystemIterator($root);
97
98
        /** @var SplFileInfo $item */
99 3
        foreach ($items as $item) {
100 3
            if ($item->isDir() && !$item->isLink()) {
101 3
                yield from $this->findFiles($item->getPathname(), $filter);
102
            } else {
103 3
                if ($filter($item)) {
104 3
                    yield $item;
105
                }
106
            }
107
        }
108 3
    }
109
110
    /**
111
     * 取得变量的存储文件名
112
     * @access protected
113
     * @param string $name 缓存变量名
114
     * @param bool   $auto 是否自动创建目录
115
     * @return string
116
     */
117 3
    protected function getFileName(string $name, bool $auto = false): string
118
    {
119 3
        if ($this->config['prefix']) {
120
            // 使用子目录
121
            $name = $this->config['prefix'] . DIRECTORY_SEPARATOR . 'sess_' . $name;
122
        } else {
123 3
            $name = 'sess_' . $name;
124
        }
125
126 3
        $filename = $this->config['path'] . $name;
127 3
        $dir      = dirname($filename);
128
129 3
        if ($auto && !is_dir($dir)) {
130
            try {
131
                mkdir($dir, 0755, true);
132
            } catch (\Exception $e) {
133
                // 创建失败
134
            }
135
        }
136
137 3
        return $filename;
138
    }
139
140
    /**
141
     * 读取Session
142
     * @access public
143
     * @param string $sessID
144
     * @return string
145
     */
146 3
    public function read(string $sessID): string
147
    {
148 3
        $filename = $this->getFileName($sessID);
149
150 3
        if (is_file($filename) && filemtime($filename) >= time() - $this->config['expire']) {
151 3
            $content = $this->readFile($filename);
152
153 3
            if ($this->config['data_compress'] && function_exists('gzcompress')) {
154
                //启用数据压缩
155
                $content = (string) gzuncompress($content);
156
            }
157
158 3
            return $content;
159
        }
160
161
        return '';
162
    }
163
164
    /**
165
     * 写文件(加锁)
166
     * @param $path
167
     * @param $content
168
     * @return bool
169
     */
170
    protected function writeFile($path, $content): bool
171
    {
172
        return (bool) file_put_contents($path, $content, LOCK_EX);
173
    }
174
175
    /**
176
     * 读取文件内容(加锁)
177
     * @param $path
178
     * @return string
179
     */
180 3
    protected function readFile($path): string
181
    {
182 3
        $contents = '';
183
184 3
        $handle = fopen($path, 'rb');
185
186 3
        if ($handle) {
187
            try {
188 3
                if (flock($handle, LOCK_SH)) {
189 3
                    clearstatcache(true, $path);
190
191 3
                    $contents = fread($handle, filesize($path) ?: 1);
192
193 3
                    flock($handle, LOCK_UN);
194
                }
195 3
            } finally {
196 3
                fclose($handle);
197
            }
198
        }
199
200 3
        return $contents;
201
    }
202
203
    /**
204
     * 写入Session
205
     * @access public
206
     * @param string $sessID
207
     * @param string $sessData
208
     * @return bool
209
     */
210 3
    public function write(string $sessID, string $sessData): bool
211
    {
212 3
        $filename = $this->getFileName($sessID, true);
213 3
        $data     = $sessData;
214
215 3
        if ($this->config['data_compress'] && function_exists('gzcompress')) {
216
            //数据压缩
217
            $data = gzcompress($data, 3);
218
        }
219
220 3
        return $this->writeFile($filename, $data);
221
    }
222
223
    /**
224
     * 删除Session
225
     * @access public
226
     * @param string $sessID
227
     * @return bool
228
     */
229 3
    public function delete(string $sessID): bool
230
    {
231
        try {
232 3
            return $this->unlink($this->getFileName($sessID));
233
        } catch (\Exception $e) {
234
            return false;
235
        }
236
    }
237
238
    /**
239
     * 判断文件是否存在后,删除
240
     * @access private
241
     * @param string $file
242
     * @return bool
243
     */
244 3
    private function unlink(string $file): bool
245
    {
246 3
        return is_file($file) && unlink($file);
247
    }
248
249
}
250