ci4

Форк
0
/
BasePreparedQuery.php 
262 строки · 6.9 Кб
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 BadMethodCallException;
18
use CodeIgniter\Database\Exceptions\DatabaseException;
19
use CodeIgniter\Events\Events;
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         object|resource|null
35
     * @phpstan-var TStatement|null
36
     */
37
    protected $statement;
38

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

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

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

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

69
    public function __construct(BaseConnection $db)
70
    {
71
        $this->db = $db;
72
    }
73

74
    /**
75
     * Prepares the query against the database, and saves the connection
76
     * info necessary to execute the query later.
77
     *
78
     * NOTE: This version is based on SQL code. Child classes should
79
     * override this method.
80
     *
81
     * @return $this
82
     */
83
    public function prepare(string $sql, array $options = [], string $queryClass = Query::class)
84
    {
85
        // We only supports positional placeholders (?)
86
        // in order to work with the execute method below, so we
87
        // need to replace our named placeholders (:name)
88
        $sql = preg_replace('/:[^\s,)]+/', '?', $sql);
89

90
        /** @var Query $query */
91
        $query = new $queryClass($this->db);
92

93
        $query->setQuery($sql);
94

95
        if (! empty($this->db->swapPre) && ! empty($this->db->DBPrefix)) {
96
            $query->swapPrefix($this->db->DBPrefix, $this->db->swapPre);
97
        }
98

99
        $this->query = $query;
100

101
        return $this->_prepare($query->getOriginalQuery(), $options);
102
    }
103

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

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

125
        try {
126
            $exception = null;
127
            $result    = $this->_execute($data);
128
        } catch (ArgumentCountError|ErrorException $exception) {
129
            $result = false;
130
        }
131

132
        // Update our query object
133
        $query = clone $this->query;
134
        $query->setBinds($data);
135

136
        if ($result === false) {
137
            $query->setDuration($startTime, $startTime);
138

139
            // This will trigger a rollback if transactions are being used
140
            if ($this->db->transDepth !== 0) {
141
                $this->db->transStatus = false;
142
            }
143

144
            if ($this->db->DBDebug) {
145
                // We call this function in order to roll-back queries
146
                // if transactions are enabled. If we don't call this here
147
                // the error message will trigger an exit, causing the
148
                // transactions to remain in limbo.
149
                while ($this->db->transDepth !== 0) {
150
                    $transDepth = $this->db->transDepth;
151
                    $this->db->transComplete();
152

153
                    if ($transDepth === $this->db->transDepth) {
154
                        log_message('error', 'Database: Failure during an automated transaction commit/rollback!');
155
                        break;
156
                    }
157
                }
158

159
                // Let others do something with this query.
160
                Events::trigger('DBQuery', $query);
161

162
                if ($exception !== null) {
163
                    throw new DatabaseException($exception->getMessage(), $exception->getCode(), $exception);
164
                }
165

166
                return false;
167
            }
168

169
            // Let others do something with this query.
170
            Events::trigger('DBQuery', $query);
171

172
            return false;
173
        }
174

175
        $query->setDuration($startTime);
176

177
        // Let others do something with this query
178
        Events::trigger('DBQuery', $query);
179

180
        if ($this->db->isWriteType((string) $query)) {
181
            return true;
182
        }
183

184
        // Return a result object
185
        $resultClass = str_replace('PreparedQuery', 'Result', static::class);
186

187
        $resultID = $this->_getResult();
188

189
        return new $resultClass($this->db->connID, $resultID);
190
    }
191

192
    /**
193
     * The database dependant version of the execute method.
194
     */
195
    abstract public function _execute(array $data): bool;
196

197
    /**
198
     * Returns the result object for the prepared query.
199
     *
200
     * @return object|resource|null
201
     */
202
    abstract public function _getResult();
203

204
    /**
205
     * Explicitly closes the prepared statement.
206
     *
207
     * @throws BadMethodCallException
208
     */
209
    public function close(): bool
210
    {
211
        if (! isset($this->statement)) {
212
            throw new BadMethodCallException('Cannot call close on a non-existing prepared statement.');
213
        }
214

215
        try {
216
            return $this->_close();
217
        } finally {
218
            $this->statement = null;
219
        }
220
    }
221

222
    /**
223
     * The database-dependent version of the close method.
224
     */
225
    abstract protected function _close(): bool;
226

227
    /**
228
     * Returns the SQL that has been prepared.
229
     */
230
    public function getQueryString(): string
231
    {
232
        if (! $this->query instanceof QueryInterface) {
233
            throw new BadMethodCallException('Cannot call getQueryString on a prepared query until after the query has been prepared.');
234
        }
235

236
        return $this->query->getQuery();
237
    }
238

239
    /**
240
     * A helper to determine if any error exists.
241
     */
242
    public function hasError(): bool
243
    {
244
        return ! empty($this->errorString);
245
    }
246

247
    /**
248
     * Returns the error code created while executing this statement.
249
     */
250
    public function getErrorCode(): int
251
    {
252
        return $this->errorCode;
253
    }
254

255
    /**
256
     * Returns the error message created while executing this statement.
257
     */
258
    public function getErrorMessage(): string
259
    {
260
        return $this->errorString;
261
    }
262
}
263

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.