1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This file is part of the Kdyby (http://www.kdyby.org) |
5
|
|
|
* |
6
|
|
|
* Copyright (c) 2008 Filip Procházka ([email protected]) |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the file license.txt that was distributed with this source code. |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
namespace Kdyby\Doctrine; |
12
|
|
|
|
13
|
|
|
use Doctrine; |
14
|
|
|
use Doctrine\Common\EventManager; |
15
|
|
|
use Doctrine\DBAL\Types\Type; |
16
|
|
|
use Doctrine\DBAL\Driver; |
17
|
|
|
use Kdyby; |
18
|
|
|
use Nette; |
19
|
|
|
use PDO; |
20
|
|
|
use Tracy; |
21
|
|
|
|
22
|
|
|
|
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @author Filip Procházka <[email protected]> |
26
|
|
|
*/ |
27
|
|
|
class Connection extends Doctrine\DBAL\Connection |
28
|
|
|
{ |
29
|
|
|
|
30
|
|
|
use \Kdyby\StrictObjects\Scream; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var bool |
34
|
|
|
*/ |
35
|
|
|
public $throwOldKdybyExceptions = FALSE; |
36
|
|
|
|
37
|
|
|
/** @deprecated */ |
38
|
|
|
const MYSQL_ERR_UNIQUE = 1062; |
39
|
|
|
/** @deprecated */ |
40
|
|
|
const MYSQL_ERR_NOT_NULL = 1048; |
41
|
|
|
|
42
|
|
|
/** @deprecated */ |
43
|
|
|
const SQLITE_ERR_UNIQUE = 19; |
44
|
|
|
|
45
|
|
|
/** @deprecated */ |
46
|
|
|
const POSTGRE_ERR_UNIQUE = 23505; // todo: verify, source: http://www.postgresql.org/docs/8.2/static/errcodes-appendix.html |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var Doctrine\ORM\EntityManager |
50
|
|
|
*/ |
51
|
|
|
private $entityManager; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @var array |
55
|
|
|
*/ |
56
|
|
|
private $schemaTypes = []; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* @var array |
60
|
|
|
*/ |
61
|
|
|
private $dbalTypes = []; |
62
|
|
|
|
63
|
|
|
|
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* @internal |
67
|
|
|
* @param Doctrine\ORM\EntityManager $em |
68
|
|
|
* @return $this |
69
|
|
|
*/ |
70
|
|
|
public function bindEntityManager(Doctrine\ORM\EntityManager $em) |
|
|
|
|
71
|
|
|
{ |
72
|
|
|
$this->entityManager = $em; |
73
|
|
|
return $this; |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
|
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Tries to autodetect, if identifier has to be quoted and quotes it. |
80
|
|
|
* |
81
|
|
|
* @param string $expression |
82
|
|
|
* @return string |
83
|
|
|
*/ |
84
|
|
|
public function quoteIdentifier($expression) |
85
|
|
|
{ |
86
|
|
|
$expression = trim($expression); |
87
|
|
|
if ($expression[0] === $this->getDatabasePlatform()->getIdentifierQuoteCharacter()) { |
88
|
|
|
return $expression; // already quoted |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
return parent::quoteIdentifier($expression); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
|
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* {@inheritdoc} |
98
|
|
|
*/ |
99
|
|
|
public function delete($tableExpression, array $identifier, array $types = []) |
100
|
|
|
{ |
101
|
|
|
$fixedIdentifier = []; |
102
|
|
|
foreach ($identifier as $columnName => $value) { |
103
|
|
|
$fixedIdentifier[$this->quoteIdentifier($columnName)] = $value; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
return parent::delete($this->quoteIdentifier($tableExpression), $fixedIdentifier, $types); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
|
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* {@inheritdoc} |
113
|
|
|
*/ |
114
|
|
|
public function update($tableExpression, array $data, array $identifier, array $types = []) |
115
|
|
|
{ |
116
|
|
|
$fixedData = []; |
117
|
|
|
foreach ($data as $columnName => $value) { |
118
|
|
|
$fixedData[$this->quoteIdentifier($columnName)] = $value; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
$fixedIdentifier = []; |
122
|
|
|
foreach ($identifier as $columnName => $value) { |
123
|
|
|
$fixedIdentifier[$this->quoteIdentifier($columnName)] = $value; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
return parent::update($this->quoteIdentifier($tableExpression), $fixedData, $fixedIdentifier, $types); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
|
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* {@inheritdoc} |
133
|
|
|
*/ |
134
|
|
|
public function insert($tableExpression, array $data, array $types = []) |
135
|
|
|
{ |
136
|
|
|
$fixedData = []; |
137
|
|
|
foreach ($data as $columnName => $value) { |
138
|
|
|
$fixedData[$this->quoteIdentifier($columnName)] = $value; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
return parent::insert($this->quoteIdentifier($tableExpression), $fixedData, $types); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
|
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* @param string $query |
148
|
|
|
* @param array $params |
149
|
|
|
* @param array $types |
150
|
|
|
* @param \Doctrine\DBAL\Cache\QueryCacheProfile $qcp |
151
|
|
|
* @return Driver\ResultStatement |
152
|
|
|
* @throws DBALException |
153
|
|
|
*/ |
154
|
|
|
public function executeQuery($query, array $params = [], $types = [], Doctrine\DBAL\Cache\QueryCacheProfile $qcp = NULL) |
155
|
|
|
{ |
156
|
|
|
try { |
157
|
|
|
return parent::executeQuery($query, $params, $types, $qcp); |
158
|
|
|
|
159
|
|
|
} catch (\Exception $e) { |
160
|
|
|
throw $this->resolveException($e, $query, $params); |
|
|
|
|
161
|
|
|
} |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
|
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* @param string $query |
168
|
|
|
* @param array $params |
169
|
|
|
* @param array $types |
170
|
|
|
* @return int |
171
|
|
|
* @throws DBALException |
172
|
|
|
*/ |
173
|
|
|
public function executeUpdate($query, array $params = [], array $types = []) |
174
|
|
|
{ |
175
|
|
|
try { |
176
|
|
|
return parent::executeUpdate($query, $params, $types); |
177
|
|
|
|
178
|
|
|
} catch (\Exception $e) { |
179
|
|
|
throw $this->resolveException($e, $query, $params); |
|
|
|
|
180
|
|
|
} |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
|
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* @param string $statement |
187
|
|
|
* @return int |
188
|
|
|
* @throws DBALException |
189
|
|
|
*/ |
190
|
|
|
public function exec($statement) |
191
|
|
|
{ |
192
|
|
|
try { |
193
|
|
|
return parent::exec($statement); |
194
|
|
|
|
195
|
|
|
} catch (\Exception $e) { |
196
|
|
|
throw $this->resolveException($e, $statement); |
|
|
|
|
197
|
|
|
} |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
|
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* @return \Doctrine\DBAL\Driver\Statement|mixed |
204
|
|
|
* @throws DBALException |
205
|
|
|
*/ |
206
|
|
|
public function query() |
207
|
|
|
{ |
208
|
|
|
$args = func_get_args(); |
209
|
|
|
try { |
210
|
|
|
return call_user_func_array('parent::query', $args); |
211
|
|
|
|
212
|
|
|
} catch (\Exception $e) { |
213
|
|
|
throw $this->resolveException($e, func_get_arg(0)); |
|
|
|
|
214
|
|
|
} |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
|
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* Prepares an SQL statement. |
221
|
|
|
* |
222
|
|
|
* @param string $statement The SQL statement to prepare. |
223
|
|
|
* @throws DBALException |
224
|
|
|
* @return PDOStatement The prepared statement. |
225
|
|
|
*/ |
226
|
|
|
public function prepare($statement) |
227
|
|
|
{ |
228
|
|
|
$this->connect(); |
229
|
|
|
|
230
|
|
|
try { |
231
|
|
|
$stmt = new PDOStatement($statement, $this); |
232
|
|
|
|
233
|
|
|
} catch (\Exception $ex) { |
234
|
|
|
throw $this->resolveException(Doctrine\DBAL\DBALException::driverExceptionDuringQuery($this->getDriver(), $ex, $statement), $statement); |
|
|
|
|
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
$stmt->setFetchMode(PDO::FETCH_ASSOC); |
238
|
|
|
|
239
|
|
|
return $stmt; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
|
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* @return Doctrine\DBAL\Query\QueryBuilder|NativeQueryBuilder |
246
|
|
|
*/ |
247
|
|
|
public function createQueryBuilder() |
248
|
|
|
{ |
249
|
|
|
if (!$this->entityManager) { |
250
|
|
|
return parent::createQueryBuilder(); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
return new NativeQueryBuilder($this->entityManager); |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
|
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* @param array $schemaTypes |
260
|
|
|
*/ |
261
|
|
|
public function setSchemaTypes(array $schemaTypes) |
262
|
|
|
{ |
263
|
|
|
$this->schemaTypes = $schemaTypes; |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
|
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* @param array $dbalTypes |
270
|
|
|
*/ |
271
|
|
|
public function setDbalTypes(array $dbalTypes) |
272
|
|
|
{ |
273
|
|
|
$this->dbalTypes = $dbalTypes; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
|
277
|
|
|
|
278
|
|
|
public function connect() |
279
|
|
|
{ |
280
|
|
|
if ($this->isConnected()) { |
281
|
|
|
return FALSE; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
foreach ($this->dbalTypes as $name => $className) { |
285
|
|
|
if (Type::hasType($name)) { |
286
|
|
|
Type::overrideType($name, $className); |
287
|
|
|
|
288
|
|
|
} else { |
289
|
|
|
Type::addType($name, $className); |
290
|
|
|
} |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
parent::connect(); |
294
|
|
|
|
295
|
|
|
$platform = $this->getDatabasePlatform(); |
296
|
|
|
|
297
|
|
|
foreach ($this->schemaTypes as $dbType => $doctrineType) { |
298
|
|
|
$platform->registerDoctrineTypeMapping($dbType, $doctrineType); |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
foreach ($this->dbalTypes as $type => $className) { |
302
|
|
|
$platform->markDoctrineTypeCommented(Type::getType($type)); |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
return TRUE; |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
|
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* @return Doctrine\DBAL\Platforms\AbstractPlatform |
312
|
|
|
*/ |
313
|
|
|
public function getDatabasePlatform() |
314
|
|
|
{ |
315
|
|
|
if (!$this->isConnected()) { |
316
|
|
|
$this->connect(); |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
return parent::getDatabasePlatform(); |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
|
323
|
|
|
|
324
|
|
|
public function ping() |
325
|
|
|
{ |
326
|
|
|
$conn = $this->getWrappedConnection(); |
327
|
|
|
if ($conn instanceof Driver\PingableConnection) { |
328
|
|
|
return $conn->ping(); |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
set_error_handler(function ($severity, $message) { |
332
|
|
|
throw new \PDOException($message, $severity); |
333
|
|
|
}); |
334
|
|
|
|
335
|
|
|
try { |
336
|
|
|
$this->query($this->getDatabasePlatform()->getDummySelectSQL()); |
337
|
|
|
restore_error_handler(); |
338
|
|
|
|
339
|
|
|
return TRUE; |
340
|
|
|
|
341
|
|
|
} catch (Doctrine\DBAL\DBALException $e) { |
342
|
|
|
restore_error_handler(); |
343
|
|
|
return FALSE; |
344
|
|
|
|
345
|
|
|
} catch (\Exception $e) { |
346
|
|
|
restore_error_handler(); |
347
|
|
|
throw $e; |
348
|
|
|
} |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
|
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* @param array $params |
355
|
|
|
* @param \Doctrine\DBAL\Configuration $config |
356
|
|
|
* @param \Doctrine\Common\EventManager $eventManager |
357
|
|
|
* @return \Kdyby\Doctrine\Connection |
358
|
|
|
*/ |
359
|
|
|
public static function create(array $params, Doctrine\DBAL\Configuration $config, EventManager $eventManager) |
360
|
|
|
{ |
361
|
|
|
if (!isset($params['wrapperClass'])) { |
362
|
|
|
$params['wrapperClass'] = get_called_class(); |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
/** @var \Kdyby\Doctrine\Connection $connection */ |
366
|
|
|
$connection = Doctrine\DBAL\DriverManager::getConnection($params, $config, $eventManager); |
367
|
|
|
return $connection; |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
|
371
|
|
|
|
372
|
|
|
/** |
373
|
|
|
* @deprecated |
374
|
|
|
* @internal |
375
|
|
|
* @param \Exception|\Throwable $e |
376
|
|
|
* @param string $query |
377
|
|
|
* @param array $params |
378
|
|
|
* @return \Kdyby\Doctrine\DBALException|\Exception|\Throwable |
379
|
|
|
*/ |
380
|
|
|
public function resolveException($e, $query = NULL, $params = []) |
381
|
|
|
{ |
382
|
|
|
if ($this->throwOldKdybyExceptions !== TRUE) { |
383
|
|
|
return $e; |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
if ($e instanceof Doctrine\DBAL\DBALException && ($pe = $e->getPrevious()) instanceof \PDOException) { |
387
|
|
|
$info = $pe->errorInfo; |
388
|
|
|
|
389
|
|
|
} elseif ($e instanceof \PDOException) { |
390
|
|
|
$info = $e->errorInfo; |
391
|
|
|
|
392
|
|
|
} else { |
393
|
|
|
return new DBALException($e, $query, $params, $this); |
|
|
|
|
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
if ($this->getDriver() instanceof Doctrine\DBAL\Driver\PDOMySql\Driver) { |
397
|
|
|
if ($info[0] == 23000 && $info[1] == self::MYSQL_ERR_UNIQUE) { // unique fail |
|
|
|
|
398
|
|
|
$columns = []; |
399
|
|
|
|
400
|
|
|
try { |
401
|
|
|
if (preg_match('~Duplicate entry .*? for key \'([^\']+)\'~', $info[2], $m)) { |
402
|
|
|
$table = self::resolveExceptionTable($e); |
403
|
|
|
if ($table !== NULL) { |
404
|
|
|
$indexes = $this->getSchemaManager()->listTableIndexes($table); |
405
|
|
|
if (array_key_exists($m[1], $indexes)) { |
406
|
|
|
$columns[$m[1]] = $indexes[$m[1]]->getColumns(); |
407
|
|
|
} |
408
|
|
|
} |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
} catch (\Exception $e) { } |
|
|
|
|
412
|
|
|
|
413
|
|
|
return new DuplicateEntryException($e, $columns, $query, $params, $this); |
|
|
|
|
414
|
|
|
|
415
|
|
|
} elseif ($info[0] == 23000 && $info[1] == self::MYSQL_ERR_NOT_NULL) { // notnull fail |
|
|
|
|
416
|
|
|
$column = NULL; |
417
|
|
|
if (preg_match('~Column \'([^\']+)\'~', $info[2], $m)) { |
418
|
|
|
$column = $m[1]; |
419
|
|
|
} |
420
|
|
|
|
421
|
|
|
return new EmptyValueException($e, $column, $query, $params, $this); |
|
|
|
|
422
|
|
|
} |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
$raw = $e; |
426
|
|
|
do { |
427
|
|
|
$raw = $raw->getPrevious(); |
428
|
|
|
} while ($raw && !$raw instanceof \PDOException); |
429
|
|
|
|
430
|
|
|
return new DBALException($e, $query, $params, $this, $raw ? $raw->getMessage() : $e->getMessage()); |
|
|
|
|
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
|
434
|
|
|
|
435
|
|
|
/** |
436
|
|
|
* @param \Exception|\Throwable $e |
437
|
|
|
* @return string|NULL |
438
|
|
|
*/ |
439
|
|
|
private static function resolveExceptionTable($e) |
440
|
|
|
{ |
441
|
|
|
if (!$e instanceof Doctrine\DBAL\DBALException) { |
442
|
|
|
return NULL; |
443
|
|
|
} |
444
|
|
|
|
445
|
|
|
if ($caused = Tracy\Helpers::findTrace($e->getTrace(), Doctrine\DBAL\DBALException::class . '::driverExceptionDuringQuery')) { |
446
|
|
|
if (preg_match('~(?:INSERT|UPDATE|REPLACE)(?:[A-Z_\s]*)`?([^\s`]+)`?\s*~', is_string($caused['args'][1]) ? $caused['args'][1] : $caused['args'][2], $m)) { |
447
|
|
|
return $m[1]; |
448
|
|
|
} |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
return NULL; |
452
|
|
|
} |
453
|
|
|
|
454
|
|
|
} |
455
|
|
|
|
The
EntityManager
might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:If that code throws an exception and the
EntityManager
is closed. Any other code which depends on the same instance of theEntityManager
during this request will fail.On the other hand, if you instead inject the
ManagerRegistry
, thegetManager()
method guarantees that you will always get a usable manager instance.