Completed
Push — master ( 98e4ef...5952d3 )
by Jáchym
03:01
created

Panel   F

Complexity

Total Complexity 139

Size/Duplication

Total Lines 848
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 39

Importance

Changes 0
Metric Value
wmc 139
lcom 1
cbo 39
dl 0
loc 848
rs 1.0434
c 0
b 0
f 0

26 Methods

Rating   Name   Duplication   Size   Complexity  
D startQuery() 0 26 10
A filterTracePaths() 0 12 4
A stopQuery() 0 9 1
A queryFailed() 0 4 1
A getTab() 0 10 2
B getPanel() 0 32 6
B renderPanelCacheStatistics() 0 24 5
A renderPanelQueries() 0 13 3
A renderStyles() 0 11 1
A processQuery() 0 13 3
C renderQueryException() 0 43 12
D renderException() 0 100 27
C dumpQuery() 0 31 7
B highlightQuery() 0 34 5
C formatQuery() 0 68 17
C highlightAnnotationLine() 0 36 7
C calculateErrorLine() 0 29 7
A calculateAffectedLine() 0 12 1
B findRenamed() 0 23 5
A cleanedPhpDoc() 0 4 1
A editorLink() 0 12 4
A enableLogging() 0 16 3
A bindConnection() 0 14 2
A bindEntityManager() 0 15 3
A registerBarPanel() 0 4 1
A registerBluescreen() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like Panel 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 Panel, 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\Diagnostics;
12
13
use Doctrine;
14
use Doctrine\Common\Collections\ArrayCollection;
15
use Doctrine\Common\Persistence\Proxy;
16
use Doctrine\Common\Annotations\AnnotationException;
17
use Doctrine\DBAL\Platforms\AbstractPlatform;
18
use Doctrine\DBAL\Types\Type;
19
use Kdyby;
20
use Nette;
21
use Nette\Utils\Strings;
22
use Tracy\Bar;
23
use Tracy\BlueScreen;
24
use Tracy\Debugger;
25
use Tracy\Dumper;
26
use Tracy\Helpers;
27
use Tracy\IBarPanel;
28
29
30
31
/**
32
 * Debug panel for Doctrine
33
 *
34
 * @author David Grudl
35
 * @author Patrik Votoček
36
 * @author Filip Procházka <[email protected]>
37
 */
38
class Panel implements IBarPanel, Doctrine\DBAL\Logging\SQLLogger
39
{
40
41
	use \Kdyby\StrictObjects\Scream;
42
43
	/**
44
	 * @var int logged time
45
	 */
46
	public $totalTime = 0;
47
48
	/**
49
	 * @var array
50
	 */
51
	public $queries = [];
52
53
	/**
54
	 * @var array
55
	 */
56
	public $failed = [];
57
58
	/**
59
	 * @var array
60
	 */
61
	public $skipPaths = [
62
		'vendor/nette/', 'src/Nette/',
63
		'vendor/doctrine/collections/', 'lib/Doctrine/Collections/',
64
		'vendor/doctrine/common/', 'lib/Doctrine/Common/',
65
		'vendor/doctrine/dbal/', 'lib/Doctrine/DBAL/',
66
		'vendor/doctrine/orm/', 'lib/Doctrine/ORM/',
67
		'vendor/kdyby/doctrine/', 'src/Kdyby/Doctrine/',
68
		'vendor/phpunit',
69
	];
70
71
	/**
72
	 * @var \Doctrine\DBAL\Connection
73
	 */
74
	private $connection;
75
76
	/**
77
	 * @var \Doctrine\ORM\EntityManager
78
	 */
79
	private $em;
80
81
82
83
	/***************** Doctrine\DBAL\Logging\SQLLogger ********************/
84
85
86
87
	/**
88
	 * @param string
89
	 * @param array
90
	 * @param array
91
	 */
92
	public function startQuery($sql, array $params = NULL, array $types = NULL)
93
	{
94
		Debugger::timer('doctrine');
95
96
		$source = NULL;
97
		foreach (debug_backtrace(FALSE) as $row) {
98
			if (isset($row['file']) && $this->filterTracePaths(realpath($row['file']))) {
99
				if (isset($row['class']) && stripos($row['class'], '\\' . Proxy::MARKER) !== FALSE) {
100
					if (!in_array(Doctrine\Common\Persistence\Proxy::class, class_implements($row['class']))) {
101
						continue;
102
103
					} elseif (isset($row['function']) && $row['function'] === '__load') {
104
						continue;
105
					}
106
107
				} elseif (stripos($row['file'], DIRECTORY_SEPARATOR . Proxy::MARKER) !== FALSE) {
108
					continue;
109
				}
110
111
				$source = [$row['file'], (int) $row['line']];
112
				break;
113
			}
114
		}
115
116
		$this->queries[] = [$sql, $params, NULL, $types, $source];
117
	}
118
119
120
121
	/**
122
	 * @param string $file
123
	 * @return boolean
124
	 */
125
	protected function filterTracePaths($file)
126
	{
127
		$file = str_replace(DIRECTORY_SEPARATOR, '/', $file);
128
		$return = is_file($file);
129
		foreach ($this->skipPaths as $path) {
130
			if (!$return) {
131
				break;
132
			}
133
			$return = $return && strpos($file, '/' . trim($path, '/') . '/') === FALSE;
134
		}
135
		return $return;
136
	}
137
138
139
140
	/**
141
	 * @return array
142
	 */
143
	public function stopQuery()
144
	{
145
		$keys = array_keys($this->queries);
146
		$key = end($keys);
147
		$this->queries[$key][2] = $time = Debugger::timer('doctrine');
148
		$this->totalTime += $time;
149
150
		return $this->queries[$key] + array_fill_keys(range(0, 4), NULL);
151
	}
152
153
154
155
	/**
156
	 * @param \Exception|\Throwable $exception
157
	 */
158
	public function queryFailed($exception)
159
	{
160
		$this->failed[spl_object_hash($exception)] = $this->stopQuery();
161
	}
162
163
164
165
	/***************** Tracy\IBarPanel ********************/
166
167
168
169
	/**
170
	 * @return string
171
	 */
172
	public function getTab()
173
	{
174
		return '<span title="Doctrine 2">'
175
			. '<svg viewBox="0 0 2048 2048"><path fill="#aaa" d="M1024 896q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0 768q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0-384q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0-1152q208 0 385 34.5t280 93.5 103 128v128q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-128q0-69 103-128t280-93.5 385-34.5z"></path></svg>'
176
			. '<span class="tracy-label">'
177
			. count($this->queries) . ' queries'
178
			. ($this->totalTime ? ' / ' . sprintf('%0.1f', $this->totalTime * 1000) . ' ms' : '')
179
			. '</span>'
180
			. '</span>';
181
	}
182
183
184
185
	/**
186
	 * @return string
187
	 */
188
	public function getPanel()
189
	{
190
		if (empty($this->queries)) {
191
			return '';
192
		}
193
194
		$connParams = $this->connection->getParams();
195
		if ($connParams['driver'] === 'pdo_sqlite' && isset($connParams['path'])) {
196
			$host = 'path: ' . basename($connParams['path']);
197
198
		} else {
199
			$host = sprintf('host: %s%s/%s',
200
				$this->connection->getHost(),
201
				(($p = $this->connection->getPort()) ? ':' . $p : ''),
202
				$this->connection->getDatabase()
203
			);
204
		}
205
206
		return
207
			$this->renderStyles() .
208
			sprintf('<h1>Queries: %s %s, %s</h1>',
209
				count($this->queries),
210
				($this->totalTime ? ', time: ' . sprintf('%0.3f', $this->totalTime * 1000) . ' ms' : ''),
211
				$host
212
			) .
213
			'<div class="nette-inner tracy-inner nette-Doctrine2Panel">' .
214
				implode('<br>', array_filter([
215
					$this->renderPanelCacheStatistics(),
216
					$this->renderPanelQueries()
217
				])) .
218
			'</div>';
219
	}
220
221
222
223
	private function renderPanelCacheStatistics()
224
	{
225
		if (empty($this->em)) {
226
			return '';
227
		}
228
229
		$config = $this->em->getConfiguration();
230
		if (!$config->isSecondLevelCacheEnabled()) {
231
			return '';
232
		}
233
234
		$loggerChain = $config->getSecondLevelCacheConfiguration()
235
			->getCacheLogger();
236
237
		if (!$loggerChain instanceof Doctrine\ORM\Cache\Logging\CacheLoggerChain) {
238
			return '';
239
		}
240
241
		if (!$statistics = $loggerChain->getLogger('statistics')) {
242
			return '';
243
		}
244
245
		return Dumper::toHtml($statistics, [Dumper::DEPTH => 5]);
246
	}
247
248
249
250
	private function renderPanelQueries()
251
	{
252
		if (empty($this->queries)) {
253
			return "";
254
		}
255
256
		$s = "";
257
		foreach ($this->queries as $query) {
258
			$s .= $this->processQuery($query);
259
		}
260
261
		return '<table><tr><th>ms</th><th>SQL Statement</th></tr>' . $s . '</table>';
262
	}
263
264
265
266
	/**
267
	 * @return string
268
	 */
269
	protected function renderStyles()
270
	{
271
		return '<style>
272
			#nette-debug td.nette-Doctrine2Panel-sql { background: white !important}
273
			#nette-debug .nette-Doctrine2Panel-source { color: #BBB !important }
274
			#nette-debug nette-Doctrine2Panel tr table { margin: 8px 0; max-height: 150px; overflow:auto }
275
			#tracy-debug td.nette-Doctrine2Panel-sql { background: white !important}
276
			#tracy-debug .nette-Doctrine2Panel-source { color: #BBB !important }
277
			#tracy-debug nette-Doctrine2Panel tr table { margin: 8px 0; max-height: 150px; overflow:auto }
278
		</style>';
279
	}
280
281
282
283
	/**
284
	 * @param array
285
	 * @return string
286
	 */
287
	protected function processQuery(array $query)
288
	{
289
		$h = 'htmlspecialchars';
290
		list($sql, $params, $time, $types, $source) = $query;
291
292
		$s = self::highlightQuery(static::formatQuery($sql, (array) $params, (array) $types, $this->connection ? $this->connection->getDatabasePlatform() : NULL));
293
		if ($source) {
294
			$s .= self::editorLink($source[0], $source[1], $h('.../' . basename(dirname($source[0]))) . '/<b>' . $h(basename($source[0])) . '</b>');
295
		}
296
297
		return '<tr><td>' . sprintf('%0.3f', $time * 1000) . '</td>' .
298
			'<td class = "nette-Doctrine2Panel-sql">' . $s . '</td></tr>';
299
	}
300
301
302
303
	/****************** Exceptions handling *********************/
304
305
306
307
	/**
308
	 * @param \Exception|\Throwable $e
309
	 * @return array|NULL
310
	 */
311
	public function renderQueryException($e)
312
	{
313
		if ($e instanceof \PDOException && count($this->queries)) {
314
			$types = $params = [];
315
316
			if ($this->connection !== NULL) {
317
				if (!$e instanceof Kdyby\Doctrine\DBALException || $e->connection !== $this->connection) {
318
					return NULL;
319
320
				} elseif (!isset($this->failed[spl_object_hash($e)])) {
321
					return NULL;
322
				}
323
324
				list($sql, $params, , , $source) = $this->failed[spl_object_hash($e)];
325
326
			} else {
327
				list($sql, $params, , $types, $source) = end($this->queries) + range(1, 5);
328
			}
329
330
			if (!$sql) {
331
				return NULL;
332
			}
333
334
			return [
335
				'tab' => 'SQL',
336
				'panel' => $this->dumpQuery($sql, $params, $types, $source),
337
			];
338
339
		} elseif ($e instanceof Kdyby\Doctrine\QueryException && $e->query !== NULL) {
340
			if ($e->query instanceof Doctrine\ORM\Query) {
341
				return [
342
					'tab' => 'DQL',
343
					'panel' => $this->dumpQuery($e->query->getDQL(), $e->query->getParameters()),
344
				];
345
346
			} elseif ($e->query instanceof Kdyby\Doctrine\NativeQueryWrapper) {
347
				return [
348
					'tab' => 'Native SQL',
349
					'panel' => $this->dumpQuery($e->query->getSQL(), $e->query->getParameters()),
350
				];
351
			}
352
		}
353
	}
354
355
356
357
	/**
358
	 * @param \Exception|\Throwable $e
359
	 * @param \Nette\DI\Container $dic
360
	 * @return array|NULL
361
	 */
362
	public static function renderException($e, Nette\DI\Container $dic)
363
	{
364
		if ($e instanceof AnnotationException) {
365
			if ($dump = self::highlightAnnotationLine($e)) {
366
				return [
367
					'tab' => 'Annotation',
368
					'panel' => $dump,
369
				];
370
			}
371
372
		} elseif ($e instanceof Doctrine\ORM\Mapping\MappingException) {
373
			if ($invalidEntity = Strings::match($e->getMessage(), '~^Class "([\\S]+)" .*? is not .*? valid~i')) {
374
				$refl = Nette\Reflection\ClassType::from($invalidEntity[1]);
375
				$file = $refl->getFileName();
376
				$errorLine = $refl->getStartLine();
377
378
				return [
379
					'tab' => 'Invalid entity',
380
					'panel' => '<p><b>File:</b> ' . self::editorLink($file, $errorLine) . '</p>' .
381
						BlueScreen::highlightFile($file, $errorLine),
382
				];
383
			}
384
385
		} elseif ($e instanceof Doctrine\DBAL\Schema\SchemaException && $dic && ($em = $dic->getByType(Kdyby\Doctrine\EntityManager::class, FALSE))) {
386
			/** @var Kdyby\Doctrine\EntityManager $em */
387
388
			if ($invalidTable = Strings::match($e->getMessage(), '~table \'(.*?)\'~i')) {
389
				foreach ($em->getMetadataFactory()->getAllMetadata() as $class) {
390
					/** @var Kdyby\Doctrine\Mapping\ClassMetadata $class */
391
					if ($class->getTableName() === $invalidTable[1]) {
392
						$refl = $class->getReflectionClass();
393
						break;
394
					}
395
				}
396
397
				if (!isset($refl)) {
398
					return NULL;
399
				}
400
401
				$file = $refl->getFileName();
402
				$errorLine = $refl->getStartLine();
403
404
				return [
405
					'tab' => 'Invalid schema',
406
					'panel' => '<p><b>File:</b> ' . self::editorLink($file, $errorLine) . '</p>' .
407
						BlueScreen::highlightFile($file, $errorLine),
408
				];
409
			}
410
411
		} elseif ($e instanceof Kdyby\Doctrine\DBALException && $e->query !== NULL) {
412
			return [
413
				'tab' => 'SQL',
414
				'panel' => self::highlightQuery(static::formatQuery($e->query, $e->params, [])),
415
			];
416
417
		} elseif ($e instanceof Doctrine\DBAL\Exception\DriverException) {
418
			if (($prev = $e->getPrevious()) && ($item = Helpers::findTrace($e->getTrace(), Doctrine\DBAL\DBALException::class . '::driverExceptionDuringQuery'))) {
419
				/** @var \Doctrine\DBAL\Driver $driver */
420
				$driver = $item['args'][0];
421
				$params = isset($item['args'][3]) ? $item['args'][3] : [];
422
423
				return [
424
					'tab' => 'SQL',
425
					'panel' => self::highlightQuery(static::formatQuery($item['args'][2], $params, [], $driver->getDatabasePlatform())),
426
				];
427
			}
428
429
		} elseif ($e instanceof Doctrine\ORM\Query\QueryException) {
430
			if (($prev = $e->getPrevious()) && preg_match('~^(SELECT|INSERT|UPDATE|DELETE)\s+.*~i', $prev->getMessage())) {
431
				return [
432
					'tab' => 'DQL',
433
					'panel' => self::highlightQuery(static::formatQuery($prev->getMessage(), [], [])),
434
				];
435
			}
436
437
		} elseif ($e instanceof \PDOException) {
438
			$params = [];
439
440
			if (isset($e->queryString)) {
441
				$sql = $e->queryString;
0 ignored issues
show
Bug introduced by
The property queryString does not seem to exist. Did you mean string?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
442
443
			} elseif ($item = Helpers::findTrace($e->getTrace(), Doctrine\DBAL\Connection::class . '::executeQuery')) {
444
				$sql = $item['args'][0];
445
				$params = $item['args'][1];
446
447
			} elseif ($item = Helpers::findTrace($e->getTrace(), \PDO::class . '::query')) {
448
				$sql = $item['args'][0];
449
450
			} elseif ($item = Helpers::findTrace($e->getTrace(), \PDO::class . '::prepare')) {
451
				$sql = $item['args'][0];
452
			}
453
454
			return isset($sql) ? [
455
				'tab' => 'SQL',
456
				'panel' => self::highlightQuery(static::formatQuery($sql, $params, [])),
457
			] : NULL;
458
		}
459
460
		return NULL;
461
	}
462
463
464
465
	/**
466
	 * @param string $query
467
	 * @param array|Doctrine\Common\Collections\ArrayCollection $params
468
	 * @param array $types
469
	 * @param string $source
470
	 * @return string
471
	 */
472
	protected function dumpQuery($query, $params, array $types = [], $source = NULL)
473
	{
474
		if ($params instanceof ArrayCollection) {
475
			$tmp = [];
476
			$tmpTypes = [];
477
			foreach ($params as $key => $param) {
478
				if ($param instanceof Doctrine\ORM\Query\Parameter) {
479
					$tmpTypes[$param->getName()] = $param->getType();
480
					$tmp[$param->getName()] = $param->getValue();
481
					continue;
482
				}
483
				$tmp[$key] = $param;
484
			}
485
			$params = $tmp;
486
			$types = $tmpTypes;
487
		}
488
489
		// query
490
		$s = '<p><b>Query</b></p><table><tr><td class="nette-Doctrine2Panel-sql">';
491
		$s .= self::highlightQuery(static::formatQuery($query, $params, $types, $this->connection ? $this->connection->getDatabasePlatform() : NULL));
492
		$s .= '</td></tr></table>';
493
494
		$e = NULL;
495
		if ($source && is_array($source)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $source of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
496
			list($file, $line) = $source;
497
			$e = '<p><b>File:</b> ' . self::editorLink($file, $line) . '</p>';
498
		}
499
500
		// styles and dump
501
		return $this->renderStyles() . '<div class="nette-inner tracy-inner nette-Doctrine2Panel">' . $e . $s . '</div>';
502
	}
503
504
505
506
	/**
507
	 * Returns syntax highlighted SQL command.
508
	 * This method is same as Nette\Database\Helpers::dumpSql except for parameters handling.
509
	 * @link https://github.com/nette/database/blob/667143b2d5b940f78c8dc9212f95b1bbc033c6a3/src/Database/Helpers.php#L75-L138
510
	 * @author David Grudl
511
	 * @param string $sql
512
	 * @return string
513
	 */
514
	public static function highlightQuery($sql)
515
	{
516
		static $keywords1 = 'SELECT|(?:ON\s+DUPLICATE\s+KEY)?UPDATE|INSERT(?:\s+INTO)?|REPLACE(?:\s+INTO)?|DELETE|CALL|UNION|FROM|WHERE|HAVING|GROUP\s+BY|ORDER\s+BY|LIMIT|OFFSET|SET|VALUES|LEFT\s+JOIN|INNER\s+JOIN|TRUNCATE';
517
		static $keywords2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|[RI]?LIKE|REGEXP|TRUE|FALSE|WITH|INSTANCE\s+OF';
518
519
		// insert new lines
520
		$sql = " $sql ";
521
		$sql = preg_replace("#(?<=[\\s,(])($keywords1)(?=[\\s,)])#i", "\n\$1", $sql);
522
523
		// reduce spaces
524
		$sql = preg_replace('#[ \t]{2,}#', ' ', $sql);
525
526
		$sql = wordwrap($sql, 100);
527
		$sql = preg_replace('#([ \t]*\r?\n){2,}#', "\n", $sql);
528
529
		// syntax highlight
530
		$sql = htmlspecialchars($sql, ENT_IGNORE, 'UTF-8');
531
		$sql = preg_replace_callback("#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])($keywords1)(?=[\\s,)])|(?<=[\\s,(=])($keywords2)(?=[\\s,)=])#is", function ($matches) {
532
			if (!empty($matches[1])) { // comment
533
				return '<em style="color:gray">' . $matches[1] . '</em>';
534
535
			} elseif (!empty($matches[2])) { // error
536
				return '<strong style="color:red">' . $matches[2] . '</strong>';
537
538
			} elseif (!empty($matches[3])) { // most important keywords
539
				return '<strong style="color:blue">' . $matches[3] . '</strong>';
540
541
			} elseif (!empty($matches[4])) { // other keywords
542
				return '<strong style="color:green">' . $matches[4] . '</strong>';
543
			}
544
		}, $sql);
545
546
		return '<pre class="dump">' . trim($sql) . "</pre>\n";
547
	}
548
549
550
551
	/**
552
	 * @param string $query
553
	 * @param array $params
554
	 * @param array $types
555
	 * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
556
	 * @throws \Doctrine\DBAL\DBALException
557
	 * @throws \Nette\Utils\RegexpException
558
	 * @return string
559
	 */
560
	public static function formatQuery($query, $params, array $types = [], AbstractPlatform $platform = NULL)
561
	{
562
		if ($platform === NULL) {
563
			$platform = new Doctrine\DBAL\Platforms\MySqlPlatform();
564
		}
565
566
		if (!$types) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $types of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
567
			foreach ($params as $key => $param) {
568
				if (is_array($param)) {
569
					$types[$key] = Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
570
571
				} else {
572
					$types[$key] = 'string';
573
				}
574
			}
575
		}
576
577
		try {
578
			list($query, $params, $types) = \Doctrine\DBAL\SQLParserUtils::expandListParameters($query, $params, $types);
579
		} catch (Doctrine\DBAL\SQLParserUtilsException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
580
		}
581
582
		$formattedParams = [];
583
		foreach ($params as $key => $param) {
584
			if (isset($types[$key])) {
585
				if (is_scalar($types[$key]) && array_key_exists($types[$key], Type::getTypesMap())) {
586
					$types[$key] = Type::getType($types[$key]);
587
				}
588
589
				/** @var Type[] $types */
590
				if ($types[$key] instanceof Type) {
591
					$param = $types[$key]->convertToDatabaseValue($param, $platform);
592
				}
593
			}
594
595
			$formattedParams[] = SimpleParameterFormatter::format($param);
596
		}
597
		$params = $formattedParams;
598
599
		if (Nette\Utils\Validators::isList($params)) {
600
			$parts = explode('?', $query);
601
			if (count($params) > $parts) {
602
				throw new Kdyby\Doctrine\InvalidStateException("Too mny parameters passed to query.");
603
			}
604
605
			return implode('', Kdyby\Doctrine\Helpers::zipper($parts, $params));
606
		}
607
608
		return Strings::replace($query, '~(\\:[a-z][a-z0-9]*|\\?[0-9]*)~i', function ($m) use (&$params) {
609
			if (substr($m[0], 0, 1) === '?') {
610
				if (strlen($m[0]) > 1) {
611
					if (isset($params[$k = substr($m[0], 1)])) {
612
						return $params[$k];
613
					}
614
615
				} else {
616
					return array_shift($params);
617
				}
618
619
			} else {
620
				if (isset($params[$k = substr($m[0], 1)])) {
621
					return $params[$k];
622
				}
623
			}
624
625
			return $m[0];
626
		});
627
	}
628
629
630
631
	/**
632
	 * @param \Doctrine\Common\Annotations\AnnotationException $e
633
	 * @return string|bool
634
	 */
635
	public static function highlightAnnotationLine(AnnotationException $e)
636
	{
637
		foreach ($e->getTrace() as $step) {
638
			if (@$step['class'] . @$step['type'] . @$step['function'] !== Doctrine\Common\Annotations\DocParser::class . '->parse') {
639
				continue;
640
			}
641
642
			$context = Strings::match($step['args'][1], '~^(?P<type>[^\s]+)\s*(?P<class>[^:]+)(?:::\$?(?P<property>[^\\(]+))?$~i');
643
			break;
644
		}
645
646
		if (!isset($context)) {
647
			return FALSE;
648
		}
649
650
		$refl = Nette\Reflection\ClassType::from($context['class']);
651
		$file = $refl->getFileName();
652
		$line = NULL;
653
654
		if ($context['type'] === 'property') {
655
			$refl = $refl->getProperty($context['property']);
656
			$line = Kdyby\Doctrine\Helpers::getPropertyLine($refl);
657
658
		} elseif ($context['type'] === 'method') {
659
			$refl = $refl->getProperty($context['method']);
660
		}
661
662
		$errorLine = self::calculateErrorLine($refl, $e, $line);
663
		if ($errorLine === NULL) {
664
			return FALSE;
665
		}
666
667
		$dump = BlueScreen::highlightFile($file, $errorLine);
668
669
		return '<p><b>File:</b> ' . self::editorLink($file, $errorLine) . '</p>' . $dump;
670
	}
