Failed Conditions
Pull Request — develop (#3367)
by Benjamin
61:25
created

SQLSrvConnection::exceptionFromSqlSrvErrors()   A

Complexity

Conditions 5
Paths 10

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 25
ccs 0
cts 0
cp 0
rs 9.4888
c 0
b 0
f 0
cc 5
nc 10
nop 0
crap 30
1
<?php
2
3
namespace Doctrine\DBAL\Driver\SQLSrv;
4
5
use Doctrine\DBAL\Driver\Connection;
6
use Doctrine\DBAL\Driver\DriverException;
7
use Doctrine\DBAL\Driver\ResultStatement;
8
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
9
use Doctrine\DBAL\Driver\Statement as DriverStatement;
10
use const SQLSRV_ERR_ERRORS;
11
use function rtrim;
12
use function sqlsrv_begin_transaction;
13
use function sqlsrv_commit;
14
use function sqlsrv_configure;
15
use function sqlsrv_connect;
16
use function sqlsrv_errors;
17
use function sqlsrv_query;
18
use function sqlsrv_rollback;
19
use function sqlsrv_rows_affected;
20
use function sqlsrv_server_info;
21
use function str_replace;
22
23
/**
24
 * SQL Server implementation for the Connection interface.
25
 */
26
class SQLSrvConnection implements Connection, ServerInfoAwareConnection
27
{
28
    /** @var resource */
29
    protected $conn;
30
31
    /** @var LastInsertId */
32
    protected $lastInsertId;
33
34
    /**
35
     * @param string  $serverName
36
     * @param mixed[] $connectionOptions
37
     *
38
     * @throws DriverException
39
     */
40
    public function __construct($serverName, $connectionOptions)
41
    {
42 26
        if (! sqlsrv_configure('WarningsReturnAsErrors', 0)) {
43
            throw self::exceptionFromSqlSrvErrors();
44 26
        }
45
46
        $this->conn = sqlsrv_connect($serverName, $connectionOptions);
0 ignored issues
show
Documentation Bug introduced by
It seems like sqlsrv_connect($serverName, $connectionOptions) can also be of type false. However, the property $conn is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
47
        if (! $this->conn) {
48 26
            throw self::exceptionFromSqlSrvErrors();
49 26
        }
50 1
        $this->lastInsertId = new LastInsertId();
51
    }
52 26
53 26
    /**
54
     * {@inheritdoc}
55
     */
56
    public function getServerVersion()
57
    {
58 23
        $serverInfo = sqlsrv_server_info($this->conn);
59
60 23
        return $serverInfo['SQLServerVersion'];
61
    }
62 23
63
    /**
64
     * {@inheritdoc}
65
     */
66
    public function requiresQueryForServerVersion()
67
    {
68 24
        return false;
69
    }
70 24
71
    /**
72
     * {@inheritDoc}
73
     */
74
    public function prepare(string $sql) : DriverStatement
75
    {
76 242
        return new SQLSrvStatement($this->conn, $sql, $this->lastInsertId);
77
    }
78 242
79
    /**
80
     * {@inheritDoc}
81
     */
82
    public function query(string $sql) : ResultStatement
83
    {
84 179
        $stmt = $this->prepare($sql);
85
        $stmt->execute();
86 179
87 179
        return $stmt;
88
    }
89 179
90
    /**
91
     * {@inheritDoc}
92
     */
93
    public function quote(string $value) : string
94
    {
95 8
        return "'" . str_replace("'", "''", $value) . "'";
96
    }
97 8
98 2
    /**
99 7
     * {@inheritDoc}
100
     */
101
    public function exec(string $statement) : int
102
    {
103 7
        $stmt = sqlsrv_query($this->conn, $statement);
104
105
        if ($stmt === false) {
106
            throw self::exceptionFromSqlSrvErrors();
107
        }
108
109 160
        return sqlsrv_rows_affected($stmt);
110
    }
111 160
112
    /**
113 160
     * {@inheritDoc}
114 101
     */
115
    public function lastInsertId(?string $name = null) : string
116
    {
117 135
        if ($name !== null) {
118
            $stmt = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?');
119
            $stmt->execute([$name]);
120
        } else {
121
            $stmt = $this->query('SELECT @@IDENTITY');
122
        }
123 3
124
        $result = $stmt->fetchColumn();
125 3
126 1
        if ($name !== null) {
127 1
            if ($result === false) {
128
                throw DriverException::noSuchSequence($name);
129 2
            }
130
        } else {
131
            if ($result === null) {
132 3
                throw DriverException::noInsertId();
133
            }
134
        }
135
136
        return (string) $result;
137
    }
138 15
139
    /**
140 15
     * {@inheritDoc}
141
     */
142
    public function beginTransaction() : void
143 15
    {
144
        if (! sqlsrv_begin_transaction($this->conn)) {
145
            throw self::exceptionFromSqlSrvErrors();
146
        }
147
    }
148 6
149
    /**
150 6
     * {@inheritDoc}
151
     */
152
    public function commit() : void
153 6
    {
154
        if (! sqlsrv_commit($this->conn)) {
155
            throw self::exceptionFromSqlSrvErrors();
156
        }
157
    }
158 10
159
    /**
160 10
     * {@inheritDoc}
161
     */
162
    public function rollBack() : void
163 10
    {
164
        if (! sqlsrv_rollback($this->conn)) {
165
            throw self::exceptionFromSqlSrvErrors();
166
        }
167
    }
168
169
    /**
170
     * {@inheritDoc}
171
     */
172
    public function errorCode()
173
    {
174
        $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
175
        if ($errors) {
176
            return $errors[0]['code'];
177
        }
178
179
        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\Connection::errorCode() of null|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...
180
    }
181
182
    /**
183
     * {@inheritDoc}
184
     */
185
    public function errorInfo()
186
    {
187
        return sqlsrv_errors(SQLSRV_ERR_ERRORS);
188
    }
189
190
    /**
191
     * Helper method to turn sql server errors into exception.
192
     */
193
    public static function exceptionFromSqlSrvErrors() : DriverException
194
    {
195
        $errors    = sqlsrv_errors(SQLSRV_ERR_ERRORS);
196
        $message   = '';
197
        $sqlState  = null;
198
        $errorCode = null;
199
200
        foreach ($errors as $error) {
201
            $message .= 'SQLSTATE [' . $error['SQLSTATE'] . ', ' . $error['code'] . ']: ' . $error['message'] . "\n";
202
203
            if ($sqlState === null) {
204
                $sqlState = $error['SQLSTATE'];
205
            }
206
207
            if ($errorCode !== null) {
208
                continue;
209
            }
210
211
            $errorCode = $error['code'];
212
        }
213
        if (! $message) {
214
            $message = 'SQL Server error occurred but no error message was retrieved from driver.';
215
        }
216
217
        return new DriverException(rtrim($message), $sqlState, $errorCode);
218
    }
219
}
220