Completed
Push — master ( f4415a...651f7b )
by Anton
12s
created

Session::start()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

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