Failed Conditions
Push — master ( 656579...2742cd )
by Marco
11:55
created

Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php (3 issues)

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Driver\SQLSrv;
21
22
use Doctrine\DBAL\Driver\StatementIterator;
23
use PDO;
24
use IteratorAggregate;
25
use Doctrine\DBAL\Driver\Statement;
26
27
/**
28
 * SQL Server Statement.
29
 *
30
 * @since 2.3
31
 * @author Benjamin Eberlei <[email protected]>
32
 */
33
class SQLSrvStatement implements IteratorAggregate, Statement
34
{
35
    /**
36
     * The SQLSRV Resource.
37
     *
38
     * @var resource
39
     */
40
    private $conn;
41
42
    /**
43
     * The SQL statement to execute.
44
     *
45
     * @var string
46
     */
47
    private $sql;
48
49
    /**
50
     * The SQLSRV statement resource.
51
     *
52
     * @var resource
53
     */
54
    private $stmt;
55
56
    /**
57
     * References to the variables bound as statement parameters.
58
     *
59
     * @var array
60
     */
61
    private $variables = [];
62
63
    /**
64
     * Bound parameter types.
65
     *
66
     * @var array
67
     */
68
    private $types = [];
69
70
    /**
71
     * Translations.
72
     *
73
     * @var array
74
     */
75
    private static $fetchMap = [
76
        PDO::FETCH_BOTH => SQLSRV_FETCH_BOTH,
77
        PDO::FETCH_ASSOC => SQLSRV_FETCH_ASSOC,
78
        PDO::FETCH_NUM => SQLSRV_FETCH_NUMERIC,
79
    ];
80
81
    /**
82
     * The name of the default class to instantiate when fetch mode is \PDO::FETCH_CLASS.
83
     *
84
     * @var string
85
     */
86
    private $defaultFetchClass = '\stdClass';
87
88
    /**
89
     * The constructor arguments for the default class to instantiate when fetch mode is \PDO::FETCH_CLASS.
90
     *
91
     * @var string
92
     */
93
    private $defaultFetchClassCtorArgs = [];
94
95
    /**
96
     * The fetch style.
97
     *
98
     * @param integer
99
     */
100
    private $defaultFetchMode = PDO::FETCH_BOTH;
101
102
    /**
103
     * The last insert ID.
104
     *
105
     * @var \Doctrine\DBAL\Driver\SQLSrv\LastInsertId|null
106
     */
107
    private $lastInsertId;
108
109
    /**
110
     * Indicates whether the statement is in the state when fetching results is possible
111
     *
112
     * @var bool
113
     */
114
    private $result = false;
115
116
    /**
117
     * Append to any INSERT query to retrieve the last insert id.
118
     *
119
     * @var string
120
     */
121
    const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;';
122
123
    /**
124
     * @param resource                                       $conn
125
     * @param string                                         $sql
126
     * @param \Doctrine\DBAL\Driver\SQLSrv\LastInsertId|null $lastInsertId
127
     */
128
    public function __construct($conn, $sql, LastInsertId $lastInsertId = null)
129
    {
130
        $this->conn = $conn;
131
        $this->sql = $sql;
132
133
        if (stripos($sql, 'INSERT INTO ') === 0) {
134
            $this->sql .= self::LAST_INSERT_ID_SQL;
135
            $this->lastInsertId = $lastInsertId;
136
        }
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142
    public function bindValue($param, $value, $type = null)
143
    {
144
        if (!is_numeric($param)) {
145
            throw new SQLSrvException(
146
                'sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.'
147
            );
148
        }
149
150
        $this->variables[$param] = $value;
151
        $this->types[$param] = $type;
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157
    public function bindParam($column, &$variable, $type = null, $length = null)
158
    {
159
        if (!is_numeric($column)) {
160
            throw new SQLSrvException("sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.");
161
        }
162
163
        $this->variables[$column] =& $variable;
164
        $this->types[$column] = $type;
165
166
        // unset the statement resource if it exists as the new one will need to be bound to the new variable
167
        $this->stmt = null;
168
    }
169
170
    /**
171
     * {@inheritdoc}
172
     */
173
    public function closeCursor()
174
    {
175
        // not having the result means there's nothing to close
176
        if (!$this->result) {
177
            return true;
178
        }
179
180
        // emulate it by fetching and discarding rows, similarly to what PDO does in this case
181
        // @link http://php.net/manual/en/pdostatement.closecursor.php
182
        // @link https://github.com/php/php-src/blob/php-7.0.11/ext/pdo/pdo_stmt.c#L2075
183
        // deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them
184
        while (sqlsrv_fetch($this->stmt));
185
186
        $this->result = false;
187
188
        return true;
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194
    public function columnCount()
195
    {
196
        return sqlsrv_num_fields($this->stmt);
0 ignored issues
show
Bug Best Practice introduced by
The expression return sqlsrv_num_fields($this->stmt) also could return the type false which is incompatible with the return type mandated by Doctrine\DBAL\Driver\Res...tatement::columnCount() of integer.
Loading history...
197
    }
198
199
    /**
200
     * {@inheritdoc}
201
     */
202 View Code Duplication
    public function errorCode()
203
    {
204
        $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
205
        if ($errors) {
206
            return $errors[0]['code'];
207
        }
208
209
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the return type mandated by Doctrine\DBAL\Driver\Statement::errorCode() of string.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215
    public function errorInfo()
216
    {
217
        return sqlsrv_errors(SQLSRV_ERR_ERRORS);
218
    }
219
220
    /**
221
     * {@inheritdoc}
222
     */
223
    public function execute($params = null)
224
    {
225 View Code Duplication
        if ($params) {
226
            $hasZeroIndex = array_key_exists(0, $params);
227
            foreach ($params as $key => $val) {
228
                $key = ($hasZeroIndex && is_numeric($key)) ? $key + 1 : $key;
229
                $this->bindValue($key, $val);
230
            }
231
        }
232
233
        if ( ! $this->stmt) {
234
            $this->stmt = $this->prepare();
235
        }
236
237
        if (!sqlsrv_execute($this->stmt)) {
238
            throw SQLSrvException::fromSqlSrvErrors();
239
        }
240
241
        if ($this->lastInsertId) {
242
            sqlsrv_next_result($this->stmt);
243
            sqlsrv_fetch($this->stmt);
244
            $this->lastInsertId->setId(sqlsrv_get_field($this->stmt, 0));
245
        }
246
247
        $this->result = true;
248
    }
249
250
    /**
251
     * Prepares SQL Server statement resource
252
     *
253
     * @return resource
254
     * @throws SQLSrvException
255
     */
256
    private function prepare()
257
    {
258
        $params = [];
259
260
        foreach ($this->variables as $column => &$variable) {
261
            if (PDO::PARAM_LOB === $this->types[$column]) {
262
                $params[$column - 1] = [
263
                    &$variable,
264
                    SQLSRV_PARAM_IN,
265
                    SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY),
266
                    SQLSRV_SQLTYPE_VARBINARY('max'),
267
                ];
268
            } else {
269
                $params[$column - 1] =& $variable;
270
            }
271
        }
272
273
        $stmt = sqlsrv_prepare($this->conn, $this->sql, $params);
274
275
        if (!$stmt) {
276
            throw SQLSrvException::fromSqlSrvErrors();
277
        }
278
279
        return $stmt;
280
    }
281
282
    /**
283
     * {@inheritdoc}
284
     */
285 View Code Duplication
    public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
286
    {
287
        $this->defaultFetchMode          = $fetchMode;
288
        $this->defaultFetchClass         = $arg2 ?: $this->defaultFetchClass;
289
        $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;
290
291
        return true;
292
    }
293
294
    /**
295
     * {@inheritdoc}
296
     */
297
    public function getIterator()
298
    {
299
        return new StatementIterator($this);
300
    }
301
302
    /**
303
     * {@inheritdoc}
304
     *
305
     * @throws SQLSrvException
306
     */
307
    public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
308
    {
309
        // do not try fetching from the statement if it's not expected to contain result
310
        // in order to prevent exceptional situation
311
        if (!$this->result) {
312
            return false;
313
        }
314
315
        $args      = func_get_args();
316
        $fetchMode = $fetchMode ?: $this->defaultFetchMode;
317
318
        if (isset(self::$fetchMap[$fetchMode])) {
319
            return sqlsrv_fetch_array($this->stmt, self::$fetchMap[$fetchMode]) ?: false;
320
        }
321
322
        if (in_array($fetchMode, [PDO::FETCH_OBJ, PDO::FETCH_CLASS], true)) {
323
            $className = $this->defaultFetchClass;
324
            $ctorArgs  = $this->defaultFetchClassCtorArgs;
325
326
            if (count($args) >= 2) {
327
                $className = $args[1];
328
                $ctorArgs  = isset($args[2]) ? $args[2] : [];
329
            }
330
331
            return sqlsrv_fetch_object($this->stmt, $className, $ctorArgs) ?: false;
332
        }
333
334
        throw new SQLSrvException('Fetch mode is not supported!');
335
    }
336
337
    /**
338
     * {@inheritdoc}
339
     */
340 View Code Duplication
    public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
341
    {
342
        $rows = [];
343
344
        switch ($fetchMode) {
345
            case PDO::FETCH_CLASS:
346
                while ($row = call_user_func_array([$this, 'fetch'], func_get_args())) {
347
                    $rows[] = $row;
348
                }
349
                break;
350
            case PDO::FETCH_COLUMN:
351
                while ($row = $this->fetchColumn()) {
352
                    $rows[] = $row;
353
                }
354
                break;
355
            default:
356
                while ($row = $this->fetch($fetchMode)) {
357
                    $rows[] = $row;
358
                }
359
        }
360
361
        return $rows;
362
    }
363
364
    /**
365
     * {@inheritdoc}
366
     */
367 View Code Duplication
    public function fetchColumn($columnIndex = 0)
368
    {
369
        $row = $this->fetch(PDO::FETCH_NUM);
370
371
        if (false === $row) {
372
            return false;
373
        }
374
375
        return isset($row[$columnIndex]) ? $row[$columnIndex] : null;
376
    }
377
378
    /**
379
     * {@inheritdoc}
380
     */
381
    public function rowCount()
382
    {
383
        return sqlsrv_rows_affected($this->stmt);
0 ignored issues
show
Bug Best Practice introduced by
The expression return sqlsrv_rows_affected($this->stmt) also could return the type false which is incompatible with the return type mandated by Doctrine\DBAL\Driver\Statement::rowCount() of integer.
Loading history...
384
    }
385
}
386