Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
93.10% |
81 / 87 |
|
88.89% |
16 / 18 |
CRAP | |
0.00% |
0 / 1 |
Session | |
93.10% |
81 / 87 |
|
88.89% |
16 / 18 |
39.50 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
3 | |||
getInstance | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
getName | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
regenerateId | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
init | |
50.00% |
5 / 10 |
|
0.00% |
0 / 1 |
4.12 | |||
kill | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
5.03 | |||
setTimedValue | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
setRequestValue | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
checkRequest | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
checkRequests | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
checkExpiration | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
checkExpirations | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
toArray | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
__set | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
__get | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
3 | |||
__isset | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
__unset | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Pop PHP Framework (https://www.popphp.org/) |
4 | * |
5 | * @link https://github.com/popphp/popphp-framework |
6 | * @author Nick Sagona, III <dev@noladev.com> |
7 | * @copyright Copyright (c) 2009-2025 NOLA Interactive, LLC. |
8 | * @license https://www.popphp.org/license New BSD License |
9 | */ |
10 | |
11 | /** |
12 | * @namespace |
13 | */ |
14 | namespace Pop\Session; |
15 | |
16 | /** |
17 | * Session class |
18 | * |
19 | * @category Pop |
20 | * @package Pop\Session |
21 | * @author Nick Sagona, III <dev@noladev.com> |
22 | * @copyright Copyright (c) 2009-2025 NOLA Interactive, LLC. |
23 | * @license https://www.popphp.org/license New BSD License |
24 | * @version 4.0.2 |
25 | */ |
26 | class Session extends AbstractSession |
27 | { |
28 | |
29 | /** |
30 | * Instance of the session |
31 | * @var ?object |
32 | */ |
33 | private static ?object $instance = null; |
34 | |
35 | /** |
36 | * Session Name |
37 | * @var ?string |
38 | */ |
39 | private ?string $sessionName = null; |
40 | |
41 | /** |
42 | * Session ID |
43 | * @var ?string |
44 | */ |
45 | private ?string $sessionId = null; |
46 | |
47 | /** |
48 | * Constructor |
49 | * |
50 | * @param array $options |
51 | * |
52 | * Private method to instantiate the session object |
53 | */ |
54 | private function __construct(array $options = []) |
55 | { |
56 | // Start a session and set the session id. |
57 | if (session_id() == '') { |
58 | if (!empty($options)) { |
59 | $sessionParams = session_get_cookie_params(); |
60 | $lifetime = $options['lifetime'] ?? $sessionParams['lifetime']; |
61 | $path = $options['path'] ?? $sessionParams['lifetime']; |
62 | $domain = $options['domain'] ?? $sessionParams['domain']; |
63 | $secure = $options['secure'] ?? $sessionParams['secure']; |
64 | $httponly = $options['httponly'] ?? $sessionParams['httponly']; |
65 | $sameSite = $options['samesite'] ?? $sessionParams['samesite']; |
66 | |
67 | session_set_cookie_params([ |
68 | 'lifetime' => $lifetime, |
69 | 'path' => $path, |
70 | 'domain' => $domain, |
71 | 'secure' => $secure, |
72 | 'httponly' => $httponly, |
73 | 'samesite' => $sameSite |
74 | ]); |
75 | } |
76 | session_start(); |
77 | $this->sessionId = session_id(); |
78 | $this->sessionName = session_name(); |
79 | $this->init(); |
80 | } |
81 | } |
82 | |
83 | /** |
84 | * Determine whether or not an instance of the session object exists already, |
85 | * and instantiate the object if it does not exist. |
86 | * |
87 | * @param array $options |
88 | * @return Session |
89 | */ |
90 | public static function getInstance(array $options = []): Session |
91 | { |
92 | if (null === self::$instance) { |
93 | self::$instance = new Session($options); |
94 | } |
95 | |
96 | self::$instance->checkRequests(); |
97 | self::$instance->checkExpirations(); |
98 | |
99 | return self::$instance; |
100 | } |
101 | |
102 | /** |
103 | * Return the current the session name |
104 | * |
105 | * @return string |
106 | */ |
107 | public function getName(): string |
108 | { |
109 | return $this->sessionName; |
110 | } |
111 | |
112 | /** |
113 | * Return the current the session id |
114 | * |
115 | * @return string |
116 | */ |
117 | public function getId(): string |
118 | { |
119 | return $this->sessionId; |
120 | } |
121 | |
122 | /** |
123 | * Regenerate the session id |
124 | * |
125 | * @param bool $deleteOldSession |
126 | * @return void |
127 | */ |
128 | public function regenerateId(bool $deleteOldSession = true): void |
129 | { |
130 | session_regenerate_id($deleteOldSession); |
131 | $this->sessionId = session_id(); |
132 | $this->sessionName = session_name(); |
133 | } |
134 | |
135 | /** |
136 | * Init the session |
137 | * |
138 | * @return void |
139 | */ |
140 | private function init(): void |
141 | { |
142 | if (!isset($_SESSION['_POP_SESSION_'])) { |
143 | $_SESSION['_POP_SESSION_'] = [ |
144 | 'requests' => [], |
145 | 'expirations' => [] |
146 | ]; |
147 | } else if (!isset($_SESSION['_POP_SESSION_']['requests'])) { |
148 | $_SESSION['_POP_SESSION_']['requests'] = []; |
149 | $_SESSION['_POP_SESSION_']['expirations'] = []; |
150 | } else { |
151 | $this->checkRequests(); |
152 | $this->checkExpirations(); |
153 | } |
154 | } |
155 | |
156 | /** |
157 | * Destroy the session |
158 | * |
159 | * @return void |
160 | */ |
161 | public function kill(): void |
162 | { |
163 | if (!empty($this->sessionName) && !empty($this->sessionId) && |
164 | isset($_COOKIE[$this->sessionName]) && ($_COOKIE[$this->sessionName] == $this->sessionId)) { |
165 | setcookie($this->sessionName, $this->sessionId, time() - 3600); |
166 | } |
167 | |
168 | $_SESSION = null; |
169 | session_unset(); |
170 | session_destroy(); |
171 | self::$instance = null; |
172 | $this->sessionId = null; |
173 | $this->sessionName = null; |
174 | } |
175 | |
176 | /** |
177 | * Set a time-based value |
178 | * |
179 | * @param string $key |
180 | * @param mixed $value |
181 | * @param int $expire |
182 | * @return Session |
183 | */ |
184 | public function setTimedValue(string $key, mixed $value, int $expire = 300): Session |
185 | { |
186 | $_SESSION[$key] = $value; |
187 | $_SESSION['_POP_SESSION_']['expirations'][$key] = time() + (int)$expire; |
188 | return $this; |
189 | } |
190 | |
191 | /** |
192 | * Set a request-based value |
193 | * |
194 | * @param string $key |
195 | * @param mixed $value |
196 | * @param int $hops |
197 | * @return Session |
198 | */ |
199 | public function setRequestValue(string $key, mixed $value, int $hops = 1): Session |
200 | { |
201 | $_SESSION[$key] = $value; |
202 | $_SESSION['_POP_SESSION_']['requests'][$key] = [ |
203 | 'current' => 0, |
204 | 'limit' => (int)$hops |
205 | ]; |
206 | return $this; |
207 | } |
208 | |
209 | /** |
210 | * Check the request-based session value |
211 | * |
212 | * @return void |
213 | */ |
214 | private function checkRequest($key): void |
215 | { |
216 | if (isset($_SESSION['_POP_SESSION_']['requests'][$key])) { |
217 | $_SESSION['_POP_SESSION_']['requests'][$key]['current']++; |
218 | $current = $_SESSION['_POP_SESSION_']['requests'][$key]['current']; |
219 | $limit = $_SESSION['_POP_SESSION_']['requests'][$key]['limit']; |
220 | if ($current > $limit) { |
221 | unset($_SESSION[$key]); |
222 | unset($_SESSION['_POP_SESSION_']['requests'][$key]); |
223 | } |
224 | } |
225 | } |
226 | |
227 | /** |
228 | * Check the request-based session values |
229 | * |
230 | * @return void |
231 | */ |
232 | private function checkRequests(): void |
233 | { |
234 | foreach ($_SESSION as $key => $value) { |
235 | $this->checkRequest($key); |
236 | } |
237 | } |
238 | |
239 | /** |
240 | * Check the time-based session value |
241 | * |
242 | * @return void |
243 | */ |
244 | private function checkExpiration($key): void |
245 | { |
246 | if (isset($_SESSION['_POP_SESSION_']['expirations'][$key]) && |
247 | (time() > $_SESSION['_POP_SESSION_']['expirations'][$key])) { |
248 | unset($_SESSION[$key]); |
249 | unset($_SESSION['_POP_SESSION_']['expirations'][$key]); |
250 | } |
251 | } |
252 | |
253 | /** |
254 | * Check the time-based session values |
255 | * |
256 | * @return void |
257 | */ |
258 | private function checkExpirations(): void |
259 | { |
260 | foreach ($_SESSION as $key => $value) { |
261 | $this->checkExpiration($key); |
262 | } |
263 | } |
264 | |
265 | /** |
266 | * Get the session values as an array |
267 | * |
268 | * @return array |
269 | */ |
270 | public function toArray(): array |
271 | { |
272 | $session = $_SESSION; |
273 | |
274 | if (isset($session['_POP_SESSION_'])) { |
275 | unset($session['_POP_SESSION_']); |
276 | } |
277 | |
278 | return $session; |
279 | } |
280 | |
281 | /** |
282 | * Set a property in the session object that is linked to the $_SESSION global variable |
283 | * |
284 | * @param string $name |
285 | * @param mixed $value |
286 | * @throws Exception |
287 | * @return void |
288 | */ |
289 | public function __set(string $name, mixed $value): void |
290 | { |
291 | if ($name == '_POP_SESSION_') { |
292 | throw new Exception("Error: Cannot use the reserved name '_POP_SESSION_'."); |
293 | } |
294 | $_SESSION[$name] = $value; |
295 | } |
296 | |
297 | /** |
298 | * Get method to return the value of the $_SESSION global variable |
299 | * |
300 | * @param string $name |
301 | * @return mixed |
302 | */ |
303 | public function __get(string $name): mixed |
304 | { |
305 | return (($name !== '_POP_SESSION_') && isset($_SESSION[$name])) ? $_SESSION[$name] : null; |
306 | } |
307 | |
308 | /** |
309 | * Return the isset value of the $_SESSION global variable |
310 | * |
311 | * @param string $name |
312 | * @return bool |
313 | */ |
314 | public function __isset(string $name): bool |
315 | { |
316 | return (($name !== '_POP_SESSION_') && isset($_SESSION[$name])); |
317 | } |
318 | |
319 | /** |
320 | * Unset the $_SESSION global variable |
321 | * |
322 | * @param string $name |
323 | * @throws Exception |
324 | * @return void |
325 | */ |
326 | public function __unset(string $name): void |
327 | { |
328 | if ($name == '_POP_SESSION_') { |
329 | throw new Exception("Error: Cannot use the reserved name '_POP_SESSION_'."); |
330 | } |
331 | |
332 | $_SESSION[$name] = null; |
333 | unset($_SESSION[$name]); |
334 | } |
335 | |
336 | } |