Failed Conditions
Pull Request — develop (#3367)
by Benjamin
73:27 queued 70:28
created

SQLSrvConnection   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 186
Duplicated Lines 0 %

Test Coverage

Coverage 53.26%

Importance

Changes 0
Metric Value
wmc 28
eloc 55
dl 0
loc 186
ccs 57
cts 107
cp 0.5326
rs 10
c 0
b 0
f 0

14 Methods

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