Issues (41)

code/PostgreSQLDatabaseConfigurationHelper.php (2 issues)

1
<?php
2
3
namespace SilverStripe\PostgreSQL;
4
5
use SilverStripe\Dev\Install\DatabaseAdapterRegistry;
6
use SilverStripe\Dev\Install\DatabaseConfigurationHelper;
7
use Exception;
8
use PDO;
9
10
/**
11
 * This is a helper class for the SS installer.
12
 *
13
 * It does all the specific checking for PostgreSQLDatabase
14
 * to ensure that the configuration is setup correctly.
15
 *
16
 * @package postgresql
17
 */
18
class PostgreSQLDatabaseConfigurationHelper implements DatabaseConfigurationHelper
19
{
20
    /**
21
     * Create a connection of the appropriate type
22
     *
23
     * @skipUpgrade
24
     * @param array $databaseConfig
25
     * @param string $error Error message passed by value
26
     * @return mixed|null Either the connection object, or null if error
27
     */
28
    protected function createConnection($databaseConfig, &$error)
29
    {
30
        $error = null;
31
        $username = empty($databaseConfig['username']) ? '' : $databaseConfig['username'];
32
        $password = empty($databaseConfig['password']) ? '' : $databaseConfig['password'];
33
        $server = $databaseConfig['server'];
34
35
        try {
36
            switch ($databaseConfig['type']) {
37
                case 'PostgreSQLDatabase':
38
                    $userPart = $username ? " user=$username" : '';
39
                    $passwordPart = $password ? " password=$password" : '';
40
                    $connstring = "host=$server port=5432 dbname=postgres{$userPart}{$passwordPart}";
41
                    $conn = pg_connect($connstring);
42
                    break;
43
                case 'PostgrePDODatabase':
44
                    // May throw a PDOException if fails
45
                    $conn = @new PDO('pgsql:host='.$server.';dbname=postgres;port=5432', $username, $password);
46
                    break;
47
                default:
48
                    $error = 'Invalid connection type: ' . $databaseConfig['type'];
49
                    return null;
50
            }
51
        } catch (Exception $ex) {
52
            $error = $ex->getMessage();
53
            return null;
54
        }
55
        if ($conn) {
56
            return $conn;
57
        } else {
58
            $error = 'PostgreSQL requires a valid username and password to determine if the server exists.';
59
            return null;
60
        }
61
    }
62
63
    public function requireDatabaseFunctions($databaseConfig)
64
    {
65
        $data = DatabaseAdapterRegistry::get_adapter($databaseConfig['type']);
66
        return !empty($data['supported']);
67
    }
68
69
    public function requireDatabaseServer($databaseConfig)
70
    {
71
        $conn = $this->createConnection($databaseConfig, $error);
72
        $success = !empty($conn);
73
        return array(
74
            'success' => $success,
75
            'error' => $error
76
        );
77
    }
78
79
    public function requireDatabaseConnection($databaseConfig)
80
    {
81
        $conn = $this->createConnection($databaseConfig, $error);
82
        $success = !empty($conn);
83
        return array(
84
            'success' => $success,
85
            'connection' => $conn,
86
            'error' => $error
87
        );
88
    }
89
90
    public function getDatabaseVersion($databaseConfig)
91
    {
92
        $conn = $this->createConnection($databaseConfig, $error);
93
        if (!$conn) {
94
            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 SilverStripe\Dev\Install...r::getDatabaseVersion() 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...
95
        } elseif ($conn instanceof PDO) {
96
            return $conn->getAttribute(PDO::ATTR_SERVER_VERSION);
97
        } elseif (is_resource($conn)) {
98
            $info = pg_version($conn);
99
            return $info['server'];
100
        } else {
101
            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 SilverStripe\Dev\Install...r::getDatabaseVersion() 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...
102
        }
103
    }
104
105
    /**
106
     * Ensure that the PostgreSQL version is at least 8.3.
107
     *
108
     * @param array $databaseConfig Associative array of db configuration, e.g. "server", "username" etc
109
     * @return array Result - e.g. array('success' => true, 'error' => 'details of error')
110
     */
111
    public function requireDatabaseVersion($databaseConfig)
112
    {
113
        $success = false;
114
        $error = '';
115
        $version = $this->getDatabaseVersion($databaseConfig);
116
117
        if ($version) {
118
            $success = version_compare($version, '8.3', '>=');
119
            if (!$success) {
120
                $error = "Your PostgreSQL version is $version. It's recommended you use at least 8.3.";
121
            }
122
        } else {
123
            $error = "Your PostgreSQL version could not be determined.";
124
        }
125
126
        return array(
127
            'success' => $success,
128
            'error' => $error
129
        );
130
    }
131
132
    /**
133
     * Helper function to execute a query
134
     *
135
     * @param mixed $conn Connection object/resource
136
     * @param string $sql SQL string to execute
137
     * @return array List of first value from each resulting row
138
     */
139
    protected function query($conn, $sql)
140
    {
141
        $items = array();
142
        if ($conn instanceof PDO) {
143
            foreach ($conn->query($sql) as $row) {
144
                $items[] = $row[0];
145
            }
146
        } elseif (is_resource($conn)) {
147
            $result =  pg_query($conn, $sql);
148
            while ($row = pg_fetch_row($result)) {
149
                $items[] = $row[0];
150
            }
151
        }
152
        return $items;
153
    }
154
155
    public function requireDatabaseOrCreatePermissions($databaseConfig)
156
    {
157
        $success = false;
158
        $alreadyExists = false;
159
        $conn = $this->createConnection($databaseConfig, $error);
160
        if ($conn) {
161
            // Check if db already exists
162
            $existingDatabases = $this->query($conn, "SELECT datname FROM pg_database");
163
            $alreadyExists = in_array($databaseConfig['database'], $existingDatabases);
164
            if ($alreadyExists) {
165
                $success = true;
166
            } else {
167
                // Check if this user has create privileges
168
                $allowedUsers = $this->query($conn, "select rolname from pg_authid where rolcreatedb = true;");
169
                $success = in_array($databaseConfig['username'], $allowedUsers);
170
            }
171
        }
172
173
        return array(
174
            'success' => $success,
175
            'alreadyExists' => $alreadyExists
176
        );
177
    }
178
179
    public function requireDatabaseAlterPermissions($databaseConfig)
180
    {
181
        $conn = $this->createConnection($databaseConfig, $error);
182
        if ($conn) {
183
            // if the account can even log in, it can alter tables
184
            return array(
185
                'success' => true,
186
                'applies' => true
187
            );
188
        }
189
        return array(
190
            'success' => false,
191
            'applies' => true
192
        );
193
    }
194
}
195