671
672
673
674
	/**
675
	 * @param \Reflector|\Nette\Reflection\ClassType|\Nette\Reflection\Method|\Nette\Reflection\Property $refl
676
	 * @param \Exception|\Throwable $e
677
	 * @param int|NULL $startLine
678
	 * @return int|NULL
679
	 */
680
	public static function calculateErrorLine(\Reflector $refl, $e, $startLine = NULL)
681
	{
682
		if ($startLine === NULL && method_exists($refl, 'getStartLine')) {
683
			$startLine = $refl->getStartLine();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Reflector as the method getStartLine() does only exist in the following implementations of said interface: Doctrine\Common\Reflection\StaticReflectionClass, Doctrine\Common\Reflection\StaticReflectionMethod, Nette\Reflection\ClassType, Nette\Reflection\GlobalFunction, Nette\Reflection\Method, ReflectionClass, ReflectionFunction, ReflectionFunctionAbstract, ReflectionMethod, ReflectionObject.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
684
		}
685
		if ($startLine === NULL) {
686
			return NULL;
687
		}
688
689
		if ($pos = Strings::match($e->getMessage(), '~position\s*(\d+)~')) {
690
			$targetLine = self::calculateAffectedLine($refl, $pos[1]);
691
692
		} elseif ($notImported = Strings::match($e->getMessage(), '~^\[Semantical Error\]\s+The annotation "([^"]*?)"~i')) {
693
			$parts = explode(self::findRenamed($refl, $notImported[1]), self::cleanedPhpDoc($refl), 2);
694
			$targetLine = self::calculateAffectedLine($refl, strlen($parts[0]));
695
696
		} elseif ($notFound = Strings::match($e->getMessage(), '~^\[Semantical Error\]\s+Couldn\'t find\s+(.*?)\s+(.*?),\s+~')) {
697
			// this is just a guess
698
			$parts = explode(self::findRenamed($refl, $notFound[2]), self::cleanedPhpDoc($refl), 2);
699
			$targetLine = self::calculateAffectedLine($refl, strlen($parts[0]));
700
701
		} else {
702
			$targetLine = self::calculateAffectedLine($refl, 1);
703
		}
704
705
		$phpDocLines = count(Strings::split($refl->getDocComment(), '~[\n\r]+~'));
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Reflector as the method getDocComment() does only exist in the following implementations of said interface: Doctrine\Common\Reflecti...ublicReflectionProperty, Doctrine\Common\Reflection\StaticReflectionClass, Doctrine\Common\Reflection\StaticReflectionMethod, Doctrine\Common\Reflecti...taticReflectionProperty, Doctrine\ORM\Mapping\ReflectionEmbeddedProperty, Nette\Reflection\ClassType, Nette\Reflection\GlobalFunction, Nette\Reflection\Method, Nette\Reflection\Property, ReflectionClass, ReflectionFunction, ReflectionFunctionAbstract, ReflectionMethod, ReflectionObject, ReflectionProperty.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
706
707
		return $startLine - ($phpDocLines - ($targetLine - 1));
708
	}
