Passed
Pull Request — master (#3070)
by Sergei
07:45
created

SQLSrvStatement   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 302
Duplicated Lines 0 %

Test Coverage

Coverage 62.33%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 41
eloc 98
dl 0
loc 302
ccs 91
cts 146
cp 0.6233
rs 9.1199
c 2
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A rowCount() 0 7 3
A setFetchMode() 0 3 1
B fetch() 0 19 7
B execute() 0 27 7
A fetchColumn() 0 9 2
A bindParam() 0 9 1
A bindValue() 0 6 1
A columnCount() 0 7 3
A prepare() 0 36 5
A fetchAll() 0 19 4
A closeCursor() 0 15 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
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Driver\SQLSrv;
6
7
use Doctrine\DBAL\Driver\Statement;
8
use Doctrine\DBAL\Driver\StatementIterator;
9
use Doctrine\DBAL\FetchMode;
10
use Doctrine\DBAL\ParameterType;
11
use IteratorAggregate;
12
use function assert;
13
use function is_int;
14
use function sqlsrv_execute;
15
use function sqlsrv_fetch;
16
use function sqlsrv_fetch_array;
17
use function sqlsrv_get_field;
18
use function sqlsrv_next_result;
19
use function sqlsrv_num_fields;
20
use function SQLSRV_PHPTYPE_STREAM;
21
use function SQLSRV_PHPTYPE_STRING;
22
use function sqlsrv_prepare;
23
use function sqlsrv_rows_affected;
24
use function SQLSRV_SQLTYPE_VARBINARY;
25
use function stripos;
26
use const SQLSRV_ENC_BINARY;
27
use const SQLSRV_FETCH_ASSOC;
28
use const SQLSRV_FETCH_BOTH;
29
use const SQLSRV_FETCH_NUMERIC;
30
use const SQLSRV_PARAM_IN;
31
32
/**
33
 * SQL Server Statement.
34
 */
35
final class SQLSrvStatement implements IteratorAggregate, Statement
36
{
37
    /**
38
     * The SQLSRV Resource.
39
     *
40
     * @var resource
41
     */
42
    private $conn;
43
44
    /**
45
     * The SQL statement to execute.
46
     *
47
     * @var string
48
     */
49
    private $sql;
50
51
    /**
52
     * The SQLSRV statement resource.
53
     *
54
     * @var resource|null
55
     */
56
    private $stmt;
57
58
    /**
59
     * References to the variables bound as statement parameters.
60
     *
61
     * @var mixed
62
     */
63
    private $variables = [];
64
65
    /**
66
     * Bound parameter types.
67
     *
68
     * @var int[]
69
     */
70
    private $types = [];
71
72
    /**
73
     * Translations.
74
     *
75
     * @var int[]
76
     */
77
    private static $fetchMap = [
78
        FetchMode::MIXED       => SQLSRV_FETCH_BOTH,
79
        FetchMode::ASSOCIATIVE => SQLSRV_FETCH_ASSOC,
80
        FetchMode::NUMERIC     => SQLSRV_FETCH_NUMERIC,
81
    ];
82
83
    /**
84
     * The fetch style.
85
     *
86
     * @var int
87
     */
88
    private $defaultFetchMode = FetchMode::MIXED;
89
90
    /**
91
     * The last insert ID.
92
     *
93
     * @var LastInsertId|null
94
     */
95
    private $lastInsertId;
96
97
    /**
98
     * Indicates whether the statement is in the state when fetching results is possible
99
     *
100
     * @var bool
101
     */
102
    private $result = false;
103
104
    /**
105
     * Append to any INSERT query to retrieve the last insert id.
106
     */
107
    private const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;';
108
109
    /**
110
     * @param resource $conn
111
     */
112 317
    public function __construct($conn, string $sql, ?LastInsertId $lastInsertId = null)
113
    {
114 317
        $this->conn = $conn;
115 317
        $this->sql  = $sql;
116
117 317
        if (stripos($sql, 'INSERT INTO ') !== 0) {
118 310
            return;
119
        }
120
121 87
        $this->sql         .= self::LAST_INSERT_ID_SQL;
122 87
        $this->lastInsertId = $lastInsertId;
123 87
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128 109
    public function bindValue($param, $value, int $type = ParameterType::STRING) : void
129
    {
130 109
        assert(is_int($param));
131
132 109
        $this->variables[$param] = $value;
133 109
        $this->types[$param]     = $type;
134 109
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139 24
    public function bindParam($param, &$variable, int $type = ParameterType::STRING, ?int $length = null) : void
140
    {
141 24
        assert(is_int($param));
142
143 24
        $this->variables[$param] =& $variable;
144 24
        $this->types[$param]     = $type;
145
146
        // unset the statement resource if it exists as the new one will need to be bound to the new variable
147 24
        $this->stmt = null;
148 24
    }
149
150 19
    public function closeCursor() : void
151
    {
152
        // not having the result means there's nothing to close
153 19
        if ($this->stmt === null || ! $this->result) {
154 3
            return;
155
        }
156
157
        // emulate it by fetching and discarding rows, similarly to what PDO does in this case
158
        // @link http://php.net/manual/en/pdostatement.closecursor.php
159
        // @link https://github.com/php/php-src/blob/php-7.0.11/ext/pdo/pdo_stmt.c#L2075
160
        // deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them
161 16
        while (sqlsrv_fetch($this->stmt) !== false) {
162
        }
163
164 16
        $this->result = false;
165 16
    }
166
167 4
    public function columnCount() : int
168
    {
169 4
        if ($this->stmt === null) {
170
            return 0;
171
        }
172
173 4
        return sqlsrv_num_fields($this->stmt) ?: 0;
174
    }
175
176
    /**
177
     * {@inheritdoc}
178
     */
179 310
    public function execute(?array $params = null) : void
180
    {
181 310
        if ($params) {
182 78
            foreach ($params as $key => $val) {
183 78
                if (is_int($key)) {
184 78
                    $this->bindValue($key + 1, $val);
185
                } else {
186
                    $this->bindValue($key, $val);
187
                }
188
            }
189
        }
190
191 310
        if (! $this->stmt) {
192 310
            $this->stmt = $this->prepare();
193
        }
194
195 309
        if (! sqlsrv_execute($this->stmt)) {
196
            throw SQLSrvException::fromSqlSrvErrors();
197
        }
198
199 309
        if ($this->lastInsertId) {
200 87
            sqlsrv_next_result($this->stmt);
201 87
            sqlsrv_fetch($this->stmt);
202 87
            $this->lastInsertId->setId(sqlsrv_get_field($this->stmt, 0));
203
        }
204
205 309
        $this->result = true;
206 309
    }
207
208 310
    public function setFetchMode(int $fetchMode) : void
209
    {
210 310
        $this->defaultFetchMode = $fetchMode;
211 310
    }
212
213
    /**
214
     * {@inheritdoc}
215
     */
216 1
    public function getIterator()
217
    {
218 1
        return new StatementIterator($this);
219
    }
220
221
    /**
222
     * {@inheritdoc}
223
     *
224
     * @throws SQLSrvException
225
     */
226 301
    public function fetch(?int $fetchMode = null)
227
    {
228
        // do not try fetching from the statement if it's not expected to contain result
229
        // in order to prevent exceptional situation
230 301
        if ($this->stmt === null || ! $this->result) {
231 9
            return false;
232
        }
233
234 292
        $fetchMode = $fetchMode ?: $this->defaultFetchMode;
235
236 292
        if ($fetchMode === FetchMode::COLUMN) {
237 1
            return $this->fetchColumn();
238
        }
239
240 292
        if (isset(self::$fetchMap[$fetchMode])) {
241 292
            return sqlsrv_fetch_array($this->stmt, self::$fetchMap[$fetchMode]) ?: false;
242
        }
243
244
        throw new SQLSrvException('Fetch mode is not supported.');
245
    }
246
247
    /**
248
     * {@inheritdoc}
249
     */
250 117
    public function fetchAll(?int $fetchMode = null) : array
251
    {
252 117
        $rows = [];
253
254 117
        switch ($fetchMode) {
255
            case FetchMode::COLUMN:
256 10
                while (($row = $this->fetchColumn()) !== false) {
257 10
                    $rows[] = $row;
258
                }
259
260 10
                break;
261
262
            default:
263 107
                while (($row = $this->fetch($fetchMode)) !== false) {
264 99
                    $rows[] = $row;
265
                }
266
        }
267
268 117
        return $rows;
269
    }
270
271
    /**
272
     * {@inheritdoc}
273
     */
274 195
    public function fetchColumn()
275
    {
276 195
        $row = $this->fetch(FetchMode::NUMERIC);
277
278 195
        if ($row === false) {
279 16
            return false;
280
        }
281
282 189
        return $row[0];
283
    }
284
285 86
    public function rowCount() : int
286
    {
287 86
        if ($this->stmt === null) {
288
            return 0;
289
        }
290
291 86
        return sqlsrv_rows_affected($this->stmt) ?: 0;
292
    }
293
294
    /**
295
     * Prepares SQL Server statement resource
296
     *
297
     * @return resource
298
     *
299
     * @throws SQLSrvException
300
     */
301 310
    private function prepare()
302
    {
303 310
        $params = [];
304
305 310
        foreach ($this->variables as $column => &$variable) {
306 131
            switch ($this->types[$column]) {
307
                case ParameterType::LARGE_OBJECT:
308 7
                    $params[$column - 1] = [
309 7
                        &$variable,
310
                        SQLSRV_PARAM_IN,
311 7
                        SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY),
312 7
                        SQLSRV_SQLTYPE_VARBINARY('max'),
313
                    ];
314 7
                    break;
315
316
                case ParameterType::BINARY:
317 1
                    $params[$column - 1] = [
318 1
                        &$variable,
319
                        SQLSRV_PARAM_IN,
320 1
                        SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY),
321
                    ];
322 1
                    break;
323
324
                default:
325 128
                    $params[$column - 1] =& $variable;
326 128
                    break;
327
            }
328
        }
329
330 310
        $stmt = sqlsrv_prepare($this->conn, $this->sql, $params);
331
332 310
        if (! $stmt) {
0 ignored issues
show
introduced by
$stmt is of type false|resource, thus it always evaluated to false.
Loading history...
333 1
            throw SQLSrvException::fromSqlSrvErrors();
334
        }
335
336 309
        return $stmt;
337
    }
338
}
339