Failed Conditions
Push — develop ( 152bc9...e39bc0 )
by Sergei
102:42 queued 37:39
created

SQLSrvStatement::fetchAll()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 9.0146

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 24
ccs 9
cts 16
cp 0.5625
rs 9.2222
c 0
b 0
f 0
cc 6
nc 6
nop 2
crap 9.0146
1
<?php
2
3
namespace Doctrine\DBAL\Driver\SQLSrv;
4
5
use Doctrine\DBAL\Driver\Statement;
6
use Doctrine\DBAL\Driver\StatementIterator;
7
use Doctrine\DBAL\FetchMode;
8
use Doctrine\DBAL\ParameterType;
9
use IteratorAggregate;
10
use const SQLSRV_ENC_BINARY;
11
use const SQLSRV_ERR_ERRORS;
12
use const SQLSRV_FETCH_ASSOC;
13
use const SQLSRV_FETCH_BOTH;
14
use const SQLSRV_FETCH_NUMERIC;
15
use const SQLSRV_PARAM_IN;
16
use function array_key_exists;
17
use function count;
18
use function in_array;
19
use function is_int;
20
use function is_numeric;
21
use function sqlsrv_errors;
22
use function sqlsrv_execute;
23
use function sqlsrv_fetch;
24
use function sqlsrv_fetch_array;
25
use function sqlsrv_fetch_object;
26
use function sqlsrv_get_field;
27
use function sqlsrv_next_result;
28
use function sqlsrv_num_fields;
29
use function SQLSRV_PHPTYPE_STREAM;
30
use function SQLSRV_PHPTYPE_STRING;
31
use function sqlsrv_prepare;
32
use function sqlsrv_rows_affected;
33
use function SQLSRV_SQLTYPE_VARBINARY;
34
use function stripos;
35
36
/**
37
 * SQL Server Statement.
38
 */