709
710
711
712
	/**
713
	 * @param \Reflector|\Nette\Reflection\ClassType|\Nette\Reflection\Method $refl
714
	 * @param int $symbolPos
715
	 * @return int
716
	 */
717
	protected static function calculateAffectedLine(\Reflector $refl, $symbolPos)
718
	{
719
		$doc = $refl->getDocComment();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Reflector as the method getDocComment() does only exist in the following implementations of said interface: Doctrine\Common\Reflecti...ublicReflectionProperty, Doctrine\Common\Reflection\StaticReflectionClass, Doctrine\Common\Reflection\StaticReflectionMethod, Doctrine\Common\Reflecti...taticReflectionProperty, Doctrine\ORM\Mapping\ReflectionEmbeddedProperty, Nette\Reflection\ClassType, Nette\Reflection\GlobalFunction, Nette\Reflection\Method, Nette\Reflection\Property, ReflectionClass, ReflectionFunction, ReflectionFunctionAbstract, ReflectionMethod, ReflectionObject, ReflectionProperty.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
720
		/** @var int|NULL $atPos */
721
		$atPos = NULL;
722
		$cleanedDoc = self::cleanedPhpDoc($refl, $atPos);
723
		$beforeCleanLines = count(Strings::split(substr($doc, 0, $atPos), '~[\n\r]+~'));
724
		$parsedDoc = substr($cleanedDoc, 0, $symbolPos + 1);
725
		$parsedLines = count(Strings::split($parsedDoc, '~[\n\r]+~'));
726
727
		return $parsedLines + max($beforeCleanLines - 1, 0);
728
	}
