Completed
Push — master ( 8575c2...a53269 )
by Marco
02:31 queued 01:15
created

MysqliStatement::fetch()   C

Complexity

Conditions 11
Paths 10

Size

Total Lines 48
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 0
Metric Value
dl 0
loc 48
ccs 0
cts 34
cp 0
rs 5.2653
c 0
b 0
f 0
cc 11
eloc 27
nc 10
nop 3
crap 132

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
        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
        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
        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
        if ( ! $this->_stmt->execute()) {
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);
0 ignored issues
show
Bug introduced by
It seems like $this->_columnNames can also be of type true; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

208
            $this->_rowBindedValues = array_fill(0, count(/** @scrutinizer ignore-type */ $this->_columnNames), null);
Loading history...
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
        $fetchMode = $fetchMode ?: $this->_defaultFetchMode;
276
277
        if ($fetchMode === PDO::FETCH_COLUMN) {
278
            return $this->fetchColumn();
279
        }
280
281
        $values = $this->_fetch();
282
        if (null === $values) {
283
            return false;
284
        }
285
286
        if (false === $values) {
287
            throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno);
288
        }
289
290
        switch ($fetchMode) {
291
            case PDO::FETCH_NUM:
292
                return $values;
293
294
            case PDO::FETCH_ASSOC:
295
                return array_combine($this->_columnNames, $values);
0 ignored issues
show
Bug introduced by
It seems like $values can also be of type true; however, parameter $values of array_combine() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

295
                return array_combine($this->_columnNames, /** @scrutinizer ignore-type */ $values);
Loading history...
Bug introduced by
It seems like $this->_columnNames can also be of type null and boolean; however, parameter $keys of array_combine() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

295
                return array_combine(/** @scrutinizer ignore-type */ $this->_columnNames, $values);
Loading history...
296
297
            case PDO::FETCH_BOTH:
298
                $ret = array_combine($this->_columnNames, $values);
299
                $ret += $values;
300
301
                return $ret;
302
303
            case PDO::FETCH_OBJ:
304
                $assoc = array_combine($this->_columnNames, $values);
305
                $ret = new \stdClass();
306
307
                foreach ($assoc as $column => $value) {
308
                    $ret->$column = $value;
309
                }
310
311
                return $ret;
312
313
            default:
314
                throw new MysqliException("Unknown fetch type '{$fetchMode}'");
315
        }
316
    }
317
318
    /**
319
     * {@inheritdoc}
320
     */
321
    public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
322
    {
323
        $fetchMode = $fetchMode ?: $this->_defaultFetchMode;
324
325
        $rows = [];
326
        if (PDO::FETCH_COLUMN == $fetchMode) {
327
            while (($row = $this->fetchColumn()) !== false) {
328
                $rows[] = $row;
329
            }
330
        } else {
331
            while (($row = $this->fetch($fetchMode)) !== false) {
332
                $rows[] = $row;
333
            }
334
        }
335
336
        return $rows;
337
    }
338
339
    /**
340
     * {@inheritdoc}
341
     */
342
    public function fetchColumn($columnIndex = 0)
343
    {
344
        $row = $this->fetch(PDO::FETCH_NUM);
345
        if (false === $row) {
346
            return false;
347
        }
348
349
        return $row[$columnIndex] ?? null;
350
    }
351
352
    /**
353
     * {@inheritdoc}
354
     */
355
    public function errorCode()
356
    {
357
        return $this->_stmt->errno;
358
    }
359
360
    /**
361
     * {@inheritdoc}
362
     */
363
    public function errorInfo()
364
    {
365
        return $this->_stmt->error;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_stmt->error returns the type string which is incompatible with the return type mandated by Doctrine\DBAL\Driver\Statement::errorInfo() of array.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
366
    }
367
368
    /**
369
     * {@inheritdoc}
370
     */
371
    public function closeCursor()
372
    {
373
        $this->_stmt->free_result();
374
        $this->result = false;
375
376
        return true;
377
    }
378
379
    /**
380
     * {@inheritdoc}
381
     */
382
    public function rowCount()
383
    {
384
        if (false === $this->_columnNames) {
385
            return $this->_stmt->affected_rows;
386
        }
387
388
        return $this->_stmt->num_rows;
389
    }
390
391
    /**
392
     * {@inheritdoc}
393
     */
394
    public function columnCount()
395
    {
396
        return $this->_stmt->field_count;
397
    }
398
399
    /**
400
     * {@inheritdoc}
401
     */
402
    public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
403
    {
404
        $this->_defaultFetchMode = $fetchMode;
405
406
        return true;
407
    }
408
409
    /**
410
     * {@inheritdoc}
411
     */
412
    public function getIterator()
413
    {
414
        return new StatementIterator($this);
415
    }
416
}
417