MySql::disconnect()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
namespace Darya\Database\Connection;
3
4
use mysqli as php_mysqli;
5
use mysqli_result;
6
use mysqli_stmt;
7
use Darya\Database\AbstractConnection;
8
use Darya\Database\Error;
9
use Darya\Database\Result;
10
use Darya\Database\Query;
11
use Darya\Database\Query\Translator;
12
13
/**
14
 * Darya's MySQL database interface. Uses mysqli.
15
 *
16
 * @author Chris Andrew <[email protected]>
17
 */
18
class MySql extends AbstractConnection
19
{
20
	/**
21
	 * @var php_mysqli
22
	 */
23
	protected $connection;
24
25
	/**
26
	 * Copy a flat array. Aids copying fetched results without the mysqlnd
27
	 * extension installed without retaining references to array elements.
28
	 *
29
	 * Who knew references could be so awkward to get rid of?
30
	 *
31
	 * @param array $array
32
	 * @return array
33
	 */
34
	protected static function copyArray($array)
35
	{
36
		$copy = array();
37
38
		foreach ($array as $key => $value) {
39
			$copy[$key] = $value;
40
		}
41
42
		return $copy;
43
	}
44
45
	/**
46
	 * Fetch result data from the given MySQLi statement.
47
	 *
48
	 * Expects the statement to have been executed.
49
	 *
50
	 * Attempts to use mysqli_stmt::get_result() and mysqli_result::fetch_all(),
51
	 * but falls back to fetching from the statement directly if get_result()
52
	 * isn't found (mysqlnd isn't installed).
53
	 *
54
	 * @param mysqli_stmt $statement
55
	 * @return array array($data, $fields, $count)
56
	 */
57
	protected function fetchResult(mysqli_stmt $statement)
58
	{
59
		if (!method_exists($statement, 'get_result')) {
60
			return $this->fetchResultWithoutNativeDriver($statement);
61
		}
62
63
		$result = $statement->get_result();
64
65
		if (is_object($result) && $result instanceof mysqli_result) {
66
			return array(
67
				$result->fetch_all(MYSQLI_ASSOC),
68
				$result->fetch_fields(),
69
				$result->num_rows
70
			);
71
		}
72
73
		return array(array(), array(), null);
74
	}
75
76
	/**
77
	 * Method for fetching the same information in a way that doesn't require
78
	 * mysqlnd to be installed.
79
	 *
80
	 * Fetches directly from the statement with variable binding instead.
81
	 *
82
	 * @param mysqli_stmt $statement
83
	 * @return array
84
	 */
85
	protected function fetchResultWithoutNativeDriver(mysqli_stmt $statement)
86
	{
87
		$statement->store_result();
88
89
		$data = array();
90
		$metadata = $statement->result_metadata();
91
92
		$row = array();
93
		$count = 0;
94
		$fields = array();
95
		$arguments = array();
96
97
		if ($metadata) {
0 ignored issues
show
introduced by
$metadata is of type mysqli_result, thus it always evaluated to true.
Loading history...
98
			while ($field = $metadata->fetch_field()) {
99
				$fields[] = (array) $field;
100
				$arguments[] = &$row[$field->name];
101
			}
102
103
			call_user_func_array(array($statement, 'bind_result'), $arguments);
104
105
			while ($statement->fetch()) {
106
				$data[] = static::copyArray($row);
107
				$count++;
108
			}
109
110
		}
111
112
		return array($data, $fields, $count);
113
	}
114
115
	/**
116
	 * Retrieve the type of a variable for binding mysqli parameters.
117
	 *
118
	 * @param mixed $parameter
119
	 * @return string
120
	 */
121
	protected function prepareType($parameter)
122
	{
123
		if (is_int($parameter)) {
124
			return 'i';
125
		}
126
127
		if (is_float($parameter)) {
128
			return 'd';
129
		}
130
131
		return 's';
132
	}
133
134
	/**
135
	 * Prepares an array of values as an array of references to those values.
136
	 *
137
	 * Required for PHP 5.3+ to prevent warnings when dynamically invoking
138
	 * mysqli_stmt::bind_param().
139
	 *
140
	 * @param array $parameters
141
	 * @return array
142
	 */
143
	protected function prepareReferences(array $parameters)
144
	{
145
		$references = array();
146
147
		foreach ($parameters as $key => $value) {
148
			$references[$key] = &$parameters[$key];
149
		}
150
151
		return $references;
152
	}
153
154
	/**
155
	 * Prepare the given query and parameters as a mysqli statement.
156
	 *
157
	 * @param string $query
158
	 * @param array  $parameters [optional]
159
	 * @return \mysqli_stmt
160
	 */
161
	protected function prepareStatement($query, $parameters = array())
162
	{
163
		$statement = $this->connection->stmt_init();
164
165
		if (!$statement->prepare($query)) {
166
			return $statement;
167
		}
168
169
		if (empty($parameters)) {
170
			return $statement;
171
		}
172
173
		$types = '';
174
175
		foreach ((array) $parameters as $parameter) {
176
			$types .= $this->prepareType($parameter);
177
		}
178
179
		array_unshift($parameters, $types);
180
181
		call_user_func_array(
182
			array($statement, 'bind_param'),
183
			$this->prepareReferences($parameters)
184
		);
185
186
		return $statement;
187
	}
188
189
	/**
190
	 * Prepare a result array using the given mysqli statement.
191
	 *
192
	 * @param mysqli_stmt $statement
193
	 * @return array
194
	 */
195
	protected function prepareStatementResult(mysqli_stmt $statement)
196
	{
197
		list($data, $fields, $count) = $this->fetchResult($statement);
198
199
		$result = array(
200
			'data'      => $data,
201
			'fields'    => $fields,
202
			'affected'  => $statement->affected_rows,
203
			'num_rows'  => $count,
204
			'insert_id' => $statement->insert_id
205
		);
206
207
		$statement->free_result();
208
209
		return $result;
210
	}
211
212
	/**
213
	 * Initiate the connection.
214
	 *
215
	 * @return bool
216
	 */
217
	public function connect()
218
	{
219
		if ($this->connected()) {
220
			return true;
221
		}
222
223
		$this->connection = new php_mysqli(
224
			$this->details['host'],
225
			$this->details['user'],
226
			$this->details['pass'],
227
			$this->details['name'],
228
			$this->details['port']
229
		);
230
231
		if ($this->connection->connect_errno) {
232
			return false;
233
		}
234
235
		return $this->connected = true;
236
	}
237
238
	/**
239
	 * Determine whether the connection is currently active.
240
	 *
241
	 * @return bool
242
	 */
243
	public function connected()
244
	{
245
		return $this->connected && !$this->connection->connect_errno;
246
	}
247
248
	/**
249
	 * Close the connection.
250
	 */
251
	public function disconnect()
252
	{
253
		$this->connection->close();
254
		$this->connected = false;
255
	}
256
257
	/**
258
	 * Retrieve the query translator.
259
	 *
260
	 * @return Translator
261
	 */
262
	public function translator()
263
	{
264
		if (!$this->translator) {
265
			$this->translator = new Translator\MySql;
266
		}
267
268
		return $this->translator;
269
	}
270
271
	/**
272
	 * Query the database with the given query and optional parameters.
273
	 *
274
	 * TODO: Simplify this.
275
	 *
276
	 * @param Query|string $query
277
	 * @param array        $parameters [optional]
278
	 * @return Result
279
	 */
280
	public function query($query, array $parameters = array())
281
	{
282
		if (!($query instanceof Query)) {
283
			$query = new Query((string) $query, $parameters);
284
		}
285
286
		$this->lastResult = null;
287
288
		$this->connect();
289
290
		if (!$this->connected()) {
291
			$this->lastResult = new Result($query, array(), array(), $this->error());
292
293
			$this->event('mysql.query', array($this->lastResult));
294
295
			return $this->lastResult;
296
		}
297
298
		$this->event('mysql.prequery', array($query));
299
300
		$statement = $this->prepareStatement($query->string, $query->parameters);
301
302
		if ($statement->errno) {
303
			$error = new Error($statement->errno, $statement->error);
304
			$this->lastResult = new Result($query, array(), array(), $error);
305
306
			$this->event('mysql.query', array($this->lastResult));
307
308
			return $this->lastResult;
309
		}
310
311
		$statement->execute();
312
313
		$error = $this->error();
314
315
		if ($error) {
0 ignored issues
show
introduced by
$error is of type Darya\Database\Error, thus it always evaluated to true.
Loading history...
316
			$this->lastResult = new Result($query, array(), array(), $error);
317
318
			$this->event('mysql.query', array($this->lastResult));
319
320
			return $this->lastResult;
321
		}
322
323
		$result = $this->prepareStatementResult($statement);
324
325
		if ($statement->errno) {
326
			$error = new Error($statement->errno, $statement->error);
327
			$this->lastResult = new Result($query, array(), array(), $error);
328
329
			$this->event('mysql.query', array($this->lastResult));
330
331
			return $this->lastResult;
332
		}
333
334
		$statement->close();
335
336
		$info = array(
337
			'count'     => $result['num_rows'],
338
			'fields'    => $result['fields'],
339
			'affected'  => $result['affected'],
340
			'insert_id' => $result['insert_id']
341
		);
342
343
		$this->lastResult = new Result($query, $result['data'], $info, $error);
344
345
		$this->event('mysql.query', array($this->lastResult));
346
347
		return $this->lastResult;
348
	}
349
350
	/**
351
	 * Escape the given string for a MySQL query.
352
	 *
353
	 * @param string $string
354
	 * @return string
355
	 */
356
	public function escape($string)
357
	{
358
		$this->connect();
359
360
		return $this->connection->real_escape_string($string);
361
	}
362
363
	/**
364
	 * Retrieve error information regarding the last query or connection
365
	 * attempt.
366
	 *
367
	 * Returns null if there is no error.
368
	 *
369
	 * @return Error
370
	 */
371
	public function error()
372
	{
373
		$connectionError = $this->connectionError();
374
375
		if ($connectionError) {
0 ignored issues
show
introduced by
$connectionError is of type Darya\Database\Error, thus it always evaluated to true.
Loading history...
376
			return $connectionError;
377
		}
378
379
		if ($this->lastResult && $this->lastResult->error) {
380
			return $this->lastResult->error;
381
		}
382
383
		return null;
384
	}
385
386
	/**
387
	 * Retrieve error information from the mysqli connection object.
388
	 *
389
	 * Checks for general errors first, then connection errors.
390
	 *
391
	 * Returns null if there is no error.
392
	 *
393
	 * @return Error
394
	 */
395
	protected function connectionError()
396
	{
397
		if (!$this->connection) {
398
			return null;
399
		}
400
401
		if ($this->connection->errno) {
402
			return new Error($this->connection->errno, $this->connection->error);
403
		}
404
405
		if ($this->connection->connect_errno) {
406
			return new Error($this->connection->connect_errno, $this->connection->connect_error);
407
		}
408
409
		return null;
410
	}
411
}
412