Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.29% covered (success)
75.29%
64 / 85
66.67% covered (warning)
66.67%
12 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
Session
75.29% covered (success)
75.29%
64 / 85
66.67% covered (warning)
66.67%
12 / 18
61.94
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
3
 getInstance
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 regenerateId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 init
50.00% covered (warning)
50.00%
5 / 10
0.00% covered (danger)
0.00%
0 / 1
4.12
 kill
88.89% covered (success)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
5.03
 setTimedValue
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 setRequestValue
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 checkRequest
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 checkRequests
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 checkExpiration
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 checkExpirations
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 toArray
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 __set
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 __get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
 __isset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 __unset
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Session;
15
16/**
17 * Session class
18 *
19 * @category   Pop
20 * @package    Pop\Session
21 * @author     Nick Sagona, III <dev@nolainteractive.com>
22 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
23 * @license    http://www.popphp.org/license     New BSD License
24 * @version    4.0.0
25 */
26class 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        return self::$instance;
97    }
98
99    /**
100     * Return the current the session name
101     *
102     * @return string
103     */
104    public function getName(): string
105    {
106        return $this->sessionName;
107    }
108
109    /**
110     * Return the current the session id
111     *
112     * @return string
113     */
114    public function getId(): string
115    {
116        return $this->sessionId;
117    }
118
119    /**
120     * Regenerate the session id
121     *
122     * @param  bool $deleteOldSession
123     * @return void
124     */
125    public function regenerateId(bool $deleteOldSession = true): void
126    {
127        session_regenerate_id($deleteOldSession);
128        $this->sessionId   = session_id();
129        $this->sessionName = session_name();
130    }
131
132    /**
133     * Init the session
134     *
135     * @return void
136     */
137    private function init(): void
138    {
139        if (!isset($_SESSION['_POP_SESSION_'])) {
140            $_SESSION['_POP_SESSION_'] = [
141                'requests'    => [],
142                'expirations' => []
143            ];
144        } else if (!isset($_SESSION['_POP_SESSION_']['requests'])) {
145            $_SESSION['_POP_SESSION_']['requests']    = [];
146            $_SESSION['_POP_SESSION_']['expirations'] = [];
147        } else {
148            $this->checkRequests();
149            $this->checkExpirations();
150        }
151    }
152
153    /**
154     * Destroy the session
155     *
156     * @return void
157     */
158    public function kill(): void
159    {
160        if (!empty($this->sessionName) && !empty($this->sessionId) &&
161            isset($_COOKIE[$this->sessionName]) && ($_COOKIE[$this->sessionName] == $this->sessionId)) {
162            setcookie($this->sessionName, $this->sessionId, time() - 3600);
163        }
164
165        $_SESSION = null;
166        session_unset();
167        session_destroy();
168        self::$instance    = null;
169        $this->sessionId   = null;
170        $this->sessionName = null;
171    }
172
173    /**
174     * Set a time-based value
175     *
176     * @param  string $key
177     * @param  mixed  $value
178     * @param  int    $expire
179     * @return Session
180     */
181    public function setTimedValue(string $key, mixed $value, int $expire = 300): Session
182    {
183        $_SESSION[$key] = $value;
184        $_SESSION['_POP_SESSION_']['expirations'][$key] = time() + (int)$expire;
185        return $this;
186    }
187
188    /**
189     * Set a request-based value
190     *
191     * @param  string $key
192     * @param  mixed  $value
193     * @param  int    $hops
194     * @return Session
195     */
196    public function setRequestValue(string $key, mixed $value, int $hops = 1): Session
197    {
198        $_SESSION[$key] = $value;
199        $_SESSION['_POP_SESSION_']['requests'][$key] = [
200            'current' => 0,
201            'limit'   => (int)$hops
202        ];
203        return $this;
204    }
205
206    /**
207     * Check the request-based session value
208     *
209     * @return void
210     */
211    private function checkRequest($key): void
212    {
213        if (isset($_SESSION['_POP_SESSION_']['requests'][$key])) {
214            $_SESSION['_POP_SESSION_']['requests'][$key]['current']++;
215            $current = $_SESSION['_POP_SESSION_']['requests'][$key]['current'];
216            $limit   = $_SESSION['_POP_SESSION_']['requests'][$key]['limit'];
217            if ($current > $limit) {
218                unset($_SESSION[$key]);
219                unset($_SESSION['_POP_SESSION_']['requests'][$key]);
220            }
221        }
222    }
223
224    /**
225     * Check the request-based session values
226     *
227     * @return void
228     */
229    private function checkRequests(): void
230    {
231        foreach ($_SESSION as $key => $value) {
232            $this->checkRequest($key);
233        }
234    }
235
236    /**
237     * Check the time-based session value
238     *
239     * @return void
240     */
241    private function checkExpiration($key): void
242    {
243        if (isset($_SESSION['_POP_SESSION_']['expirations'][$key]) &&
244            (time() > $_SESSION['_POP_SESSION_']['expirations'][$key])) {
245            unset($_SESSION[$key]);
246            unset($_SESSION['_POP_SESSION_']['expirations'][$key]);
247        }
248    }
249
250    /**
251     * Check the time-based session values
252     *
253     * @return void
254     */
255    private function checkExpirations(): void
256    {
257        foreach ($_SESSION as $key => $value) {
258            $this->checkExpiration($key);
259        }
260    }
261
262    /**
263     * Get the session values as an array
264     *
265     * @return array
266     */
267    public function toArray(): array
268    {
269        $session = $_SESSION;
270
271        if (isset($session['_POP_SESSION_'])) {
272            unset($session['_POP_SESSION_']);
273        }
274
275        return $session;
276    }
277
278    /**
279     * Set a property in the session object that is linked to the $_SESSION global variable
280     *
281     * @param  string $name
282     * @param  mixed  $value
283     * @throws Exception
284     * @return void
285     */
286    public function __set(string $name, mixed $value): void
287    {
288        if ($name == '_POP_SESSION_') {
289            throw new Exception("Error: Cannot use the reserved name '_POP_SESSION_'.");
290        }
291        $_SESSION[$name] = $value;
292    }
293
294    /**
295     * Get method to return the value of the $_SESSION global variable
296     *
297     * @param  string $name
298     * @return mixed
299     */
300    public function __get(string $name): mixed
301    {
302        return (($name !== '_POP_SESSION_') && isset($_SESSION[$name])) ? $_SESSION[$name] : null;
303    }
304
305    /**
306     * Return the isset value of the $_SESSION global variable
307     *
308     * @param  string $name
309     * @return bool
310     */
311    public function __isset(string $name): bool
312    {
313        return (($name !== '_POP_SESSION_') && isset($_SESSION[$name]));
314    }
315
316    /**
317     * Unset the $_SESSION global variable
318     *
319     * @param  string $name
320     * @throws Exception
321     * @return void
322     */
323    public function __unset(string $name): void
324    {
325        if ($name == '_POP_SESSION_') {
326            throw new Exception("Error: Cannot use the reserved name '_POP_SESSION_'.");
327        }
328
329        $_SESSION[$name] = null;
330        unset($_SESSION[$name]);
331    }
332
333}