1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Spiral Framework. |
4
|
|
|
* |
5
|
|
|
* @license MIT |
6
|
|
|
* @author Anton Titov (Wolfy-J) |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace Spiral\Database\Entities; |
10
|
|
|
|
11
|
|
|
use PDO; |
12
|
|
|
use Psr\Log\LoggerAwareInterface; |
13
|
|
|
use Spiral\Core\Component; |
14
|
|
|
use Spiral\Core\Exceptions\SugarException; |
15
|
|
|
use Spiral\Database\DatabaseManager; |
16
|
|
|
use Spiral\Database\Exceptions\DriverException; |
17
|
|
|
use Spiral\Database\Exceptions\QueryException; |
18
|
|
|
use Spiral\Database\Helpers\QueryInterpolator; |
19
|
|
|
use Spiral\Database\Injections\Parameter; |
20
|
|
|
use Spiral\Database\Injections\ParameterInterface; |
21
|
|
|
use Spiral\Database\Query\PDOQuery; |
22
|
|
|
use Spiral\Debug\Traits\BenchmarkTrait; |
23
|
|
|
use Spiral\Debug\Traits\LoggerTrait; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Basic implementation of DBAL Driver, basically decorates PDO. Extends component to provide access |
27
|
|
|
* to functionality like shared loggers and benchmarking. |
28
|
|
|
*/ |
29
|
|
|
abstract class PDODriver extends Component implements LoggerAwareInterface |
30
|
|
|
{ |
31
|
|
|
use LoggerTrait, BenchmarkTrait; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* One of DatabaseInterface types, must be set on implementation. |
35
|
|
|
*/ |
36
|
|
|
const TYPE = null; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* DateTime format to be used to perform automatic conversion of DateTime objects. |
40
|
|
|
* |
41
|
|
|
* @var string |
42
|
|
|
*/ |
43
|
|
|
const DATETIME = 'Y-m-d H:i:s'; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Query result class. |
47
|
|
|
*/ |
48
|
|
|
const QUERY_RESULT = PDOQuery::class; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Driver name. |
52
|
|
|
* |
53
|
|
|
* @var string |
54
|
|
|
*/ |
55
|
|
|
private $name = ''; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Transaction level (count of nested transactions). Not all drives can support nested |
59
|
|
|
* transactions. |
60
|
|
|
* |
61
|
|
|
* @var int |
62
|
|
|
*/ |
63
|
|
|
private $transactionLevel = 0; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* @var PDO|null |
67
|
|
|
*/ |
68
|
|
|
private $pdo = null; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Connection configuration described in DBAL config file. Any driver can be used as data source |
72
|
|
|
* for multiple databases as table prefix and quotation defined on Database instance level. |
73
|
|
|
* |
74
|
|
|
* @var array |
75
|
|
|
*/ |
76
|
|
|
protected $config = [ |
77
|
|
|
'profiling' => false, |
78
|
|
|
|
79
|
|
|
//DSN |
80
|
|
|
'connection' => '', |
81
|
|
|
'username' => '', |
82
|
|
|
'password' => '', |
83
|
|
|
'options' => [], |
84
|
|
|
]; |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* PDO connection options set. |
88
|
|
|
* |
89
|
|
|
* @var array |
90
|
|
|
*/ |
91
|
|
|
protected $options = [ |
92
|
|
|
PDO::ATTR_CASE => PDO::CASE_NATURAL, |
93
|
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
94
|
|
|
PDO::ATTR_STRINGIFY_FETCHES => true, |
95
|
|
|
]; |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* @param string $name |
99
|
|
|
* @param array $connection |
100
|
|
|
* @throws SugarException |
101
|
|
|
*/ |
102
|
|
|
public function __construct(string $name, array $connection) |
103
|
|
|
{ |
104
|
|
|
$this->name = $name; |
105
|
|
|
|
106
|
|
|
$this->config = $connection + $this->config; |
107
|
|
|
|
108
|
|
|
//PDO connection options has to be stored under key "options" of config |
109
|
|
|
$this->options = $connection['options'] + $this->options; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Source name, can include database name or database file. |
114
|
|
|
* |
115
|
|
|
* @return string |
116
|
|
|
*/ |
117
|
|
|
public function getName(): string |
118
|
|
|
{ |
119
|
|
|
return $this->name; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Get driver source database or file name. |
124
|
|
|
* |
125
|
|
|
* @return string |
126
|
|
|
* |
127
|
|
|
* @throws DriverException |
128
|
|
|
*/ |
129
|
|
|
public function getSource(): string |
130
|
|
|
{ |
131
|
|
|
if (preg_match('/(?:dbname|database)=([^;]+)/i', $this->config['connection'], $matches)) { |
132
|
|
|
return $matches[1]; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
throw new DriverException('Unable to locate source name.'); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Database type driver linked to. |
140
|
|
|
* |
141
|
|
|
* @return string |
142
|
|
|
*/ |
143
|
|
|
public function getType(): string |
144
|
|
|
{ |
145
|
|
|
return static::TYPE; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Connection specific timezone, at this moment locked to UTC. |
150
|
|
|
* |
151
|
|
|
* @todo Support connection specific timezones. |
152
|
|
|
* |
153
|
|
|
* @return \DateTimeZone |
154
|
|
|
*/ |
155
|
|
|
public function getTimezone(): \DateTimeZone |
156
|
|
|
{ |
157
|
|
|
return new \DateTimeZone(DatabaseManager::DEFAULT_TIMEZONE); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* Enabled profiling will raise set of log messages and benchmarks associated with PDO queries. |
162
|
|
|
* |
163
|
|
|
* @param bool $enabled Enable or disable driver profiling. |
164
|
|
|
* |
165
|
|
|
* @return self |
166
|
|
|
*/ |
167
|
|
|
public function setProfiling(bool $enabled = true): PDODriver |
168
|
|
|
{ |
169
|
|
|
$this->config['profiling'] = $enabled; |
170
|
|
|
|
171
|
|
|
return $this; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Check if profiling mode is enabled. |
176
|
|
|
* |
177
|
|
|
* @return bool |
178
|
|
|
*/ |
179
|
|
|
public function isProfiling(): bool |
180
|
|
|
{ |
181
|
|
|
return $this->config['profiling']; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Force driver to connect. |
186
|
|
|
* |
187
|
|
|
* @return PDO |
188
|
|
|
* |
189
|
|
|
* @throws DriverException |
190
|
|
|
*/ |
191
|
|
|
public function connect(): PDO |
192
|
|
|
{ |
193
|
|
|
if ($this->isConnected()) { |
194
|
|
|
throw new DriverException("Driver '{$this->name}' already connected"); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
$benchmark = $this->benchmark('connect', $this->config['connection']); |
198
|
|
|
try { |
199
|
|
|
$this->pdo = $this->createPDO(); |
200
|
|
|
} finally { |
201
|
|
|
$this->benchmark($benchmark); |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
return $this->pdo; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Disconnect driver. |
209
|
|
|
* |
210
|
|
|
* @return self |
211
|
|
|
*/ |
212
|
|
|
public function disconnect(): PDODriver |
213
|
|
|
{ |
214
|
|
|
$this->pdo = null; |
215
|
|
|
|
216
|
|
|
return $this; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* Check if driver already connected. |
221
|
|
|
* |
222
|
|
|
* @return bool |
223
|
|
|
*/ |
224
|
|
|
public function isConnected(): bool |
225
|
|
|
{ |
226
|
|
|
return !empty($this->pdo); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* Change PDO instance associated with driver. |
231
|
|
|
* |
232
|
|
|
* @param PDO $pdo |
233
|
|
|
* |
234
|
|
|
* @return self |
235
|
|
|
*/ |
236
|
|
|
public function setPDO(PDO $pdo): PDODriver |
237
|
|
|
{ |
238
|
|
|
$this->pdo = $pdo; |
239
|
|
|
|
240
|
|
|
return $this; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Get associated PDO connection. Will automatically connect if such connection does not exists. |
245
|
|
|
* |
246
|
|
|
* @return PDO |
247
|
|
|
*/ |
248
|
|
|
public function getPDO(): PDO |
249
|
|
|
{ |
250
|
|
|
if (!$this->isConnected()) { |
251
|
|
|
$this->connect(); |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
return $this->pdo; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Driver specific database/table identifier quotation. |
259
|
|
|
* |
260
|
|
|
* @param string $identifier |
261
|
|
|
* |
262
|
|
|
* @return string |
263
|
|
|
*/ |
264
|
|
|
public function identifier(string $identifier): string |
265
|
|
|
{ |
266
|
|
|
return $identifier == '*' ? '*' : '"' . str_replace('"', '""', $identifier) . '"'; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* Wraps PDO query method with custom representation class. |
271
|
|
|
* |
272
|
|
|
* @param string $statement |
273
|
|
|
* @param array $parameters |
274
|
|
|
* @param string $class Class name to be used, by default driver specific query result to be |
275
|
|
|
* used. |
276
|
|
|
* @param array $args Class construction arguments (by default filtered parameters). |
277
|
|
|
* |
278
|
|
|
* @return \PDOStatement|PDOQuery |
279
|
|
|
*/ |
280
|
|
|
public function query( |
281
|
|
|
string $statement, |
282
|
|
|
array $parameters = [], |
283
|
|
|
$class = null, |
284
|
|
|
array $args = [] |
285
|
|
|
): \PDOStatement { |
286
|
|
|
return $this->statement($statement, $parameters, $class ?? static::QUERY_RESULT, $args); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
/** |
290
|
|
|
* Create instance of PDOStatement using provided SQL query and set of parameters and execute |
291
|
|
|
* it. |
292
|
|
|
* |
293
|
|
|
* @param string $query |
294
|
|
|
* @param array $parameters Parameters to be binded into query. |
295
|
|
|
* @param string $class Class to represent PDO statement. |
296
|
|
|
* @param array $args Class construction arguments (by default filtered parameters) |
297
|
|
|
* |
298
|
|
|
* @return \PDOStatement |
299
|
|
|
* |
300
|
|
|
* @throws QueryException |
301
|
|
|
*/ |
302
|
|
|
public function statement( |
303
|
|
|
string $query, |
304
|
|
|
array $parameters = [], |
305
|
|
|
string $class = 'PDOStatement', |
306
|
|
|
array $args = [] |
307
|
|
|
): \PDOStatement { |
308
|
|
|
try { |
309
|
|
|
//Filtered and normalized parameters |
310
|
|
|
$parameters = $this->flattenParameters($parameters); |
311
|
|
|
|
312
|
|
|
if ($this->isProfiling()) { |
313
|
|
|
$queryString = QueryInterpolator::interpolate($query, $parameters); |
314
|
|
|
$benchmark = $this->benchmark($this->name, $queryString); |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
//PDOStatement instance (prepared) |
318
|
|
|
$pdoStatement = $this->prepare($query, $class, !empty($args) ? $args : [$parameters]); |
319
|
|
|
|
320
|
|
|
//Mounting all input parameters |
321
|
|
|
$pdoStatement = $this->bindParameters($pdoStatement, $parameters); |
322
|
|
|
|
323
|
|
|
try { |
324
|
|
|
$pdoStatement->execute(); |
325
|
|
|
} finally { |
326
|
|
|
if (!empty($benchmark)) { |
327
|
|
|
$this->benchmark($benchmark); |
328
|
|
|
} |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
//Only exists if profiling on |
332
|
|
|
if (!empty($queryString)) { |
333
|
|
|
$this->logger()->info($queryString, compact('query', 'parameters')); |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
} catch (\PDOException $e) { |
337
|
|
|
if (empty($queryString)) { |
338
|
|
|
$queryString = QueryInterpolator::interpolate($query, $parameters); |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
//Logging error even when no profiling is enabled |
342
|
|
|
$this->logger()->error($queryString, compact('query', 'parameters')); |
343
|
|
|
|
344
|
|
|
//Converting exception into query or integrity exception |
345
|
|
|
throw $this->clarifyException($e); |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
return $pdoStatement; |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
/** |
352
|
|
|
* Get prepared PDO statement. |
353
|
|
|
* |
354
|
|
|
* @param string $statement Query statement. |
355
|
|
|
* @param string $class Class to represent PDO statement. |
356
|
|
|
* @param array $args Class construction arguments (by default paramaters) |
357
|
|
|
* |
358
|
|
|
* @return \PDOStatement |
359
|
|
|
*/ |
360
|
|
|
public function prepare( |
361
|
|
|
string $statement, |
362
|
|
|
string $class = 'PDOStatement', |
363
|
|
|
array $args = [] |
364
|
|
|
): \PDOStatement { |
365
|
|
|
$pdo = $this->getPDO(); |
366
|
|
|
$pdo->setAttribute(PDO::ATTR_STATEMENT_CLASS, [$class, $args]); |
367
|
|
|
|
368
|
|
|
return $this->getPDO()->prepare($statement); |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* Get id of last inserted row, this method must be called after insert query. Attention, |
373
|
|
|
* such functionality may not work in some DBMS property (Postgres). |
374
|
|
|
* |
375
|
|
|
* @param string|null $sequence Name of the sequence object from which the ID should be |
376
|
|
|
* returned. |
377
|
|
|
* |
378
|
|
|
* @return mixed |
379
|
|
|
*/ |
380
|
|
|
public function lastInsertID(string $sequence = null) |
381
|
|
|
{ |
382
|
|
|
return $sequence |
383
|
|
|
? (int)$this->getPDO()->lastInsertId($sequence) |
384
|
|
|
: (int)$this->getPDO()->lastInsertId(); |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
/** |
388
|
|
|
* Prepare set of query builder/user parameters to be send to PDO. Must convert DateTime |
389
|
|
|
* instances into valid database timestamps and resolve values of ParameterInterface. |
390
|
|
|
* |
391
|
|
|
* Every value has to wrapped with parameter interface. |
392
|
|
|
* |
393
|
|
|
* @param array $parameters |
394
|
|
|
* |
395
|
|
|
* @return ParameterInterface[] |
396
|
|
|
* |
397
|
|
|
* @throws DriverException |
398
|
|
|
*/ |
399
|
|
|
public function flattenParameters(array $parameters): array |
400
|
|
|
{ |
401
|
|
|
$flatten = []; |
402
|
|
|
foreach ($parameters as $key => $parameter) { |
403
|
|
|
if (!$parameter instanceof ParameterInterface) { |
404
|
|
|
//Let's wrap value |
405
|
|
|
$parameter = new Parameter($parameter, Parameter::DETECT_TYPE); |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
if ($parameter->isArray()) { |
409
|
|
|
if (!is_numeric($key)) { |
410
|
|
|
throw new DriverException("Array parameters can not be named"); |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
//Flattening arrays |
414
|
|
|
$nestedParameters = $parameter->flatten(); |
415
|
|
|
|
416
|
|
|
/** |
417
|
|
|
* @var ParameterInterface $parameter [] |
418
|
|
|
*/ |
419
|
|
|
foreach ($nestedParameters as &$nestedParameter) { |
420
|
|
|
if ($nestedParameter->getValue() instanceof \DateTime) { |
421
|
|
|
|
422
|
|
|
//Original parameter must not be altered |
423
|
|
|
$nestedParameter = $nestedParameter->withValue( |
424
|
|
|
$this->resolveDateTime($nestedParameter->getValue()) |
425
|
|
|
); |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
unset($nestedParameter); |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
//Quick and dirty |
432
|
|
|
$flatten = array_merge($flatten, $nestedParameters); |
433
|
|
|
|
434
|
|
|
} else { |
435
|
|
|
if ($parameter->getValue() instanceof \DateTime) { |
436
|
|
|
//Original parameter must not be altered |
437
|
|
|
$parameter = $parameter->withValue( |
438
|
|
|
$this->resolveDateTime($parameter->getValue()) |
439
|
|
|
); |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
if (is_numeric($key)) { |
443
|
|
|
//Numeric keys can be shifted |
444
|
|
|
$flatten[] = $parameter; |
445
|
|
|
} else { |
446
|
|
|
$flatten[$key] = $parameter; |
447
|
|
|
} |
448
|
|
|
} |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
return $flatten; |
452
|
|
|
} |
453
|
|
|
|
454
|
|
|
/** |
455
|
|
|
* Start SQL transaction with specified isolation level (not all DBMS support it). Nested |
456
|
|
|
* transactions are processed using savepoints. |
457
|
|
|
* |
458
|
|
|
* @link http://en.wikipedia.org/wiki/Database_transaction |
459
|
|
|
* @link http://en.wikipedia.org/wiki/Isolation_(database_systems) |
460
|
|
|
* |
461
|
|
|
* @param string $isolationLevel |
462
|
|
|
* |
463
|
|
|
* @return bool |
464
|
|
|
*/ |
465
|
|
|
public function beginTransaction(string $isolationLevel = null): bool |
466
|
|
|
{ |
467
|
|
|
++$this->transactionLevel; |
468
|
|
|
|
469
|
|
|
if ($this->transactionLevel == 1) { |
470
|
|
|
if (!empty($isolationLevel)) { |
471
|
|
|
$this->isolationLevel($isolationLevel); |
472
|
|
|
} |
473
|
|
|
|
474
|
|
|
if ($this->isProfiling()) { |
475
|
|
|
$this->logger()->info('Begin transaction'); |
476
|
|
|
} |
477
|
|
|
|
478
|
|
|
return $this->getPDO()->beginTransaction(); |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
$this->savepointCreate($this->transactionLevel); |
482
|
|
|
|
483
|
|
|
return true; |
484
|
|
|
} |
485
|
|
|
|
486
|
|
|
/** |
487
|
|
|
* Commit the active database transaction. |
488
|
|
|
* |
489
|
|
|
* @return bool |
490
|
|
|
*/ |
491
|
|
View Code Duplication |
public function commitTransaction(): bool |
|
|
|
|
492
|
|
|
{ |
493
|
|
|
--$this->transactionLevel; |
494
|
|
|
|
495
|
|
|
if ($this->transactionLevel == 0) { |
496
|
|
|
if ($this->isProfiling()) { |
497
|
|
|
$this->logger()->info('Commit transaction'); |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
return $this->getPDO()->commit(); |
501
|
|
|
} |
502
|
|
|
|
503
|
|
|
$this->savepointRelease($this->transactionLevel + 1); |
504
|
|
|
|
505
|
|
|
return true; |
506
|
|
|
} |
507
|
|
|
|
508
|
|
|
/** |
509
|
|
|
* Rollback the active database transaction. |
510
|
|
|
* |
511
|
|
|
* @return bool |
512
|
|
|
*/ |
513
|
|
View Code Duplication |
public function rollbackTransaction(): bool |
|
|
|
|
514
|
|
|
{ |
515
|
|
|
--$this->transactionLevel; |
516
|
|
|
|
517
|
|
|
if ($this->transactionLevel == 0) { |
518
|
|
|
if ($this->isProfiling()) { |
519
|
|
|
$this->logger()->info('Rollback transaction'); |
520
|
|
|
} |
521
|
|
|
|
522
|
|
|
return $this->getPDO()->rollBack(); |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
$this->savepointRollback($this->transactionLevel + 1); |
526
|
|
|
|
527
|
|
|
return true; |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
/** |
531
|
|
|
* @return array |
532
|
|
|
*/ |
533
|
|
|
public function __debugInfo() |
534
|
|
|
{ |
535
|
|
|
return [ |
536
|
|
|
'connection' => $this->config['connection'], |
537
|
|
|
'connected' => $this->isConnected(), |
538
|
|
|
'profiling' => $this->isProfiling(), |
539
|
|
|
'source' => $this->getSource(), |
540
|
|
|
'options' => $this->options, |
541
|
|
|
]; |
542
|
|
|
} |
543
|
|
|
|
544
|
|
|
/** |
545
|
|
|
* Create instance of configured PDO class. |
546
|
|
|
* |
547
|
|
|
* @return PDO |
548
|
|
|
*/ |
549
|
|
|
protected function createPDO(): PDO |
550
|
|
|
{ |
551
|
|
|
return new PDO( |
552
|
|
|
$this->config['connection'], |
553
|
|
|
$this->config['username'], |
554
|
|
|
$this->config['password'], |
555
|
|
|
$this->options |
556
|
|
|
); |
557
|
|
|
} |
558
|
|
|
|
559
|
|
|
/** |
560
|
|
|
* Convert PDO exception into query or integrity exception. |
561
|
|
|
* |
562
|
|
|
* @param \PDOException $exception |
563
|
|
|
* |
564
|
|
|
* @return QueryException |
565
|
|
|
*/ |
566
|
|
|
protected function clarifyException(\PDOException $exception): QueryException |
567
|
|
|
{ |
568
|
|
|
//@todo more exceptions to be thrown |
569
|
|
|
return new QueryException($exception); |
570
|
|
|
} |
571
|
|
|
|
572
|
|
|
/** |
573
|
|
|
* Set transaction isolation level, this feature may not be supported by specific database |
574
|
|
|
* driver. |
575
|
|
|
* |
576
|
|
|
* @param string $level |
577
|
|
|
*/ |
578
|
|
|
protected function isolationLevel(string $level) |
579
|
|
|
{ |
580
|
|
|
if ($this->isProfiling()) { |
581
|
|
|
$this->logger()->info("Set transaction isolation level to '{$level}'"); |
582
|
|
|
} |
583
|
|
|
|
584
|
|
|
if (!empty($level)) { |
585
|
|
|
$this->statement("SET TRANSACTION ISOLATION LEVEL {$level}"); |
586
|
|
|
} |
587
|
|
|
} |
588
|
|
|
|
589
|
|
|
/** |
590
|
|
|
* Create nested transaction save point. |
591
|
|
|
* |
592
|
|
|
* @link http://en.wikipedia.org/wiki/Savepoint |
593
|
|
|
* |
594
|
|
|
* @param string $name Savepoint name/id, must not contain spaces and be valid database |
595
|
|
|
* identifier. |
596
|
|
|
*/ |
597
|
|
View Code Duplication |
protected function savepointCreate(string $name) |
|
|
|
|
598
|
|
|
{ |
599
|
|
|
if ($this->isProfiling()) { |
600
|
|
|
$this->logger()->info("Creating savepoint '{$name}'"); |
601
|
|
|
} |
602
|
|
|
|
603
|
|
|
$this->statement('SAVEPOINT ' . $this->identifier("SVP{$name}")); |
604
|
|
|
} |
605
|
|
|
|
606
|
|
|
/** |
607
|
|
|
* Commit/release savepoint. |
608
|
|
|
* |
609
|
|
|
* @link http://en.wikipedia.org/wiki/Savepoint |
610
|
|
|
* |
611
|
|
|
* @param string $name Savepoint name/id, must not contain spaces and be valid database |
612
|
|
|
* identifier. |
613
|
|
|
*/ |
614
|
|
View Code Duplication |
protected function savepointRelease(string $name) |
|
|
|
|
615
|
|
|
{ |
616
|
|
|
if ($this->isProfiling()) { |
617
|
|
|
$this->logger()->info("Releasing savepoint '{$name}'"); |
618
|
|
|
} |
619
|
|
|
|
620
|
|
|
$this->statement('RELEASE SAVEPOINT ' . $this->identifier("SVP{$name}")); |
621
|
|
|
} |
622
|
|
|
|
623
|
|
|
/** |
624
|
|
|
* Rollback savepoint. |
625
|
|
|
* |
626
|
|
|
* @link http://en.wikipedia.org/wiki/Savepoint |
627
|
|
|
* |
628
|
|
|
* @param string $name Savepoint name/id, must not contain spaces and be valid database |
629
|
|
|
* identifier. |
630
|
|
|
*/ |
631
|
|
View Code Duplication |
protected function savepointRollback(string $name) |
|
|
|
|
632
|
|
|
{ |
633
|
|
|
if ($this->isProfiling()) { |
634
|
|
|
$this->logger()->info("Rolling back savepoint '{$name}'"); |
635
|
|
|
} |
636
|
|
|
$this->statement('ROLLBACK TO SAVEPOINT ' . $this->identifier("SVP{$name}")); |
637
|
|
|
} |
638
|
|
|
|
639
|
|
|
/** |
640
|
|
|
* Convert DateTime object into local database representation. Driver will automatically force |
641
|
|
|
* needed timezone. |
642
|
|
|
* |
643
|
|
|
* @param \DateTime $dateTime |
644
|
|
|
* |
645
|
|
|
* @return string |
646
|
|
|
*/ |
647
|
|
|
protected function resolveDateTime(\DateTime $dateTime): string |
648
|
|
|
{ |
649
|
|
|
return $dateTime->setTimezone($this->getTimezone())->format(static::DATETIME); |
650
|
|
|
} |
651
|
|
|
|
652
|
|
|
/** |
653
|
|
|
* Bind parameters into statement. |
654
|
|
|
* |
655
|
|
|
* @param \PDOStatement $statement |
656
|
|
|
* @param ParameterInterface[] $parameters Named hash of ParameterInterface. |
657
|
|
|
* @return \PDOStatement |
658
|
|
|
*/ |
659
|
|
|
private function bindParameters(\PDOStatement $statement, array $parameters): \PDOStatement |
660
|
|
|
{ |
661
|
|
|
foreach ($parameters as $index => $parameter) { |
662
|
|
|
if (is_numeric($index)) { |
663
|
|
|
//Numeric, @see http://php.net/manual/en/pdostatement.bindparam.php |
664
|
|
|
$statement->bindValue($index + 1, $parameter->getValue(), $parameter->getType()); |
665
|
|
|
} else { |
666
|
|
|
//Named |
667
|
|
|
$statement->bindValue($index, $parameter->getValue(), $parameter->getType()); |
668
|
|
|
} |
669
|
|
|
} |
670
|
|
|
|
671
|
|
|
return $statement; |
672
|
|
|
} |
673
|
|
|
} |
674
|
|
|
|
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.