1+ <?php
2+
3+ namespace suffi \RedisSessionHandler ;
4+
5+ /**
6+ * Class RedisSessionHandler
7+ * @package suffi\RedisSessionHandler
8+ */
9+ class RedisSessionHandler implements \SessionHandlerInterface
10+ {
11+ /**
12+ * Инстанс редиса
13+ * @var \Redis
14+ */
15+ protected $ redis ;
16+
17+ /**
18+ * Время жизни сессии
19+ * @var int
20+ */
21+ protected $ ttl ;
22+
23+ /**
24+ * Префикс
25+ * @var string
26+ */
27+ protected $ prefix ;
28+
29+ /**
30+ * Флаг блокировки
31+ * @var bool
32+ */
33+ protected $ locked ;
34+
35+ /**
36+ * Ключ блокировки
37+ * @var string
38+ */
39+ private $ lockKey ;
40+
41+ /**
42+ * Токен блокировки
43+ * @var string
44+ */
45+ private $ token ;
46+
47+ /**
48+ * Время между попытками разблокировки
49+ * @var int
50+ */
51+ private $ spinLockWait ;
52+
53+ /**
54+ * Максимальное время ожидания разблокировки
55+ * @var int
56+ */
57+ private $ lockMaxWait ;
58+
59+ /**
60+ * RedisSessionHandler constructor.
61+ * @param \Redis $redis
62+ * @param string $prefix
63+ * @param int $spinLockWait
64+ */
65+ public function __construct (\Redis $ redis , $ prefix = 'session_key ' , $ spinLockWait = 200000 )
66+ {
67+ $ this ->redis = $ redis ;
68+
69+ $ this ->ttl = ini_get ('gc_maxlifetime ' );
70+ $ iniMaxExecutionTime = ini_get ('max_execution_time ' );
71+ $ this ->lockMaxWait = $ iniMaxExecutionTime ? $ iniMaxExecutionTime * 0.7 : 20 ;
72+
73+ $ this ->prefix = $ prefix ;
74+ $ this ->locked = false ;
75+ $ this ->lockKey = null ;
76+ $ this ->spinLockWait = $ spinLockWait ;
77+
78+ }
79+
80+ /**
81+ * @inheritdoc
82+ */
83+ public function open ($ savePath , $ sessionName )
84+ {
85+ return true ;
86+ }
87+
88+ /**
89+ * Попытка разблокировать сессию
90+ */
91+ protected function lockSession ($ sessionId )
92+ {
93+ $ attempts = (1000000 * $ this ->lockMaxWait ) / $ this ->spinLockWait ;
94+
95+ $ this ->token = uniqid ();
96+ $ this ->lockKey = $ sessionId . '.lock ' ;
97+ for ($ i = 0 ; $ i < $ attempts ; ++$ i ) {
98+ $ success = $ this ->redis ->set (
99+ $ this ->getRedisKey ($ this ->lockKey ),
100+ $ this ->token ,
101+ [
102+ 'NX ' , //Установить ключ только, если он уже не существует.
103+ ]
104+ );
105+ if ($ success ) {
106+ $ this ->locked = true ;
107+ return true ;
108+ }
109+ usleep ($ this ->spinLockWait );
110+ }
111+ return false ;
112+ }
113+
114+ /**
115+ * Снятие блокировки сессии
116+ */
117+ private function unlockSession ()
118+ {
119+ $ script = <<<LUA
120+ if redis.call("GET", KEYS[1]) == ARGV[1] then
121+ return redis.call("DEL", KEYS[1])
122+ else
123+ return 0
124+ end
125+ LUA ;
126+
127+ $ this ->redis ->eval ($ script , array ($ this ->getRedisKey ($ this ->lockKey ), $ this ->token ), 1 );
128+
129+ $ this ->locked = false ;
130+ $ this ->token = null ;
131+ }
132+
133+ /**
134+ * @inheritdoc
135+ */
136+ public function close ()
137+ {
138+
139+ if ($ this ->locked ) {
140+ $ this ->unlockSession ();
141+ }
142+
143+ return true ;
144+ }
145+
146+ /**
147+ * @inheritdoc
148+ */
149+ public function read ($ sessionId )
150+ {
151+
152+ if (!$ this ->locked ) {
153+ if (!$ this ->lockSession ($ sessionId )) {
154+ return false ;
155+ }
156+ }
157+
158+ return $ this ->redis ->get ($ this ->getRedisKey ($ sessionId )) ?: '' ;
159+ }
160+
161+ /**
162+ * @inheritdoc
163+ */
164+ public function write ($ sessionId , $ data )
165+ {
166+ if ($ this ->ttl > 0 ) {
167+ $ this ->redis ->setex ($ this ->getRedisKey ($ sessionId ), $ this ->ttl , $ data );
168+ } else {
169+ $ this ->redis ->set ($ this ->getRedisKey ($ sessionId ), $ data );
170+ }
171+ return true ;
172+ }
173+
174+ /**
175+ * @inheritdoc
176+ */
177+ public function destroy ($ sessionId )
178+ {
179+ $ this ->redis ->del ($ this ->getRedisKey ($ sessionId ));
180+ $ this ->close ();
181+ return true ;
182+ }
183+
184+ /**
185+ * @inheritdoc
186+ */
187+ public function gc ($ lifetime )
188+ {
189+ return true ;
190+ }
191+
192+ /**
193+ * Установка времени жизни сессии
194+ * @param int $ttl
195+ */
196+ public function setTtl ($ ttl )
197+ {
198+ $ this ->ttl = $ ttl ;
199+ }
200+
201+ /**
202+ * Максимальное время ожидания разблокировки
203+ * @return int
204+ */
205+ public function getLockMaxWait ()
206+ {
207+ return $ this ->lockMaxWait ;
208+ }
209+
210+ /**
211+ * Максимальное время ожидания разблокировки
212+ * @param int $lockMaxWait
213+ */
214+ public function setLockMaxWait ($ lockMaxWait )
215+ {
216+ $ this ->lockMaxWait = $ lockMaxWait ;
217+ }
218+
219+ /**
220+ * Подготовка ключа
221+ * @param string $key key
222+ * @return string prefixed key
223+ */
224+ protected function getRedisKey ($ key )
225+ {
226+ if (empty ($ this ->prefix )) {
227+ return $ key ;
228+ }
229+ return $ this ->prefix . $ key ;
230+ }
231+
232+ public function __destruct ()
233+ {
234+ $ this ->close ();
235+ }
236+ }
0 commit comments