Session   B
last analyzed

Complexity

Total Complexity 46

Size/Duplication

Total Lines 391
Duplicated Lines 0 %

Test Coverage

Coverage 39.45%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 46
eloc 86
c 4
b 0
f 0
dl 0
loc 391
ccs 43
cts 109
cp 0.3945
rs 8.72

21 Methods

Rating   Name   Duplication   Size   Complexity  
A setNamespace() 0 3 1
A destroy() 0 13 3
A setId() 0 8 2
A start() 0 11 3
A regenerateId() 0 6 3
A getId() 0 3 1
A getAdapter() 0 3 1
A setAdapter() 0 3 1
A getNamespace() 0 3 1
A getName() 0 10 2
A cookieExists() 0 3 1
A sessionExists() 0 3 1
A setName() 0 16 3
A setSessionCookieLifetime() 0 8 2
A setSavePath() 0 9 3
A get() 0 6 2
A set() 0 8 2
A expireSessionCookie() 0 12 2
B init() 0 23 7
A delete() 0 4 2
A contains() 0 8 3

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
 * Bluz Framework Component
5
 *
6
 * @copyright Bluz PHP Team
7
 * @link      https://github.com/bluzphp/framework
8
 */
9
10
declare(strict_types=1);
11
12
namespace Bluz\Session;
13
14
use Bluz\Common\Exception\ComponentException;
15
use Bluz\Common\Options;
16
use SessionHandlerInterface;
17
18
/**
19
 * Session
20
 *
21
 * @package  Bluz\Session
22
 * @author   Anton Shevchuk
23
 * @link     https://github.com/bluzphp/framework/wiki/Session
24
 */
25
class Session
26
{
27
    use Options;
28
29
    /**
30
     * @var string value returned by session_name()
31
     */
32
    protected $name;
33
34
    /**
35
     * @var string namespace
36
     */
37
    protected $namespace = 'bluz';
38
39
    /**
40
     * @var string Session handler name
41
     */
42
    protected $adapter = 'files';
43
44
    /**
45
     * @var SessionHandlerInterface Session save handler
46
     */
47
    protected $sessionHandler;
48
49
    /**
50
     * Attempt to set the session name
51
     *
52
     * If the session has already been started, or if the name provided fails
53
     * validation, an exception will be raised.
54
     *
55
     * @param string $name
56
     *
57
     * @return void
58
     * @throws SessionException
59
     */
60
    public function setName(string $name): void
61
    {
62
        if ($this->sessionExists()) {
63
            throw new SessionException(
64
                'Cannot set session name after a session has already started'
65
            );
66
        }
67
68
        if (!preg_match('/^[a-zA-Z0-9]+$/', $name)) {
69
            throw new SessionException(
70
                'Name provided contains invalid characters; must be alphanumeric only'
71
            );
72
        }
73
74
        $this->name = $name;
75
        session_name($name);
76
    }
77
78
    /**
79
     * Get session name
80
     *
81
     * Proxies to {@link session_name()}.
82
     *
83
     * @return string
84
     */
85
    public function getName(): string
86
    {
87
        if (null === $this->name) {
88
            // If we're grabbing via session_name(), we don't need our
89
            // validation routine; additionally, calling setName() after
90
            // session_start() can lead to issues, and often we just need the name
91
            // in order to do things such as setting cookies.
92
            $this->name = session_name();
93
        }
94
        return $this->name;
95
    }
96
97
    /**
98
     * Set Namespace
99
     *
100
     * @param  string $namespace
101
     *
102
     * @return void
103
     */
104
    public function setNamespace(string $namespace): void
105
    {
106
        $this->namespace = $namespace;
107
    }
108
109
    /**
110
     * Get Namespace
111
     *
112
     * @return string
113
     */
114 8
    public function getNamespace(): string
115
    {
116 8
        return $this->namespace;
117
    }
118
119
    /**
120
     * Set session ID
121
     *
122
     * Can safely be called in the middle of a session.
123
     *
124
     * @param string $id
125
     *
126
     * @return void
127
     * @throws SessionException
128
     */
129
    public function setId(string $id): void
130
    {
131
        if ($this->sessionExists()) {
132
            throw new SessionException(
133
                'Session has already been started, to change the session ID call regenerateId()'
134
            );
135
        }
136
        session_id($id);
137
    }
138
139
    /**
140
     * Get session ID
141
     *
142
     * Proxies to {@link session_id()}
143
     *
144
     * @return string
145
     */
146
    public function getId(): string
147
    {
148
        return session_id();
149
    }
150
151
    /**
152
     * Regenerate id
153
     *
154
     * Regenerate the session ID, using session save handler's
155
     * native ID generation Can safely be called in the middle of a session.
156
     *
157
     * @param bool $deleteOldSession
158
     *
159
     * @return bool
160
     */
161
    public function regenerateId(bool $deleteOldSession = true): bool
162
    {
163
        if ($this->sessionExists() && session_id() !== '') {
164
            return session_regenerate_id($deleteOldSession);
165
        }
166
        return false;
167
    }
168
169
    /**
170
     * Returns true if session ID is set
171
     *
172
     * @return bool
173
     */
174 11
    public function cookieExists(): bool
175
    {
176 11
        return isset($_COOKIE[session_name()]);
177
    }
178
179
    /**
180
     * Does a session started and is it currently active?
181
     *
182
     * @return bool
183
     */
184 13
    public function sessionExists(): bool
185
    {
186 13
        return session_status() === PHP_SESSION_ACTIVE;
187
    }
188
189
    /**
190
     * Start session
191
     *
192
     * if No session currently exists, attempt to start it. Calls
193
     * {@link isValid()} once session_start() is called, and raises an
194
     * exception if validation fails.
195
     *
196
     * @return bool
197
     * @throws ComponentException
198
     */
199 8
    public function start(): bool
200
    {
201 8
        if ($this->sessionExists()) {
202 8
            return true;
203
        }
204
205 1
        if ($this->init()) {
206 1
            return session_start();
207
        }
208
209
        throw new ComponentException('Invalid adapter settings');
210
    }
211
212
    /**
213
     * Destroy/end a session
214
     *
215
     * @return void
216
     */
217
    public function destroy(): void
218
    {
219
        if (!$this->cookieExists() || !$this->sessionExists()) {
220
            return;
221
        }
222
223
        session_destroy();
224
225
        // send expire cookies
226
        $this->expireSessionCookie();
227
228
        // clear session data
229
        unset($_SESSION[$this->getNamespace()]);
230
    }
231
232
    /**
233
     * Set session handler name
234
     *
235
     * @param string $adapter
236
     *
237
     * @return void
238
     */
239 587
    public function setAdapter(string $adapter): void
240
    {
241 587
        $this->adapter = $adapter;
242 587
    }
243
244
    /**
245
     * Get session handler name
246
     *
247
     * @return string
248
     */
249
    public function getAdapter(): string
250
    {
251
        return $this->adapter;
252
    }
253
254
    /**
255
     * Register Save Handler with ext/session
256
     *
257
     * Since ext/session is coupled to this particular session manager
258
     * register the save handler with ext/session.
259
     *
260
     * @return bool
261
     * @throws ComponentException
262
     */
263 1
    protected function init(): bool
264
    {
265 1
        if ($this->sessionHandler) {
266
            return true;
267
        }
268
269 1
        if ('files' === $this->adapter) {
270 1
            $this->sessionHandler = new \SessionHandler();
271
            // try to apply settings
272 1
            if ($settings = $this->getOption('settings', 'files')) {
273 1
                $this->setSavePath($settings['save_path']);
274
            }
275 1
            return session_set_save_handler($this->sessionHandler);
276
        }
277
278
        $adapterClass = '\\Bluz\\Session\\Adapter\\' . ucfirst($this->adapter);
279
        if (!class_exists($adapterClass) || !is_subclass_of($adapterClass, SessionHandlerInterface::class)) {
280
            throw new ComponentException("Class for session adapter `{$this->adapter}` not found");
281
        }
282
        $settings = $this->getOption('settings', $this->adapter) ?: [];
283
284
        $this->sessionHandler = new $adapterClass($settings);
285
        return session_set_save_handler($this->sessionHandler);
286
    }
287
288
    /**
289
     * Set the session cookie lifetime
290
     *
291
     * If a session already exists, destroys it (without sending an expiration
292
     * cookie), regenerates the session ID, and restarts the session.
293
     *
294
     * @param integer $ttl TTL in seconds
295
     *
296
     * @return void
297
     */
298
    public function setSessionCookieLifetime(int $ttl): void
299
    {
300
        // Set new cookie TTL
301
        session_set_cookie_params($ttl);
302
303
        if ($this->sessionExists()) {
304
            // There is a running session so we'll regenerate id to send a new cookie
305
            $this->regenerateId();
306
        }
307
    }
308
309
    /**
310
     * Expire the session cookie
311
     *
312
     * Sends a session cookie with no value, and with an expiry in the past.
313
     *
314
     * @return void
315
     */
316
    public function expireSessionCookie(): void
317
    {
318
        if (ini_get('session.use_cookies')) {
319
            $params = session_get_cookie_params();
320
            setcookie(
321
                $this->getName(),
322
                '',
323
                $_SERVER['REQUEST_TIME'] - 42000,
324
                $params['path'],
325
                $params['domain'],
326
                $params['secure'],
327
                $params['httponly']
328
            );
329
        }
330
    }
331
332
    /**
333
     * Set session save path
334
     *
335
     * @param string $savePath
336
     *
337
     * @return void
338
     * @throws ComponentException
339
     */
340 1
    protected function setSavePath(string $savePath): void
341
    {
342
        if (
343 1
            !is_dir($savePath)
344 1
            || !is_writable($savePath)
345
        ) {
346
            throw new ComponentException('Session path is not writable');
347
        }
348 1
        session_save_path($savePath);
349 1
    }
350
351
    /**
352
     * Set key/value pair
353
     *
354
     * @param string $key
355
     * @param mixed  $value
356
     *
357
     * @return void
358
     * @throws ComponentException
359
     */
360 9
    public function set(string $key, $value): void
361
    {
362 9
        $this->start();
363
        // check storage
364 9
        if (!isset($_SESSION[$this->getNamespace()])) {
365 9
            $_SESSION[$this->getNamespace()] = [];
366
        }
367 9
        $_SESSION[$this->namespace][$key] = $value;
368 9
    }
369
370
    /**
371
     * Get value by key
372
     *
373
     * @param string $key
374
     *
375
     * @return mixed
376
     * @throws ComponentException
377
     */
378 12
    public function get(string $key)
379
    {
380 12
        if ($this->contains($key)) {
381 7
            return $_SESSION[$this->namespace][$key];
382
        }
383 12
        return null;
384
    }
385
386
    /**
387
     * Isset
388
     *
389
     * @param string $key
390
     *
391
     * @return bool
392
     * @throws ComponentException
393
     */
394 12
    public function contains(string $key): bool
395
    {
396 12
        if ($this->cookieExists()) {
397
            $this->start();
398 12
        } elseif (!$this->sessionExists()) {
399
            return false;
400
        }
401 12
        return isset($_SESSION[$this->namespace][$key]);
402
    }
403
404
    /**
405
     * Unset
406
     *
407
     * @param string $key
408
     *
409
     * @return void
410
     * @throws ComponentException
411
     */
412 1
    public function delete(string $key): void
413
    {
414 1
        if ($this->contains($key)) {
415 1
            unset($_SESSION[$this->namespace][$key]);
416
        }
417 1
    }
418
}
419