Passed
Branch master (30ae21)
by Gabor
03:15
created

SessionManager::set()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
c 0
b 0
f 0
rs 9.2
cc 4
eloc 9
nc 4
nop 3
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
16
/**
17
 * Class SessionManager.
18
 *
19
 * @codeCoverageIgnore - Not testing session (yet).
20
 */
21
class SessionManager
22
{
23
    /** @var string */
24
    private $namespace = '_webhemi';
25
    /** @var string */
26
    private $cookiePrefix = 'atsn';
27
    /** @var string */
28
    private $sessionNameSalt = 'WebHemi';
29
    /** @var array */
30
    private $readonly = [];
31
    /** @var array */
32
    private $data = [];
33
34
    /**
35
     * SessionManager constructor.
36
     */
37
    public function __construct()
38
    {
39
        ini_set('session.entropy_file', '/dev/urandom');
40
        ini_set('session.entropy_length', '16');
41
        ini_set('session.hash_function', 'sha256');
42
        ini_set('session.use_only_cookies', '1');
43
        ini_set('session.use_cookies', '1');
44
        ini_set('session.use_trans_sid', '0');
45
        ini_set('session.cookie_httponly', '1');
46
        ini_set('session.save_path', __DIR__.'/../../../data/session/');
47
    }
48
49
    /**
50
     * Saves data back to session.
51
     */
52
    public function __destruct()
0 ignored issues
show
Coding Style introduced by
__destruct 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...
53
    {
54
        $_SESSION[$this->namespace] = $this->data;
55
        session_write_close();
56
    }
57
58
    /**
59
     * Check whether the session has already been started.
60
     *
61
     * @return bool
62
     */
63
    private function sessionStarted()
64
    {
65
        return session_status() === PHP_SESSION_ACTIVE;
66
    }
67
68
    /**
69
     * Starts a session.
70
     *
71
     * @param string $name
72
     * @param int    $timeOut
73
     * @param string $path
74
     * @param string $domain
75
     * @param bool   $secure
76
     * @param bool   $httpOnly
77
     * @return SessionManager
78
     */
79
    public function start($name, $timeOut = 3600, $path = '/', $domain = null, $secure = false, $httpOnly = false)
0 ignored issues
show
Coding Style introduced by
start 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...
80
    {
81
        if ($this->sessionStarted()) {
82
            throw new RuntimeException('Cannot start session. Session is already started.', 1000);
83
        }
84
85
        session_name($this->cookiePrefix.'-'.bin2hex($name . $this->sessionNameSalt));
86
        session_set_cookie_params($timeOut, $path, $domain, $secure, $httpOnly);
87
        session_start();
88
89
        if (isset($_SESSION[$this->namespace])) {
90
            $this->data = $_SESSION[$this->namespace];
91
        }
92
93
        return $this;
94
    }
95
96
    /**
97
     * Regenerates session identifier.
98
     *
99
     * @return SessionManager
100
     */
101
    public function regenerateId()
0 ignored issues
show
Coding Style introduced by
regenerateId 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...
102
    {
103
        if (!$this->sessionStarted()) {
104
            throw new RuntimeException('Cannot regenerate session identifier. Session is not started yet.', 1001);
105
        }
106
107
        // first save data.
108
        $_SESSION[$this->namespace] = $this->data;
109
110
        if (!session_regenerate_id(true)) {
111
            throw new RuntimeException('Cannot regenerate session identifier. Unknown error.', 1002);
112
        }
113
114
        return $this;
115
    }
116
117
    /**
118
     * Sets session data.
119
     *
120
     * @param string $name
121
     * @param mixed  $value
122
     * @param bool   $readonly
123
     * @throws RuntimeException
124
     * @return SessionManager
125
     */
126
    public function set($name, $value, $readonly = false)
127
    {
128
        if (!$this->sessionStarted()) {
129
            throw new RuntimeException('Cannot set session data. Session is not started yet.', 1003);
130
        }
131
132
        if (isset($this->readonly[$name])) {
133
            throw new RuntimeException('Unable to overwrite data. Permission denied.', 1004);
134
        }
135
136
        if ($readonly) {
137
            $this->readonly[$name] = $name;
138
        }
139
140
        $this->data[$name] = $value;
141
142
        return $this;
143
    }
144
145
    /**
146
     * Deletes session data.
147
     *
148
     * @param string $name
149
     * @param bool   $forceDelete
150
     * @throws RuntimeException
151
     * @return SessionManager
152
     */
153
    public function delete($name, $forceDelete = false)
154
    {
155
        if (!$this->sessionStarted()) {
156
            throw new RuntimeException('Cannot delete session data. Session is not started.', 1005);
157
        }
158
159
        if (!$forceDelete && isset($this->readonly[$name])) {
160
            throw new RuntimeException('Unable to delete data. Permission denied.', 1006);
161
        }
162
163
        // hide errors if data not exists.
164
        unset($this->readonly[$name]);
165
        unset($this->data[$name]);
166
167
        return $this;
168
    }
169
170
    /**
171
     * Unlocks readonly data.
172
     *
173
     * @param string $name
174
     * @return SessionManager
175
     */
176
    public function unlock($name)
177
    {
178
        if (isset($this->readonly[$name])) {
179
            unset($this->readonly[$name]);
180
        }
181
182
        return $this;
183
    }
184
185
    /**
186
     * Returns the internal storage.
187
     *
188
     * @return array
189
     */
190
    public function toArray()
191
    {
192
        return $this->data;
193
    }
194
}
195