39
class SQLSrvStatement implements IteratorAggregate, Statement
40
{
41
    /**
42
     * The SQLSRV Resource.
43
     *
44
     * @var resource
45
     */
46
    private $conn;
47
48
    /**
49
     * The SQL statement to execute.
50
     *
51
     * @var string
52
     */
53
    private $sql;
54
55
    /**
56
     * The SQLSRV statement resource.
57
     *
58
     * @var resource|null
59
     */
60
    private $stmt;
61
62
    /**
63
     * References to the variables bound as statement parameters.
64
     *
65
     * @var mixed
66
     */
67
    private $variables = [];
68
69
    /**
70
     * Bound parameter types.
71
     *
72
     * @var int[]
73
     */
74
    private $types = [];
75
76
    /**
77
     * Translations.
78
     *
79
     * @var int[]
80
     */
81
    private static $fetchMap = [
82
        FetchMode::MIXED       => SQLSRV_FETCH_BOTH,
83
        FetchMode::ASSOCIATIVE => SQLSRV_FETCH_ASSOC,
84
        FetchMode::NUMERIC     => SQLSRV_FETCH_NUMERIC,
85
    ];
86
87
    /**
88
     * The name of the default class to instantiate when fetching class instances.
89
     *
90
     * @var string
91
     */
92
    private $defaultFetchClass = '\stdClass';
93
94
    /**
95
     * The constructor arguments for the default class to instantiate when fetching class instances.
96
     *
97
     * @var mixed[]
98
     */
99
    private $defaultFetchClassCtorArgs = [];
100
101
    /**
102
     * The fetch style.
103
     *
104
     * @var int
105
     */
106
    private $defaultFetchMode = FetchMode::MIXED;
107
108
    /**
109
     * The last insert ID.
110
     *
111
     * @var LastInsertId|null
112
     */
113
    private $lastInsertId;
114
115
    /**
116
     * Indicates whether the statement is in the state when fetching results is possible
117
     *
118
     * @var bool
119
     */
120
    private $result = false;
121
122
    /**
123
     * Append to any INSERT query to retrieve the last insert id.
124
     */
125
    public const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;';
126
127
    /**
128
     * @param resource $conn
129
     * @param string   $sql
130 242
     */
131
    public function __construct($conn, $sql, ?LastInsertId $lastInsertId = null)
132 242
    {
133 242
        $this->conn = $conn;
134
        $this->sql  = $sql;
135 242
136 235
        if (stripos($sql, 'INSERT INTO ') !== 0) {
137
            return;
138
        }
139 85
140 85
        $this->sql         .= self::LAST_INSERT_ID_SQL;
141 85
        $this->lastInsertId = $lastInsertId;
142
    }
143
144
    /**
145
     * {@inheritdoc}
146 109
     */
147
    public function bindValue($param, $value, $type = ParameterType::STRING)
148 109
    {
149
        if (! is_numeric($param)) {
150
            throw new SQLSrvException(
151
                'sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.'
152
            );
153
        }
154 109
155 109
        $this->variables[$param] = $value;
156 109
        $this->types[$param]     = $type;
157
    }
158
159
    /**
160
     * {@inheritdoc}
161 8
     */
162
    public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
163 8
    {
164
        if (! is_numeric($column)) {
165
            throw new SQLSrvException('sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.');
166
        }
167 8
168 8
        $this->variables[$column] =& $variable;
169
        $this->types[$column]     = $type;
170
171 8
        // unset the statement resource if it exists as the new one will need to be bound to the new variable
172 8
        $this->stmt = null;
173
    }
174
175
    /**
176
     * {@inheritdoc}
177 19
     */
178
    public function closeCursor()
179
    {
180 19
        // not having the result means there's nothing to close
181 4
        if ($this->stmt === null || ! $this->result) {
182
            return true;
183
        }
184
185
        // emulate it by fetching and discarding rows, similarly to what PDO does in this case
186
        // @link http://php.net/manual/en/pdostatement.closecursor.php
187
        // @link https://github.com/php/php-src/blob/php-7.0.11/ext/pdo/pdo_stmt.c#L2075
188 15
        // deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them
189
        while (sqlsrv_fetch($this->stmt)) {
190
        }
191 15
192
        $this->result = false;
193 15
194
        return true;
195
    }
196
197
    /**
198
     * {@inheritdoc}
199 4
     */
200
    public function columnCount()
201 4
    {
202
        if ($this->stmt === null) {
203
            return 0;
204
        }
205
206
        return sqlsrv_num_fields($this->stmt) ?: 0;
207
    }
208
209
    /**
210
     * {@inheritdoc}
211
     */
212
    public function errorCode()
213
    {
214
        $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
215
        if ($errors) {
216
            return $errors[0]['code'];
217
        }
218
219
        return false;
220
    }
221
222
    /**
223
     * {@inheritdoc}
224
     */
225
    public function errorInfo()
226
    {
227
        return (array) sqlsrv_errors(SQLSRV_ERR_ERRORS);
228 234
    }
229
230 234
    /**
231 78
     * {@inheritdoc}
232 78
     */
233 78
    public function execute($params = null)
234 78
    {
235
        if ($params) {
236
            $hasZeroIndex = array_key_exists(0, $params);
237
238 234
            foreach ($params as $key => $val) {
239 234
                if ($hasZeroIndex && is_int($key)) {
240
                    $this->bindValue($key + 1, $val);
241
                } else {
242 233
                    $this->bindValue($key, $val);
243
                }
244
            }
245
        }
246 233
247 85
        if (! $this->stmt) {
248 85
            $this->stmt = $this->prepare();
249 85
        }
250
251
        if (! sqlsrv_execute($this->stmt)) {
252 233
            throw SQLSrvException::fromSqlSrvErrors();
253 233
        }
254
255
        if ($this->lastInsertId) {
256
            sqlsrv_next_result($this->stmt);
257
            sqlsrv_fetch($this->stmt);
258
            $this->lastInsertId->setId(sqlsrv_get_field($this->stmt, 0));
259
        }
260
261
        $this->result = true;
262 234
    }
263
264 234
    /**
265
     * Prepares SQL Server statement resource
266 234
     *
267 115
     * @return resource
268
     *
269 7
     * @throws SQLSrvException
270 7
     */
271
    private function prepare()
272 7
    {
273 7
        $params = [];
274
275 7
        foreach ($this->variables as $column => &$variable) {
276
            switch ($this->types[$column]) {
277
                case ParameterType::LARGE_OBJECT:
278 1
                    $params[$column - 1] = [
279 1
                        &$variable,
280
                        SQLSRV_PARAM_IN,
281 1
                        SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY),
282
                        SQLSRV_SQLTYPE_VARBINARY('max'),
0 ignored issues
show
Bug introduced by
'max' of type string is incompatible with the type integer expected by parameter $byteCount of SQLSRV_SQLTYPE_VARBINARY(). ( Ignorable by Annotation )

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

282
                        SQLSRV_SQLTYPE_VARBINARY(/** @scrutinizer ignore-type */ 'max'),
Loading history...
283 1
                    ];
284
                    break;
285
286 112
                case ParameterType::BINARY:
287 115
                    $params[$column - 1] = [
288
                        &$variable,
289
                        SQLSRV_PARAM_IN,
290
                        SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY),
291 234
                    ];
292
                    break;
293 234
294 1
                default:
295
                    $params[$column - 1] =& $variable;
296
                    break;
297 233
            }
298
        }