729
730
731
732
	/**
733
	 * @param \Reflector|Nette\Reflection\ClassType|Nette\Reflection\Method $refl
734
	 * @param $annotation
735
	 */
736
	private static function findRenamed(\Reflector $refl, $annotation)
737
	{
738
		$parser = new Doctrine\Common\Annotations\PhpParser();
739
		$imports = $parser->parseClass($refl instanceof \ReflectionClass ? $refl : $refl->getDeclaringClass());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Reflector as the method getDeclaringClass() does only exist in the following implementations of said interface: Doctrine\Common\Reflecti...ublicReflectionProperty, Doctrine\Common\Reflection\StaticReflectionMethod, Doctrine\Common\Reflecti...taticReflectionProperty, Doctrine\ORM\Mapping\ReflectionEmbeddedProperty, Nette\Reflection\Method, Nette\Reflection\Parameter, Nette\Reflection\Property, ReflectionMethod, ReflectionParameter, ReflectionProperty.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
740
741
		$annotationClass = ltrim($annotation, '@');
742
		foreach ($imports as $alias => $import) {
743
			if (!Strings::startsWith($annotationClass, $import)) {
744
				continue;
745
			}
746
747
			$aliased = str_replace(Strings::lower($import), $alias, Strings::lower($annotationClass));
748
			$searchFor = preg_quote(Strings::lower($aliased));
749
750
			if (!$m = Strings::match($refl->getDocComment(), "~(?P<usage>@?$searchFor)~i")) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Reflector as the method getDocComment() does only exist in the following implementations of said interface: Doctrine\Common\Reflecti...ublicReflectionProperty, Doctrine\Common\Reflection\StaticReflectionClass, Doctrine\Common\Reflection\StaticReflectionMethod, Doctrine\Common\Reflecti...taticReflectionProperty, Doctrine\ORM\Mapping\ReflectionEmbeddedProperty, Nette\Reflection\ClassType, Nette\Reflection\GlobalFunction, Nette\Reflection\Method, Nette\Reflection\Property, ReflectionClass, ReflectionFunction, ReflectionFunctionAbstract, ReflectionMethod, ReflectionObject, ReflectionProperty.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
751
				continue;
752
			}
753
754
			return $m['usage'];
755
		}
