Failed Conditions
Push — master ( cfe3be...296b0e )
by Sergei
33:23 queued 11s
created

SQLSrvStatement::fetchAll()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 8.304

Importance

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

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