SessionException   A
last analyzed

Complexity

Total Complexity 1

Size/Duplication

Total Lines 11
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 1
eloc 5
c 2
b 0
f 0
dl 0
loc 11
ccs 2
cts 2
cp 1
rs 10

1 Method

Rating   Name   Duplication   Size   Complexity  
A forNotFoundHandler() 0 3 1
1
<?php
2
3
/*
4
 * This file is part of the Koded package.
5
 *
6
 * (c) Mihail Binev <[email protected]>
7
 *
8
 * Please view the LICENSE distributed with this source code
9
 * for the full copyright and license information.
10
 *
11
 */
12
13
namespace Koded\Session;
14
15
use Koded\Exceptions\KodedException;
16
use Koded\Stdlib\{Immutable, UUID};
17
use Koded\Stdlib\Interfaces\Data;
18
19
20
interface Session
21
{
22
23
    /**
24
     * The timestamp when session started.
25
     */
26
    const STAMP = '_stamp';
27
28
    /**
29
     * The web browser signature.
30
     */
31
    const AGENT = '_agent';
32
33
    /**
34
     * Session token (useful for CSRF)
35
     */
36
    const TOKEN = '_token';
37
38
    /**
39
     * Flash data
40
     */
41
    const FLASH = '_flash';
42
43
    public function id(): string;
44
45
    public function get(string $name, $default = null);
46
47
    public function set(string $name, $value): Session;
48
49
    public function pull(string $name, $default = null);
50
51
    public function add(string $name, $value): void;
52
53
    public function import(array $data): Session;
54
55
    public function remove(string $name): void;
56
57
    public function all(): array;
58
59
    public function toArray(): array;
60
61
    public function toData(): Data;
62
63
    public function clear(): bool;
64
65
    /**
66
     * Destroys all of the data associated with the current session,
67
     * by closing the session and re-opening it again.
68
     * Also the object state is cleaned and internal metadata is recreated.
69
     *
70
     * @return bool TRUE on success, FALSE otherwise
71
     */
72
    public function destroy(): bool;
73
74
    /**
75
     * Update the current session id with a newly generated one
76
     * and session token with a new v4 UUID.
77
     *
78
     * @param bool $deleteOldSession [optional] Whether to delete the old associated session or not
79
     *
80
     * @return bool TRUE on success, FALSE otherwise
81
     * @link https://php.net/manual/en/function.session-regenerate-id.php
82
     */
83
    public function regenerate(bool $deleteOldSession = false): bool;
84
85
    /**
86
     * Exchange the session data for another one.
87
     *
88
     * @param array $data The new array or object to exchange with the current session data
89
     *
90
     * @return array The old session data
91
     */
92
    public function replace(array $data): array;
93
94
    public function has(string $name): bool;
95
96
    public function accessed(): bool;
97
98
    public function modified(): bool;
99
100
    public function token(): string;
101
102
    public function starttime(): float;
103
104
    public function agent(): string;
105
106
    public function isEmpty(): bool;
107
108
    public function count(): int;
109
110
    public function flash(string $name, $value = null);
111
}
112
113
/**
114
 * Class PhpSession
115
 */
116
final class PhpSession implements Session
117
{
118
    /**
119
     * @var bool
120
     */
121
    private $accessed = false;
122
123
    /**
124
     * @var bool
125
     */
126
    private $modified = false;
127
128
    /**
129
     * @var float
130
     */
131
    private $stamp = 0;
132
133
    /**
134
     * @var string
135
     */
136
    private $agent = '';
137
138
    /**
139
     * @var string
140
     */
141
    private $token = '';
142
143 64
    public function __construct()
144
    {
145
        // @codeCoverageIgnoreStart
146
        if (false === isset($_SESSION)) {
147
            global $_SESSION;
148
            $_SESSION = [];
149
        }
150
        // @codeCoverageIgnoreEnd
151
152 64
        $this->loadMetadata();
153 64
        $this->accessed = false;
154 64
        $this->modified = false;
155 64
        session($this);
156 64
    }
157
158 12
    public function get(string $name, $default = null)
159
    {
160 12
        $this->accessed = true;
161
162 12
        return $_SESSION[$name] ?? $default;
163
    }
164
165 12
    public function __get(string $name)
166
    {
167 12
        return $this->get($name);
168
    }
169
170 3
    public function all(): array
171
    {
172 3
        $this->accessed = true;
173
174 3
        return (array)$_SESSION + $this->getMetadata();
175
    }
176
177 3
    public function __set(string $name, $value)
178
    {
179 3
        return $this->set($name, $value);
180
    }
181
182 12
    public function toArray(): array
183
    {
184 12
        $this->accessed = true;
185
186 12
        return $_SESSION ?? [];
187
    }
188
189 3
    public function toData(): Data
190
    {
191 3
        $this->accessed = true;
192
193 3
        return new Immutable($_SESSION ?? []);
194
    }
195
196
    /*
197
     *
198
     * (mutator methods)
199
     *
200
     */
201
202 6
    public function set(string $name, $value): Session
203
    {
204 6
        $this->modified  = true;
205 6
        $_SESSION[$name] = $value;
206
207 6
        return $this;
208
    }
209
210 3
    public function add(string $name, $value): void
211
    {
212 3
        $this->has($name) || $this->set($name, $value);
213 3
    }
214
215 15
    public function import(array $data): Session
216
    {
217 15
        $data     = array_filter($data, 'is_string', ARRAY_FILTER_USE_KEY);
218 15
        $_SESSION = array_replace($_SESSION, $data);
219
220 15
        $this->modified = true;
221
222 15
        return $this;
223
    }
224
225 64
    public function pull(string $name, $default = null)
226
    {
227 64
        $value = $_SESSION[$name] ?? $default;
228 64
        unset($_SESSION[$name]);
229
230 64
        return $value;
231
    }
232
233 3
    public function remove(string $name): void
234
    {
235 3
        $this->modified = true;
236 3
        unset($_SESSION[$name]);
237 3
    }
238
239 3
    public function flash(string $name, $value = null)
240
    {
241 3
        if (null === $value) {
242 3
            $value = $this->pull(self::FLASH);
243
        } else {
244 3
            $_SESSION[self::FLASH][$name] = $value;
245
        }
246
247 3
        $this->modified = true;
248
249 3
        return $value;
250
    }
251
252 9
    public function has(string $name): bool
253
    {
254 9
        return array_key_exists($name, $_SESSION ?? []);
255
    }
256
257
    /*
258
     *
259
     * (support methods)
260
     *
261
     */
262
263 9
    public function replace(array $data): array
264
    {
265 9
        $oldSession = $_SESSION;
266 9
        $_SESSION   = [];
267 9
        $this->import($data);
268
269 9
        return $oldSession;
270
    }
271
272 3
    public function clear(): bool
273
    {
274 3
        $_SESSION       = [];
275 3
        $this->modified = true;
276
277 3
        return empty($_SESSION);
278
    }
279
280 3
    public function regenerate(bool $deleteOldSession = false): bool
281
    {
282 3
        $_SESSION[self::TOKEN] = $this->token = UUID::v4();
283
284 3
        return session_regenerate_id($deleteOldSession);
285
    }
286
287 3
    public function destroy(): bool
288
    {
289 3
        session_write_close();
290
291
        // @codeCoverageIgnoreStart
292
        if (false === session_start()) {
293
            return false;
294
        }
295
        // @codeCoverageIgnoreEnd
296
297 3
        $updated = session_regenerate_id(true);
298
299 3
        $this->replace([]);
300 3
        $this->resetMetadata();
301
302 3
        return $updated;
303
    }
304
305 9
    public function id(): string
306
    {
307 9
        return session_id();
308
    }
309
310 24
    public function accessed(): bool
311
    {
312 24
        return $this->accessed;
313
    }
314
315 27
    public function modified(): bool
316
    {
317 27
        return $this->modified;
318
    }
319
320 9
    public function token(): string
321
    {
322 9
        return $this->token;
323
    }
324
325 6
    public function starttime(): float
326
    {
327 6
        return $this->stamp;
328
    }
329
330 3
    public function agent(): string
331
    {
332 3
        return $this->agent;
333
    }
334
335 3
    public function isEmpty(): bool
336
    {
337 3
        return 0 === $this->count();
338
    }
339
340 6
    public function count(): int
341
    {
342 6
        return count($_SESSION);
343
    }
344
345
    /*
346
     *
347
     * Session metadata
348
     *
349
     */
350
351
    /**
352
     * move CSRF token, start time and user agent into object properties.
353
     */
354 64
    private function loadMetadata(): void
355
    {
356 64
        $this->stamp = $this->pull(self::STAMP, microtime(true));
357 64
        $this->agent = $this->pull(self::AGENT, $_SERVER['HTTP_USER_AGENT'] ?? '');
358 64
        $this->token = $this->pull(self::TOKEN, UUID::v4());
359 64
    }
360
361 3
    private function resetMetadata(): void
362
    {
363 3
        $_SESSION[self::STAMP] = $this->stamp = microtime(true);
364 3
        $_SESSION[self::AGENT] = $this->agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
365 3
        $_SESSION[self::TOKEN] = $this->token = UUID::v4();
366 3
    }
367
368 3
    private function getMetadata(): array
369
    {
370
        return [
371 3
            self::STAMP => $this->stamp,
372 3
            self::AGENT => $this->agent,
373 3
            self::TOKEN => $this->token,
374
        ];
375
    }
376
}
377
378
/**
379
 * Class SessionException
380
 */
381
class SessionException extends KodedException
382
{
383
    private const E_HANDLER_NOT_FOUND = 0;
384
385
    protected $messages = [
386
        self::E_HANDLER_NOT_FOUND => 'Failed to load the session handler class. Requested :handler',
387
    ];
388
389 1
    public static function forNotFoundHandler(string $handler): SessionException
390
    {
391 1
        return new self(self::E_HANDLER_NOT_FOUND, [':handler' => $handler]);
392
    }
393
}
394