Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
27 / 27 |
|
100.00% |
11 / 11 |
CRAP | |
100.00% |
1 / 1 |
SaveHandler | |
100.00% |
27 / 27 |
|
100.00% |
11 / 11 |
18 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
prepareConfig | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
getConfig | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
log | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setFingerprint | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
makeFingerprint | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasSameFingerprint | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMaxlifetime | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getIP | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getUA | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getKeySuffix | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
4 | |||
validateId | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
4 | |||
open | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
read | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
write | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
updateTimestamp | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
close | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
destroy | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
gc | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
lock | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
unlock | n/a |
0 / 0 |
n/a |
0 / 0 |
0 |
1 | <?php declare(strict_types=1); |
2 | /* |
3 | * This file is part of Aplus Framework Session Library. |
4 | * |
5 | * (c) Natan Felles <natanfelles@gmail.com> |
6 | * |
7 | * For the full copyright and license information, please view the LICENSE |
8 | * file that was distributed with this source code. |
9 | */ |
10 | namespace Framework\Session; |
11 | |
12 | use Framework\Log\Logger; |
13 | use Framework\Log\LogLevel; |
14 | use SensitiveParameter; |
15 | |
16 | /** |
17 | * Class SaveHandler. |
18 | * |
19 | * @see https://www.php.net/manual/en/class.sessionhandler.php |
20 | * @see https://gist.github.com/mindplay-dk/623bdd50c1b4c0553cd3 |
21 | * @see https://www.cloudways.com/blog/setup-redis-as-session-handler-php/#sessionlifecycle |
22 | * |
23 | * @package session |
24 | */ |
25 | abstract class SaveHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface |
26 | { |
27 | /** |
28 | * The configurations used by the save handler. |
29 | * |
30 | * @var array<string,mixed> |
31 | */ |
32 | protected array $config; |
33 | /** |
34 | * The current data fingerprint. |
35 | * |
36 | * @var string |
37 | */ |
38 | protected string $fingerprint; |
39 | /** |
40 | * The lock id or false if is not locked. |
41 | * |
42 | * @var false|string |
43 | */ |
44 | protected string | false $lockId = false; |
45 | /** |
46 | * Tells if the session exists (if was read). |
47 | * |
48 | * @var bool |
49 | */ |
50 | protected bool $sessionExists = false; |
51 | /** |
52 | * The current session ID. |
53 | * |
54 | * @var string|null |
55 | */ |
56 | protected ?string $sessionId; |
57 | /** |
58 | * The Logger instance or null if it was not set. |
59 | * |
60 | * @var Logger|null |
61 | */ |
62 | protected ?Logger $logger; |
63 | |
64 | /** |
65 | * SessionSaveHandler constructor. |
66 | * |
67 | * @param array<string,mixed> $config |
68 | * @param Logger|null $logger |
69 | */ |
70 | public function __construct( |
71 | #[SensitiveParameter] array $config = [], |
72 | Logger $logger = null |
73 | ) { |
74 | $this->prepareConfig($config); |
75 | $this->logger = $logger; |
76 | } |
77 | |
78 | /** |
79 | * Prepare configurations to be used by the save handler. |
80 | * |
81 | * @param array<string,mixed> $config Custom configs |
82 | * |
83 | * @codeCoverageIgnore |
84 | */ |
85 | protected function prepareConfig(#[SensitiveParameter] array $config) : void |
86 | { |
87 | $this->config = $config; |
88 | } |
89 | |
90 | /** |
91 | * @return array<string,mixed> |
92 | */ |
93 | public function getConfig() : array |
94 | { |
95 | return $this->config; |
96 | } |
97 | |
98 | /** |
99 | * Log a message if the Logger is set. |
100 | * |
101 | * @param string $message The message to log |
102 | * @param LogLevel $level The log level |
103 | */ |
104 | protected function log(string $message, LogLevel $level = LogLevel::ERROR) : void |
105 | { |
106 | $this->logger?->log($level, $message); |
107 | } |
108 | |
109 | /** |
110 | * Set the data fingerprint. |
111 | * |
112 | * @param string $data The data to set the new fingerprint |
113 | */ |
114 | protected function setFingerprint(string $data) : void |
115 | { |
116 | $this->fingerprint = $this->makeFingerprint($data); |
117 | } |
118 | |
119 | /** |
120 | * Make the fingerprint value. |
121 | * |
122 | * @param string $data The data to get the fingerprint |
123 | * |
124 | * @return string The fingerprint hash |
125 | */ |
126 | private function makeFingerprint(string $data) : string |
127 | { |
128 | return \hash('xxh3', $data); |
129 | } |
130 | |
131 | /** |
132 | * Tells if the data has the same current fingerprint. |
133 | * |
134 | * @param string $data The data to compare |
135 | * |
136 | * @return bool True if the fingerprints are the same, otherwise false |
137 | */ |
138 | protected function hasSameFingerprint(string $data) : bool |
139 | { |
140 | return $this->fingerprint === $this->makeFingerprint($data); |
141 | } |
142 | |
143 | /** |
144 | * Get the maxlifetime (TTL) used by cache handlers or locking. |
145 | * |
146 | * NOTE: It will use the `maxlifetime` config or the ini value of |
147 | * `session.gc_maxlifetime` as fallback. |
148 | * |
149 | * @return int The maximum lifetime of a session in seconds |
150 | */ |
151 | protected function getMaxlifetime() : int |
152 | { |
153 | return (int) ($this->config['maxlifetime'] ?? \ini_get('session.gc_maxlifetime')); |
154 | } |
155 | |
156 | /** |
157 | * Get the remote IP address. |
158 | * |
159 | * @return string |
160 | */ |
161 | protected function getIP() : string |
162 | { |
163 | return $_SERVER['REMOTE_ADDR'] ?? ''; |
164 | } |
165 | |
166 | /** |
167 | * Get the HTTP User-Agent. |
168 | * |
169 | * @return string |
170 | */ |
171 | protected function getUA() : string |
172 | { |
173 | return $_SERVER['HTTP_USER_AGENT'] ?? ''; |
174 | } |
175 | |
176 | protected function getKeySuffix() : string |
177 | { |
178 | $suffix = ''; |
179 | if ($this->config['match_ip']) { |
180 | $suffix .= ':' . $this->getIP(); |
181 | } |
182 | if ($this->config['match_ua']) { |
183 | $suffix .= ':' . $this->getUA(); |
184 | } |
185 | if ($suffix) { |
186 | $suffix = \hash('xxh3', $suffix); |
187 | } |
188 | return $suffix; |
189 | } |
190 | |
191 | /** |
192 | * Validate session id. |
193 | * |
194 | * @param string $id The session id |
195 | * |
196 | * @see https://www.php.net/manual/en/sessionupdatetimestamphandlerinterface.validateid.php |
197 | * |
198 | * @return bool Returns TRUE if the id is valid, otherwise FALSE |
199 | */ |
200 | public function validateId($id) : bool |
201 | { |
202 | $bits = \ini_get('session.sid_bits_per_character') ?: 5; |
203 | $length = \ini_get('session.sid_length') ?: 40; |
204 | $bitsRegex = [ |
205 | 4 => '[0-9a-f]', |
206 | 5 => '[0-9a-v]', |
207 | 6 => '[0-9a-zA-Z,-]', |
208 | ]; |
209 | return isset($bitsRegex[$bits]) |
210 | && \preg_match('#\A' . $bitsRegex[$bits] . '{' . $length . '}\z#', $id); |
211 | } |
212 | |
213 | /** |
214 | * Initialize the session. |
215 | * |
216 | * @param string $path The path where to store/retrieve the session |
217 | * @param string $name The session name |
218 | * |
219 | * @see https://www.php.net/manual/en/sessionhandlerinterface.open.php |
220 | * |
221 | * @return bool Returns TRUE on success, FALSE on failure |
222 | */ |
223 | abstract public function open($path, $name) : bool; |
224 | |
225 | /** |
226 | * Read session data. |
227 | * |
228 | * @param string $id The session id to read data for |
229 | * |
230 | * @see https://www.php.net/manual/en/sessionhandlerinterface.read.php |
231 | * |
232 | * @return string Returns an encoded string of the read data. |
233 | * If nothing was read, it returns an empty string |
234 | */ |
235 | abstract public function read($id) : string; |
236 | |
237 | /** |
238 | * Write session data. |
239 | * |
240 | * @param string $id The session id |
241 | * @param string $data The encoded session data. This data is the result |
242 | * of the PHP internally encoding the $_SESSION superglobal to a serialized |
243 | * string and passing it as this parameter. |
244 | * |
245 | * NOTE: Sessions can use an alternative serialization method |
246 | * |
247 | * @see https://www.php.net/manual/en/sessionhandlerinterface.write.php |
248 | * |
249 | * @return bool Returns TRUE on success, FALSE on failure |
250 | */ |
251 | abstract public function write($id, $data) : bool; |
252 | |
253 | /** |
254 | * Update the timestamp of a session. |
255 | * |
256 | * @param string $id The session id |
257 | * @param string $data The encoded session data. This data is the result |
258 | * of the PHP internally encoding the $_SESSION superglobal to a serialized |
259 | * string and passing it as this parameter. |
260 | * |
261 | * NOTE: Sessions can use an alternative serialization method |
262 | * |
263 | * @see https://www.php.net/manual/en/sessionupdatetimestamphandlerinterface.updatetimestamp.php |
264 | * |
265 | * @return bool Returns TRUE on success, FALSE on failure |
266 | */ |
267 | abstract public function updateTimestamp($id, $data) : bool; |
268 | |
269 | /** |
270 | * Close the session. |
271 | * |
272 | * @see https://www.php.net/manual/en/sessionhandlerinterface.close.php |
273 | * |
274 | * @return bool Returns TRUE on success, FALSE on failure |
275 | */ |
276 | abstract public function close() : bool; |
277 | |
278 | /** |
279 | * Destroy a session. |
280 | * |
281 | * @param string $id The session ID being destroyed |
282 | * |
283 | * @see https://www.php.net/manual/en/sessionhandlerinterface.destroy.php |
284 | * |
285 | * @return bool Returns TRUE on success, FALSE on failure |
286 | */ |
287 | abstract public function destroy($id) : bool; |
288 | |
289 | /** |
290 | * Cleanup old sessions. |
291 | * |
292 | * @param int $max_lifetime Sessions that have not updated for |
293 | * the last $maxLifetime seconds will be removed |
294 | * |
295 | * @see https://www.php.net/manual/en/sessionhandlerinterface.gc.php |
296 | * |
297 | * @return false|int Returns the number of deleted session data for success, |
298 | * false for failure |
299 | */ |
300 | abstract public function gc($max_lifetime) : int | false; |
301 | |
302 | /** |
303 | * Acquire a lock for a session id. |
304 | * |
305 | * @param string $id The session id |
306 | * |
307 | * @return bool Returns TRUE on success, FALSE on failure |
308 | */ |
309 | abstract protected function lock(string $id) : bool; |
310 | |
311 | /** |
312 | * Unlock the current session lock id. |
313 | * |
314 | * @return bool Returns TRUE on success, FALSE on failure |
315 | */ |
316 | abstract protected function unlock() : bool; |
317 | } |