Passed
Push — master ( 749b16...167cfa )
by Gabor
04:47
created

SessionManager::start()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 1
Bugs 1 Features 0
Metric Value
dl 0
loc 24
ccs 5
cts 5
cp 1
rs 8.9713
c 1
b 1
f 0
cc 3
eloc 16
nc 3
nop 6
crap 3
1
<?php
2
/**
3
 * WebHemi.
4
 *
5
 * PHP version 7.1
6
 *
7
 * @copyright 2012 - 2017 Gixx-web (http://www.gixx-web.com)
8
 * @license   https://opensource.org/licenses/MIT The MIT License (MIT)
9
 *
10
 * @link      http://www.gixx-web.com
11
 */
12
declare(strict_types=1);
13
14
namespace WebHemi\Application;
15
16
use RuntimeException;
17
use WebHemi\Config\ConfigInterface;
18
19
/**
20
 * Class SessionManager.
21
 */
22
class SessionManager
23
{
24
    /** @var string */
25
    private $namespace;
26
    /** @var string */
27
    private $cookiePrefix;
28
    /** @var string */
29
    private $sessionNameSalt;
30
    /** @var array */
31
    private $readOnly = [];
32
    /** @var array */
33
    private $data = [];
34
35
    /**
36
     * SessionManager constructor.
37
     *
38
     * @param ConfigInterface $sessionConfig
39
     */
40 8
    public function __construct(ConfigInterface $sessionConfig)
41
    {
42 8
        $configuration = $sessionConfig->getData('session');
43
44 8
        $this->namespace = $configuration['namespace'];
45 8
        $this->cookiePrefix = $configuration['cookie_prefix'];
46 8
        $this->sessionNameSalt = $configuration['session_name_salt'];
47
48
        // @codeCoverageIgnoreStart
49
        if (!defined('PHPUNIT_WEBHEMI_TESTSUITE')) {
50
            ini_set('session.entropy_file', '/dev/urandom');
51
            ini_set('session.entropy_length', '16');
52
            ini_set('session.hash_function', (string) $configuration['hash_function']);
53
            ini_set('session.use_only_cookies', (string) $configuration['use_only_cookies']);
54
            ini_set('session.use_cookies', (string) $configuration['use_cookies']);
55
            ini_set('session.use_trans_sid', (string) $configuration['use_trans_sid']);
56
            ini_set('session.cookie_httponly', (string) $configuration['cookie_http_only']);
57
            ini_set('session.save_path', (string) $configuration['save_path']);
58
        }
59
        // @codeCoverageIgnoreEnd
60 8
    }
61
62
    /**
63
     * Saves data back to session.
64
     */
65 8
    public function __destruct()
66
    {
67 8
        $this->write();
68
69
        // @codeCoverageIgnoreStart
70
        if (defined('PHPUNIT_WEBHEMI_TESTSUITE')) {
71
            session_write_close();
72
        }
73
        // @codeCoverageIgnoreEnd
74 8
    }
75
76
    /**
77
     * Reads PHP Session array to class property.
78
     *
79
     * @return void
80
     *
81
     * @codeCoverageIgnore
82
     */
83
    private function read() : void
84
    {
85
        if (isset($_SESSION[$this->namespace])) {
86
            $this->data = $_SESSION[$this->namespace];
87
        }
88
    }
89
90
    /**
91
     * Writes class property to PHP Session array.
92
     *
93
     * @return void
94
     *
95
     * @codeCoverageIgnore
96
     */
97
    private function write() : void
98
    {
99
        if (defined('PHPUNIT_WEBHEMI_TESTSUITE')) {
100
            return;
101
        }
102
103
        $_SESSION[$this->namespace] = $this->data;
104
    }
105
106
    /**
107
     * Check whether the session has already been started.
108
     *
109
     * @return bool
110
     *
111
     * @codeCoverageIgnore
112
     */
113
    private function sessionStarted() : bool
114
    {
115
        // For unit test we give controllable result.
116
        if (defined('PHPUNIT_WEBHEMI_TESTSUITE')) {
117
            return $this->namespace == 'TEST';
118
        }
119
120
        return session_status() === PHP_SESSION_ACTIVE;
121
    }
122
123
    /**
124
     * Starts a session.
125
     *
126
     * @param string $name
127
     * @param int    $timeOut
128
     * @param string $path
129
     * @param string $domain
130
     * @param bool   $secure
131
     * @param bool   $httpOnly
132
     * @return SessionManager
133
     */
134 1
    public function start(
135
        string $name,
136
        int $timeOut = 3600,
137
        string $path = '/',
138
        ?string $domain = null,
139
        bool $secure = false,
140
        bool $httpOnly = false
141
    ) : SessionManager {
142 1
        if ($this->sessionStarted()) {
143 1
            throw new RuntimeException('Cannot start session. Session is already started.', 1000);
144
        }
145
146
        // @codeCoverageIgnoreStart
147
        if (!defined('PHPUNIT_WEBHEMI_TESTSUITE')) {
148
            session_name($this->cookiePrefix.'-'.bin2hex($name.$this->sessionNameSalt));
149
            session_set_cookie_params($timeOut, $path, $domain, $secure, $httpOnly);
150
            session_start();
151
        }
152
        // @codeCoverageIgnoreEnd
153
154 1
        $this->read();
155
156 1
        return $this;
157
    }
158
159
    /**
160
     * Regenerates session identifier.
161
     *
162
     * @return SessionManager
163
     */
164 2
    public function regenerateId() : SessionManager
165
    {
166 2
        if (!$this->sessionStarted()) {
167 1
            throw new RuntimeException('Cannot regenerate session identifier. Session is not started yet.', 1001);
168
        }
169
170
        // first save data.
171 2
        $this->write();
172
173
        // @codeCoverageIgnoreStart
174
        if (!defined('PHPUNIT_WEBHEMI_TESTSUITE')) {
175
            if (!session_regenerate_id(true)) {
176
                throw new RuntimeException('Cannot regenerate session identifier. Unknown error.', 1002);
177
            }
178
        }
179
        // @codeCoverageIgnoreEnd
180
181 2
        return $this;
182
    }
183
184
    /**
185
     * Sets session data.
186
     *
187
     * @param string $name
188
     * @param mixed  $value
189
     * @param bool   $readOnly
190
     * @throws RuntimeException
191
     * @return SessionManager
192
     */
193 5
    public function set(string $name, $value, bool $readOnly = false) : SessionManager
194
    {
195 5
        if (!$this->sessionStarted()) {
196 1
            throw new RuntimeException('Cannot set session data. Session is not started yet.', 1005);
197
        }
198
199 5
        if (isset($this->readOnly[$name])) {
200 1
            throw new RuntimeException('Unable to overwrite data. Permission denied.', 1006);
201
        }
202
203 5
        if ($readOnly) {
204 2
            $this->readOnly[$name] = $name;
205
        }
206
207 5
        $this->data[$name] = $value;
208
209 5
        return $this;
210
    }
211
212
    /**
213
     * Gets session data.
214
     *
215
     * @param string $name
216
     * @throws RuntimeException
217
     * @return bool
218
     */
219 2
    public function has(string $name) : bool
220
    {
221 2
        if (!$this->sessionStarted()) {
222 1
            throw new RuntimeException('Cannot set session data. Session is not started yet.', 1009);
223
        }
224
225 2
        return isset($this->data[$name]);
226
    }
227
228
    /**
229
     * Gets session data.
230
     *
231
     * @param string $name
232
     * @param bool   $skipMissing
233
     * @throws RuntimeException
234
     * @return mixed
235
     */
236 4
    public function get(string $name, bool $skipMissing = true)
237
    {
238 4
        if (!$this->sessionStarted()) {
239 1
            throw new RuntimeException('Cannot set session data. Session is not started yet.', 1003);
240
        }
241
242 4
        if (isset($this->data[$name])) {
243 4
            return $this->data[$name];
244 3
        } elseif ($skipMissing) {
245 3
            return null;
246
        }
247
248 1
        throw new RuntimeException('Cannot retrieve session data. Data is not set', 1004);
249
    }
250
251
    /**
252
     * Deletes session data.
253
     *
254
     * @param string $name
255
     * @param bool   $forceDelete
256
     * @throws RuntimeException
257
     * @return SessionManager
258
     */
259 2
    public function delete(string $name, bool $forceDelete = false) : SessionManager
260
    {
261 2
        if (!$this->sessionStarted()) {
262 1
            throw new RuntimeException('Cannot delete session data. Session is not started.', 1007);
263
        }
264
265 2
        if (!$forceDelete && isset($this->readOnly[$name])) {
266 1
            throw new RuntimeException('Unable to delete data. Permission denied.', 1008);
267
        }
268
269
        // hide errors if data not exists.
270 2
        unset($this->readOnly[$name]);
271 2
        unset($this->data[$name]);
272
273 2
        return $this;
274
    }
275
276
    /**
277
     * Unlocks readOnly data.
278
     *
279
     * @param string $name
280
     * @return SessionManager
281
     */
282 1
    public function unlock(string $name) : SessionManager
283
    {
284 1
        if (isset($this->readOnly[$name])) {
285 1
            unset($this->readOnly[$name]);
286
        }
287
288 1
        return $this;
289
    }
290
291
    /**
292
     * Returns the internal storage.
293
     *
294
     * @return array
295
     */
296 1
    public function toArray() : array
297
    {
298 1
        return $this->data;
299
    }
300
}
301