Failed Conditions
Pull Request — develop (#3367)
by Benjamin
12:18
created

SQLSrvConnection::exceptionFromSqlSrvErrors()   A

Complexity

Conditions 5
Paths 10

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5.675

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 25
ccs 14
cts 20
cp 0.7
rs 9.4888
c 0
b 0
f 0
cc 5
nc 10
nop 0
crap 5.675
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 Doctrine\DBAL\ParameterType;
11
use const SQLSRV_ERR_ERRORS;
12
use function is_float;
13
use function is_int;
14
use function rtrim;
15
use function sprintf;
16
use function sqlsrv_begin_transaction;
17
use function sqlsrv_commit;
18
use function sqlsrv_configure;
19
use function sqlsrv_connect;
20
use function sqlsrv_errors;
21
use function sqlsrv_query;
22
use function sqlsrv_rollback;
23
use function sqlsrv_rows_affected;
24
use function sqlsrv_server_info;
25
use function str_replace;
26
27
/**
28
 * SQL Server implementation for the Connection interface.
29
 */
30
class SQLSrvConnection implements Connection, ServerInfoAwareConnection
31
{
32
    /** @var resource */
33
    protected $conn;
34
35
    /** @var LastInsertId */
36
    protected $lastInsertId;
37
38
    /**
39
     * @param string  $serverName
40
     * @param mixed[] $connectionOptions
41
     *
42
     * @throws DriverException
43
     */
44 26
    public function __construct($serverName, $connectionOptions)
45
    {
46 26
        if (! sqlsrv_configure('WarningsReturnAsErrors', 0)) {
47
            throw self::exceptionFromSqlSrvErrors();
48
        }
49
50 26
        $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...
51 26
        if (! $this->conn) {
52 1
            throw self::exceptionFromSqlSrvErrors();
53
        }
54 26
        $this->lastInsertId = new LastInsertId();
55 26
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60 23
    public function getServerVersion()
61
    {
62 23
        $serverInfo = sqlsrv_server_info($this->conn);
63
64 23
        return $serverInfo['SQLServerVersion'];
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70 24
    public function requiresQueryForServerVersion()
71
    {
72 24
        return false;
73
    }
74
75
    /**
76
     * {@inheritDoc}
77
     */
78 242
    public function prepare(string $sql) : DriverStatement
79
    {
80 242
        return new SQLSrvStatement($this->conn, $sql, $this->lastInsertId);
81
    }
82
83
    /**
84
     * {@inheritDoc}
85
     */
86 179
    public function query(string $sql) : ResultStatement
87
    {
88 179
        $stmt = $this->prepare($sql);
89 179
        $stmt->execute();
90
91 179
        return $stmt;
92
    }
93
94
    /**
95
     * {@inheritDoc}
96
     */
97 8
    public function quote($value, $type = ParameterType::STRING)
98
    {
99 8
        if (is_int($value)) {
100 2
            return $value;
101 7
        } elseif (is_float($value)) {
102
            return sprintf('%F', $value);
103
        }
104
105 7
        return "'" . str_replace("'", "''", $value) . "'";
106
    }
107
108
    /**
109
     * {@inheritDoc}
110
     */
111 160
    public function exec(string $statement) : int
112
    {
113 160
        $stmt = sqlsrv_query($this->conn, $statement);
114
115 160
        if ($stmt === false) {
116 101
            throw self::exceptionFromSqlSrvErrors();
117
        }
118
119 135
        return sqlsrv_rows_affected($stmt);
120
    }
121
122
    /**
123
     * {@inheritDoc}
124
     */
125 3
    public function lastInsertId($name = null)
126
    {
127 3
        if ($name !== null) {
128 1
            $stmt = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?');
129 1
            $stmt->execute([$name]);
130
        } else {
131 2
            $stmt = $this->query('SELECT @@IDENTITY');
132
        }
133
134 3
        return $stmt->fetchColumn();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $stmt->fetchColumn() also could return the type false which is incompatible with the return type mandated by Doctrine\DBAL\Driver\Connection::lastInsertId() of string.
Loading history...
135
    }
136
137
    /**
138
     * {@inheritDoc}
139
     */
140 15
    public function beginTransaction()
141
    {
142 15
        if (! sqlsrv_begin_transaction($this->conn)) {
143
            throw self::exceptionFromSqlSrvErrors();
144
        }
145 15
    }
146
147
    /**
148
     * {@inheritDoc}
149
     */
150 6
    public function commit()
151
    {
152 6
        if (! sqlsrv_commit($this->conn)) {
153
            throw self::exceptionFromSqlSrvErrors();
154
        }
155 6
    }
156
157
    /**
158
     * {@inheritDoc}
159
     */
160 10
    public function rollBack()
161
    {
162 10
        if (! sqlsrv_rollback($this->conn)) {
163
            throw self::exceptionFromSqlSrvErrors();
164
        }
165 10
    }
166
167
    /**
168
     * {@inheritDoc}
169
     */
170
    public function errorCode()
171
    {
172
        $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
173
        if ($errors) {
174
            return $errors[0]['code'];
175
        }
176
177
        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...
178
    }
179
180
    /**
181
     * {@inheritDoc}
182
     */
183
    public function errorInfo()
184
    {
185
        return sqlsrv_errors(SQLSRV_ERR_ERRORS);
186
    }
187
188
    /**
189
     * Helper method to turn sql server errors into exception.
190
     */
191 103
    public static function exceptionFromSqlSrvErrors() : DriverException
192
    {
193 103
        $errors    = sqlsrv_errors(SQLSRV_ERR_ERRORS);
194 103
        $message   = '';
195 103
        $sqlState  = null;
196 103
        $errorCode = null;
197
198 103
        foreach ($errors as $error) {
199 103
            $message .= 'SQLSTATE [' . $error['SQLSTATE'] . ', ' . $error['code'] . ']: ' . $error['message'] . "\n";
200
201 103
            if ($sqlState === null) {
202 103
                $sqlState = $error['SQLSTATE'];
203
            }
204
205 103
            if ($errorCode !== null) {
206 1
                continue;
207
            }
208
209 103
            $errorCode = $error['code'];
210
        }
211 103
        if (! $message) {
212
            $message = 'SQL Server error occurred but no error message was retrieved from driver.';
213
        }
214
215 103
        return new DriverException(rtrim($message), $sqlState, $errorCode);
216
    }
217
}
218