1 | <?php |
||
2 | |||
3 | namespace SilverStripe\ORM\Connect; |
||
4 | |||
5 | use SilverStripe\Core\Config\Config; |
||
6 | use mysqli; |
||
7 | use mysqli_stmt; |
||
8 | |||
9 | /** |
||
10 | * Connector for MySQL using the MySQLi method |
||
11 | */ |
||
12 | class MySQLiConnector extends DBConnector |
||
13 | { |
||
14 | |||
15 | /** |
||
16 | * Default strong SSL cipher to be used |
||
17 | * |
||
18 | * @config |
||
19 | * @var string |
||
20 | */ |
||
21 | private static $ssl_cipher_default = 'DHE-RSA-AES256-SHA'; |
||
22 | |||
23 | /** |
||
24 | * Connection to the MySQL database |
||
25 | * |
||
26 | * @var mysqli |
||
27 | */ |
||
28 | protected $dbConn = null; |
||
29 | |||
30 | /** |
||
31 | * Name of the currently selected database |
||
32 | * |
||
33 | * @var string |
||
34 | */ |
||
35 | protected $databaseName = null; |
||
36 | |||
37 | /** |
||
38 | * The most recent statement returned from MySQLiConnector->preparedQuery |
||
39 | * |
||
40 | * @var mysqli_stmt |
||
41 | */ |
||
42 | protected $lastStatement = null; |
||
43 | |||
44 | /** |
||
45 | * Store the most recent statement for later use |
||
46 | * |
||
47 | * @param mysqli_stmt $statement |
||
48 | */ |
||
49 | protected function setLastStatement($statement) |
||
50 | { |
||
51 | $this->lastStatement = $statement; |
||
52 | } |
||
53 | |||
54 | /** |
||
55 | * Retrieve a prepared statement for a given SQL string |
||
56 | * |
||
57 | * @param string $sql |
||
58 | * @param boolean &$success |
||
59 | * @return mysqli_stmt |
||
60 | */ |
||
61 | public function prepareStatement($sql, &$success) |
||
62 | { |
||
63 | // Record last statement for error reporting |
||
64 | $statement = $this->dbConn->stmt_init(); |
||
65 | $this->setLastStatement($statement); |
||
66 | $success = $statement->prepare($sql); |
||
67 | return $statement; |
||
68 | } |
||
69 | |||
70 | public function connect($parameters, $selectDB = false) |
||
71 | { |
||
72 | // Normally $selectDB is set to false by the MySQLDatabase controller, as per convention |
||
73 | $selectedDB = ($selectDB && !empty($parameters['database'])) ? $parameters['database'] : null; |
||
74 | |||
75 | // Connection charset and collation |
||
76 | $connCharset = Config::inst()->get('SilverStripe\ORM\Connect\MySQLDatabase', 'connection_charset'); |
||
77 | $connCollation = Config::inst()->get('SilverStripe\ORM\Connect\MySQLDatabase', 'connection_collation'); |
||
78 | |||
79 | $this->dbConn = mysqli_init(); |
||
80 | |||
81 | // Set SSL parameters if they exist. All parameters are required. |
||
82 | if (array_key_exists('ssl_key', $parameters) && |
||
83 | array_key_exists('ssl_cert', $parameters) && |
||
84 | array_key_exists('ssl_ca', $parameters)) { |
||
85 | $this->dbConn->ssl_set( |
||
86 | $parameters['ssl_key'], |
||
87 | $parameters['ssl_cert'], |
||
88 | $parameters['ssl_ca'], |
||
89 | dirname($parameters['ssl_ca']), |
||
90 | array_key_exists('ssl_cipher', $parameters) |
||
91 | ? $parameters['ssl_cipher'] |
||
92 | : self::config()->get('ssl_cipher_default') |
||
93 | ); |
||
94 | } |
||
95 | |||
96 | $this->dbConn->real_connect( |
||
97 | $parameters['server'], |
||
98 | $parameters['username'], |
||
99 | $parameters['password'], |
||
100 | $selectedDB, |
||
101 | !empty($parameters['port']) ? $parameters['port'] : ini_get("mysqli.default_port") |
||
102 | ); |
||
103 | |||
104 | if ($this->dbConn->connect_error) { |
||
105 | $this->databaseError("Couldn't connect to MySQL database | " . $this->dbConn->connect_error); |
||
106 | } |
||
107 | |||
108 | // Set charset and collation if given and not null. Can explicitly set to empty string to omit |
||
109 | $charset = isset($parameters['charset']) |
||
110 | ? $parameters['charset'] |
||
111 | : $connCharset; |
||
112 | |||
113 | if (!empty($charset)) { |
||
114 | $this->dbConn->set_charset($charset); |
||
115 | } |
||
116 | |||
117 | $collation = isset($parameters['collation']) |
||
118 | ? $parameters['collation'] |
||
119 | : $connCollation; |
||
120 | |||
121 | if (!empty($collation)) { |
||
122 | $this->dbConn->query("SET collation_connection = {$collation}"); |
||
123 | } |
||
124 | } |
||
125 | |||
126 | public function __destruct() |
||
127 | { |
||
128 | if (is_resource($this->dbConn)) { |
||
129 | mysqli_close($this->dbConn); |
||
130 | $this->dbConn = null; |
||
131 | } |
||
132 | } |
||
133 | |||
134 | public function escapeString($value) |
||
135 | { |
||
136 | return $this->dbConn->real_escape_string($value); |
||
137 | } |
||
138 | |||
139 | public function quoteString($value) |
||
140 | { |
||
141 | $value = $this->escapeString($value); |
||
142 | return "'$value'"; |
||
143 | } |
||
144 | |||
145 | public function getVersion() |
||
146 | { |
||
147 | return $this->dbConn->server_info; |
||
148 | } |
||
149 | |||
150 | /** |
||
151 | * Invoked before any query is executed |
||
152 | * |
||
153 | * @param string $sql |
||
154 | */ |
||
155 | protected function beforeQuery($sql) |
||
156 | { |
||
157 | // Clear the last statement |
||
158 | $this->setLastStatement(null); |
||
159 | } |
||
160 | |||
161 | public function query($sql, $errorLevel = E_USER_ERROR) |
||
162 | { |
||
163 | $this->beforeQuery($sql); |
||
164 | |||
165 | // Benchmark query |
||
166 | $handle = $this->dbConn->query($sql, MYSQLI_STORE_RESULT); |
||
167 | |||
168 | if (!$handle || $this->dbConn->error) { |
||
169 | $this->databaseError($this->getLastError(), $errorLevel, $sql); |
||
170 | return null; |
||
171 | } |
||
172 | |||
173 | // Some non-select queries return true on success |
||
174 | return new MySQLQuery($this, $handle); |
||
175 | } |
||
176 | |||
177 | /** |
||
178 | * Prepares the list of parameters in preparation for passing to mysqli_stmt_bind_param |
||
179 | * |
||
180 | * @param array $parameters List of parameters |
||
181 | * @param array &$blobs Out parameter for list of blobs to bind separately |
||
182 | * @return array List of parameters appropriate for mysqli_stmt_bind_param function |
||
183 | */ |
||
184 | public function parsePreparedParameters($parameters, &$blobs) |
||
185 | { |
||
186 | $types = ''; |
||
187 | $values = array(); |
||
188 | $blobs = array(); |
||
189 | for ($index = 0; $index < count($parameters); $index++) { |
||
0 ignored issues
–
show
|
|||
190 | $value = $parameters[$index]; |
||
191 | $phpType = gettype($value); |
||
192 | |||
193 | // Allow overriding of parameter type using an associative array |
||
194 | if ($phpType === 'array') { |
||
195 | $phpType = $value['type']; |
||
196 | $value = $value['value']; |
||
197 | } |
||
198 | |||
199 | // Convert php variable type to one that makes mysqli_stmt_bind_param happy |
||
200 | // @see http://www.php.net/manual/en/mysqli-stmt.bind-param.php |
||
201 | switch ($phpType) { |
||
202 | case 'boolean': |
||
203 | case 'integer': |
||
204 | $types .= 'i'; |
||
205 | break; |
||
206 | case 'float': // Not actually returnable from gettype |
||
207 | case 'double': |
||
208 | $types .= 'd'; |
||
209 | break; |
||
210 | case 'object': // Allowed if the object or resource has a __toString method |
||
211 | case 'resource': |
||
212 | case 'string': |
||
213 | case 'NULL': // Take care that a where clause should use "where XX is null" not "where XX = null" |
||
214 | $types .= 's'; |
||
215 | break; |
||
216 | case 'blob': |
||
217 | $types .= 'b'; |
||
218 | // Blobs must be sent via send_long_data and set to null here |
||
219 | $blobs[] = array( |
||
220 | 'index' => $index, |
||
221 | 'value' => $value |
||
222 | ); |
||
223 | $value = null; |
||
224 | break; |
||
225 | case 'array': |
||
226 | case 'unknown type': |
||
227 | default: |
||
228 | user_error( |
||
229 | "Cannot bind parameter \"$value\" as it is an unsupported type ($phpType)", |
||
230 | E_USER_ERROR |
||
231 | ); |
||
232 | break; |
||
233 | } |
||
234 | $values[] = $value; |
||
235 | } |
||
236 | return array_merge(array($types), $values); |
||
237 | } |
||
238 | |||
239 | /** |
||
240 | * Binds a list of parameters to a statement |
||
241 | * |
||
242 | * @param mysqli_stmt $statement MySQLi statement |
||
243 | * @param array $parameters List of parameters to pass to bind_param |
||
244 | */ |
||
245 | public function bindParameters(mysqli_stmt $statement, array $parameters) |
||
246 | { |
||
247 | // Because mysqli_stmt::bind_param arguments must be passed by reference |
||
248 | // we need to do a bit of hackery |
||
249 | $boundNames = []; |
||
250 | for ($i = 0; $i < count($parameters); $i++) { |
||
0 ignored issues
–
show
It seems like you are calling the size function
count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}
// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
|
|||
251 | $boundName = "param$i"; |
||
252 | $$boundName = $parameters[$i]; |
||
253 | $boundNames[] = &$$boundName; |
||
254 | } |
||
255 | call_user_func_array(array($statement, 'bind_param'), $boundNames); |
||
256 | } |
||
257 | |||
258 | public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR) |
||
259 | { |
||
260 | // Shortcut to basic query when not given parameters |
||
261 | if (empty($parameters)) { |
||
262 | return $this->query($sql, $errorLevel); |
||
263 | } |
||
264 | |||
265 | $this->beforeQuery($sql); |
||
266 | |||
267 | // Type check, identify, and prepare parameters for passing to the statement bind function |
||
268 | $parsedParameters = $this->parsePreparedParameters($parameters, $blobs); |
||
269 | |||
270 | // Benchmark query |
||
271 | $statement = $this->prepareStatement($sql, $success); |
||
272 | if ($success) { |
||
273 | if ($parsedParameters) { |
||
274 | $this->bindParameters($statement, $parsedParameters); |
||
275 | } |
||
276 | |||
277 | // Bind any blobs given |
||
278 | foreach ($blobs as $blob) { |
||
279 | $statement->send_long_data($blob['index'], $blob['value']); |
||
280 | } |
||
281 | |||
282 | // Safely execute the statement |
||
283 | $statement->execute(); |
||
284 | } |
||
285 | |||
286 | if (!$success || $statement->error) { |
||
287 | $values = $this->parameterValues($parameters); |
||
288 | $this->databaseError($this->getLastError(), $errorLevel, $sql, $values); |
||
289 | return null; |
||
290 | } |
||
291 | |||
292 | // Non-select queries will have no result data |
||
293 | $metaData = $statement->result_metadata(); |
||
294 | if ($metaData) { |
||
295 | return new MySQLStatement($statement, $metaData); |
||
296 | } else { |
||
297 | // Replicate normal behaviour of ->query() on non-select calls |
||
298 | return new MySQLQuery($this, true); |
||
299 | } |
||
300 | } |
||
301 | |||
302 | public function selectDatabase($name) |
||
303 | { |
||
304 | if ($this->dbConn->select_db($name)) { |
||
305 | $this->databaseName = $name; |
||
306 | return true; |
||
307 | } else { |
||
308 | return false; |
||
309 | } |
||
310 | } |
||
311 | |||
312 | public function getSelectedDatabase() |
||
313 | { |
||
314 | return $this->databaseName; |
||
315 | } |
||
316 | |||
317 | public function unloadDatabase() |
||
318 | { |
||
319 | $this->databaseName = null; |
||
320 | } |
||
321 | |||
322 | public function isActive() |
||
323 | { |
||
324 | return $this->databaseName && $this->dbConn && empty($this->dbConn->connect_error); |
||
325 | } |
||
326 | |||
327 | public function affectedRows() |
||
328 | { |
||
329 | return $this->dbConn->affected_rows; |
||
330 | } |
||
331 | |||
332 | public function getGeneratedID($table) |
||
333 | { |
||
334 | return $this->dbConn->insert_id; |
||
335 | } |
||
336 | |||
337 | public function getLastError() |
||
338 | { |
||
339 | // Check if a statement was used for the most recent query |
||
340 | if ($this->lastStatement && $this->lastStatement->error) { |
||
341 | return $this->lastStatement->error; |
||
342 | } |
||
343 | return $this->dbConn->error; |
||
344 | } |
||
345 | } |
||
346 |
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: