Completed
Push — master ( e47c19...3aaaf2 )
by Anton
15s queued 13s
created

Session::init()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 18.42

Importance

Changes 0
Metric Value
cc 7
eloc 12
nc 5
nop 0
dl 0
loc 20
ccs 5
cts 13
cp 0.3846
crap 18.42
rs 8.8333
c 0
b 0
f 0
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 604
    public function setAdapter(string $adapter): void
240
    {
241 604
        $this->adapter = $adapter;
242 604
    }
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 ('files' === $this->adapter) {
266
            // try to apply settings
267 1
            if ($settings = $this->getOption('settings', 'files')) {
268 1
                $this->setSavePath($settings['save_path']);
269
            }
270 1
            return true;
271
        }
272
        if (null === $this->sessionHandler) {
273
            $adapterClass = '\\Bluz\\Session\\Adapter\\' . ucfirst($this->adapter);
274
            if (!class_exists($adapterClass) || !is_subclass_of($adapterClass, SessionHandlerInterface::class)) {
275
                throw new ComponentException("Class for session adapter `{$this->adapter}` not found");
276
            }
277
            $settings = $this->getOption('settings', $this->adapter) ?: [];
278
279
            $this->adapter = new $adapterClass($settings);
0 ignored issues
show
Documentation Bug introduced by
It seems like new $adapterClass($settings) of type object is incompatible with the declared type string of property $adapter.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
280
            return session_set_save_handler($this->adapter);
281
        }
282
        return true;
283
    }
284
285
    /**
286
     * Set the session cookie lifetime
287
     *
288
     * If a session already exists, destroys it (without sending an expiration
289
     * cookie), regenerates the session ID, and restarts the session.
290
     *
291
     * @param integer $ttl TTL in seconds
292
     *
293
     * @return void
294
     */
295
    public function setSessionCookieLifetime(int $ttl): void
296
    {
297
        // Set new cookie TTL
298
        session_set_cookie_params($ttl);
299
300
        if ($this->sessionExists()) {
301
            // There is a running session so we'll regenerate id to send a new cookie
302
            $this->regenerateId();
303
        }
304
    }
305
306
    /**
307
     * Expire the session cookie
308
     *
309
     * Sends a session cookie with no value, and with an expiry in the past.
310
     *
311
     * @return void
312
     */
313
    public function expireSessionCookie(): void
314
    {
315
        if (ini_get('session.use_cookies')) {
316
            $params = session_get_cookie_params();
317
            setcookie(
318
                $this->getName(),
319
                '',
320
                $_SERVER['REQUEST_TIME'] - 42000,
321
                $params['path'],
322
                $params['domain'],
323
                $params['secure'],
324
                $params['httponly']
325
            );
326
        }
327
    }
328
329
    /**
330
     * Set session save path
331
     *
332
     * @param string $savePath
333
     *
334
     * @return void
335
     * @throws ComponentException
336
     */
337 1
    protected function setSavePath(string $savePath): void
338
    {
339
        if (
340 1
            !is_dir($savePath)
341 1
            || !is_writable($savePath)
342
        ) {
343
            throw new ComponentException('Session path is not writable');
344
        }
345 1
        session_save_path($savePath);
346 1
    }
347
348
    /**
349
     * Set key/value pair
350
     *
351
     * @param string $key
352
     * @param mixed  $value
353
     *
354
     * @return void
355
     * @throws ComponentException
356
     */
357 9
    public function set(string $key, $value): void
358
    {
359 9
        $this->start();
360
        // check storage
361 9
        if (!isset($_SESSION[$this->getNamespace()])) {
362 9
            $_SESSION[$this->getNamespace()] = [];
363
        }
364 9
        $_SESSION[$this->namespace][$key] = $value;
365 9
    }
366
367
    /**
368
     * Get value by key
369
     *
370
     * @param string $key
371
     *
372
     * @return mixed
373
     * @throws ComponentException
374
     */
375 12
    public function get(string $key)
376
    {
377 12
        if ($this->contains($key)) {
378 7
            return $_SESSION[$this->namespace][$key];
379
        }
380 12
        return null;
381
    }
382
383
    /**
384
     * Isset
385
     *
386
     * @param string $key
387
     *
388
     * @return bool
389
     * @throws ComponentException
390
     */
391 12
    public function contains(string $key): bool
392
    {
393 12
        if ($this->cookieExists()) {
394
            $this->start();
395 12
        } elseif (!$this->sessionExists()) {
396
            return false;
397
        }
398 12
        return isset($_SESSION[$this->namespace][$key]);
399
    }
400
401
    /**
402
     * Unset
403
     *
404
     * @param string $key
405
     *
406
     * @return void
407
     * @throws ComponentException
408
     */
409 1
    public function delete(string $key): void
410
    {
411 1
        if ($this->contains($key)) {
412 1
            unset($_SESSION[$this->namespace][$key]);
413
        }
414 1
    }
415
}
416