Connection::ping()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 9.504
c 0
b 0
f 0
cc 4
nc 6
nop 0
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)
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $em. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

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:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The method Kdyby\Doctrine\Connection::resolveException() has been deprecated.

This method has been deprecated.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The method Kdyby\Doctrine\Connection::resolveException() has been deprecated.

This method has been deprecated.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The method Kdyby\Doctrine\Connection::resolveException() has been deprecated.

This method has been deprecated.

Loading history...
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));
0 ignored issues
show
Deprecated Code introduced by
The method Kdyby\Doctrine\Connection::resolveException() has been deprecated.

This method has been deprecated.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The method Kdyby\Doctrine\Connection::resolveException() has been deprecated.

This method has been deprecated.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The class Kdyby\Doctrine\DBALException has been deprecated.

This class, trait or interface has been deprecated.

Loading history...
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
0 ignored issues
show
Deprecated Code introduced by
The constant Kdyby\Doctrine\Connection::MYSQL_ERR_UNIQUE has been deprecated.

This class constant has been deprecated.

Loading history...
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) { }
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
412
413
				return new DuplicateEntryException($e, $columns, $query, $params, $this);
0 ignored issues
show
Deprecated Code introduced by
The class Kdyby\Doctrine\DuplicateEntryException has been deprecated.

This class, trait or interface has been deprecated.

Loading history...
414
415
			} elseif ($info[0] == 23000 && $info[1] == self::MYSQL_ERR_NOT_NULL) { // notnull fail
0 ignored issues
show
Deprecated Code introduced by
The constant Kdyby\Doctrine\Connection::MYSQL_ERR_NOT_NULL has been deprecated.

This class constant has been deprecated.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The class Kdyby\Doctrine\EmptyValueException has been deprecated.

This class, trait or interface has been deprecated.

Loading history...
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());
0 ignored issues
show
Deprecated Code introduced by
The class Kdyby\Doctrine\DBALException has been deprecated.

This class, trait or interface has been deprecated.

Loading history...
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