Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
205 / 205 |
|
100.00% |
28 / 28 |
CRAP | |
100.00% |
1 / 1 |
Mydb | |
100.00% |
205 / 205 |
|
100.00% |
28 / 28 |
95 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
__destruct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
open | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
query | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
6 | |||
command | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
getEnumValues | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
6 | |||
escape | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getPrimaryKeys | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
7 | |||
beginTransaction | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
5 | |||
rollbackTransaction | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
commitTransaction | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
close | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
10 | |||
replace | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
insert | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
select | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
delete | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
update | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
deleteWhere | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
updateWhere | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
updateWhereMany | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
insertMany | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
replaceOne | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
insertOne | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
sendClientRequest | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
readServerResponse | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
6 | |||
onWarning | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
onError | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
connect | |
100.00% |
52 / 52 |
|
100.00% |
1 / 1 |
17 |
1 | <?php |
2 | /** |
3 | * This file is part of the sshilko/php-sql-mydb package. |
4 | * |
5 | * (c) Sergei Shilko <contact@sshilko.com> |
6 | * |
7 | * MIT License |
8 | * |
9 | * For the full copyright and license information, please view the LICENSE |
10 | * file that was distributed with this source code. |
11 | * @license https://opensource.org/licenses/mit-license.php MIT |
12 | */ |
13 | |
14 | declare(strict_types = 1); |
15 | |
16 | namespace sql; |
17 | |
18 | use Psr\Log\LoggerInterface; |
19 | use sql\MydbException\ConnectDefaultsException; |
20 | use sql\MydbException\ConnectException; |
21 | use sql\MydbException\DeleteException; |
22 | use sql\MydbException\DisconnectException; |
23 | use sql\MydbException\InternalException; |
24 | use sql\MydbException\ServerGoneException; |
25 | use sql\MydbException\TerminationSignalException; |
26 | use sql\MydbException\TransactionAutocommitException; |
27 | use sql\MydbException\TransactionBeginReadonlyException; |
28 | use sql\MydbException\TransactionBeginReadwriteException; |
29 | use sql\MydbException\TransactionCommitException; |
30 | use sql\MydbException\TransactionIsolationException; |
31 | use sql\MydbException\TransactionRollbackException; |
32 | use sql\MydbException\UpdateException; |
33 | use sql\MydbInterface\RemoteResourceInterface; |
34 | use sql\MydbListener\InternalListener; |
35 | use sql\MydbMysqli\MydbMysqliResultInterface; |
36 | use Throwable; |
37 | use function array_map; |
38 | use function count; |
39 | use function explode; |
40 | use function implode; |
41 | use function preg_replace; |
42 | use function sprintf; |
43 | use function stripos; |
44 | use function strpos; |
45 | use function substr; |
46 | |
47 | /** |
48 | * @author Sergei Shilko <contact@sshilko.com> |
49 | * @license https://opensource.org/licenses/mit-license.php MIT |
50 | * @see https://github.com/sshilko/php-sql-mydb |
51 | * @SuppressWarnings(PHPMD.CouplingBetweenObjects) |
52 | */ |
53 | class Mydb implements MydbInterface, RemoteResourceInterface |
54 | { |
55 | |
56 | protected MydbMysqliInterface $mysqli; |
57 | |
58 | protected MydbOptionsInterface $options; |
59 | |
60 | protected MydbEnvironmentInterface $environment; |
61 | |
62 | protected MydbQueryBuilderInterface $queryBuilder; |
63 | |
64 | protected MydbListenerInterface $eventListener; |
65 | |
66 | protected bool $terminating = false; |
67 | |
68 | public function __construct( |
69 | protected MydbCredentialsInterface $credentials, |
70 | protected LoggerInterface $logger, |
71 | ?MydbOptionsInterface $options = null, |
72 | ?MydbMysqliInterface $mysqli = null, |
73 | ?MydbEnvironmentInterface $environment = null, |
74 | ?MydbQueryBuilderInterface $queryBuilder = null, |
75 | ?MydbListenerInterface $eventListener = null, |
76 | ) { |
77 | $this->options = $options ?? new MydbOptions(); |
78 | $this->mysqli = $mysqli ?? new MydbMysqli(); |
79 | $this->environment = $environment ?? new MydbEnvironment(); |
80 | $this->queryBuilder = $queryBuilder ?? new MydbQueryBuilder($this->mysqli); |
81 | $this->eventListener = $eventListener ?? new InternalListener($logger); |
82 | } |
83 | |
84 | /** |
85 | * A destructor may be called as soon as there are no references to an object. |
86 | * |
87 | * @see http://php.net/manual/en/mysqli.close.php |
88 | * @see http://php.net/manual/en/mysqli.ping.php (MysqlND not supports reconnect) |
89 | * @throws \sql\MydbException |
90 | */ |
91 | public function __destruct() |
92 | { |
93 | $this->terminating = true; |
94 | $this->close(); |
95 | } |
96 | |
97 | /** |
98 | * Open connection to remote server |
99 | * @param int $retry retry failed connection attempts |
100 | * @throws \sql\MydbException |
101 | */ |
102 | public function open(int $retry = 0): bool |
103 | { |
104 | return $this->connect($retry); |
105 | } |
106 | |
107 | /** |
108 | * Execute raw SQL query and return results |
109 | * |
110 | * @phpcs:disable SlevomatCodingStandard.Complexity.Cognitive |
111 | * @phpcs:disable SlevomatCodingStandard.TypeHints.ReturnTypeHint |
112 | * |
113 | * @psalm-return array<array-key, array<array-key, (float|int|string|null)>>|null |
114 | * @throws \sql\MydbException\ConnectException |
115 | * @throws \sql\MydbException |
116 | */ |
117 | public function query(string $query): ?array |
118 | { |
119 | |
120 | if (!$this->connect()) { |
121 | throw new ConnectException(); |
122 | } |
123 | |
124 | $result = $this->sendClientRequest($query); |
125 | /** |
126 | * We should always read server response, no matter whether sendClientRequest result |
127 | */ |
128 | $packet = $this->readServerResponse($query); |
129 | |
130 | if (false === $result || null === $packet) { |
131 | return null; |
132 | } |
133 | |
134 | if ($packet->getFieldCount() > 0) { |
135 | $payload = $packet->getResult(); |
136 | if (null === $payload) { |
137 | $this->onError( |
138 | new InternalException($packet->getError() ?? 'Reading of the result set failed'), |
139 | $query |
140 | ); |
141 | } |
142 | |
143 | /** |
144 | * @var array<array-key, array<array-key, (float|int|string|null)>> $payload |
145 | */ |
146 | return $payload; |
147 | } |
148 | |
149 | return null; |
150 | } |
151 | |
152 | /** |
153 | * @phpcs:disable SlevomatCodingStandard.Complexity.Cognitive |
154 | * @throws \sql\MydbException |
155 | */ |
156 | public function command(string $query): bool |
157 | { |
158 | if (!$this->connect()) { |
159 | throw new ConnectException(); |
160 | } |
161 | |
162 | |
163 | $result = $this->sendClientRequest($query); |
164 | |
165 | if (false === $result) { |
166 | return false; |
167 | } |
168 | |
169 | $packet = $this->readServerResponse($query); |
170 | |
171 | return null !== $packet; |
172 | } |
173 | |
174 | /** |
175 | * @return array<string> |
176 | * @throws \sql\MydbException |
177 | * @psalm-return list<string> |
178 | */ |
179 | public function getEnumValues(string $table, string $column): array |
180 | { |
181 | $query = $this->queryBuilder->showColumnsLike($table, $column); |
182 | |
183 | $resultArray = $this->query($query); |
184 | $result = isset($resultArray[0]['Type']) |
185 | ? (string) $resultArray[0]['Type'] |
186 | : null; |
187 | |
188 | $match = false; |
189 | $types = ['enum', 'set']; |
190 | foreach ($types as $type) { |
191 | if (0 === stripos((string)$result, $type . '(')) { |
192 | $match = $type; |
193 | |
194 | break; |
195 | } |
196 | } |
197 | |
198 | if (false === $match) { |
199 | $this->onError(new MydbException("Column not of type '" . implode(',', $types) . "'")); |
200 | } |
201 | |
202 | $input = substr((string) $result, (int) strpos((string) $result, '(') + 1, -1); |
203 | |
204 | // @codeCoverageIgnoreStart |
205 | /** |
206 | * @psalm-suppress DocblockTypeContradiction |
207 | */ |
208 | if ('' === $input) { |
209 | throw new MydbException(); |
210 | } |
211 | // @codeCoverageIgnoreEnd |
212 | |
213 | $values = explode(',', preg_replace("/'/", '', $input)); |
214 | |
215 | return array_map('strval', $values); |
216 | } |
217 | |
218 | /** |
219 | * @param float|int|string|\sql\MydbExpressionInterface|null $unescaped |
220 | * @throws \sql\MydbException\ConnectException |
221 | * @throws \sql\MydbException |
222 | * @SuppressWarnings(PHPMD.NPathComplexity) |
223 | * @todo reduce NPathComplexity |
224 | */ |
225 | public function escape($unescaped, string $quote = "'"): string |
226 | { |
227 | if (!$this->connect()) { |
228 | throw new ConnectException(); |
229 | } |
230 | |
231 | return $this->queryBuilder->escape($unescaped, $quote); |
232 | } |
233 | |
234 | /** |
235 | * @throws \sql\MydbException |
236 | * @throws \sql\MydbException\ConnectException |
237 | * @return ?array<string> |
238 | */ |
239 | public function getPrimaryKeys(string $table): ?array |
240 | { |
241 | $result = $this->query($this->queryBuilder->showKeys($table)); |
242 | |
243 | if (null === $result) { |
244 | return null; |
245 | } |
246 | |
247 | $keys = []; |
248 | foreach ($result as $row) { |
249 | if (isset($row['Key_name']) && 'PRIMARY' === $row['Key_name'] && isset($row['Column_name'])) { |
250 | $keys[] = (string) $row['Column_name']; |
251 | } |
252 | } |
253 | |
254 | return [] !== $keys ? $keys : null; |
255 | } |
256 | |
257 | /** |
258 | * @throws \sql\MydbException |
259 | */ |
260 | public function beginTransaction(): void |
261 | { |
262 | if (!$this->connect()) { |
263 | throw new ConnectException(); |
264 | } |
265 | |
266 | if ($this->options->isReadonly()) { |
267 | if ($this->mysqli->beginTransactionReadonly()) { |
268 | return; |
269 | } |
270 | $this->onError(new TransactionBeginReadonlyException()); |
271 | } else { |
272 | if ($this->mysqli->beginTransactionReadwrite()) { |
273 | return; |
274 | } |
275 | $this->onError(new TransactionBeginReadwriteException()); |
276 | } |
277 | } |
278 | |
279 | /** |
280 | * @throws \sql\MydbException |
281 | */ |
282 | public function rollbackTransaction(): void |
283 | { |
284 | if (!$this->mysqli->isConnected()) { |
285 | throw new ConnectException(); |
286 | } |
287 | |
288 | if ($this->mysqli->rollback()) { |
289 | return; |
290 | } |
291 | |
292 | $this->onError(new TransactionRollbackException()); |
293 | } |
294 | |
295 | /** |
296 | * @throws \sql\MydbException\ConnectException |
297 | * @throws \sql\MydbException |
298 | */ |
299 | public function commitTransaction(): void |
300 | { |
301 | if (!$this->mysqli->isConnected()) { |
302 | throw new ConnectException(); |
303 | } |
304 | |
305 | if ($this->mysqli->commit()) { |
306 | return; |
307 | } |
308 | |
309 | $this->onError(new TransactionCommitException()); |
310 | } |
311 | |
312 | /** |
313 | * @throws \sql\MydbException |
314 | */ |
315 | public function close(): void |
316 | { |
317 | if (false === $this->mysqli->isConnected()) { |
318 | return; |
319 | } |
320 | |
321 | try { |
322 | /** |
323 | * No autocommit |
324 | * No transaction |
325 | * |
326 | * Default: commit all commands if transaction was NOT open |
327 | */ |
328 | if (false === $this->options->isAutocommit() && false === $this->mysqli->isTransactionOpen()) { |
329 | /** |
330 | * RELEASE clause causes the server to disconnect the current client session |
331 | * after terminating the current transaction. |
332 | */ |
333 | $commit = $this->options->isPersistent() |
334 | ? $this->mysqli->commit() |
335 | : $this->mysqli->commitAndRelease(); |
336 | |
337 | if (false === $commit) { |
338 | throw new TransactionCommitException(); |
339 | } |
340 | } |
341 | |
342 | /** |
343 | * Explicitly closing open connections and freeing result sets is optional but recommended |
344 | * Server already closed connection from server-side |
345 | */ |
346 | if (false === $this->mysqli->close()) { |
347 | throw new DisconnectException(); |
348 | } |
349 | } catch (MydbException $e) { |
350 | $this->onError($e); |
351 | } catch (Throwable $e) { |
352 | $this->onError(new InternalException($e->getMessage())); |
353 | } |
354 | |
355 | if ($this->terminating) { |
356 | return; |
357 | } |
358 | |
359 | $this->environment->gc_collect_cycles(); |
360 | } |
361 | |
362 | /** |
363 | * @throws \sql\MydbException\ConnectException |
364 | * @throws \sql\MydbException |
365 | */ |
366 | public function replace(string $query): ?string |
367 | { |
368 | return $this->insert($query); |
369 | } |
370 | |
371 | /** |
372 | * @throws \sql\MydbException\ConnectException |
373 | * @throws \sql\MydbException |
374 | */ |
375 | public function insert(string $query): ?string |
376 | { |
377 | if ($this->command($query)) { |
378 | return (string) $this->mysqli->getInsertId(); |
379 | } |
380 | |
381 | return null; |
382 | } |
383 | |
384 | /** |
385 | * @phpcs:disable SlevomatCodingStandard.TypeHints.ReturnTypeHint |
386 | * @throws \sql\MydbException |
387 | * @throws \sql\MydbException\ConnectException |
388 | */ |
389 | public function select(string $query): ?array |
390 | { |
391 | return $this->query($query); |
392 | } |
393 | |
394 | /** |
395 | * @throws \sql\MydbException |
396 | */ |
397 | public function delete(string $query): ?int |
398 | { |
399 | if ($this->command($query)) { |
400 | $rows = $this->mysqli->getAffectedRows(); |
401 | if (null === $rows) { |
402 | $this->onError(new DeleteException(), $query); |
403 | } |
404 | |
405 | return $rows; |
406 | } |
407 | |
408 | return null; |
409 | } |
410 | |
411 | /** |
412 | * @throws \sql\MydbException |
413 | */ |
414 | public function update(string $query): ?int |
415 | { |
416 | if ($this->command($query)) { |
417 | $rows = $this->mysqli->getAffectedRows(); |
418 | if (null === $rows) { |
419 | $this->onError(new UpdateException(), $query); |
420 | } |
421 | |
422 | return $rows; |
423 | } |
424 | |
425 | return null; |
426 | } |
427 | |
428 | /** |
429 | * @throws \sql\MydbException |
430 | */ |
431 | public function deleteWhere(array $whereFields, string $table, array $whereNotFields = []): ?int |
432 | { |
433 | $query = $this->queryBuilder->buildDeleteWhere($table, $whereFields, $whereNotFields); |
434 | if (null === $query) { |
435 | return null; |
436 | } |
437 | |
438 | return $this->delete($query); |
439 | } |
440 | |
441 | /** |
442 | * @param array<string, (float|int|string|\sql\MydbExpressionInterface|null)> $update |
443 | * @throws \sql\MydbException |
444 | */ |
445 | public function updateWhere(array $update, array $whereFields, string $table, array $whereNotFields = []): ?int |
446 | { |
447 | $query = $this->queryBuilder->buildUpdateWhere($update, $whereFields, $table, $whereNotFields); |
448 | |
449 | if ('' !== $query && null !== $query) { |
450 | return $this->update($query); |
451 | } |
452 | |
453 | return null; |
454 | } |
455 | |
456 | /** |
457 | * @param array $columnSetWhere ['col1' => [ ['current1', 'new1'], ['current2', 'new2']] |
458 | * @param array $where ['col2' => 'value2', 'col3' => ['v3', 'v4']] |
459 | * @param string $table 'mytable' |
460 | * @throws \sql\MydbException |
461 | */ |
462 | public function updateWhereMany(array $columnSetWhere, array $where, string $table): void |
463 | { |
464 | $sql = $this->queryBuilder->buildUpdateWhereMany($columnSetWhere, $where, $table); |
465 | $this->update($sql); |
466 | } |
467 | |
468 | /** |
469 | * @throws \sql\MydbException\ConnectException |
470 | * @throws \sql\MydbException |
471 | * @psalm-param array<array-key, array<array-key, (float|int|string|\sql\MydbExpressionInterface|null)>> $data |
472 | * @param array<string> $cols |
473 | */ |
474 | public function insertMany( |
475 | array $data, |
476 | array $cols, |
477 | string $table, |
478 | bool $ignore = false, |
479 | string $onDuplicateSql = '', |
480 | ): void { |
481 | $sql = $this->queryBuilder->buildInsertMany($data, $cols, $table, $ignore, $onDuplicateSql); |
482 | $this->insert($sql); |
483 | } |
484 | |
485 | /** |
486 | * @throws \sql\MydbException |
487 | * @param array<string, (float|int|\sql\MydbExpressionInterface|string|null)> $data |
488 | */ |
489 | public function replaceOne(array $data, string $table): ?string |
490 | { |
491 | $query = $this->queryBuilder->insertOne($data, $table, MydbQueryBuilderInterface::SQL_REPLACE); |
492 | |
493 | return $this->replace($query); |
494 | } |
495 | |
496 | /** |
497 | * @throws \sql\MydbException |
498 | * @param array<string, (float|int|\sql\MydbExpressionInterface|string|null)> $data |
499 | */ |
500 | public function insertOne(array $data, string $table): ?string |
501 | { |
502 | $query = $this->queryBuilder->insertOne($data, $table, MydbQueryBuilderInterface::SQL_INSERT); |
503 | |
504 | return $this->insert($query); |
505 | } |
506 | |
507 | /** |
508 | * @throws \sql\MydbException\EnvironmentException |
509 | * @throws \sql\MydbException\TerminationSignalException |
510 | * @throws \sql\MydbException\EventException |
511 | */ |
512 | protected function sendClientRequest(string $query): bool |
513 | { |
514 | $this->environment->startSignalsTrap(); |
515 | $this->environment->set_error_handler(); |
516 | |
517 | (new MydbEvent\InternalQueryBegin($query))->setListeners([$this->eventListener])->notify(); |
518 | $result = $this->mysqli->realQuery($query); |
519 | (new MydbEvent\InternalQueryEnd($query, $result))->setListeners([$this->eventListener])->notify(); |
520 | |
521 | $this->environment->restore_error_handler(); |
522 | $hasPendingSignals = $this->environment->endSignalsTrap(); |
523 | if (null !== $hasPendingSignals && count($hasPendingSignals) > 0) { |
524 | throw new TerminationSignalException(); |
525 | } |
526 | |
527 | return $result; |
528 | } |
529 | |
530 | /** |
531 | * @throws \sql\MydbException |
532 | */ |
533 | protected function readServerResponse(string $query): ?MydbMysqliResultInterface |
534 | { |
535 | $packet = $this->mysqli->readServerResponse($this->environment); |
536 | if (null === $packet) { |
537 | return null; |
538 | } |
539 | |
540 | $warnings = $packet->getWarnings(); |
541 | if (count($warnings) > 0) { |
542 | foreach ($warnings as $warningMessage) { |
543 | $this->onWarning($warningMessage, $query); |
544 | } |
545 | } |
546 | |
547 | $errorMessage = $packet->getError(); |
548 | if (null !== $errorMessage) { |
549 | if ($this->mysqli->isServerGone()) { |
550 | /** |
551 | * server closed connection, do close() and ignore result of operation |
552 | */ |
553 | $this->mysqli->close(); |
554 | $this->onError(new ServerGoneException($errorMessage), $query); |
555 | } else { |
556 | $this->onError(new InternalException($errorMessage), $query); |
557 | } |
558 | } |
559 | |
560 | return $packet; |
561 | } |
562 | |
563 | protected function onWarning(string $warningMessage, ?string $sql = null): void |
564 | { |
565 | $this->logger->warning($warningMessage, ['sql' => $sql]); |
566 | } |
567 | |
568 | /** |
569 | * @throws \sql\MydbException |
570 | */ |
571 | protected function onError(MydbException $exception, ?string $sql = null): void |
572 | { |
573 | $this->logger->error($exception->getMessage(), ['sql' => $sql]); |
574 | |
575 | throw $exception; |
576 | } |
577 | |
578 | /** |
579 | * @throws \sql\MydbException\DisconnectException |
580 | * @throws \sql\MydbException\TransactionAutocommitException |
581 | * @throws \sql\MydbException\EnvironmentException |
582 | * @throws \sql\MydbException\EventException |
583 | * @throws \sql\MydbException |
584 | * @SuppressWarnings(PHPMD.NPathComplexity) |
585 | * @todo reduce NPathComplexity |
586 | */ |
587 | protected function connect(int $retry = 0): bool |
588 | { |
589 | if ($this->mysqli->isConnected()) { |
590 | return true; |
591 | } |
592 | |
593 | $connected = false; |
594 | $init0 = $this->mysqli->init(); |
595 | $init1 = $init0 && $this->mysqli->setTransportOptions($this->options, $this->environment); |
596 | |
597 | if ($init0 && $init1) { |
598 | $reportingLevel = $this->environment->error_reporting($this->options->getErrorReporting()); |
599 | |
600 | $host = ($this->options->isPersistent() ? 'p:' : '') . $this->credentials->getHost(); |
601 | $dbname = $this->credentials->getDbname(); |
602 | |
603 | (new MydbEvent\InternalConnectionBegin($host, $dbname)) |
604 | ->setListeners([$this->eventListener])->notify(); |
605 | $connected = $this->mysqli->realConnect( |
606 | $host, |
607 | $this->credentials->getUsername(), |
608 | $this->credentials->getPasswd(), |
609 | $dbname, |
610 | $this->credentials->getPort(), |
611 | $this->credentials->getSocket(), |
612 | $this->credentials->getFlags() |
613 | ); |
614 | (new MydbEvent\InternalConnectionEnd($host, $dbname, $connected)) |
615 | ->setListeners([$this->eventListener])->notify(); |
616 | |
617 | $this->environment->error_reporting($reportingLevel); |
618 | } |
619 | |
620 | if (false === $connected) { |
621 | $errorNumber = (string) ($this->mysqli->getConnectErrno() ?: $this->mysqli->getErrNo()); |
622 | $errorText = (string) ($this->mysqli->getConnectError() ?: $this->mysqli->getError()); |
623 | |
624 | if (false === $this->mysqli->close()) { |
625 | throw new DisconnectException(); |
626 | } |
627 | |
628 | $this->onWarning($errorNumber . ' ' . $errorText); |
629 | |
630 | if ($retry > 0) { |
631 | --$retry; |
632 | |
633 | return $this->connect($retry); |
634 | } |
635 | |
636 | return false; |
637 | } |
638 | |
639 | $this->mysqli->mysqliReport($this->options->getClientErrorLevel()); |
640 | |
641 | if (false === $this->mysqli->autocommit($this->options->isAutocommit())) { |
642 | throw new TransactionAutocommitException(); |
643 | } |
644 | |
645 | /** |
646 | * Here's a summary of what variables are set by each statement: SET NAMES vs SET CHARSET |
647 | * |
648 | * Variable SET NAMES SET CHARSET |
649 | * character_set_client argument argument |
650 | * character_set_results argument argument |
651 | * character_set_connection argument default for default db |
652 | * collation_connection argument* default for default db |
653 | * |
654 | * SET CHARSET doesn't set the connection charset to the charset you specify in the argument, |
655 | * instead it sets it to the charset for your current default database |
656 | */ |
657 | $defaults = $this->mysqli->realQuery( |
658 | sprintf( |
659 | "SET time_zone = '%s', wait_timeout = %d, names '%s'", |
660 | $this->options->getTimeZone(), |
661 | $this->options->getNonInteractiveTimeout(), |
662 | $this->options->getCharset() |
663 | ) |
664 | ); |
665 | |
666 | if (false === $defaults) { |
667 | throw new ConnectDefaultsException(); |
668 | } |
669 | |
670 | $isoLevel = $this->options->getTransactionIsolationLevel(); |
671 | if (null !== $isoLevel && false === $this->mysqli->setTransactionIsolationLevel($isoLevel)) { |
672 | throw new TransactionIsolationException(); |
673 | } |
674 | |
675 | if ($this->options->isReadonly() && false === $this->mysqli->beginTransactionReadonly()) { |
676 | throw new TransactionAutocommitException(); |
677 | } |
678 | |
679 | return true; |
680 | } |
681 | } |