• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codeigniter4 / CodeIgniter4 / 3755643596

pending completion
3755643596

push

github

GitHub
Merge pull request #7004 from kenjis/fix-db-session-tests

16207 of 18979 relevant lines covered (85.39%)

174.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.89
/system/Session/Handlers/DatabaseHandler.php
1
<?php
2

3
/**
4
 * This file is part of CodeIgniter 4 framework.
5
 *
6
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11

12
namespace CodeIgniter\Session\Handlers;
13

14
use CodeIgniter\Database\BaseBuilder;
15
use CodeIgniter\Database\BaseConnection;
16
use CodeIgniter\Session\Exceptions\SessionException;
17
use Config\App as AppConfig;
18
use Config\Database;
19
use ReturnTypeWillChange;
20

21
/**
22
 * Base database session handler
23
 *
24
 * Do not use this class. Use database specific handler class.
25
 */
26
class DatabaseHandler extends BaseHandler
27
{
28
    /**
29
     * The database group to use for storage.
30
     *
31
     * @var string
32
     */
33
    protected $DBGroup;
34

35
    /**
36
     * The name of the table to store session info.
37
     *
38
     * @var string
39
     */
40
    protected $table;
41

42
    /**
43
     * The DB Connection instance.
44
     *
45
     * @var BaseConnection
46
     */
47
    protected $db;
48

49
    /**
50
     * The database type
51
     *
52
     * @var string
53
     */
54
    protected $platform;
55

56
    /**
57
     * Row exists flag
58
     *
59
     * @var bool
60
     */
61
    protected $rowExists = false;
62

63
    /**
64
     * ID prefix for multiple session cookies
65
     */
66
    protected string $idPrefix;
67

68
    /**
69
     * @throws SessionException
70
     */
71
    public function __construct(AppConfig $config, string $ipAddress)
72
    {
73
        parent::__construct($config, $ipAddress);
12✔
74

75
        $this->table = $config->sessionSavePath;
12✔
76

77
        if (empty($this->table)) {
12✔
78
            throw SessionException::forMissingDatabaseTable();
×
79
        }
80

81
        $this->DBGroup = $config->sessionDBGroup ?? config(Database::class)->defaultGroup;
12✔
82

83
        $this->db = Database::connect($this->DBGroup);
12✔
84

85
        $this->platform = $this->db->getPlatform();
12✔
86

87
        // Add sessionCookieName for multiple session cookies.
88
        $this->idPrefix = $config->sessionCookieName . ':';
12✔
89
    }
90

91
    /**
92
     * Re-initialize existing session, or creates a new one.
93
     *
94
     * @param string $path The path where to store/retrieve the session
95
     * @param string $name The session name
96
     */
97
    public function open($path, $name): bool
98
    {
99
        if (empty($this->db->connID)) {
2✔
100
            $this->db->initialize();
×
101
        }
102

103
        return true;
2✔
104
    }
105

106
    /**
107
     * Reads the session data from the session storage, and returns the results.
108
     *
109
     * @param string $id The session ID
110
     *
111
     * @return false|string Returns an encoded string of the read data.
112
     *                      If nothing was read, it must return false.
113
     */
114
    #[ReturnTypeWillChange]
115
    public function read($id)
116
    {
117
        if ($this->lockSession($id) === false) {
4✔
118
            $this->fingerprint = md5('');
×
119

120
            return '';
×
121
        }
122

123
        if (! isset($this->sessionID)) {
4✔
124
            $this->sessionID = $id;
4✔
125
        }
126

127
        $builder = $this->db->table($this->table)->where('id', $this->idPrefix . $id);
4✔
128

129
        if ($this->matchIP) {
4✔
130
            $builder = $builder->where('ip_address', $this->ipAddress);
×
131
        }
132

133
        $this->setSelect($builder);
4✔
134

135
        $result = $builder->get()->getRow();
4✔
136

137
        if ($result === null) {
4✔
138
            // PHP7 will reuse the same SessionHandler object after
139
            // ID regeneration, so we need to explicitly set this to
140
            // FALSE instead of relying on the default ...
141
            $this->rowExists   = false;
2✔
142
            $this->fingerprint = md5('');
2✔
143

144
            return '';
2✔
145
        }
146

147
        $result = is_bool($result) ? '' : $this->decodeData($result->data);
2✔
148

149
        $this->fingerprint = md5($result);
2✔
150
        $this->rowExists   = true;
2✔
151

152
        return $result;
2✔
153
    }
154

155
    /**
156
     * Sets SELECT clause
157
     */
158
    protected function setSelect(BaseBuilder $builder)
159
    {
160
        $builder->select('data');
2✔
161
    }
162

163
    /**
164
     * Decodes column data
165
     *
166
     * @param mixed $data
167
     *
168
     * @return false|string
169
     */
170
    protected function decodeData($data)
171
    {
172
        return $data;
1✔
173
    }
174

175
    /**
176
     * Writes the session data to the session storage.
177
     *
178
     * @param string $id   The session ID
179
     * @param string $data The encoded session data
180
     */
181
    public function write($id, $data): bool
182
    {
183
        if ($this->lock === false) {
4✔
184
            return $this->fail();
×
185
        }
186

187
        if ($this->sessionID !== $id) {
4✔
188
            $this->rowExists = false;
2✔
189
            $this->sessionID = $id;
2✔
190
        }
191

192
        if ($this->rowExists === false) {
4✔
193
            $insertData = [
2✔
194
                'id'         => $this->idPrefix . $id,
2✔
195
                'ip_address' => $this->ipAddress,
2✔
196
                'data'       => $this->prepareData($data),
2✔
197
            ];
2✔
198

199
            if (! $this->db->table($this->table)->set('timestamp', 'now()', false)->insert($insertData)) {
2✔
200
                return $this->fail();
×
201
            }
202

203
            $this->fingerprint = md5($data);
2✔
204
            $this->rowExists   = true;
2✔
205

206
            return true;
2✔
207
        }
208

209
        $builder = $this->db->table($this->table)->where('id', $this->idPrefix . $id);
2✔
210

211
        if ($this->matchIP) {
2✔
212
            $builder = $builder->where('ip_address', $this->ipAddress);
×
213
        }
214

215
        $updateData = [];
2✔
216

217
        if ($this->fingerprint !== md5($data)) {
2✔
218
            $updateData['data'] = $this->prepareData($data);
2✔
219
        }
220

221
        if (! $builder->set('timestamp', 'now()', false)->update($updateData)) {
2✔
222
            return $this->fail();
×
223
        }
224

225
        $this->fingerprint = md5($data);
2✔
226

227
        return true;
2✔
228
    }
229

230
    /**
231
     * Prepare data to insert/update
232
     */
233
    protected function prepareData(string $data): string
234
    {
235
        return $data;
2✔
236
    }
237

238
    /**
239
     * Closes the current session.
240
     */
241
    public function close(): bool
242
    {
243
        return ($this->lock && ! $this->releaseLock()) ? $this->fail() : true;
×
244
    }
245

246
    /**
247
     * Destroys a session
248
     *
249
     * @param string $id The session ID being destroyed
250
     */
251
    public function destroy($id): bool
252
    {
253
        if ($this->lock) {
×
254
            $builder = $this->db->table($this->table)->where('id', $this->idPrefix . $id);
×
255

256
            if ($this->matchIP) {
×
257
                $builder = $builder->where('ip_address', $this->ipAddress);
×
258
            }
259

260
            if (! $builder->delete()) {
×
261
                return $this->fail();
×
262
            }
263
        }
264

265
        if ($this->close()) {
×
266
            $this->destroyCookie();
×
267

268
            return true;
×
269
        }
270

271
        return $this->fail();
×
272
    }
273

274
    /**
275
     * Cleans up expired sessions.
276
     *
277
     * @param int $max_lifetime Sessions that have not updated
278
     *                          for the last max_lifetime seconds will be removed.
279
     *
280
     * @return false|int Returns the number of deleted sessions on success, or false on failure.
281
     */
282
    #[ReturnTypeWillChange]
283
    public function gc($max_lifetime)
284
    {
285
        $separator = ' ';
1✔
286
        $interval  = implode($separator, ['', "{$max_lifetime} second", '']);
1✔
287

288
        return $this->db->table($this->table)->where(
1✔
289
            'timestamp <',
1✔
290
            "now() - INTERVAL {$interval}",
1✔
291
            false
1✔
292
        )->delete() ? 1 : $this->fail();
1✔
293
    }
294

295
    /**
296
     * Releases the lock, if any.
297
     */
298
    protected function releaseLock(): bool
299
    {
300
        if (! $this->lock) {
×
301
            return true;
×
302
        }
303

304
        // Unsupported DB? Let the parent handle the simple version.
305
        return parent::releaseLock();
×
306
    }
307
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc