Completed
Push — master ( c7757e...39cb21 )
by Luís
16s
created

Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php (1 issue)

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Driver\Mysqli;
21
22
use Doctrine\DBAL\Driver\Statement;
23
use Doctrine\DBAL\Driver\StatementIterator;
24
use PDO;
25
26
/**
27
 * @author Kim Hemsø Rasmussen <[email protected]>
28
 */
29
class MysqliStatement implements \IteratorAggregate, Statement
30
{
31
    /**
32
     * @var array
33
     */
34
    protected static $_paramTypeMap = [
35
        PDO::PARAM_STR => 's',
36
        PDO::PARAM_BOOL => 'i',
37
        PDO::PARAM_NULL => 's',
38
        PDO::PARAM_INT => 'i',
39
        PDO::PARAM_LOB => 's' // TODO Support LOB bigger then max package size.
40
    ];
41
42
    /**
43
     * @var \mysqli
44
     */
45
    protected $_conn;
46
47
    /**
48
     * @var \mysqli_stmt
49
     */
50
    protected $_stmt;
51
52
    /**
53
     * @var null|boolean|array
54
     */
55
    protected $_columnNames;
56
57
    /**
58
     * @var null|array
59
     */
60
    protected $_rowBindedValues;
61
62
    /**
63
     * @var array
64
     */
65
    protected $_bindedValues;
66
67
    /**
68
     * @var string
69
     */
70
    protected $types;
71
72
    /**
73
     * Contains ref values for bindValue().
74
     *
75
     * @var array
76
     */
77
    protected $_values = [];
78
79
    /**
80
     * @var integer
81
     */
82
    protected $_defaultFetchMode = PDO::FETCH_BOTH;
83
84
    /**
85
     * Indicates whether the statement is in the state when fetching results is possible
86
     *
87
     * @var bool
88
     */
89
    private $result = false;
90
91
    /**
92
     * @param \mysqli $conn
93
     * @param string  $prepareString
94
     *
95
     * @throws \Doctrine\DBAL\Driver\Mysqli\MysqliException
96
     */
97
    public function __construct(\mysqli $conn, $prepareString)
98
    {
99
        $this->_conn = $conn;
100
        $this->_stmt = $conn->prepare($prepareString);
101 View Code Duplication
        if (false === $this->_stmt) {
102
            throw new MysqliException($this->_conn->error, $this->_conn->sqlstate, $this->_conn->errno);
103
        }
104
105
        $paramCount = $this->_stmt->param_count;
106
        if (0 < $paramCount) {
107
            $this->types = str_repeat('s', $paramCount);
108
            $this->_bindedValues = array_fill(1, $paramCount, null);
109
        }
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115
    public function bindParam($column, &$variable, $type = null, $length = null)
116
    {
117 View Code Duplication
        if (null === $type) {
118
            $type = 's';
119
        } else {
120
            if (isset(self::$_paramTypeMap[$type])) {
121
                $type = self::$_paramTypeMap[$type];
122
            } else {
123
                throw new MysqliException("Unknown type: '{$type}'");
124
            }
125
        }
126
127
        $this->_bindedValues[$column] =& $variable;
128
        $this->types[$column - 1] = $type;
129
130
        return true;
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136
    public function bindValue($param, $value, $type = null)
137
    {
138 View Code Duplication
        if (null === $type) {
139
            $type = 's';
140
        } else {
141
            if (isset(self::$_paramTypeMap[$type])) {
142
                $type = self::$_paramTypeMap[$type];
143
            } else {
144
                throw new MysqliException("Unknown type: '{$type}'");
145
            }
146
        }
147
148
        $this->_values[$param] = $value;
149
        $this->_bindedValues[$param] =& $this->_values[$param];
150
        $this->types[$param - 1] = $type;
151
152
        return true;
153
    }
154
155
    /**
156
     * {@inheritdoc}
157
     */
158
    public function execute($params = null)
159
    {
160
        if (null !== $this->_bindedValues) {
161
            if (null !== $params) {
162
                if ( ! $this->_bindValues($params)) {
163
                    throw new MysqliException($this->_stmt->error, $this->_stmt->errno);
164
                }
165
            } else {
166
                if (!call_user_func_array([$this->_stmt, 'bind_param'], [$this->types] + $this->_bindedValues)) {
167
                    throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno);
168
                }
169
            }
170
        }
171
172 View Code Duplication
        if ( ! $this->_stmt->execute()) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
173
            throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno);
174
        }
175
176
        if (null === $this->_columnNames) {
177
            $meta = $this->_stmt->result_metadata();
178
            if (false !== $meta) {
179
                $columnNames = [];
180
                foreach ($meta->fetch_fields() as $col) {
181
                    $columnNames[] = $col->name;
182
                }
183
                $meta->free();
184
185
                $this->_columnNames = $columnNames;
186
            } else {
187
                $this->_columnNames = false;
188
            }
189
        }
190
191
        if (false !== $this->_columnNames) {
192
            // Store result of every execution which has it. Otherwise it will be impossible
193
            // to execute a new statement in case if the previous one has non-fetched rows
194
            // @link http://dev.mysql.com/doc/refman/5.7/en/commands-out-of-sync.html
195
            $this->_stmt->store_result();
196
197
            // Bind row values _after_ storing the result. Otherwise, if mysqli is compiled with libmysql,
198
            // it will have to allocate as much memory as it may be needed for the given column type
199
            // (e.g. for a LONGBLOB field it's 4 gigabytes)
200
            // @link https://bugs.php.net/bug.php?id=51386#1270673122
201
            //
202
            // Make sure that the values are bound after each execution. Otherwise, if closeCursor() has been
203
            // previously called on the statement, the values are unbound making the statement unusable.
204
            //
205
            // It's also important that row values are bound after _each_ call to store_result(). Otherwise,
206
            // if mysqli is compiled with libmysql, subsequently fetched string values will get truncated
207
            // to the length of the ones fetched during the previous execution.
208
            $this->_rowBindedValues = array_fill(0, count($this->_columnNames), null);
209
210
            $refs = [];
211
            foreach ($this->_rowBindedValues as $key => &$value) {
212
                $refs[$key] =& $value;
213
            }
214
215
            if (!call_user_func_array([$this->_stmt, 'bind_result'], $refs)) {
216
                throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno);
217
            }
218
        }
