Failed Conditions
Push — 3.0.x ( 66f057...14f5f1 )
by Sergei
36s queued 17s
created

SQLSrvStatement::bindParam()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 4
dl 0
loc 13
rs 10
c 0
b 0
f 0
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
     * @deprecated This constant has been deprecated and will be made private in 3.0
126
     */
127
    public const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;';
128
129
    /**
130
     * @param resource $conn
131
     * @param string   $sql
132
     */
133
    public function __construct($conn, $sql, ?LastInsertId $lastInsertId = null)
134
    {
135
        $this->conn = $conn;
136
        $this->sql  = $sql;
137
138
        if (stripos($sql, 'INSERT INTO ') !== 0) {
139
            return;
140
        }
141
142
        $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

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