Completed
Push — master ( 9309cf...551ccf )
by Sean
10s
created

MSSQLDatabaseConfigurationHelper   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 252
Duplicated Lines 7.54 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 41
c 2
b 1
f 0
lcom 1
cbo 1
dl 19
loc 252
rs 8.2769

12 Methods

Rating   Name   Duplication   Size   Complexity  
A isAzure() 0 5 1
C createConnection() 0 57 11
A getPDODriver() 0 11 4
A quote() 0 12 3
B query() 0 20 7
A requireDatabaseFunctions() 0 5 1
A requireDatabaseServer() 9 10 1
A requireDatabaseConnection() 10 11 1
A getDatabaseVersion() 0 6 2
A requireDatabaseVersion() 0 20 3
B requireDatabaseOrCreatePermissions() 0 29 4
A requireDatabaseAlterPermissions() 0 18 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like MSSQLDatabaseConfigurationHelper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MSSQLDatabaseConfigurationHelper, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\MSSQL;
4
5
use DatabaseConfigurationHelper;
6
use PDO;
7
use Exception;
8
use DatabaseAdapterRegistry;
9
10
/**
11
 * This is a helper class for the SS installer.
12
 *
13
 * It does all the specific checking for MSSQLDatabase
14
 * to ensure that the configuration is setup correctly.
15
 *
16
 * @package mssql
17
 */
