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\DBALException; |
16
|
|
|
use Doctrine\DBAL\Driver; |
17
|
|
|
use Doctrine\DBAL\Statement; |
18
|
|
|
use Kdyby; |
19
|
|
|
use Nette; |
20
|
|
|
use Nette\Utils\ObjectMixin; |
21
|
|
|
use PDO; |
22
|
|
|
use Tracy; |
23
|
|
|
|
24
|
|
|
|
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @author Filip Procházka <[email protected]> |
28
|
|
|
*/ |
29
|
|
|
class Connection extends Doctrine\DBAL\Connection |
30
|
|
|
{ |
31
|
|
|
/** |
32
|
|
|
* @var bool |
33
|
|
|
*/ |
34
|
|
|
public $throwOldKdybyExceptions = FALSE; |
35
|
|
|
|
36
|
|
|
/** @deprecated */ |
37
|
|
|
const MYSQL_ERR_UNIQUE = 1062; |
38
|
|
|
/** @deprecated */ |
39
|
|
|
const MYSQL_ERR_NOT_NULL = 1048; |
40
|
|
|
|
41
|
|
|
/** @deprecated */ |
42
|
|
|
const SQLITE_ERR_UNIQUE = 19; |
43
|
|
|
|
44
|
|
|
/** @deprecated */ |
45
|
|
|
const POSTGRE_ERR_UNIQUE = 23505; // todo: verify, source: http://www.postgresql.org/docs/8.2/static/errcodes-appendix.html |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @var Doctrine\ORM\EntityManager |
49
|
|
|
*/ |
50
|
|
|
private $entityManager; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @var array |
54
|
|
|
*/ |
55
|
|
|
private $schemaTypes = []; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* @var array |
59
|
|
|
*/ |
60
|
|
|
private $dbalTypes = []; |
61
|
|
|
|
62
|
|
|
|
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @internal |
66
|
|
|
* @param Doctrine\ORM\EntityManager $em |
67
|
|
|
* @return $this |
68
|
|
|
*/ |
69
|
|
|
public function bindEntityManager(Doctrine\ORM\EntityManager $em) |
|
|
|
|
70
|
|
|
{ |
71
|
|
|
$this->entityManager = $em; |
72
|
|
|
return $this; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
|
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Tries to autodetect, if identifier has to be quoted and quotes it. |
79
|
|
|
* |
80
|
|
|
* @param string $expression |
81
|
|
|
* @return string |
82
|
|
|
*/ |
83
|
|
|
public function quoteIdentifier($expression) |
84
|
|
|
{ |
85
|
|
|
$expression = trim($expression); |
86
|
|
|
if ($expression[0] === $this->getDatabasePlatform()->getIdentifierQuoteCharacter()) { |
87
|
|
|
return $expression; // already quoted |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
return parent::quoteIdentifier($expression); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
|
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* {@inheritdoc} |
97
|
|
|
*/ |
98
|
|
|
public function delete($tableExpression, array $identifier, array $types = []) |
99
|
|
|
{ |
100
|
|
|
$fixedIdentifier = []; |
101
|
|
|
foreach ($identifier as $columnName => $value) { |
102
|
|
|
$fixedIdentifier[$this->quoteIdentifier($columnName)] = $value; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
return parent::delete($this->quoteIdentifier($tableExpression), $fixedIdentifier, $types); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
|
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* {@inheritdoc} |
112
|
|
|
*/ |
113
|
|
|
public function update($tableExpression, array $data, array $identifier, array $types = []) |
114
|
|
|
{ |
115
|
|
|
$fixedData = []; |
116
|
|
|
foreach ($data as $columnName => $value) { |
117
|
|
|
$fixedData[$this->quoteIdentifier($columnName)] = $value; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
$fixedIdentifier = []; |
121
|
|
|
foreach ($identifier as $columnName => $value) { |
122
|
|
|
$fixedIdentifier[$this->quoteIdentifier($columnName)] = $value; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
return parent::update($this->quoteIdentifier($tableExpression), $fixedData, $fixedIdentifier, $types); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
|
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* {@inheritdoc} |
132
|
|
|
*/ |
133
|
|
|
public function insert($tableExpression, array $data, array $types = []) |
134
|
|
|
{ |
135
|
|
|
$fixedData = []; |
136
|
|
|
foreach ($data as $columnName => $value) { |
137
|
|
|
$fixedData[$this->quoteIdentifier($columnName)] = $value; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
return parent::insert($this->quoteIdentifier($tableExpression), $fixedData, $types); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
|
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Prepares an SQL statement. |
147
|
|
|
* |
148
|
|
|
* @param string $statement The SQL statement to prepare. |
149
|
|
|
* @throws DBALException |
150
|
|
|
* @return Statement The prepared statement. |
151
|
|
|
*/ |
152
|
|
|
public function prepare($statement) |
153
|
|
|
{ |
154
|
|
|
$this->connect(); |
155
|
|
|
|
156
|
|
|
try { |
157
|
|
|
$stmt = new Statement($statement, $this); |
158
|
|
|
|
159
|
|
|
} catch (\Exception $ex) { |
160
|
|
|
throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
$stmt->setFetchMode(PDO::FETCH_ASSOC); |
164
|
|
|
|
165
|
|
|
return $stmt; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
|
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* @return Doctrine\DBAL\Query\QueryBuilder|NativeQueryBuilder |
172
|
|
|
*/ |
173
|
|
|
public function createQueryBuilder() |
174
|
|
|
{ |
175
|
|
|
if (!$this->entityManager) { |
176
|
|
|
return parent::createQueryBuilder(); |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
return new NativeQueryBuilder($this->entityManager); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
|
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* @param array $schemaTypes |
186
|
|
|
*/ |
187
|
|
|
public function setSchemaTypes(array $schemaTypes) |
188
|
|
|
{ |
189
|
|
|
$this->schemaTypes = $schemaTypes; |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
|
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* @param array $dbalTypes |
196
|
|
|
*/ |
197
|
|
|
public function setDbalTypes(array $dbalTypes) |
198
|
|
|
{ |
199
|
|
|
$this->dbalTypes = $dbalTypes; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
|
203
|
|
|
|
204
|
|
|
public function connect() |
205
|
|
|
{ |
206
|
|
|
if ($this->isConnected()) { |
207
|
|
|
return FALSE; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
foreach ($this->dbalTypes as $name => $className) { |
211
|
|
|
if (DbalType::hasType($name)) { |
212
|
|
|
DbalType::overrideType($name, $className); |
213
|
|
|
|
214
|
|
|
} else { |
215
|
|
|
DbalType::addType($name, $className); |
216
|
|
|
} |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
parent::connect(); |
220
|
|
|
|
221
|
|
|
$platform = $this->getDatabasePlatform(); |
222
|
|
|
|
223
|
|
|
foreach ($this->schemaTypes as $dbType => $doctrineType) { |
224
|
|
|
$platform->registerDoctrineTypeMapping($dbType, $doctrineType); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
foreach ($this->dbalTypes as $type => $className) { |
228
|
|
|
$platform->markDoctrineTypeCommented(DbalType::getType($type)); |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
return TRUE; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
|
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* @return Doctrine\DBAL\Platforms\AbstractPlatform |
238
|
|
|
*/ |
239
|
|
|
public function getDatabasePlatform() |
240
|
|
|
{ |
241
|
|
|
if (!$this->isConnected()) { |
242
|
|
|
$this->connect(); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
return parent::getDatabasePlatform(); |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
|
249
|
|
|
|
250
|
|
|
public function ping() |
251
|
|
|
{ |
252
|
|
|
$conn = $this->getWrappedConnection(); |
253
|
|
|
if ($conn instanceof Driver\PingableConnection) { |
254
|
|
|
return $conn->ping(); |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
set_error_handler(function ($severity, $message) { |
258
|
|
|
throw new \PDOException($message, $severity); |
259
|
|
|
}); |
260
|
|
|
|
261
|
|
|
try { |
262
|
|
|
$this->query($this->getDatabasePlatform()->getDummySelectSQL()); |
263
|
|
|
restore_error_handler(); |
264
|
|
|
|
265
|
|
|
return TRUE; |
266
|
|
|
|
267
|
|
|
} catch (DBALException $e) { |
268
|
|
|
restore_error_handler(); |
269
|
|
|
return FALSE; |
270
|
|
|
|
271
|
|
|
} catch (\Exception $e) { |
272
|
|
|
restore_error_handler(); |
273
|
|
|
throw $e; |
274
|
|
|
} |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
|
278
|
|
|
|
279
|
|
|
/** |
280
|
|
|
* @param array $params |
281
|
|
|
* @param \Doctrine\DBAL\Configuration $config |
282
|
|
|
* @param \Doctrine\Common\EventManager $eventManager |
283
|
|
|
* @param array $dbalTypes |
|
|
|
|
284
|
|
|
* @param array $schemaTypes |
|
|
|
|
285
|
|
|
* @return Connection |
286
|
|
|
*/ |
287
|
|
|
public static function create(array $params, Doctrine\DBAL\Configuration $config, EventManager $eventManager) |
288
|
|
|
{ |
289
|
|
|
if (!isset($params['wrapperClass'])) { |
290
|
|
|
$params['wrapperClass'] = get_called_class(); |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
return Doctrine\DBAL\DriverManager::getConnection($params, $config, $eventManager); |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
|
297
|
|
|
|
298
|
|
|
/*************************** Nette\Object ***************************/ |
299
|
|
|
|
300
|
|
|
|
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* Access to reflection. |
304
|
|
|
* @return \Nette\Reflection\ClassType |
305
|
|
|
*/ |
306
|
|
|
public static function getReflection() |
307
|
|
|
{ |
308
|
|
|
return new Nette\Reflection\ClassType(get_called_class()); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
|
312
|
|
|
|
313
|
|
|
/** |
314
|
|
|
* Call to undefined method. |
315
|
|
|
* |
316
|
|
|
* @param string $name |
317
|
|
|
* @param array $args |
318
|
|
|
* |
319
|
|
|
* @throws \Nette\MemberAccessException |
320
|
|
|
* @return mixed |
321
|
|
|
*/ |
322
|
|
|
public function __call($name, $args) |
323
|
|
|
{ |
324
|
|
|
return ObjectMixin::call($this, $name, $args); |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
|
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* Call to undefined static method. |
331
|
|
|
* |
332
|
|
|
* @param string $name |
333
|
|
|
* @param array $args |
334
|
|
|
* |
335
|
|
|
* @throws \Nette\MemberAccessException |
336
|
|
|
* @return mixed |
337
|
|
|
*/ |
338
|
|
|
public static function __callStatic($name, $args) |
339
|
|
|
{ |
340
|
|
|
return ObjectMixin::callStatic(get_called_class(), $name, $args); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
|
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* Adding method to class. |
347
|
|
|
* |
348
|
|
|
* @param $name |
349
|
|
|
* @param null $callback |
350
|
|
|
* |
351
|
|
|
* @throws \Nette\MemberAccessException |
352
|
|
|
* @return callable|null |
353
|
|
|
*/ |
354
|
|
|
public static function extensionMethod($name, $callback = NULL) |
355
|
|
|
{ |
356
|
|
|
if (strpos($name, '::') === FALSE) { |
357
|
|
|
$class = get_called_class(); |
358
|
|
|
} else { |
359
|
|
|
list($class, $name) = explode('::', $name); |
360
|
|
|
} |
361
|
|
|
if ($callback === NULL) { |
362
|
|
|
return ObjectMixin::getExtensionMethod($class, $name); |
363
|
|
|
} else { |
364
|
|
|
ObjectMixin::setExtensionMethod($class, $name, $callback); |
365
|
|
|
} |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
|
369
|
|
|
|
370
|
|
|
/** |
371
|
|
|
* Returns property value. Do not call directly. |
372
|
|
|
* |
373
|
|
|
* @param string $name |
374
|
|
|
* |
375
|
|
|
* @throws \Nette\MemberAccessException |
376
|
|
|
* @return mixed |
377
|
|
|
*/ |
378
|
|
|
public function &__get($name) |
379
|
|
|
{ |
380
|
|
|
return ObjectMixin::get($this, $name); |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
|
384
|
|
|
|
385
|
|
|
/** |
386
|
|
|
* Sets value of a property. Do not call directly. |
387
|
|
|
* |
388
|
|
|
* @param string $name |
389
|
|
|
* @param mixed $value |
390
|
|
|
* |
391
|
|
|
* @throws \Nette\MemberAccessException |
392
|
|
|
* @return void |
393
|
|
|
*/ |
394
|
|
|
public function __set($name, $value) |
395
|
|
|
{ |
396
|
|
|
if ($name === '_conn') { |
397
|
|
|
$this->_conn = $value; |
398
|
|
|
return; |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
ObjectMixin::set($this, $name, $value); |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
|
405
|
|
|
|
406
|
|
|
/** |
407
|
|
|
* Is property defined? |
408
|
|
|
* |
409
|
|
|
* @param string $name |
410
|
|
|
* |
411
|
|
|
* @return bool |
412
|
|
|
*/ |
413
|
|
|
public function __isset($name) |
414
|
|
|
{ |
415
|
|
|
return ObjectMixin::has($this, $name); |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
|
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* Access to undeclared property. |
422
|
|
|
* |
423
|
|
|
* @param string $name |
424
|
|
|
* |
425
|
|
|
* @throws \Nette\MemberAccessException |
426
|
|
|
* @return void |
427
|
|
|
*/ |
428
|
|
|
public function __unset($name) |
429
|
|
|
{ |
430
|
|
|
if ($name === '_conn') { |
431
|
|
|
$this->$name = NULL; |
432
|
|
|
return; |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
ObjectMixin::remove($this, $name); |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
} |
439
|
|
|
|
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.