Completed
Push — master ( 05d193...e557e0 )
by Filip
04:55
created

Connection   C

Complexity

Total Complexity 74

Size/Duplication

Total Lines 563
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 5

Importance

Changes 0
Metric Value
wmc 74
lcom 2
cbo 5
dl 0
loc 563
rs 5.5244
c 0
b 0
f 0

27 Methods

Rating   Name   Duplication   Size   Complexity  
A bindEntityManager() 0 5 1
A quoteIdentifier() 0 9 2
A executeQuery() 0 9 2
A executeUpdate() 0 9 2
A exec() 0 9 2
A query() 0 10 2
A prepare() 0 15 2
A createQueryBuilder() 0 8 2
A setSchemaTypes() 0 4 1
A setDbalTypes() 0 4 1
A create() 0 8 2
B resolveExceptionTable() 0 14 5
A getReflection() 0 4 1
A __call() 0 4 1
A __callStatic() 0 4 1
A extensionMethod() 0 13 3
A __get() 0 4 1
A __set() 0 9 2
A __isset() 0 4 1
A __unset() 0 9 2
A delete() 0 9 2
A update() 0 14 3
A insert() 0 9 2
B connect() 0 29 6
A getDatabasePlatform() 0 8 2
B ping() 0 26 4
C resolveException() 0 50 19

How to fix   Complexity   

Complex Class

Complex classes like Connection often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Connection, and based on these observations, apply Extract Interface, too.

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 Nette\Utils\ObjectMixin;
20
use PDO;
21
use Tracy;
22
23
24
25
/**
26
 * @author Filip Procházka <[email protected]>
27
 */
28
class Connection extends Doctrine\DBAL\Connection
29
{
30
	/**
31
	 * @var bool
32
	 */
33
	public $throwOldKdybyExceptions = FALSE;
34
35
	/** @deprecated */
36
	const MYSQL_ERR_UNIQUE = 1062;
37
	/** @deprecated */
38
	const MYSQL_ERR_NOT_NULL = 1048;
39
40
	/** @deprecated */
41
	const SQLITE_ERR_UNIQUE = 19;
42
43
	/** @deprecated */
44
	const POSTGRE_ERR_UNIQUE = 23505; // todo: verify, source: http://www.postgresql.org/docs/8.2/static/errcodes-appendix.html
45
46
	/**
47
	 * @var Doctrine\ORM\EntityManager
48
	 */
49
	private $entityManager;
50
51
	/**
52
	 * @var array
53
	 */
54
	private $schemaTypes = [];
55
56
	/**
57
	 * @var array
58
	 */
59
	private $dbalTypes = [];
60
61
62
63
	/**
64
	 * @internal
65
	 * @param Doctrine\ORM\EntityManager $em
66
	 * @return $this
67
	 */
68
	public function bindEntityManager(Doctrine\ORM\EntityManager $em)
69
	{
70
		$this->entityManager = $em;
71
		return $this;
72
	}
73
74
75
76
	/**
77
	 * Tries to autodetect, if identifier has to be quoted and quotes it.
78
	 *
79
	 * @param string $expression
80
	 * @return string
81
	 */
82
	public function quoteIdentifier($expression)
83
	{
84
		$expression = trim($expression);
85
		if ($expression[0] === $this->getDatabasePlatform()->getIdentifierQuoteCharacter()) {
86
			return $expression; // already quoted
87
		}
88
89
		return parent::quoteIdentifier($expression);
90
	}
91
92
93
94
	/**
95
	 * {@inheritdoc}
96
	 */
97
	public function delete($tableExpression, array $identifier, array $types = [])
98
	{
99
		$fixedIdentifier = [];
100
		foreach ($identifier as $columnName => $value) {
101
			$fixedIdentifier[$this->quoteIdentifier($columnName)] = $value;
102
		}
103
104
		return parent::delete($this->quoteIdentifier($tableExpression), $fixedIdentifier, $types);
105
	}
106
107
108
109
	/**
110
	 * {@inheritdoc}
111
	 */
112
	public function update($tableExpression, array $data, array $identifier, array $types = [])
113
	{
114
		$fixedData = [];
115
		foreach ($data as $columnName => $value) {
116
			$fixedData[$this->quoteIdentifier($columnName)] = $value;
117
		}
118
119
		$fixedIdentifier = [];
120
		foreach ($identifier as $columnName => $value) {
121
			$fixedIdentifier[$this->quoteIdentifier($columnName)] = $value;
122
		}
123
124
		return parent::update($this->quoteIdentifier($tableExpression), $fixedData, $fixedIdentifier, $types);
125
	}
126
127
128
129
	/**
130
	 * {@inheritdoc}
131
	 */
132
	public function insert($tableExpression, array $data, array $types = [])
133
	{
134
		$fixedData = [];
135
		foreach ($data as $columnName => $value) {
136
			$fixedData[$this->quoteIdentifier($columnName)] = $value;
137
		}
138
139
		return parent::insert($this->quoteIdentifier($tableExpression), $fixedData, $types);
140
	}
141
142
143
144
	/**
145
	 * @param string $query
146
	 * @param array $params
147
	 * @param array $types
148
	 * @param \Doctrine\DBAL\Cache\QueryCacheProfile $qcp
149
	 * @return \Doctrine\DBAL\Driver\Statement
150
	 * @throws DBALException
151
	 */
152
	public function executeQuery($query, array $params = [], $types = [], Doctrine\DBAL\Cache\QueryCacheProfile $qcp = NULL)
153
	{
154
		try {
155
			return parent::executeQuery($query, $params, $types, $qcp);
156
157
		} catch (\Exception $e) {
158
			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...
159
		}
160
	}
161
162
163
164
	/**
165
	 * @param string $query
166
	 * @param array $params
167
	 * @param array $types
168
	 * @return int
169
	 * @throws DBALException
170
	 */
171
	public function executeUpdate($query, array $params = [], array $types = [])
172
	{
173
		try {
174
			return parent::executeUpdate($query, $params, $types);
175
176
		} catch (\Exception $e) {
177
			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...
178
		}
179
	}
180
181
182
183
	/**
184
	 * @param string $statement
185
	 * @return int
186
	 * @throws DBALException
187
	 */
188
	public function exec($statement)
189
	{
190
		try {
191
			return parent::exec($statement);
192
193
		} catch (\Exception $e) {
194
			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...
195
		}
196
	}
197
198
199
200
	/**
201
	 * @return \Doctrine\DBAL\Driver\Statement|mixed
202
	 * @throws DBALException
203
	 */
204
	public function query()
205
	{
206
		$args = func_get_args();
207
		try {
208
			return call_user_func_array('parent::query', $args);
209
210
		} catch (\Exception $e) {
211
			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...
212
		}
213
	}
214
215
216
217
	/**
218
	 * Prepares an SQL statement.
219
	 *
220
	 * @param string $statement The SQL statement to prepare.
221
	 * @throws DBALException
222
	 * @return PDOStatement The prepared statement.
223
	 */
224
	public function prepare($statement)
225
	{
226
		$this->connect();
227
228
		try {
229
			$stmt = new PDOStatement($statement, $this);
230
231
		} catch (\Exception $ex) {
232
			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...
233
		}
234
235
		$stmt->setFetchMode(PDO::FETCH_ASSOC);
236
237
		return $stmt;
238
	}
239
240
241
242
	/**
243
	 * @return Doctrine\DBAL\Query\QueryBuilder|NativeQueryBuilder
244
	 */
245
	public function createQueryBuilder()
246
	{
247
		if (!$this->entityManager) {
248
			return parent::createQueryBuilder();
249
		}
250
251
		return new NativeQueryBuilder($this->entityManager);
252
	}
253
254
255
256
	/**
257
	 * @param array $schemaTypes
258
	 */
259
	public function setSchemaTypes(array $schemaTypes)
260
	{
261
		$this->schemaTypes = $schemaTypes;
262
	}
263
264
265
266
	/**
267
	 * @param array $dbalTypes
268
	 */
269
	public function setDbalTypes(array $dbalTypes)
270
	{
271
		$this->dbalTypes = $dbalTypes;
272
	}
273
274
275
276
	public function connect()
277
	{
278
		if ($this->isConnected()) {
279
			return FALSE;
280
		}
281
282
		foreach ($this->dbalTypes as $name => $className) {
283
			if (Type::hasType($name)) {
284
				Type::overrideType($name, $className);
285
286
			} else {
287
				Type::addType($name, $className);
288
			}
289
		}
290
291
		parent::connect();
292
293
		$platform = $this->getDatabasePlatform();
294
295
		foreach ($this->schemaTypes as $dbType => $doctrineType) {
296
			$platform->registerDoctrineTypeMapping($dbType, $doctrineType);
297
		}
298
299
		foreach ($this->dbalTypes as $type => $className) {
300
			$platform->markDoctrineTypeCommented(Type::getType($type));
301
		}
302
303
		return TRUE;
304
	}
305
306
307
308
	/**
309
	 * @return Doctrine\DBAL\Platforms\AbstractPlatform
310
	 */
311
	public function getDatabasePlatform()
312
	{
313
		if (!$this->isConnected()) {
314
			$this->connect();
315
		}
316
317
		return parent::getDatabasePlatform();
318
	}
319
320
321
322
	public function ping()
323
	{
324
		$conn = $this->getWrappedConnection();
325
		if ($conn instanceof Driver\PingableConnection) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\Driver\PingableConnection does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
326
			return $conn->ping();
327
		}
328
329
		set_error_handler(function ($severity, $message) {
330
			throw new \PDOException($message, $severity);
331
		});
332
333
		try {
334
			$this->query($this->getDatabasePlatform()->getDummySelectSQL());
335
			restore_error_handler();
336
337
			return TRUE;
338
339
		} catch (Doctrine\DBAL\DBALException $e) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\DBALException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
340
			restore_error_handler();
341
			return FALSE;
342
343
		} catch (\Exception $e) {
344
			restore_error_handler();
345
			throw $e;
346
		}
347
	}
348
349
350
351
	/**
352
	 * @param array $params
353
	 * @param \Doctrine\DBAL\Configuration $config
354
	 * @param \Doctrine\Common\EventManager $eventManager
355
	 * @return Connection
356
	 */
357
	public static function create(array $params, Doctrine\DBAL\Configuration $config, EventManager $eventManager)
358
	{
359
		if (!isset($params['wrapperClass'])) {
360
			$params['wrapperClass'] = get_called_class();
361
		}
362
363
		return Doctrine\DBAL\DriverManager::getConnection($params, $config, $eventManager);
364
	}
365
366
367
368
	/**
369
	 * @deprecated
370
	 * @internal
371
	 * @param \Exception|\Throwable $e
372
	 * @param string $query
373
	 * @param array $params
374
	 * @return DBALException
375
	 */
376
	public function resolveException($e, $query = NULL, $params = [])
377
	{
378
		if ($this->throwOldKdybyExceptions !== TRUE) {
379
			return $e;
0 ignored issues
show
Bug Compatibility introduced by
The expression return $e; of type Exception|Throwable is incompatible with the return type documented by Kdyby\Doctrine\Connection::resolveException of type Kdyby\Doctrine\DBALException as it can also be of type Throwable which is not included in this return type.
Loading history...
380
		}
381
382
		if ($e instanceof Doctrine\DBAL\DBALException && ($pe = $e->getPrevious()) instanceof \PDOException) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\DBALException does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
383
			$info = $pe->errorInfo;
384
385
		} elseif ($e instanceof \PDOException) {
386
			$info = $e->errorInfo;
387
388
		} else {
389
			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...
390
		}
391
392
		if ($this->getDriver() instanceof Doctrine\DBAL\Driver\PDOMySql\Driver) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\Driver\PDOMySql\Driver does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
393
			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...
394
				$columns = [];
395
396
				try {
397
					if (preg_match('~Duplicate entry .*? for key \'([^\']+)\'~', $info[2], $m)
398
						&& ($table = self::resolveExceptionTable($e))
399
						&& ($indexes = $this->getSchemaManager()->listTableIndexes($table))
400
						&& isset($indexes[$m[1]])
401
					) {
402
						$columns[$m[1]] = $indexes[$m[1]]->getColumns();
403
					}
404
405
				} catch (\Exception $e) { }
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
406
407
				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...
408
409
			} 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...
410
				$column = NULL;
411
				if (preg_match('~Column \'([^\']+)\'~', $info[2], $m)) {
412
					$column = $m[1];
413
				}
414
415
				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...
416
			}
417
		}
418
419
		$raw = $e;
420
		do {
421
			$raw = $raw->getPrevious();
422
		} while ($raw && !$raw instanceof \PDOException);
423
424
		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...
425
	}
426
427
428
429
	/**
430
	 * @param \Exception|\Throwable $e
431
	 * @return string|NULL
432
	 */
433
	private static function resolveExceptionTable($e)
434
	{
435
		if (!$e instanceof Doctrine\DBAL\DBALException) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\DBALException does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
436
			return NULL;
437
		}
438
439
		if ($caused = Tracy\Helpers::findTrace($e->getTrace(), 'Doctrine\DBAL\DBALException::driverExceptionDuringQuery')) {
440
			if (preg_match('~(?:INSERT|UPDATE|REPLACE)(?:[A-Z_\s]*)`?([^\s`]+)`?\s*~', is_string($caused['args'][1]) ? $caused['args'][1] : $caused['args'][2], $m)) {
441
				return $m[1];
442
			}
443
		}
444
445
		return NULL;
446
	}
447
448
449
450
	/*************************** Nette\Object ***************************/
451
452
453
454
	/**
455
	 * Access to reflection.
456
	 * @return \Nette\Reflection\ClassType
457
	 */
458
	public static function getReflection()
459
	{
460
		return new Nette\Reflection\ClassType(get_called_class());
461
	}
462
463
464
465
	/**
466
	 * Call to undefined method.
467
	 *
468
	 * @param string $name
469
	 * @param array $args
470
	 *
471
	 * @throws \Nette\MemberAccessException
472
	 * @return mixed
473
	 */
474
	public function __call($name, $args)
475
	{
476
		return ObjectMixin::call($this, $name, $args);
477
	}
478
479
480
481
	/**
482
	 * Call to undefined static method.
483
	 *
484
	 * @param string $name
485
	 * @param array $args
486
	 *
487
	 * @throws \Nette\MemberAccessException
488
	 * @return mixed
489
	 */
490
	public static function __callStatic($name, $args)
491
	{
492
		return ObjectMixin::callStatic(get_called_class(), $name, $args);
493
	}
494
495
496
497
	/**
498
	 * Adding method to class.
499
	 *
500
	 * @param $name
501
	 * @param null $callback
502
	 *
503
	 * @throws \Nette\MemberAccessException
504
	 * @return callable|null
505
	 */
506
	public static function extensionMethod($name, $callback = NULL)
507
	{
508
		if (strpos($name, '::') === FALSE) {
509
			$class = get_called_class();
510
		} else {
511
			list($class, $name) = explode('::', $name);
512
		}
513
		if ($callback === NULL) {
514
			return ObjectMixin::getExtensionMethod($class, $name);
515
		} else {
516
			ObjectMixin::setExtensionMethod($class, $name, $callback);
517
		}
518
	}
519
520
521
522
	/**
523
	 * Returns property value. Do not call directly.
524
	 *
525
	 * @param string $name
526
	 *
527
	 * @throws \Nette\MemberAccessException
528
	 * @return mixed
529
	 */
530
	public function &__get($name)
531
	{
532
		return ObjectMixin::get($this, $name);
533
	}
534
535
536
537
	/**
538
	 * Sets value of a property. Do not call directly.
539
	 *
540
	 * @param string $name
541
	 * @param mixed $value
542
	 *
543
	 * @throws \Nette\MemberAccessException
544
	 * @return void
545
	 */
546
	public function __set($name, $value)
547
	{
548
		if ($name === '_conn') {
549
			$this->_conn = $value;
550
			return;
551
		}
552
553
		ObjectMixin::set($this, $name, $value);
554
	}
555
556
557
558
	/**
559
	 * Is property defined?
560
	 *
561
	 * @param string $name
562
	 *
563
	 * @return bool
564
	 */
565
	public function __isset($name)
566
	{
567
		return ObjectMixin::has($this, $name);
568
	}
569
570
571
572
	/**
573
	 * Access to undeclared property.
574
	 *
575
	 * @param string $name
576
	 *
577
	 * @throws \Nette\MemberAccessException
578
	 * @return void
579
	 */
580
	public function __unset($name)
581
	{
582
		if ($name === '_conn') {
583
			$this->$name = NULL;
584
			return;
585
		}
586
587
		ObjectMixin::remove($this, $name);
588
	}
589
590
}
591