219
220
        $this->result = true;
221
222
        return true;
223
    }
224
225
    /**
226
     * Binds a array of values to bound parameters.
227
     *
228
     * @param array $values
229
     *
230
     * @return boolean
231
     */
232
    private function _bindValues($values)
233
    {
234
        $params = [];
235
        $types = str_repeat('s', count($values));
236
        $params[0] = $types;
237
238
        foreach ($values as &$v) {
239
            $params[] =& $v;
240
        }
241
242
        return call_user_func_array([$this->_stmt, 'bind_param'], $params);
243
    }
244
245
    /**
246
     * @return boolean|array
247
     */
248
    private function _fetch()
249
    {
250
        $ret = $this->_stmt->fetch();
251
252
        if (true === $ret) {
253
            $values = [];
254
            foreach ($this->_rowBindedValues as $v) {
255
                $values[] = $v;
256
            }
257
258
            return $values;
259
        }
260
261
        return $ret;
262
    }
263
264
    /**
265
     * {@inheritdoc}
266
     */
267
    public function fetch($fetchMode = null, $cursorOrientation = \PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
268
    {
269
        // do not try fetching from the statement if it's not expected to contain result
270
        // in order to prevent exceptional situation
271
        if (!$this->result) {
272
            return false;
273
        }
274
275
        $values = $this->_fetch();
276
        if (null === $values) {
277
            return false;
278
        }
279
280 View Code Duplication
        if (false === $values) {
281
            throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno);
282
        }
283
284
        $fetchMode = $fetchMode ?: $this->_defaultFetchMode;
285
286
        switch ($fetchMode) {
287
            case PDO::FETCH_NUM:
288
                return $values;
289
290
            case PDO::FETCH_ASSOC:
291
                return array_combine($this->_columnNames, $values);
292
293
            case PDO::FETCH_BOTH:
294
                $ret = array_combine($this->_columnNames, $values);
295
                $ret += $values;
296
297
                return $ret;
298
299
            case PDO::FETCH_OBJ:
300
                $assoc = array_combine($this->_columnNames, $values);
301
                $ret = new \stdClass();
302
303
                foreach ($assoc as $column => $value) {
304
                    $ret->$column = $value;
305
                }
306
307
                return $ret;
308
309
            default:
310
                throw new MysqliException("Unknown fetch type '{$fetchMode}'");
311
        }
312
    }
313
314
    /**
315
     * {@inheritdoc}
316
     */
317
    public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
318
    {
319
        $fetchMode = $fetchMode ?: $this->_defaultFetchMode;
320
321
        $rows = [];
322
        if (PDO::FETCH_COLUMN == $fetchMode) {
323
            while (($row = $this->fetchColumn()) !== false) {
324
                $rows[] = $row;
325
            }
326
        } else {
327
            while (($row = $this->fetch($fetchMode)) !== false) {
328
                $rows[] = $row;
329
            }
330
        }
331
332
        return $rows;
333
    }
334
335
    /**
336
     * {@inheritdoc}
337
     */
338 View Code Duplication
    public function fetchColumn($columnIndex = 0)
339
    {
340
        $row = $this->fetch(PDO::FETCH_NUM);
341
        if (false === $row) {
342
            return false;
343
        }
344
345
        return isset($row[$columnIndex]) ? $row[$columnIndex] : null;
346
    }
347
348
    /**
349
     * {@inheritdoc}
350
     */
351
    public function errorCode()
352
    {
353
        return $this->_stmt->errno;
354
    }
355
356
    /**
357
     * {@inheritdoc}
358
     */
359
    public function errorInfo()
360
    {
361
        return $this->_stmt->error;
362
    }
363
364
    /**
365
     * {@inheritdoc}
366
     */
367
    public function closeCursor()
368
    {
369
        $this->_stmt->free_result();
370
        $this->result = false;
371
372
        return true;
373
    }
374
375
    /**
376
     * {@inheritdoc}
377
     */
378
    public function rowCount()
379
    {
380
        if (false === $this->_columnNames) {
381
            return $this->_stmt->affected_rows;
382
        }
383
384
        return $this->_stmt->num_rows;
385
    }
386
387
    /**
388
     * {@inheritdoc}
389
     */
390
    public function columnCount()
391
    {
392
        return $this->_stmt->field_count;
393
    }
394
395
    /**
396
     * {@inheritdoc}
397
     */
398
    public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
399
    {
400
        $this->_defaultFetchMode = $fetchMode;
401
402
        return true;
403
    }
404
405
    /**
406
     * {@inheritdoc}
407
     */
408
    public function getIterator()
409
    {
410
        return new StatementIterator($this);
411
    }
412
}
413