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) { |
|
|
|
|
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
|
|
|
|
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.