18
class MSSQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
19
{
20
21
    protected function isAzure($databaseConfig)
22
    {
23
        /** @skipUpgrade */
24
        return $databaseConfig['type'] === 'MSSQLAzureDatabase';
25
    }
26
27
    /**
28
     * Create a connection of the appropriate type
29
     *
30
     * @param array $databaseConfig
31
     * @param string $error Error message passed by value
32
     * @return mixed|null Either the connection object, or null if error
33
     */
34
    protected function createConnection($databaseConfig, &$error)
35
    {
36
        $error = null;
37
        /** @skipUpgrade */
38
        try {
39
            switch ($databaseConfig['type']) {
40
                case 'MSSQLDatabase':
41
                case 'MSSQLAzureDatabase':
42
                    $parameters = array(
43
                        'UID' => $databaseConfig['username'],
44
                        'PWD' => $databaseConfig['password']
45
                    );
46
47
                    // Azure has additional parameter requirements
48
                    if ($this->isAzure($databaseConfig)) {
49
                        $parameters['database'] = $databaseConfig['database'];
50
                        $parameters['multipleactiveresultsets'] = 0;
51
                    }
52
                    $conn = @sqlsrv_connect($databaseConfig['server'], $parameters);
53
                    if ($conn) {
54
                        return $conn;
55
                    }
56
57
                    // Get error
58
                    if ($errors = sqlsrv_errors()) {
59
                        $error = '';
60
                        foreach ($errors as $detail) {
61
                            $error .= "{$detail['message']}\n";
62
                        }
63
                    } else {
64
                        $error = 'Unknown connection error';
65
                    }
66
                    return null;
67
                case 'MSSQLPDODatabase':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
68
                    $driver = $this->getPDODriver();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $driver is correct as $this->getPDODriver() (which targets SilverStripe\MSSQL\MSSQL...nHelper::getPDODriver()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
69
                    if (!$driver) {
70
                        $error = 'No supported PDO driver';
71
                        return null;
72
                    }
73
74
                    // May throw a PDOException if fails
75
                    $conn = @new PDO($driver.':Server='.$databaseConfig['server'], $databaseConfig['username'], $databaseConfig['password']);
76
                    if ($conn) {
77
                        return $conn;
78
                    } else {
79
                        $error = 'Unknown connection error';
80
                        return null;
81
                    }
82
                default:
83
                    $error = 'Invalid connection type';
84
                    return null;
85
            }
86
        } catch (Exception $ex) {
87
            $error = $ex->getMessage();
88
            return null;
89
        }
90
    }
91
92
    /**
93
     * Get supported PDO driver
94
     *
95
     * @return null
96
     */
97
    public static function getPDODriver() {
98
        if (!class_exists('PDO')) {
99
            return null;
100
        }
101
        foreach(PDO::getAvailableDrivers() as $driver) {
102
            if(in_array($driver, array('sqlsrv', 'dblib'))) {
103
                return $driver;
104
            }
105
        }
106
        return null;
107
    }
108
109
    /**
110
     * Helper function to quote a string value
111
     *
112
     * @param mixed $conn Connection object/resource
113
     * @param string $value Value to quote
114
     * @return string Quoted strieng
115
     */
116
    protected function quote($conn, $value)
117
    {
118
        if ($conn instanceof PDO) {
119
            return $conn->quote($value);
120
        } elseif (is_resource($conn)) {
121
            $value = str_replace("'", "''", $value);
122
            $value = str_replace("\0", "[NULL]", $value);
123
            return "N'$value'";
124
        } else {
125
            user_error('Invalid database connection', E_USER_ERROR);
126
        }
127
    }
128
129
    /**
130
     * Helper function to execute a query
131
     *
132
     * @param mixed $conn Connection object/resource
133
     * @param string $sql SQL string to execute
134
     * @return array List of first value from each resulting row
135
     */
136
    protected function query($conn, $sql)
137
    {
138
        $items = array();
139
        if ($conn instanceof PDO) {
140
            $result = $conn->query($sql);
141
            if ($result) {
142
                foreach ($result as $row) {
143
                    $items[] = $row[0];
144
                }
145
            }
146
        } elseif (is_resource($conn)) {
147
            $result = sqlsrv_query($conn, $sql);
148
            if ($result) {
149
                while ($row = sqlsrv_fetch_array($result, SQLSRV_FETCH_NUMERIC)) {
150
                    $items[] = $row[0];
151
                }
152
            }
153
        }
154
        return $items;
155
    }
156
157
    public function requireDatabaseFunctions($databaseConfig)
158
    {
159
        $data = DatabaseAdapterRegistry::get_adapter($databaseConfig['type']);
160
        return !empty($data['supported']);
161
    }
162
163 View Code Duplication
    public function requireDatabaseServer($databaseConfig)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
164
    {
165
        $conn = $this->createConnection($databaseConfig, $error);
166
        $success = !empty($conn);
167
168
        return array(
169
            'success' => $success,
170
            'error' => $error
171
        );
172
    }
173
174 View Code Duplication
    public function requireDatabaseConnection($databaseConfig)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
175
    {
176
        $conn = $this->createConnection($databaseConfig, $error);
177
        $success = !empty($conn);
178
179
        return array(
180
            'success' => $success,
181
            'connection' => $conn,
182
            'error' => $error
183
        );
184
    }
185
186
    public function getDatabaseVersion($databaseConfig)
187
    {
188
        $conn = $this->createConnection($databaseConfig, $error);
189
        $result = $this->query($conn, "SELECT CONVERT(char(15), SERVERPROPERTY('ProductVersion'))");
190
        return empty($result) ? 0 : reset($result);
191
    }
192
193
    /**
194
     * Ensure that the SQL Server version is at least 10.00.2531 (SQL Server 2008 SP1).
195
     *
196
     * @see http://www.sqlteam.com/article/sql-server-versions
197
     * @param array $databaseConfig Associative array of db configuration, e.g. "server", "username" etc
198
     * @return array Result - e.g. array('success' => true, 'error' => 'details of error')
199
     */
200
    public function requireDatabaseVersion($databaseConfig)
201
    {
202
        $success = false;
203
        $error = '';
204
        $version = $this->getDatabaseVersion($databaseConfig);
205
206
        if ($version) {
207
            $success = version_compare($version, '10.00.2531', '>=');
208
            if (!$success) {
209
                $error = "Your SQL Server version is $version. It's recommended you use at least 10.00.2531 (SQL Server 2008 SP1).";
210
            }
211
        } else {
212
            $error = "Your SQL Server version could not be determined.";
213
        }
214
215
        return array(
216
            'success' => $success,
217
            'error' => $error
218
        );
219
    }
220
221
    public function requireDatabaseOrCreatePermissions($databaseConfig)
222
    {
223
        $conn = $this->createConnection($databaseConfig, $error);
224
        /** @skipUpgrade */
225
        if (empty($conn)) {
226
            $success = false;
227
            $alreadyExists = false;
228
        } elseif ($databaseConfig['type'] == 'MSSQLAzureDatabase') {
229
            // Don't bother with DB selection for azure, as it's not supported
230
            $success = true;
231
            $alreadyExists = true;
232
        } else {
233
            // does this database exist already?
234
            $list = $this->query($conn, 'SELECT NAME FROM sys.sysdatabases');
235
            if (in_array($databaseConfig['database'], $list)) {
236
                $success = true;
237
                $alreadyExists = true;
238
            } else {
239
                $permissions = $this->query($conn, "select COUNT(*) from sys.fn_my_permissions('','') where permission_name like 'CREATE ANY DATABASE' or permission_name like 'CREATE DATABASE';");
240
                $success = $permissions[0] > 0;
241
                $alreadyExists = false;
242
            }
243
        }
244
245
        return array(
246
            'success' => $success,
247
            'alreadyExists' => $alreadyExists
248
        );
249
    }
250
251
    public function requireDatabaseAlterPermissions($databaseConfig)
252
    {
253
        $success = false;
254
        $conn = $this->createConnection($databaseConfig, $error);
255
        if (!empty($conn)) {
256
            if (!$this->isAzure($databaseConfig)) {
257
                // Make sure to select the current database when checking permission against this database
258
                $this->query($conn, "USE \"{$databaseConfig['database']}\"");
259
            }
260
            $permissions = $this->query($conn, "select COUNT(*) from sys.fn_my_permissions(NULL,'DATABASE') WHERE permission_name like 'create table';");
261
            $success = $permissions[0] > 0;
262
        }
263
264
        return array(
265
            'success' => $success,
266
            'applies' => true
267
        );
268
    }
269
}
270