299
300
        $stmt = sqlsrv_prepare($this->conn, $this->sql, $params);
301
302
        if (! $stmt) {
0 ignored issues
show
introduced by
$stmt is of type false|resource, thus it always evaluated to false.
Loading history...
303 235
            throw SQLSrvException::fromSqlSrvErrors();
304
        }
305 235
306
        return $stmt;
307 235
    }
308 2
309
    /**
310
     * {@inheritdoc}
311 235
     */
312
    public function setFetchMode($fetchMode, ...$args)
313
    {
314
        $this->defaultFetchMode = $fetchMode;
315 235
316
        if (isset($args[0])) {
317
            $this->defaultFetchClass = $args[0];
318
        }
319
320
        if (isset($args[1])) {
321 3
            $this->defaultFetchClassCtorArgs = (array) $args[1];
322
        }
323 3
324
        return true;
325
    }
326
327
    /**
328
     * {@inheritdoc}
329
     */
330
    public function getIterator()
331 225
    {
332
        return new StatementIterator($this);
333
    }
334
335 225
    /**
336 9
     * {@inheritdoc}
337
     *
338
     * @throws SQLSrvException
339 216
     */
340
    public function fetch($fetchMode = null, ...$args)
341 216
    {
342 1
        // do not try fetching from the statement if it's not expected to contain result
343
        // in order to prevent exceptional situation
344
        if ($this->stmt === null || ! $this->result) {
345 216
            return false;
346 212
        }
347
348
        $fetchMode = $fetchMode ?: $this->defaultFetchMode;
349 4
350 4
        if ($fetchMode === FetchMode::COLUMN) {
351 4
            return $this->fetchColumn();
352
        }
353 4
354 1
        if (isset(self::$fetchMap[$fetchMode])) {
355 1
            return sqlsrv_fetch_array($this->stmt, self::$fetchMap[$fetchMode]) ?: false;
356
        }
357
358 4
        if (in_array($fetchMode, [FetchMode::STANDARD_OBJECT, FetchMode::CUSTOM_OBJECT], true)) {
359
            $className = $this->defaultFetchClass;
360
            $ctorArgs  = $this->defaultFetchClassCtorArgs;
361
362
            if (count($args) > 0) {
363
                $className = $args[0];
364
                $ctorArgs  = $args[1] ?? [];
365
            }
366
367 108
            return sqlsrv_fetch_object($this->stmt, $className, $ctorArgs) ?: false;
368
        }
369 108
370
        throw new SQLSrvException('Fetch mode is not supported!');
371 108
    }
372
373 1
    /**
374 1
     * {@inheritdoc}
375
     */
376 1
    public function fetchAll($fetchMode = null, ...$args)
377
    {
378
        $rows = [];
379 9
380 9
        switch ($fetchMode) {
381
            case FetchMode::CUSTOM_OBJECT:
382 9
                while (($row = $this->fetch($fetchMode, ...$args)) !== false) {
383
                    $rows[] = $row;
384
                }
385 98
                break;
386 91
387
            case FetchMode::COLUMN:
388
                while (($row = $this->fetchColumn()) !== false) {
389
                    $rows[] = $row;
390 108
                }
391
                break;
392
393
            default:
394
                while (($row = $this->fetch($fetchMode)) !== false) {
395
                    $rows[] = $row;
396 61
                }
397
        }
398 61
399
        return $rows;
400 61
    }
401 15
402
    /**
403
     * {@inheritdoc}
404 55
     */
405
    public function fetchColumn($columnIndex = 0)
406
    {
407
        $row = $this->fetch(FetchMode::NUMERIC);
408
409
        if ($row === false) {
410 85
            return false;
411
        }
412 85
413
        return $row[$columnIndex] ?? null;
414
    }
415
416
    /**
417
     * {@inheritdoc}
418
     */
419
    public function rowCount() : int
420
    {
421
        if ($this->stmt === null) {
422
            return 0;
423
        }
424
425
        return sqlsrv_rows_affected($this->stmt) ?: 0;
426
    }
427
}
428