Passed
Push — master ( 4b954d...b01812 )
by Gabor
03:38
created

SessionManager   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 250
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 32
lcom 1
cbo 1
dl 0
loc 250
ccs 52
cts 52
cp 1
rs 9.6
c 2
b 1
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 21 2
A __destruct() 0 10 2
A read() 0 6 2
A write() 0 8 2
A sessionStarted() 0 9 2
A start() 0 18 3
A regenerateId() 0 19 4
A get() 0 14 4
A set() 0 18 4
A delete() 0 16 4
A unlock() 0 8 2
A toArray() 0 4 1
1
<?php
2
/**
3
 * WebHemi.
4
 *
5
 * PHP version 5.6
6
 *
7
 * @copyright 2012 - 2016 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
namespace WebHemi\Application;
13
14
use RuntimeException;
15
use WebHemi\Config\ConfigInterface;
16
17
/**
18
 * Class SessionManager.
19
 */
20
class SessionManager
21
{
22
    /** @var string */
23
    private $namespace;
24
    /** @var string */
25
    private $cookiePrefix;
26
    /** @var string */
27
    private $sessionNameSalt;
28
    /** @var array */
29
    private $readOnly = [];
30
    /** @var array */
31
    private $data = [];
32
33
    /**
34
     * SessionManager constructor.
35
     *
36
     * @param ConfigInterface $sessionConfig
37
     */
38 8
    public function __construct(ConfigInterface $sessionConfig)
39
    {
40 8
        $config = $sessionConfig->toArray();
41
42 8
        $this->namespace = $config['namespace'];
43 8
        $this->cookiePrefix = $config['cookie_prefix'];
44 8
        $this->sessionNameSalt = $config['session_name_salt'];
45
46
        // @codeCoverageIgnoreStart
47
        if (!defined('PHPUNIT_WEBHEMI_TESTSUITE')) {
48
            ini_set('session.entropy_file', '/dev/urandom');
49
            ini_set('session.entropy_length', '16');
50
            ini_set('session.hash_function', $config['hash_function']);
51
            ini_set('session.use_only_cookies', (int)$config['use_only_cookies']);
52
            ini_set('session.use_cookies', (int)$config['use_cookies']);
53
            ini_set('session.use_trans_sid', (int)$config['use_trans_sid']);
54
            ini_set('session.cookie_httponly', (int)$config['cookie_http_only']);
55
            ini_set('session.save_path', $config['save_path']);
56
        }
57
        // @codeCoverageIgnoreEnd
58 8
    }
59
60
    /**
61
     * Saves data back to session.
62
     */
63 8
    public function __destruct()
64
    {
65 8
        $this->write();
66
67
        // @codeCoverageIgnoreStart
68
        if (defined('PHPUNIT_WEBHEMI_TESTSUITE')) {
69
            session_write_close();
70
        }
71
        // @codeCoverageIgnoreEnd
72 8
    }
73
74
    /**
75
     * Reads PHP Session array to class property.
76
     * @codeCoverageIgnore
77
     */
78
    private function read()
0 ignored issues
show
Coding Style introduced by
read uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
79
    {
80
        if (isset($_SESSION[$this->namespace])) {
81
            $this->data = $_SESSION[$this->namespace];
82
        }
83
    }
84
85
    /**
86
     * Writes class property to PHP Session array.
87
     * @codeCoverageIgnore
88
     */
89
    private function write()
0 ignored issues
show
Coding Style introduced by
write uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
90
    {
91
        if (defined('PHPUNIT_WEBHEMI_TESTSUITE')) {
92
            return;
93
        }
94
95
        $_SESSION[$this->namespace] = $this->data;
96
    }
97
98
    /**
99
     * Check whether the session has already been started.
100
     *
101
     * @return bool
102
     * @codeCoverageIgnore
103
     */
104
    private function sessionStarted()
105
    {
106
        // For unit test we give controllable result.
107
        if (defined('PHPUNIT_WEBHEMI_TESTSUITE')) {
108
            return $this->namespace == 'TEST';
109
        }
110
111
        return session_status() === PHP_SESSION_ACTIVE;
112
    }
113
114
    /**
115
     * Starts a session.
116
     *
117
     * @param string $name
118
     * @param int    $timeOut
119
     * @param string $path
120
     * @param string $domain
121
     * @param bool   $secure
122
     * @param bool   $httpOnly
123
     * @return SessionManager
124
     */
125 1
    public function start($name, $timeOut = 3600, $path = '/', $domain = null, $secure = false, $httpOnly = false)
126
    {
127 1
        if ($this->sessionStarted()) {
128 1
            throw new RuntimeException('Cannot start session. Session is already started.', 1000);
129
        }
130
131
        // @codeCoverageIgnoreStart
132
        if (!defined('PHPUNIT_WEBHEMI_TESTSUITE')) {
133
            session_name($this->cookiePrefix.'-'.bin2hex($name . $this->sessionNameSalt));
134
            session_set_cookie_params($timeOut, $path, $domain, $secure, $httpOnly);
135
            session_start();
136
        }
137
        // @codeCoverageIgnoreEnd
138
139 1
        $this->read();
140
141 1
        return $this;
142
    }
143
144
    /**
145
     * Regenerates session identifier.
146
     *
147
     * @return SessionManager
148
     */
149 2
    public function regenerateId()
150
    {
151 2
        if (!$this->sessionStarted()) {
152 1
            throw new RuntimeException('Cannot regenerate session identifier. Session is not started yet.', 1001);
153
        }
154
155
        // first save data.
156 2
        $this->write();
157
158
        // @codeCoverageIgnoreStart
159
        if (!defined('PHPUNIT_WEBHEMI_TESTSUITE')) {
160
            if (!session_regenerate_id(true)) {
161
                throw new RuntimeException('Cannot regenerate session identifier. Unknown error.', 1002);
162
            }
163
        }
164
        // @codeCoverageIgnoreEnd
165
166 2
        return $this;
167
    }
168
169
    /**
170
     * Gets session data.
171
     *
172
     * @param string $name
173
     * @param bool   $skipMissing
174
     * @throws RuntimeException
175
     * @return mixed
176
     */
177 4
    public function get($name, $skipMissing = true)
178
    {
179 4
        if (!$this->sessionStarted()) {
180 1
            throw new RuntimeException('Cannot set session data. Session is not started yet.', 1003);
181
        }
182
183 4
        if (isset($this->data[$name])) {
184 4
            return $this->data[$name];
185 3
        } elseif ($skipMissing) {
186 3
            return null;
187
        }
188
189 1
        throw new RuntimeException('Cannot retrieve session data. Data is not set', 1004);
190
    }
191
192
    /**
193
     * Sets session data.
194
     *
195
     * @param string $name
196
     * @param mixed  $value
197
     * @param bool   $readOnly
198
     * @throws RuntimeException
199
     * @return SessionManager
200
     */
201 5
    public function set($name, $value, $readOnly = false)
202
    {
203 5
        if (!$this->sessionStarted()) {
204 1
            throw new RuntimeException('Cannot set session data. Session is not started yet.', 1005);
205
        }
206
207 5
        if (isset($this->readOnly[$name])) {
208 1
            throw new RuntimeException('Unable to overwrite data. Permission denied.', 1006);
209
        }
210
211 5
        if ($readOnly) {
212 3
            $this->readOnly[$name] = $name;
213 3
        }
214
215 5
        $this->data[$name] = $value;
216
217 5
        return $this;
218
    }
219
220
    /**
221
     * Deletes session data.
222
     *
223
     * @param string $name
224
     * @param bool   $forceDelete
225
     * @throws RuntimeException
226
     * @return SessionManager
227
     */
228 2
    public function delete($name, $forceDelete = false)
229
    {
230 2
        if (!$this->sessionStarted()) {
231 1
            throw new RuntimeException('Cannot delete session data. Session is not started.', 1007);
232
        }
233
234 2
        if (!$forceDelete && isset($this->readOnly[$name])) {
235 1
            throw new RuntimeException('Unable to delete data. Permission denied.', 1008);
236
        }
237
238
        // hide errors if data not exists.
239 2
        unset($this->readOnly[$name]);
240 2
        unset($this->data[$name]);
241
242 2
        return $this;
243
    }
244
245
    /**
246
     * Unlocks readOnly data.
247
     *
248
     * @param string $name
249
     * @return SessionManager
250
     */
251 1
    public function unlock($name)
252
    {
253 1
        if (isset($this->readOnly[$name])) {
254 1
            unset($this->readOnly[$name]);
255 1
        }
256
257 1
        return $this;
258
    }
259
260
    /**
261
     * Returns the internal storage.
262
     *
263
     * @return array
264
     */
265 1
    public function toArray()
266
    {
267 1
        return $this->data;
268
    }
269
}
270