756
757
		return $annotation;
758
	}
759
760
761
762
	/**
763
	 * @param \Nette\Reflection\ClassType|\Nette\Reflection\Method|\Reflector $refl
764
	 * @param int|null $atPos
765
	 * @return string
766
	 */
767
	private static function cleanedPhpDoc(\Reflector $refl, &$atPos = NULL)
768
	{
769
		return trim(substr($doc = $refl->getDocComment(), $atPos = strpos($doc, '@') - 1), '* /');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Reflector as the method getDocComment() does only exist in the following implementations of said interface: Doctrine\Common\Reflecti...ublicReflectionProperty, Doctrine\Common\Reflection\StaticReflectionClass, Doctrine\Common\Reflection\StaticReflectionMethod, Doctrine\Common\Reflecti...taticReflectionProperty, Doctrine\ORM\Mapping\ReflectionEmbeddedProperty, Nette\Reflection\ClassType, Nette\Reflection\GlobalFunction, Nette\Reflection\Method, Nette\Reflection\Property, ReflectionClass, ReflectionFunction, ReflectionFunctionAbstract, ReflectionMethod, ReflectionObject, ReflectionProperty.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
770
	}
771
772
773
774
	/**
775
	 * Returns link to editor.
776
	 * @author David Grudl
777
	 * @param string $file
778
	 * @param string|int $line
779
	 * @param string $text
780
	 * @return Nette\Utils\Html
781
	 */
782
	private static function editorLink($file, $line, $text = NULL)
783
	{
784
		if (Debugger::$editor && is_file($file) && $text !== NULL) {
785
			return Nette\Utils\Html::el('a')
786
				->href(strtr(Debugger::$editor, ['%file' => rawurlencode($file), '%line' => $line]))
787
				->setAttribute('title', "$file:$line")
788
				->setHtml($text);
789
790
		} else {
791
			return Nette\Utils\Html::el()->setHtml(Helpers::editorLink($file, $line));
792
		}
793
	}
794
795
796
797
	/****************** Registration *********************/
798
799
800
801
	public function enableLogging()
802
	{
803
		if ($this->connection === NULL) {
804
			throw new Kdyby\Doctrine\InvalidStateException("Doctrine Panel is not bound to connection.");
805
		}
806
807
		$config = $this->connection->getConfiguration();
808
		$logger = $config->getSQLLogger();
809
810
		if ($logger instanceof Doctrine\DBAL\Logging\LoggerChain) {
811
			$logger->addLogger($this);
812
813
		} else {
814
			$config->setSQLLogger($this);
815
		}
816
	}
817
818
819
820
	/**
821
	 * @param \Doctrine\DBAL\Connection $connection
822
	 * @return Panel
823
	 */
824
	public function bindConnection(Doctrine\DBAL\Connection $connection)
825
	{
826
		if ($this->connection !== NULL) {
827
			throw new Kdyby\Doctrine\InvalidStateException("Doctrine Panel is already bound to connection.");
828
		}
829
830
		$this->connection = $connection;
831
832
		// Tracy
833
		$this->registerBarPanel(Debugger::getBar());
834
		Debugger::getBlueScreen()->addPanel([$this, 'renderQueryException']);
835
836
		return $this;
837
	}
838
839
840
841
	/**
842
	 * @param Doctrine\ORM\EntityManager $em
843
	 * @return Panel
844
	 */
845
	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...
846
	{
847
		$this->em = $em;
848
849
		if ($this->em instanceof Kdyby\Doctrine\EntityManager) {
850
			$uowPanel = new EntityManagerUnitOfWorkSnapshotPanel();
851
			$uowPanel->bindEntityManager($em);
852
		}
853
854
		if ($this->connection === NULL) {
855
			$this->bindConnection($em->getConnection());
856
		}
857
858
		return $this;
859
	}
860
861
862
863
	/**
864
	 * Registers panel to debugger
865
	 *
866
	 * @param \Tracy\Bar $bar
867
	 */
868
	public function registerBarPanel(Bar $bar)
869
	{
870
		$bar->addPanel($this);
871
	}
872
873
874
875
	/**
876
	 * Registers generic exception renderer
877
	 */
878
	public static function registerBluescreen(Nette\DI\Container $dic)
879
	{
880
		Debugger::getBlueScreen()->addPanel(function ($e) use ($dic) {
881
			return Panel::renderException($e, $dic);
882
		});
883
	}
884
885
}
886