Completed
Push — 2.10 ( 96cda3...d49d68 )
by Gabriel
23:06 queued 19:24
created

SQLSrvStatement   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 384
Duplicated Lines 0 %

Test Coverage

Coverage 57.07%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 54
eloc 125
c 2
b 0
f 0
dl 0
loc 384
ccs 105
cts 184
cp 0.5707
rs 6.4799

15 Methods

Rating   Name   Duplication   Size   Complexity  
A errorInfo() 0 3 1
A rowCount() 0 7 3
A setFetchMode() 0 7 3
B fetch() 0 32 10
B execute() 0 29 8
A fetchColumn() 0 9 2
A bindParam() 0 11 2
A bindValue() 0 10 2
A columnCount() 0 7 3
A prepare() 0 36 5
A errorCode() 0 8 2
A closeCursor() 0 17 4
A getIterator() 0 3 1
A __construct() 0 11 2
A fetchAll() 0 24 6

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

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