• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aplus-framework / session / 5802958178

pending completion
5802958178

push

github

natanfelles
Get save handler configs from main instances

9 of 9 new or added lines in 1 file covered. (100.0%)

963 of 1028 relevant lines covered (93.68%)

39.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.37
/src/Session.php
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\Session\Debug\SessionCollector;
13
use JetBrains\PhpStorm\Pure;
14
use LogicException;
15
use RuntimeException;
16

17
/**
18
 * Class Session.
19
 *
20
 * @package session
21
 */
22
class Session
23
{
24
    /**
25
     * @var array<string,mixed>
26
     */
27
    protected array $options = [];
28
    protected SaveHandler $saveHandler;
29
    protected SessionCollector $debugCollector;
30

31
    /**
32
     * Session constructor.
33
     *
34
     * @param array<string,int|string> $options
35
     * @param SaveHandler|null $handler
36
     */
37
    public function __construct(array $options = [], SaveHandler $handler = null)
38
    {
39
        $this->setOptions($options);
273✔
40
        if ($handler) {
273✔
41
            $this->saveHandler = $handler;
247✔
42
            \session_set_save_handler($handler);
247✔
43
        }
44
    }
45

46
    public function __destruct()
47
    {
48
        $this->stop();
264✔
49
    }
50

51
    public function __get(string $key) : mixed
52
    {
53
        return $this->get($key);
26✔
54
    }
55

56
    public function __set(string $key, mixed $value) : void
57
    {
58
        $this->set($key, $value);
44✔
59
    }
60

61
    public function __isset(string $key) : bool
62
    {
63
        return $this->has($key);
9✔
64
    }
65

66
    public function __unset(string $key) : void
67
    {
68
        $this->remove($key);
9✔
69
    }
70

71
    /**
72
     * @see http://php.net/manual/en/session.security.ini.php
73
     *
74
     * @param array<string,int|string> $custom
75
     */
76
    protected function setOptions(array $custom) : void
77
    {
78
        $serializer = \ini_get('session.serialize_handler');
273✔
79
        $serializer = $serializer === 'php' ? 'php_serialize' : $serializer;
273✔
80
        $secure = (isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] === 'https')
273✔
81
            || (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on');
273✔
82
        $default = [
273✔
83
            'name' => 'session_id',
273✔
84
            'serialize_handler' => $serializer,
273✔
85
            'sid_bits_per_character' => 6,
273✔
86
            'sid_length' => 48,
273✔
87
            'cookie_domain' => '',
273✔
88
            'cookie_httponly' => 1,
273✔
89
            'cookie_lifetime' => 7200,
273✔
90
            'cookie_path' => '/',
273✔
91
            'cookie_samesite' => 'Strict',
273✔
92
            'cookie_secure' => $secure,
273✔
93
            'referer_check' => '',
273✔
94
            'use_cookies' => 1,
273✔
95
            'use_only_cookies' => 1,
273✔
96
            'use_strict_mode' => 1,
273✔
97
            'use_trans_sid' => 0,
273✔
98
            // used to auto-regenerate the session id:
99
            'auto_regenerate_maxlifetime' => 0,
273✔
100
            'auto_regenerate_destroy' => true,
273✔
101
        ];
273✔
102
        $this->options = $custom
273✔
103
            ? \array_replace($default, $custom)
264✔
104
            : $default;
25✔
105
    }
106

107
    /**
108
     * @param array<string,mixed> $custom
109
     *
110
     * @return array<string,mixed>
111
     */
112
    protected function getOptions(array $custom = []) : array
113
    {
114
        $options = $custom
272✔
115
            ? \array_replace($this->options, $custom)
8✔
116
            : $this->options;
272✔
117
        unset(
272✔
118
            $options['auto_regenerate_maxlifetime'],
272✔
119
            $options['auto_regenerate_destroy']
272✔
120
        );
272✔
121
        return $options;
272✔
122
    }
123

124
    /**
125
     * @param array<string,int|string> $customOptions
126
     *
127
     * @throws LogicException if session was already active
128
     * @throws RuntimeException if session could not be started
129
     *
130
     * @return bool
131
     */
132
    public function start(array $customOptions = []) : bool
133
    {
134
        if ($this->isActive()) {
272✔
135
            throw new LogicException('Session was already active');
9✔
136
        }
137
        if ( ! @\session_start($this->getOptions($customOptions))) {
272✔
138
            $message = '';
5✔
139
            if (\error_get_last()) {
5✔
140
                $message = ': ' . \error_get_last()['message'];
5✔
141
            }
142
            throw new RuntimeException(
5✔
143
                'Session could not be started' . $message
5✔
144
            );
5✔
145
        }
146
        $time = \time();
272✔
147
        $this->autoRegenerate($time);
272✔
148
        $this->clearTemp($time);
272✔
149
        $this->clearFlash();
272✔
150
        return true;
272✔
151
    }
152

153
    /**
154
     * Make sure the session is active.
155
     *
156
     * If it is not active, it will start it.
157
     *
158
     * @throws RuntimeException if session could not be started
159
     *
160
     * @return bool
161
     */
162
    public function activate() : bool
163
    {
164
        if ($this->isActive()) {
9✔
165
            return true;
9✔
166
        }
167
        return $this->start();
9✔
168
    }
169

170
    /**
171
     * Auto regenerate the session id.
172
     *
173
     * @param int $time
174
     *
175
     * @see https://owasp.org/www-community/attacks/Session_fixation
176
     */
177
    protected function autoRegenerate(int $time) : void
178
    {
179
        $maxlifetime = (int) $this->options['auto_regenerate_maxlifetime'];
272✔
180
        $isActive = $maxlifetime > 0;
272✔
181
        if (($isActive && empty($_SESSION['$']['regenerated_at']))
272✔
182
            || ($isActive && $_SESSION['$']['regenerated_at'] < ($time - $maxlifetime))
272✔
183
        ) {
184
            $this->regenerateId((bool) $this->options['auto_regenerate_destroy']);
10✔
185
        }
186
    }
187

188
    /**
189
     * Clears the Flash Data.
190
     */
191
    protected function clearFlash() : void
192
    {
193
        unset($_SESSION['$']['flash']['old']);
272✔
194
        if (isset($_SESSION['$']['flash']['new'])) {
272✔
195
            foreach ($_SESSION['$']['flash']['new'] as $key => $value) {
19✔
196
                $_SESSION['$']['flash']['old'][$key] = $value;
19✔
197
            }
198
        }
199
        unset($_SESSION['$']['flash']['new']);
272✔
200
        if (empty($_SESSION['$']['flash'])) {
272✔
201
            unset($_SESSION['$']['flash']);
272✔
202
        }
203
    }
204

205
    /**
206
     * Clears the Temp Data.
207
     *
208
     * @param int $time The max time to temp data survive
209
     */
210
    protected function clearTemp(int $time) : void
211
    {
212
        if (isset($_SESSION['$']['temp'])) {
272✔
213
            foreach ($_SESSION['$']['temp'] as $key => $value) {
9✔
214
                if ($value['ttl'] < $time) {
9✔
215
                    unset($_SESSION['$']['temp'][$key]);
9✔
216
                }
217
            }
218
        }
219
        if (empty($_SESSION['$']['temp'])) {
272✔
220
            unset($_SESSION['$']['temp']);
272✔
221
        }
222
    }
223

224
    /**
225
     * Tells if sessions are enabled, and one exists.
226
     *
227
     * @return bool
228
     */
229
    public function isActive() : bool
230
    {
231
        return \session_status() === \PHP_SESSION_ACTIVE;
273✔
232
    }
233

234
    /**
235
     * Destroys all data registered to a session.
236
     *
237
     * @return bool true on success or false on failure
238
     */
239
    public function destroy() : bool
240
    {
241
        if ($this->isActive()) {
263✔
242
            $destroyed = \session_destroy();
232✔
243
        }
244
        unset($_SESSION);
263✔
245
        return $destroyed ?? true;
263✔
246
    }
247

248
    /**
249
     * Sets a Cookie with the session name to be destroyed in the user-agent.
250
     *
251
     * @throws RuntimeException If it could not get the session name
252
     *
253
     * @return bool True if the Set-Cookie header was set to invalidate the
254
     * session cookie, false if output exists
255
     */
256
    public function destroyCookie() : bool
257
    {
258
        $name = \session_name();
9✔
259
        if ($name === false) {
9✔
260
            throw new RuntimeException('Could not get the session name');
×
261
        }
262
        $params = \session_get_cookie_params();
9✔
263
        return \setcookie($name, '', [
9✔
264
            'expires' => 0,
9✔
265
            'path' => $params['path'],
9✔
266
            'domain' => $params['domain'],
9✔
267
            'secure' => $params['secure'],
9✔
268
            'httponly' => $params['httponly'],
9✔
269
            'samesite' => $params['samesite'],
9✔
270
        ]);
9✔
271
    }
272

273
    /**
274
     * Write session data and end session.
275
     *
276
     * @return bool returns true on success or false on failure
277
     */
278
    public function stop() : bool
279
    {
280
        if ($this->isActive()) {
265✔
281
            $closed = \session_write_close();
104✔
282
        }
283
        return $closed ?? true;
265✔
284
    }
285

286
    /**
287
     * Discard session data changes and end session.
288
     *
289
     * @return bool returns true on success or false on failure
290
     */
291
    public function abort() : bool
292
    {
293
        if ($this->isActive()) {
9✔
294
            $aborted = \session_abort();
9✔
295
        }
296
        return $aborted ?? true;
9✔
297
    }
298

299
    /**
300
     * Tells if the session has an item.
301
     *
302
     * @param string $key The item key name
303
     *
304
     * @return bool True if it has, otherwise false
305
     */
306
    #[Pure]
307
    public function has(string $key) : bool
308
    {
309
        return isset($_SESSION[$key]);
9✔
310
    }
311

312
    /**
313
     * Gets one session item.
314
     *
315
     * @param string $key The item key name
316
     *
317
     * @return mixed The item value or null if no set
318
     */
319
    #[Pure]
320
    public function get(string $key) : mixed
321
    {
322
        return $_SESSION[$key] ?? null;
53✔
323
    }
324

325
    /**
326
     * Get all session items.
327
     *
328
     * @return array<mixed> The value of the $_SESSION global
329
     */
330
    #[Pure]
331
    public function getAll() : array
332
    {
333
        return $_SESSION;
27✔
334
    }
335

336
    /**
337
     * Get multiple session items.
338
     *
339
     * @param array<string> $keys An array of key item names
340
     *
341
     * @return array<string,mixed> An associative array with items keys and
342
     * values. Item not set will return as null.
343
     */
344
    #[Pure]
345
    public function getMulti(array $keys) : array
346
    {
347
        $items = [];
9✔
348
        foreach ($keys as $key) {
9✔
349
            $items[$key] = $this->get($key);
9✔
350
        }
351
        return $items;
9✔
352
    }
353

354
    /**
355
     * Set a session item.
356
     *
357
     * @param string $key The item key name
358
     * @param mixed $value The item value
359
     *
360
     * @rerun static
361
     */
362
    public function set(string $key, mixed $value) : static
363
    {
364
        $_SESSION[$key] = $value;
74✔
365
        return $this;
74✔
366
    }
367

368
    /**
369
     * Set multiple session items.
370
     *
371
     * @param array<string,mixed> $items An associative array of items keys and
372
     * values
373
     *
374
     * @rerun static
375
     */
376
    public function setMulti(array $items) : static
377
    {
378
        foreach ($items as $key => $value) {
9✔
379
            $this->set($key, $value);
9✔
380
        }
381
        return $this;
9✔
382
    }
383

384
    /**
385
     * Remove (unset) a session item.
386
     *
387
     * @param string $key The item key name
388
     *
389
     * @rerun static
390
     */
391
    public function remove(string $key) : static
392
    {
393
        unset($_SESSION[$key]);
18✔
394
        return $this;
18✔
395
    }
396

397
    /**
398
     * Remove (unset) multiple session items.
399
     *
400
     * @param array<string> $keys A list of items keys names
401
     *
402
     * @rerun static
403
     */
404
    public function removeMulti(array $keys) : static
405
    {
406
        foreach ($keys as $key) {
9✔
407
            $this->remove($key);
9✔
408
        }
409
        return $this;
9✔
410
    }
411

412
    /**
413
     * Remove (unset) all session items.
414
     *
415
     * @rerun static
416
     */
417
    public function removeAll() : static
418
    {
419
        @\session_unset();
9✔
420
        $_SESSION = [];
9✔
421
        return $this;
9✔
422
    }
423

424
    /**
425
     * Update the current session id with a newly generated one.
426
     *
427
     * @param bool $deleteOldSession Whether to delete the old associated session item or not
428
     *
429
     * @return bool
430
     */
431
    public function regenerateId(bool $deleteOldSession = false) : bool
432
    {
433
        $regenerated = \session_regenerate_id($deleteOldSession);
18✔
434
        if ($regenerated) {
18✔
435
            $_SESSION['$']['regenerated_at'] = \time();
18✔
436
        }
437
        return $regenerated;
18✔
438
    }
439

440
    /**
441
     * Re-initialize session array with original values.
442
     *
443
     * @return bool true if the session was successfully reinitialized or false on failure
444
     */
445
    public function reset() : bool
446
    {
447
        return \session_reset();
17✔
448
    }
449

450
    /**
451
     * Get a Flash Data item.
452
     *
453
     * @param string $key The Flash item key name
454
     *
455
     * @return mixed The item value or null if not exists
456
     */
457
    #[Pure]
458
    public function getFlash(string $key) : mixed
459
    {
460
        return $_SESSION['$']['flash']['new'][$key]
18✔
461
            ?? $_SESSION['$']['flash']['old'][$key]
18✔
462
            ?? null;
18✔
463
    }
464

465
    /**
466
     * Set a Flash Data item, available only in the next time the session is started.
467
     *
468
     * @param string $key The Flash Data item key name
469
     * @param mixed $value The item value
470
     *
471
     * @rerun static
472
     */
473
    public function setFlash(string $key, mixed $value) : static
474
    {
475
        $_SESSION['$']['flash']['new'][$key] = $value;
19✔
476
        return $this;
19✔
477
    }
478

479
    /**
480
     * Remove a Flash Data item.
481
     *
482
     * @param string $key The item key name
483
     *
484
     * @rerun static
485
     */
486
    public function removeFlash(string $key) : static
487
    {
488
        unset(
9✔
489
            $_SESSION['$']['flash']['old'][$key],
9✔
490
            $_SESSION['$']['flash']['new'][$key]
9✔
491
        );
9✔
492
        return $this;
9✔
493
    }
494

495
    /**
496
     * Get a Temp Data item.
497
     *
498
     * @param string $key The item key name
499
     *
500
     * @return mixed The item value or null if it is expired or not set
501
     */
502
    public function getTemp(string $key) : mixed
503
    {
504
        if (isset($_SESSION['$']['temp'][$key])) {
27✔
505
            if ($_SESSION['$']['temp'][$key]['ttl'] > \time()) {
27✔
506
                return $_SESSION['$']['temp'][$key]['data'];
27✔
507
            }
508
            unset($_SESSION['$']['temp'][$key]);
9✔
509
        }
510
        return null;
27✔
511
    }
512

513
    /**
514
     * Set a Temp Data item.
515
     *
516
     * @param string $key The item key name
517
     * @param mixed $value The item value
518
     * @param int $ttl The Time-To-Live of the item, in seconds
519
     *
520
     * @rerun static
521
     */
522
    public function setTemp(string $key, mixed $value, int $ttl = 60) : static
523
    {
524
        $_SESSION['$']['temp'][$key] = [
28✔
525
            'ttl' => \time() + $ttl,
28✔
526
            'data' => $value,
28✔
527
        ];
28✔
528
        return $this;
28✔
529
    }
530

531
    /**
532
     * Remove (unset) a Temp Data item.
533
     *
534
     * @param string $key The item key name
535
     *
536
     * @rerun static
537
     */
538
    public function removeTemp(string $key) : static
539
    {
540
        unset($_SESSION['$']['temp'][$key]);
9✔
541
        return $this;
9✔
542
    }
543

544
    /**
545
     * Get/Set the session id.
546
     *
547
     * @param string|null $newId [optional] The new session id
548
     *
549
     * @throws LogicException when trying to set a new id and the session is active
550
     *
551
     * @return false|string The old session id or false on failure. Note: If a
552
     * $newId is set, it is accepted but not validated. When session_start is
553
     * called, the id is only used if it is valid
554
     */
555
    public function id(string $newId = null) : string | false
556
    {
557
        if ($newId !== null && $this->isActive()) {
29✔
558
            throw new LogicException(
9✔
559
                'Session ID cannot be changed when a session is active'
9✔
560
            );
9✔
561
        }
562
        return \session_id($newId);
29✔
563
    }
564

565
    /**
566
     * Perform session data garbage collection.
567
     *
568
     * If return false, use {@see error_get_last()} to get error details.
569
     *
570
     * @return false|int Returns the number of deleted session data for success,
571
     * false for failure
572
     */
573
    public function gc() : int | false
574
    {
575
        return @\session_gc();
2✔
576
    }
577

578
    public function setDebugCollector(SessionCollector $collector) : static
579
    {
580
        $this->debugCollector = $collector;
10✔
581
        $this->debugCollector->setSession($this)->setOptions($this->options);
10✔
582
        if (isset($this->saveHandler)) {
10✔
583
            $this->debugCollector->setSaveHandler($this->saveHandler);
5✔
584
        }
585
        return $this;
10✔
586
    }
587
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc