Failed Conditions
Pull Request — 3.0.x (#3980)
by Guilherme
06:55
created

SQLSrvStatement   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 366
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
eloc 118
dl 0
loc 366
ccs 0
cts 178
cp 0
rs 8.8
c 0
b 0
f 0
wmc 45

15 Methods

Rating   Name   Duplication   Size   Complexity  
A errorInfo() 0 3 1
A errorCode() 0 8 2
A rowCount() 0 13 3
A setFetchMode() 0 5 1
A fetch() 0 19 5
B execute() 0 30 8
A fetchColumn() 0 9 2
A bindParam() 0 13 2
A bindValue() 0 12 2
A columnCount() 0 13 3
A prepare() 0 36 5
A fetchAll() 0 18 4
A closeCursor() 0 17 4
A getIterator() 0 3 1
A __construct() 0 11 2

How to fix   Complexity   

Complex Class

Complex classes like SQLSrvStatement often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SQLSrvStatement, and based on these observations, apply Extract Interface, too.

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 is_int;
18
use function is_numeric;
19
use function sqlsrv_errors;
20
use function sqlsrv_execute;
21
use function sqlsrv_fetch;
22
use function sqlsrv_fetch_array;
23
use function sqlsrv_get_field;
24
use function sqlsrv_next_result;
25
use function sqlsrv_num_fields;
26
use function SQLSRV_PHPTYPE_STREAM;
27
use function SQLSRV_PHPTYPE_STRING;
28
use function sqlsrv_prepare;
29
use function sqlsrv_rows_affected;
30
use function SQLSRV_SQLTYPE_VARBINARY;
31
use function stripos;
32
33
/**
34
 * SQL Server Statement.
35
 */
36
class SQLSrvStatement implements IteratorAggregate, Statement
37
{
38
    /**
39
     * The SQLSRV Resource.
40
     *
41
     * @var resource
42
     */
43
    private $conn;
44
45
    /**
46
     * The SQL statement to execute.
47
     *
48
     * @var string
49
     */
50
    private $sql;
51
52
    /**
53
     * The SQLSRV statement resource.
54
     *
55
     * @var resource|null
56
     */
57
    private $stmt;
58
59
    /**
60
     * References to the variables bound as statement parameters.
61
     *
62
     * @var mixed
63
     */
64
    private $variables = [];
65
66
    /**
67
     * Bound parameter types.
68
     *
69
     * @var int[]
70
     */
71
    private $types = [];
72
73
    /**
74
     * Translations.
75
     *
76
     * @var int[]
77
     */
78
    private static $fetchMap = [
79
        FetchMode::MIXED       => SQLSRV_FETCH_BOTH,
80
        FetchMode::ASSOCIATIVE => SQLSRV_FETCH_ASSOC,
81
        FetchMode::NUMERIC     => SQLSRV_FETCH_NUMERIC,
82
    ];
83
84
    /**
85
     * The fetch style.
86
     *
87
     * @var int
88
     */
89
    private $defaultFetchMode = FetchMode::MIXED;
90
91
    /**
92
     * The last insert ID.
93
     *
94
     * @var LastInsertId|null
95
     */
96
    private $lastInsertId;
97
98
    /**
99
     * Indicates whether the statement is in the state when fetching results is possible
100
     *
101
     * @var bool
102
     */
103
    private $result = false;
104
105
    /**
106
     * Append to any INSERT query to retrieve the last insert id.
107
     *
108
     * @deprecated This constant has been deprecated and will be made private in 3.0
109
     */
110
    public const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;';
111
112
    /**
113
     * @param resource $conn
114
     * @param string   $sql
115
     */
116
    public function __construct($conn, $sql, ?LastInsertId $lastInsertId = null)
117
    {
118
        $this->conn = $conn;
119
        $this->sql  = $sql;
120
121
        if (stripos($sql, 'INSERT INTO ') !== 0) {
122
            return;
123
        }
124
125
        $this->sql         .= self::LAST_INSERT_ID_SQL;
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Driver\SQL...ent::LAST_INSERT_ID_SQL has been deprecated: This constant has been deprecated and will be made private in 3.0 ( Ignorable by Annotation )

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

125
        $this->sql         .= /** @scrutinizer ignore-deprecated */ self::LAST_INSERT_ID_SQL;

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
126
        $this->lastInsertId = $lastInsertId;
127
    }
128
129
    /**
130
     * {@inheritdoc}
131
     */
132
    public function bindValue($param, $value, $type = ParameterType::STRING)
133
    {
134
        if (! is_numeric($param)) {
135
            throw new SQLSrvException(
136
                'sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.'
137
            );
138
        }
139
140
        $this->variables[$param] = $value;
141
        $this->types[$param]     = $type;
142
143
        return true;
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149
    public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
150
    {
151
        if (! is_numeric($column)) {
152
            throw new SQLSrvException('sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.');
153
        }
154
155
        $this->variables[$column] =& $variable;
156
        $this->types[$column]     = $type;
157
158
        // unset the statement resource if it exists as the new one will need to be bound to the new variable
159
        $this->stmt = null;
160
161
        return true;
162
    }
163
164
    /**
165
     * {@inheritdoc}
166
     */
167
    public function closeCursor()
168
    {
169
        // not having the result means there's nothing to close
170
        if ($this->stmt === null || ! $this->result) {
171
            return true;
172
        }
173
174
        // emulate it by fetching and discarding rows, similarly to what PDO does in this case
175
        // @link http://php.net/manual/en/pdostatement.closecursor.php
176
        // @link https://github.com/php/php-src/blob/php-7.0.11/ext/pdo/pdo_stmt.c#L2075
177
        // deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them
178
        while (sqlsrv_fetch($this->stmt)) {
179
        }
180
181
        $this->result = false;
182
183
        return true;
184
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189
    public function columnCount()
190
    {
191
        if ($this->stmt === null) {
192
            return 0;
193
        }
194
195
        $count = sqlsrv_num_fields($this->stmt);
196
197
        if ($count !== false) {
198
            return $count;
199
        }
200
201
        return 0;
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207
    public function errorCode()
208
    {
209
        $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
210
        if ($errors !== null) {
211
            return $errors[0]['code'];
212
        }
213
214
        return false;
215
    }
216
217
    /**
218
     * {@inheritdoc}
219
     */
220
    public function errorInfo()
221
    {
222
        return (array) sqlsrv_errors(SQLSRV_ERR_ERRORS);
223
    }
224
225
    /**
226
     * {@inheritdoc}
227
     */
228
    public function execute($params = null)
229
    {
230
        if ($params !== null) {
231
            $hasZeroIndex = array_key_exists(0, $params);
232
            foreach ($params as $key => $val) {
233
                if ($hasZeroIndex && is_int($key)) {
234
                    $this->bindValue($key + 1, $val);
235
                } else {
236
                    $this->bindValue($key, $val);
237
                }
238
            }
239
        }
240
241
        if ($this->stmt === null) {
242
            $this->stmt = $this->prepare();
243
        }
244
245
        if (! sqlsrv_execute($this->stmt)) {
246
            throw SQLSrvException::fromSqlSrvErrors();
247
        }
248
249
        if ($this->lastInsertId !== null) {
250
            sqlsrv_next_result($this->stmt);
251
            sqlsrv_fetch($this->stmt);
252
            $this->lastInsertId->setId(sqlsrv_get_field($this->stmt, 0));
253
        }
254
255
        $this->result = true;
256
257
        return true;
258
    }
259
260
    /**
261
     * Prepares SQL Server statement resource
262
     *
263
     * @return resource
264
     *
265
     * @throws SQLSrvException
266
     */
267
    private function prepare()
268
    {
269
        $params = [];
270
271
        foreach ($this->variables as $column => &$variable) {
272
            switch ($this->types[$column]) {
273
                case ParameterType::LARGE_OBJECT:
274
                    $params[$column - 1] = [
275
                        &$variable,
276
                        SQLSRV_PARAM_IN,
277
                        SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY),
278
                        SQLSRV_SQLTYPE_VARBINARY('max'),
279
                    ];
280
                    break;
281
282
                case ParameterType::BINARY:
283
                    $params[$column - 1] = [
284
                        &$variable,
285
                        SQLSRV_PARAM_IN,
286
                        SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY),
287
                    ];
288
                    break;
289
290
                default:
291
                    $params[$column - 1] =& $variable;
292
                    break;
293
            }
294
        }
295
296
        $stmt = sqlsrv_prepare($this->conn, $this->sql, $params);
297
298
        if ($stmt === false) {
299
            throw SQLSrvException::fromSqlSrvErrors();
300
        }
301
302
        return $stmt;
303
    }
304
305
    /**
306
     * {@inheritdoc}
307
     */
308
    public function setFetchMode($fetchMode)
309
    {
310
        $this->defaultFetchMode = $fetchMode;
311
312
        return true;
313
    }
314
315
    /**
316
     * {@inheritdoc}
317
     */
318
    public function getIterator()
319
    {
320
        return new StatementIterator($this);
321
    }
322
323
    /**
324
     * {@inheritdoc}
325
     *
326
     * @throws SQLSrvException
327
     */
328
    public function fetch($fetchMode = null)
329
    {
330
        // do not try fetching from the statement if it's not expected to contain result
331
        // in order to prevent exceptional situation
332
        if ($this->stmt === null || ! $this->result) {
333
            return false;
334
        }
335
336
        $fetchMode = $fetchMode ?? $this->defaultFetchMode;
337
338
        if ($fetchMode === FetchMode::COLUMN) {
339
            return $this->fetchColumn();
340
        }
341
342
        if (isset(self::$fetchMap[$fetchMode])) {
343
            return sqlsrv_fetch_array($this->stmt, self::$fetchMap[$fetchMode]) ?? false;
344
        }
345
346
        throw new SQLSrvException('Fetch mode is not supported!');
347
    }
348
349
    /**
350
     * {@inheritdoc}
351
     */
352
    public function fetchAll($fetchMode = null)
353
    {
354
        $rows = [];
355
356
        switch ($fetchMode) {
357
            case FetchMode::COLUMN:
358
                while (($row = $this->fetchColumn()) !== false) {
359
                    $rows[] = $row;
360
                }
361
                break;
362
363
            default:
364
                while (($row = $this->fetch($fetchMode)) !== false) {
365
                    $rows[] = $row;
366
                }
367
        }
368
369
        return $rows;
370
    }
371
372
    /**
373
     * {@inheritdoc}
374
     */
375
    public function fetchColumn()
376
    {
377
        $row = $this->fetch(FetchMode::NUMERIC);
378
379
        if ($row === false) {
380
            return false;
381
        }
382
383
        return $row[0] ?? null;
384
    }
385
386
    /**
387
     * {@inheritdoc}
388
     */
389
    public function rowCount() : int
390
    {
391
        if ($this->stmt === null) {
392
            return 0;
393
        }
394
395
        $count = sqlsrv_rows_affected($this->stmt);
396
397
        if ($count !== false) {
398
            return $count;
399
        }
400
401
        return 0;
402
    }
403
}
404