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

codeigniter4 / CodeIgniter4 / 31241405865

08 Aug 2026 05:18AM UTC coverage: 89.638% (+0.007%) from 89.631%
31241405865

Pull #10439

github

web-flow
Merge 993fa11b4 into ba6c22446
Pull Request #10439: refactor: fix uses of `empty()` calls

185 of 203 new or added lines in 61 files covered. (91.13%)

1 existing line in 1 file now uncovered.

22561 of 25169 relevant lines covered (89.64%)

214.52 hits per line

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

77.55
/system/Database/BasePreparedQuery.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\Database;
15

16
use ArgumentCountError;
17
use CodeIgniter\Database\Exceptions\DatabaseException;
18
use CodeIgniter\Events\Events;
19
use CodeIgniter\Exceptions\BadMethodCallException;
20
use ErrorException;
21

22
/**
23
 * @template TConnection
24
 * @template TStatement
25
 * @template TResult
26
 *
27
 * @implements PreparedQueryInterface<TConnection, TStatement, TResult>
28
 */
29
abstract class BasePreparedQuery implements PreparedQueryInterface
30
{
31
    /**
32
     * The prepared statement itself.
33
     *
34
     * @var TStatement|null
35
     */
36
    protected $statement;
37

38
    /**
39
     * The error code, if any.
40
     *
41
     * @var int
42
     */
43
    protected $errorCode;
44

45
    /**
46
     * The error message, if any.
47
     *
48
     * @var string
49
     */
50
    protected $errorString = '';
51

52
    /**
53
     * Holds the prepared query object
54
     * that is cloned during execute.
55
     *
56
     * @var Query
57
     */
58
    protected $query;
59

60
    /**
61
     * A reference to the db connection to use.
62
     *
63
     * @var BaseConnection<TConnection, TResult>
64
     */
65
    protected $db;
66

67
    public function __construct(BaseConnection $db)
68
    {
69
        $this->db = $db;
19✔
70
    }
71

72
    /**
73
     * Prepares the query against the database, and saves the connection
74
     * info necessary to execute the query later.
75
     *
76
     * NOTE: This version is based on SQL code. Child classes should
77
     * override this method.
78
     *
79
     * @return $this
80
     */
81
    public function prepare(string $sql, array $options = [], string $queryClass = Query::class)
82
    {
83
        // We only support positional placeholders (?), so convert
84
        // named placeholders (:name or :name:) while leaving dialect
85
        // syntax like PostgreSQL casts (::type) untouched.
86
        $sql = preg_replace('/(?<!:):([a-zA-Z_]\w*):?(?!:)/', '?', $sql);
19✔
87

88
        /** @var Query $query */
89
        $query = new $queryClass($this->db);
19✔
90

91
        $query->setQuery($sql);
19✔
92

93
        if ($this->db->swapPre !== '' && $this->db->DBPrefix !== '') {
19✔
94
            $query->swapPrefix($this->db->DBPrefix, $this->db->swapPre);
×
95
        }
96

97
        $this->query = $query;
19✔
98

99
        return $this->_prepare($query->getOriginalQuery(), $options);
19✔
100
    }
101

102
    /**
103
     * The database-dependent portion of the prepare statement.
104
     *
105
     * @return $this
106
     */
107
    abstract public function _prepare(string $sql, array $options = []);
108

109
    /**
110
     * Takes a new set of data and runs it against the currently
111
     * prepared query. Upon success, will return a Results object.
112
     *
113
     * @return bool|ResultInterface<TConnection, TResult>
114
     *
115
     * @throws DatabaseException
116
     */
117
    public function execute(...$data)
118
    {
119
        // Execute the Query.
120
        $startTime = microtime(true);
13✔
121

122
        try {
123
            $exception = null;
13✔
124
            $result    = $this->_execute($data);
13✔
125
        } catch (ArgumentCountError|ErrorException $exception) {
5✔
126
            $result = false;
4✔
127
        }
128

129
        // Update our query object
130
        $query = clone $this->query;
12✔
131
        $query->setBinds($data);
12✔
132

133
        if ($result === false) {
12✔
134
            $query->setDuration($startTime, $startTime);
4✔
135

136
            // This will trigger a rollback if transactions are being used
137
            $this->db->handleTransStatus();
4✔
138

139
            if ($this->db->DBDebug) {
4✔
140
                // We call this function in order to roll-back queries
141
                // if transactions are enabled. If we don't call this here
142
                // the error message will trigger an exit, causing the
143
                // transactions to remain in limbo.
144
                while ($this->db->transDepth !== 0) {
1✔
145
                    $transDepth = $this->db->transDepth;
×
146
                    $this->db->transComplete();
×
147

148
                    if ($transDepth === $this->db->transDepth) {
×
149
                        log_message('error', 'Database: Failure during an automated transaction commit/rollback!');
×
150
                        break;
×
151
                    }
152
                }
153

154
                // Let others do something with this query.
155
                Events::trigger('DBQuery', $query);
1✔
156

157
                if ($exception !== null) {
1✔
158
                    throw new DatabaseException($exception->getMessage(), $exception->getCode(), $exception);
1✔
159
                }
160

161
                return false;
×
162
            }
163

164
            // Let others do something with this query.
165
            Events::trigger('DBQuery', $query);
3✔
166

167
            return false;
3✔
168
        }
169

170
        $query->setDuration($startTime);
11✔
171

172
        // Let others do something with this query
173
        Events::trigger('DBQuery', $query);
11✔
174

175
        if ($this->db->isWriteType((string) $query)) {
11✔
176
            return true;
7✔
177
        }
178

179
        // Return a result object
180
        $resultClass = str_replace('PreparedQuery', 'Result', static::class);
4✔
181

182
        $resultID = $this->_getResult();
4✔
183

184
        return new $resultClass($this->db->connID, $resultID);
4✔
185
    }
186

187
    /**
188
     * The database dependant version of the execute method.
189
     */
190
    abstract public function _execute(array $data): bool;
191

192
    /**
193
     * Returns the result object for the prepared query.
194
     *
195
     * @return false|object|resource|null
196
     */
197
    abstract public function _getResult();
198

199
    /**
200
     * Explicitly closes the prepared statement.
201
     *
202
     * @throws BadMethodCallException
203
     */
204
    public function close(): bool
205
    {
206
        if (! isset($this->statement)) {
15✔
207
            throw new BadMethodCallException('Cannot call close on a non-existing prepared statement.');
2✔
208
        }
209

210
        try {
211
            return $this->_close();
15✔
212
        } finally {
213
            $this->statement = null;
15✔
214
        }
215
    }
216

217
    /**
218
     * The database-dependent version of the close method.
219
     */
220
    abstract protected function _close(): bool;
221

222
    /**
223
     * Returns the SQL that has been prepared.
224
     */
225
    public function getQueryString(): string
226
    {
227
        if (! $this->query instanceof QueryInterface) {
15✔
228
            throw new BadMethodCallException('Cannot call getQueryString on a prepared query until after the query has been prepared.');
×
229
        }
230

231
        return $this->query->getQuery();
15✔
232
    }
233

234
    /**
235
     * A helper to determine if any error exists.
236
     */
237
    public function hasError(): bool
238
    {
NEW
239
        return $this->errorString !== '';
×
240
    }
241

242
    /**
243
     * Returns the error code created while executing this statement.
244
     */
245
    public function getErrorCode(): int
246
    {
247
        return $this->errorCode;
×
248
    }
249

250
    /**
251
     * Returns the error message created while executing this statement.
252
     */
253
    public function getErrorMessage(): string
254
    {
255
        return $this->errorString;
×
256
    }
257

258
    /**
259
     * Whether the input contain binary data.
260
     */
261
    protected function isBinary(string $input): bool
262
    {
263
        return mb_detect_encoding($input, 'UTF-8', true) === false;
12✔
264
    }
265
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc