Failed Conditions
Push — develop ( 4c31fe...9f548a )
by Florian
02:02
created

Session   F

Complexity

Total Complexity 66

Size/Duplication

Total Lines 417
Duplicated Lines 0 %

Test Coverage

Coverage 46.38%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 117
c 2
b 0
f 0
dl 0
loc 417
ccs 64
cts 138
cp 0.4638
rs 3.12
wmc 66

20 Methods

Rating   Name   Duplication   Size   Complexity  
A renew() 0 20 4
A doStartupChecks() 0 15 5
A config() 0 3 1
A consume() 0 14 3
A destroy() 0 12 5
A id() 0 3 1
A started() 0 3 2
A overwrite() 0 12 5
A write() 0 17 4
A check() 0 11 4
A start() 0 26 5
A exists() 0 6 5
A __construct() 0 18 4
A time() 0 3 1
A setSaveHandler() 0 7 2
A delete() 0 4 2
A read() 0 15 5
A setId() 0 9 2
A clear() 0 5 2
A hasExpired() 0 13 4

How to fix   Complexity   

Complex Class

Complex classes like Session often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Session, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
5
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
6
 *
7
 * Licensed under The MIT License
8
 * For full copyright and license information, please see the LICENSE.txt
9
 * Redistributions of files must retain the above copyright notice.
10
 *
11
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
12
 * @link          https://cakephp.org CakePHP(tm) Project
13
 * @since         0.10.0
14
 * @license       https://opensource.org/licenses/mit-license.php MIT License
15
 */
16
17
declare(strict_types=1);
18
19
namespace Phauthentic\Infrastructure\Http\Session;
20
21
use Adbar\Dot;
22
use SessionHandlerInterface;
23
24
/**
25
 * This class is a wrapper for the native PHP session functions. It provides
26
 * several defaults for the most common session configuration
27
 * via external handlers and helps with using session in cli without any warnings.
28
 *
29
 * Sessions can be created from the defaults using `Session::create()` or you can get
30
 * an instance of a new session by just instantiating this class and passing the complete
31
 * options you want to use.
32
 *
33
 * When specific options are omitted, this class will take its defaults from the configuration
34
 * values from the `session.*` directives in php.ini. This class will also alter such
35
 * directives when configuration values are provided.
36
 */
37
class Session implements SessionInterface
38
{
39
    /**
40
     * The Session handler instance used as an engine for persisting the session data.
41
     *
42
     * @var \SessionHandlerInterface
43
     */
44
    protected SessionHandlerInterface $handler;
45
46
    /**
47
     * The Session handler instance used as an engine for persisting the session data.
48
     *
49
     * @var \Phauthentic\Infrastructure\Http\Session\ConfigInterface
50
     */
51
    protected ConfigInterface $config;
52
53
    /**
54
     * Indicates whether the sessions has already started
55
     *
56
     * @var bool
57
     */
58
    protected bool $started = false;
59
60
    /**
61
     * The time in seconds the session will be valid for
62
     *
63
     * @var int
64
     */
65
    protected int $lifetime = 0;
66
67
    /**
68
     * Whether this session is running under a CLI environment
69
     *
70
     * @var bool
71
     */
72
    protected bool $isCli = false;
73
74
    /**
75
     * @param \Phauthentic\Infrastructure\Http\Session\ConfigInterface|null $config The Configuration to apply to this session object
76
     * @param \SessionHandlerInterface|null $handler
77
     */
78 2
    public function __construct(
79
        ?ConfigInterface $config = null,
80
        ?SessionHandlerInterface $handler = null
81
    ) {
82 2
        if ($config !== null) {
83
            $this->config = $config;
84
        } else {
85 2
            $this->config = new Config();
86
        }
87
88 2
        if ($handler !== null) {
89
            $this->setSaveHandler($handler);
90
        }
91
92 2
        $this->lifetime = (int)ini_get('session.gc_maxlifetime');
93 2
        $this->isCli = (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg');
94
95 2
        session_register_shutdown();
96 2
    }
97
98
    /**
99
     * @return \Phauthentic\Infrastructure\Http\Session\ConfigInterface
100
     */
101
    public function config(): ConfigInterface
102
    {
103
        return $this->config;
104
    }
105
106
    /**
107
     * Set the engine property and update the session handler in PHP.
108
     *
109
     * @param \SessionHandlerInterface $handler The handler to set
110
     * @return void
111
     */
112
    protected function setSaveHandler(SessionHandlerInterface $handler): void
113
    {
114
        if (!headers_sent()) {
115
            session_set_save_handler($handler, false);
116
        }
117
118
        $this->handler = $handler;
119
    }
120
121
    /**
122
     * Startup checks
123
     *
124
     * @return bool
125
     */
126
    protected function doStartupChecks(): bool
127
    {
128
        if (session_status() === PHP_SESSION_ACTIVE) {
129
            throw SessionException::alreadyStarted();
130
        }
131
132
        if (ini_get('session.use_cookies') && headers_sent($file, $line)) {
133
            return false;
134
        }
135
136
        if (!session_start()) {
137
            throw SessionException::couldNotStart();
138
        }
139
140
        return true;
141
    }
142
143
    /**
144
     * Starts the Session.
145
     *
146
     * @return bool True if session was started
147
     * @throws \Phauthentic\Infrastructure\Http\Session\SessionException if the session was already started
148
     */
149 1
    public function start(): bool
150
    {
151 1
        if ($this->started) {
152
            return true;
153
        }
154
155 1
        if ($this->isCli) {
156 1
            $_SESSION = [];
157 1
            $this->setId('cli');
158
159 1
            return $this->started = true;
160
        }
161
162
        if (!$this->doStartupChecks()) {
163
            return false;
164
        }
165
166
        $this->started = true;
167
168
        if ($this->hasExpired()) {
169
            $this->destroy();
170
171
            return $this->start();
172
        }
173
174
        return $this->started;
175
    }
176
177
    /**
178
     * @return array|string|null
179
     */
180
    protected function time()
181
    {
182
        return $this->read('Config.time');
183
    }
184
185
    /**
186
     * Determine if Session has already been started.
187
     *
188
     * @return bool True if session has been started.
189
     */
190 1
    public function started(): bool
191
    {
192 1
        return $this->started || session_status() === PHP_SESSION_ACTIVE;
193
    }
194
195
    /**
196
     * Returns true if given variable name is set in session.
197
     *
198
     * @param string|null $name Variable name to check for
199
     * @return bool True if variable is there
200
     */
201 1
    public function check(?string $name = null): bool
202
    {
203 1
        if ($this->exists() && !$this->started()) {
204
            $this->start();
205
        }
206
207 1
        if (!isset($_SESSION)) {
208
            return false;
209
        }
210
211 1
        return (new Dot($_SESSION))->get($name) !== null;
212
    }
213
214
    /**
215
     * Returns given session variable, or all of them, if no parameters given.
216
     *
217
     * @param string|null $name The name of the session variable (or a path as sent to Hash.extract)
218
     * @return string|array|null The value of the session variable, null if session not available,
219
     *   session not started, or provided name not found in the session.
220
     */
221 1
    public function read(?string $name = null)
222
    {
223 1
        if ($this->exists() && !$this->started()) {
224
            $this->start();
225
        }
226
227 1
        if (!isset($_SESSION)) {
228
            return null;
229
        }
230
231 1
        if ($name === null) {
232
            return $_SESSION ?? [];
233
        }
234
235 1
        return (new Dot($_SESSION))->get($name);
236
    }
237
238
    /**
239
     * Reads and deletes a variable from session.
240
     *
241
     * @param string $name The key to read and remove (or a path as sent to Hash.extract).
242
     * @return mixed The value of the session variable, null if session not available,
243
     *   session not started, or provided name not found in the session.
244
     */
245 1
    public function consume(string $name)
246
    {
247 1
        if (empty($name)) {
248
            return null;
249
        }
250
251 1
        $value = $this->read($name);
252 1
        if ($value !== null) {
253 1
            $dot = new Dot($_SESSION);
254 1
            $dot->delete($name);
255 1
            $this->overwrite($_SESSION, (array)$dot->get());
256
        }
257
258 1
        return $value;
259
    }
260
261
    /**
262
     * Writes value to given session variable name.
263
     *
264
     * @param string|array $name Name of variable
265
     * @param mixed $value Value to write
266
     * @return void
267
     */
268 1
    public function write($name, $value = null): void
269
    {
270 1
        if (!$this->started()) {
271
            $this->start();
272
        }
273
274 1
        $write = $name;
275 1
        if (!is_array($name)) {
276 1
            $write = [$name => $value];
277
        }
278
279 1
        $data = new Dot($_SESSION ?? []);
280 1
        foreach ($write as $key => $val) {
281 1
            $data->add($key, $val);
282
        }
283
284 1
        $this->overwrite($_SESSION, $data->get());
285 1
    }
286
287
    /**
288
     * Returns the current sessions id
289
     *
290
     * @return string
291
     */
292
    public function id(): string
293
    {
294
        return (string)session_id();
295
    }
296
297
    /**
298
     * Sets the session id
299
     *
300
     * Calling this method will not auto start the session. You might have to manually
301
     * assert a started session.
302
     *
303
     * Passing an id into it, you can also replace the session id if the session
304
     * has not already been started.
305
     *
306
     * Note that depending on the session handler, not all characters are allowed
307
     * within the session id. For example, the file session handler only allows
308
     * characters in the range a-z A-Z 0-9 , (comma) and - (minus).
309
     *
310
     * @param string $id Session Id
311
     * @return $this
312
     */
313 1
    public function setId(string $id): self
314
    {
315 1
        if (headers_sent()) {
316
            throw SessionException::headersAlreadySent();
317
        }
318
319 1
        session_id($id);
320
321 1
        return $this;
322
    }
323
324
    /**
325
     * Removes a variable from session.
326
     *
327
     * @param string $name Session variable to remove
328
     * @return void
329
     */
330 1
    public function delete(string $name): void
331
    {
332 1
        if ($this->check($name)) {
333 1
            $this->overwrite($_SESSION, (array)(new Dot($_SESSION))->delete($name));
0 ignored issues
show
Bug introduced by
Are you sure the usage of new Adbar\Dot($_SESSION)->delete($name) targeting Adbar\Dot::delete() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
334
        }
335 1
    }
336
337
    /**
338
     * Used to write new data to _SESSION, since PHP doesn't like us setting the _SESSION var itself.
339
     *
340
     * @param array $old Set of old variables => values
341
     * @param array $new New set of variable => value
342
     * @return void
343
     */
344 1
    protected function overwrite(&$old, $new)
345
    {
346 1
        if (!empty($old)) {
347 1
            foreach ($old as $key => $var) {
348 1
                if (!isset($new[$key])) {
349 1
                    unset($old[$key]);
350
                }
351
            }
352
        }
353
354 1
        foreach ($new as $key => $var) {
355 1
            $old[$key] = $var;
356
        }
357 1
    }
358
359
    /**
360
     * Helper method to destroy invalid sessions.
361
     *
362
     * @return void
363
     */
364
    public function destroy()
365
    {
366
        if ($this->exists() && !$this->started()) {
367
            $this->start();
368
        }
369
370
        if (!$this->isCli && session_status() === PHP_SESSION_ACTIVE) {
371
            session_destroy();
372
        }
373
374
        $_SESSION = [];
375
        $this->started = false;
376
    }
377
378
    /**
379
     * Clears the session.
380
     *
381
     * Optionally it also clears the session id and renews the session.
382
     *
383
     * @param bool $renew If session should be renewed, as well. Defaults to false.
384
     * @return void
385
     */
386
    public function clear(bool $renew = false)
387
    {
388
        $_SESSION = [];
389
        if ($renew) {
390
            $this->renew();
391
        }
392
    }
393
394
    /**
395
     * Returns whether a session exists
396
     *
397
     * @return bool
398
     */
399 1
    public function exists()
400
    {
401 1
        return !ini_get('session.use_cookies')
402 1
            || isset($_COOKIE[session_name()])
403 1
            || $this->isCli
404 1
            || (ini_get('session.use_trans_sid') && isset($_GET[session_name()]));
405
    }
406
407
    /**
408
     * Restarts this session.
409
     *
410
     * @return void
411
     */
412
    public function renew(): void
413
    {
414
        if (!$this->exists() || $this->isCli) {
415
            return;
416
        }
417
418
        $this->start();
419
        $params = session_get_cookie_params();
420
        setcookie(
421
            session_name(),
422
            '',
423
            time() - 42000,
424
            $params['path'],
425
            $params['domain'],
426
            $params['secure'],
427
            $params['httponly']
428
        );
429
430
        if (session_id()) {
431
            session_regenerate_id(true);
432
        }
433
    }
434
435
    /**
436
     * Returns true if the session is no longer valid because the last time it was
437
     * accessed was after the configured timeout.
438
     *
439
     * @return bool
440
     */
441
    public function hasExpired(): bool
442
    {
443
        $time = $this->time();
444
        $result = false;
445
446
        $checkTime = $time !== null && $this->lifetime > 0;
447
        if ($checkTime && (time() - (int)$time > $this->lifetime)) {
448
            $result = true;
449
        }
450
451
        $this->write('Config.time', time());
452
453
        return $result;
454
    }
455
}
456