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

codeigniter4 / CodeIgniter4 / 21568681844

01 Feb 2026 07:16PM UTC coverage: 85.41% (+1.0%) from 84.387%
21568681844

push

github

web-flow
Merge pull request #9916 from codeigniter4/4.7

4.7.0 Merge code

1603 of 1888 new or added lines in 101 files covered. (84.9%)

31 existing lines in 11 files now uncovered.

22163 of 25949 relevant lines covered (85.41%)

205.52 hits per line

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

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

3
declare(strict_types=1);
4

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

14
namespace CodeIgniter\Session\Handlers;
15

16
use CodeIgniter\Database\BaseBuilder;
17
use CodeIgniter\Database\BaseConnection;
18
use CodeIgniter\Session\Exceptions\SessionException;
19
use Config\Database;
20
use Config\Session as SessionConfig;
21

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

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

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

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

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

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

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

76
        $this->table = $this->savePath;
12✔
77

78
        if ($this->table === '') {
12✔
UNCOV
79
            throw SessionException::forMissingDatabaseTable();
×
80
        }
81

82
        // Store Session configurations
83
        $this->DBGroup = $config->DBGroup ?? config(Database::class)->defaultGroup;
12✔
84
        // Add session cookie name for multiple session cookies.
85
        $this->idPrefix = $config->cookieName . ':';
12✔
86
        $this->db       = Database::connect($this->DBGroup);
12✔
87
        $this->platform = $this->db->getPlatform();
12✔
88
    }
89

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

102
        return true;
2✔
103
    }
104

105
    /**
106
     * Reads the session data from the session storage, and returns the results.
107
     *
108
     * @param string $id The session ID
109
     */
110
    public function read($id): false|string
111
    {
112
        if ($this->lockSession($id) === false) {
4✔
113
            $this->fingerprint = md5('');
×
114

115
            return '';
×
116
        }
117

118
        if (! isset($this->sessionID)) {
4✔
119
            $this->sessionID = $id;
4✔
120
        }
121

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

124
        if ($this->matchIP) {
4✔
125
            $builder = $builder->where('ip_address', $this->ipAddress);
×
126
        }
127

128
        $this->setSelect($builder);
4✔
129

130
        $result = $builder->get()->getRow();
4✔
131

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

139
            return '';
2✔
140
        }
141

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

144
        $this->fingerprint = md5($result);
2✔
145
        $this->rowExists   = true;
2✔
146

147
        return $result;
2✔
148
    }
149

150
    /**
151
     * Sets SELECT clause.
152
     *
153
     * @return void
154
     */
155
    protected function setSelect(BaseBuilder $builder)
156
    {
157
        $builder->select('data');
2✔
158
    }
159

160
    /**
161
     * Decodes column data.
162
     *
163
     * @param string $data
164
     *
165
     * @return false|string
166
     */
167
    protected function decodeData($data)
168
    {
169
        return $data;
1✔
170
    }
171

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

184
        if ($this->sessionID !== $id) {
4✔
185
            $this->rowExists = false;
2✔
186
            $this->sessionID = $id;
2✔
187
        }
188

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

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

200
            $this->fingerprint = md5($data);
2✔
201
            $this->rowExists   = true;
2✔
202

203
            return true;
2✔
204
        }
205

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

208
        if ($this->matchIP) {
2✔
209
            $builder = $builder->where('ip_address', $this->ipAddress);
×
210
        }
211

212
        $updateData = [];
2✔
213

214
        if ($this->fingerprint !== md5($data)) {
2✔
215
            $updateData['data'] = $this->prepareData($data);
2✔
216
        }
217

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

222
        $this->fingerprint = md5($data);
2✔
223

224
        return true;
2✔
225
    }
226

227
    /**
228
     * Prepare data to insert/update.
229
     */
230
    protected function prepareData(string $data): string
231
    {
232
        return $data;
2✔
233
    }
234

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

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

253
            if ($this->matchIP) {
×
254
                $builder = $builder->where('ip_address', $this->ipAddress);
×
255
            }
256

257
            if (! $builder->delete()) {
×
258
                return $this->fail();
×
259
            }
260
        }
261

262
        if ($this->close()) {
×
263
            $this->destroyCookie();
×
264

265
            return true;
×
266
        }
267

268
        return $this->fail();
×
269
    }
270

271
    /**
272
     * Cleans up expired sessions.
273
     *
274
     * @param int $max_lifetime Sessions that have not updated
275
     *                          for the last max_lifetime seconds will be removed.
276
     *
277
     * @return false|int Returns the number of deleted sessions on success, or false on failure.
278
     */
279
    public function gc($max_lifetime): false|int
280
    {
281
        return $this->db->table($this->table)->where(
1✔
282
            'timestamp <',
1✔
283
            "now() - INTERVAL {$max_lifetime} second",
1✔
284
            false,
1✔
285
        )->delete() ? 1 : $this->fail();
1✔
286
    }
287

288
    /**
289
     * Releases the lock, if any.
290
     */
291
    protected function releaseLock(): bool
292
    {
293
        if (! $this->lock) {
×
294
            return true;
×
295
        }
296

297
        // Unsupported DB? Let the parent handle the simple version.
298
        return parent::releaseLock();
×
299
    }
300
}
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