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

Panel::renderPanelCacheStatistics()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 24
rs 8.5125
cc 5
eloc 13
nc 5
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\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 extends Nette\Object implements IBarPanel, Doctrine\DBAL\Logging\SQLLogger
39
{
40
41
	/**
42
	 * @var int logged time
43
	 */
44
	public $totalTime = 0;
45
46
	/**
47
	 * @var array
48
	 */
49
	public $queries = [];
50
51
	/**
52
	 * @var array
53
	 */
54
	public $failed = [];
55
56
	/**
57
	 * @var array
58
	 */
59
	public $skipPaths = [
60
		'vendor/nette/', 'src/Nette/',
61
		'vendor/doctrine/collections/', 'lib/Doctrine/Collections/',
62
		'vendor/doctrine/common/', 'lib/Doctrine/Common/',
63
		'vendor/doctrine/dbal/', 'lib/Doctrine/DBAL/',
64
		'vendor/doctrine/orm/', 'lib/Doctrine/ORM/',
65
		'vendor/kdyby/doctrine/', 'src/Kdyby/Doctrine/',
66
		'vendor/phpunit',
67
	];
68
69
	/**
70
	 * @var \Doctrine\DBAL\Connection
71
	 */
72
	private $connection;
73
74
	/**
75
	 * @var \Doctrine\ORM\EntityManager
76
	 */
77
	private $em;
78
79
	/**
80
	 * @var array
81
	 */
82
	private $whitelistExceptions = [];
83
84
	/**
85
	 * @var Doctrine\ORM\UnitOfWork
86
	 */
87
	private $unitOfWorkSnapshot;
88
89
90
91
	public function markExceptionOwner(Doctrine\ORM\EntityManager $em, $exception)
92
	{
93
		if ($this->em !== $em) {
94
			return;
95
		}
96
97
		$this->whitelistExceptions[] = $exception;
98
	}
99
100
101
102
	public function snapshotUnitOfWork(Doctrine\ORM\EntityManager $em)
103
	{
104
		if ($this->em !== $em) {
105
			return;
106
		}
107
108
		$this->unitOfWorkSnapshot = clone $em->getUnitOfWork();
109
	}
110
111
112
113
	/***************** Doctrine\DBAL\Logging\SQLLogger ********************/
114
115
116
117
	/**
118
	 * @param string
119
	 * @param array
120
	 * @param array
121
	 */
122
	public function startQuery($sql, array $params = NULL, array $types = NULL)
123
	{
124
		Debugger::timer('doctrine');
125
126
		$source = NULL;
127
		foreach (debug_backtrace(FALSE) as $row) {
128
			if (isset($row['file']) && $this->filterTracePaths(realpath($row['file']))) {
129
				if (isset($row['class']) && stripos($row['class'], '\\' . Proxy::MARKER) !== FALSE) {
130
					if (!in_array('Doctrine\Common\Persistence\Proxy', class_implements($row['class']))) {
131
						continue;
132
133
					} elseif (isset($row['function']) && $row['function'] === '__load') {
134
						continue;
135
					}
136
137
				} elseif (stripos($row['file'], DIRECTORY_SEPARATOR . Proxy::MARKER) !== FALSE) {
138
					continue;
139
				}
140
141
				$source = [$row['file'], (int) $row['line']];
142
				break;
143
			}
144
		}
145
146
		$this->queries[] = [$sql, $params, NULL, $types, $source];
147
	}
148
149
150
151
	/**
152
	 * @param string $file
153
	 * @return boolean
154
	 */
155
	protected function filterTracePaths($file)
156
	{
157
		$file = str_replace(DIRECTORY_SEPARATOR, '/', $file);
158
		$return = is_file($file);
159
		foreach ($this->skipPaths as $path) {
160
			if (!$return) {
161
				break;
162
			}
163
			$return = $return && strpos($file, '/' . trim($path, '/') . '/') === FALSE;
164
		}
165
		return $return;
166
	}
167
168
169
170
	/**
171
	 * @return array
172
	 */
173
	public function stopQuery()
174
	{
175
		$keys = array_keys($this->queries);
176
		$key = end($keys);
177
		$this->queries[$key][2] = $time = Debugger::timer('doctrine');
178
		$this->totalTime += $time;
179
180
		return $this->queries[$key] + array_fill_keys(range(0, 4), NULL);
181
	}
182
183
184
185
	/**
186
	 * @param \Exception|\Throwable $exception
187
	 */
188
	public function queryFailed($exception)
189
	{
190
		$this->failed[spl_object_hash($exception)] = $this->stopQuery();
191
	}
192
193
194
195
	/***************** Tracy\IBarPanel ********************/
196
197
198
199
	/**
200
	 * @return string
201
	 */
202
	public function getTab()
203
	{
204
		return '<span title="Doctrine 2">'
205
			. '<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>'
206
			. '<span class="tracy-label">'
207
			. count($this->queries) . ' queries'
208
			. ($this->totalTime ? ' / ' . sprintf('%0.1f', $this->totalTime * 1000) . ' ms' : '')
209
			. '</span>'
210
			. '</span>';
211
	}
212
213
214
215
	/**
216
	 * @return string
217
	 */
218
	public function getPanel()
219
	{
220
		if (empty($this->queries)) {
221
			return '';
222
		}
223
224
		$connParams = $this->connection->getParams();
225
		if ($connParams['driver'] === 'pdo_sqlite' && isset($connParams['path'])) {
226
			$host = 'path: ' . basename($connParams['path']);
227
228
		} else {
229
			$host = sprintf('host: %s%s/%s',
230
				$this->connection->getHost(),
231
				(($p = $this->connection->getPort()) ? ':' . $p : ''),
232
				$this->connection->getDatabase()
233
			);
234
		}
235
236
		return
237
			$this->renderStyles() .
238
			sprintf('<h1>Queries: %s %s, %s</h1>',
239
				count($this->queries),
240
				($this->totalTime ? ', time: ' . sprintf('%0.3f', $this->totalTime * 1000) . ' ms' : ''),
241
				$host
242
			) .
243
			'<div class="nette-inner tracy-inner nette-Doctrine2Panel">' .
244
				implode('<br>', array_filter([
245
					$this->renderPanelCacheStatistics(),
246
					$this->renderPanelQueries()
247
				])) .
248
			'</div>';
249
	}
250
251
252
253
	private function renderPanelCacheStatistics()
254
	{
255
		if (empty($this->em)) {
256
			return '';
257
		}
258
259
		$config = $this->em->getConfiguration();
260
		if (!$config->isSecondLevelCacheEnabled()) {
261
			return '';
262
		}
263
264
		$loggerChain = $config->getSecondLevelCacheConfiguration()
265
			->getCacheLogger();
266
267
		if (!$loggerChain instanceof Doctrine\ORM\Cache\Logging\CacheLoggerChain) {
0 ignored issues
show
Bug introduced by
The class Doctrine\ORM\Cache\Logging\CacheLoggerChain 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...
268
			return '';
269
		}
270
271
		if (!$statistics = $loggerChain->getLogger('statistics')) {
272
			return '';
273
		}
274
275
		return Dumper::toHtml($statistics, [Dumper::DEPTH => 5]);
276
	}
277
278
279
280
	private function renderPanelQueries()
281
	{
282
		if (empty($this->queries)) {
283
			return "";
284
		}
285
286
		$s = "";
287
		foreach ($this->queries as $query) {
288
			$s .= $this->processQuery($query);
289
		}
290
291
		return '<table><tr><th>ms</th><th>SQL Statement</th></tr>' . $s . '</table>';
292
	}
293
294
295
296
	/**
297
	 * @return string
298
	 */
299
	protected function renderStyles()
300
	{
301
		return '<style>
302
			#nette-debug td.nette-Doctrine2Panel-sql { background: white !important}
303
			#nette-debug .nette-Doctrine2Panel-source { color: #BBB !important }
304
			#nette-debug nette-Doctrine2Panel tr table { margin: 8px 0; max-height: 150px; overflow:auto }
305
			#tracy-debug td.nette-Doctrine2Panel-sql { background: white !important}
306
			#tracy-debug .nette-Doctrine2Panel-source { color: #BBB !important }
307
			#tracy-debug nette-Doctrine2Panel tr table { margin: 8px 0; max-height: 150px; overflow:auto }
308
		</style>';
309
	}
310
311
312
313
	/**
314
	 * @param array
315
	 * @return string
316
	 */
317
	protected function processQuery(array $query)
318
	{
319
		$h = 'htmlspecialchars';
320
		list($sql, $params, $time, $types, $source) = $query;
321
322
		$s = self::highlightQuery(static::formatQuery($sql, (array) $params, (array) $types, $this->connection ? $this->connection->getDatabasePlatform() : NULL));
323
		if ($source) {
324
			$s .= self::editorLink($source[0], $source[1], $h('.../' . basename(dirname($source[0]))) . '/<b>' . $h(basename($source[0])) . '</b>');
325
		}
326
327
		return '<tr><td>' . sprintf('%0.3f', $time * 1000) . '</td>' .
328
			'<td class = "nette-Doctrine2Panel-sql">' . $s . '</td></tr>';
329
	}
330
331
332
333
	/****************** Exceptions handling *********************/
334
335
336
337
	/**
338
	 * @param \Exception|\Throwable $e
339
	 * @return void|array
340
	 */
341
	public function renderQueryException($e)
342
	{
343
		if ($e instanceof \PDOException && count($this->queries)) {
344
			$types = $params = [];
345
346
			if ($this->connection !== NULL) {
347
				if (!$e instanceof Kdyby\Doctrine\DBALException || $e->connection !== $this->connection) {
348
					return NULL;
349
350
				} elseif (!isset($this->failed[spl_object_hash($e)])) {
351
					return NULL;
352
				}
353
354
				list($sql, $params, , , $source) = $this->failed[spl_object_hash($e)];
355
356
			} else {
357
				list($sql, $params, , $types, $source) = end($this->queries) + range(1, 5);
358
			}
359
360
			if (!$sql) {
361
				return NULL;
362
			}
363
364
			return [
365
				'tab' => 'SQL',
366
				'panel' => $this->dumpQuery($sql, $params, $types, $source),
367
			];
368
369
		} elseif ($e instanceof Kdyby\Doctrine\QueryException && $e->query !== NULL) {
370
			if ($e->query instanceof Doctrine\ORM\Query) {
0 ignored issues
show
Bug introduced by
The class Doctrine\ORM\Query 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...
371
				return [
372
					'tab' => 'DQL',
373
					'panel' => $this->dumpQuery($e->query->getDQL(), $e->query->getParameters()),
374
				];
375
376
			} elseif ($e->query instanceof Kdyby\Doctrine\NativeQueryWrapper) {
377
				return [
378
					'tab' => 'Native SQL',
379
					'panel' => $this->dumpQuery($e->query->getSQL(), $e->query->getParameters()),
380
				];
381
			}
382
		}
383
	}
384
385
386
387
	/**
388
	 * @param \Exception|\Throwable $e
389
	 * @param \Nette\DI\Container $dic
0 ignored issues
show
Bug introduced by
There is no parameter named $dic. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
390
	 * @return array
391
	 */
392
	public function renderEntityManagerException($e)
393
	{
394
		if (!in_array($e, $this->whitelistExceptions, TRUE)) {
395
			return NULL; // ignore
396
		}
397
398
		if (strpos(get_class($e), 'Doctrine\\ORM\\') !== FALSE && Helpers::findTrace($e->getTrace(), 'Doctrine\ORM\EntityManager::flush')) {
399
			$UoW = $this->unitOfWorkSnapshot ?: $this->em->getUnitOfWork();
400
401
			$panel = '<div class="inner"><p><b>IdentityMap</b></p>' .
402
				Dumper::toHtml($UoW->getIdentityMap(), [Dumper::COLLAPSE => TRUE]) .
403
				'</div>';
404
405
			if ($scheduled = $UoW->getScheduledEntityInsertions()) {
406
				$panel .= '<div class="inner"><p><b>Scheduled entity insertions</b></p>' .
407
					Dumper::toHtml($scheduled, [Dumper::COLLAPSE => TRUE]) .
408
					'</div>';
409
			}
410
411
			if ($scheduled = $UoW->getScheduledEntityDeletions()) {
412
				$panel .= '<div class="inner"><p><b>Scheduled entity deletions</b></p>' .
413
					Dumper::toHtml($scheduled, [Dumper::COLLAPSE => TRUE]) .
414
					'</div>';
415
			}
416
417
			if ($scheduled = $UoW->getScheduledEntityUpdates()) {
418
				$panel .= '<div class="inner"><p><b>Scheduled entity updates</b></p>' .
419
					Dumper::toHtml($scheduled, [Dumper::COLLAPSE => TRUE]) .
420
					'</div>';
421
			}
422
423
			return [
424
				'tab' => 'Doctrine\\ORM\\UnitOfWork',
425
				'panel' => $panel,
426
			];
427
		}
428
	}
429
430
431
432
	/**
433
	 * @param \Exception|\Throwable $e
434
	 * @param \Nette\DI\Container $dic
435
	 * @return array
436
	 */
437
	public static function renderException($e, Nette\DI\Container $dic)
438
	{
439
		if ($e instanceof AnnotationException) {
0 ignored issues
show
Bug introduced by
The class Doctrine\Common\Annotations\AnnotationException 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...
440
			if ($dump = self::highlightAnnotationLine($e)) {
441
				return [
442
					'tab' => 'Annotation',
443
					'panel' => $dump,
444
				];
445
			}
446
447
		} elseif ($e instanceof Doctrine\ORM\Mapping\MappingException) {
0 ignored issues
show
Bug introduced by
The class Doctrine\ORM\Mapping\MappingException 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...
448
			if ($invalidEntity = Strings::match($e->getMessage(), '~^Class "([\\S]+)" .*? is not .*? valid~i')) {
449
				$refl = Nette\Reflection\ClassType::from($invalidEntity[1]);
450
				$file = $refl->getFileName();
451
				$errorLine = $refl->getStartLine();
452
453
				return [
454
					'tab' => 'Invalid entity',
455
					'panel' => '<p><b>File:</b> ' . self::editorLink($file, $errorLine) . '</p>' .
456
						BlueScreen::highlightFile($file, $errorLine),
457
				];
458
			}
459
460
		} elseif ($e instanceof Doctrine\DBAL\Schema\SchemaException && $dic && ($em = $dic->getByType('Kdyby\Doctrine\EntityManager', FALSE))) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\Schema\SchemaException 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...
461
			/** @var Kdyby\Doctrine\EntityManager $em */
462
463
			if ($invalidTable = Strings::match($e->getMessage(), '~table \'(.*?)\'~i')) {
464
				foreach ($em->getMetadataFactory()->getAllMetadata() as $class) {
465
					/** @var Kdyby\Doctrine\Mapping\ClassMetadata $class */
466
					if ($class->getTableName() === $invalidTable[1]) {
467
						$refl = $class->getReflectionClass();
468
						break;
469
					}
470
				}
471
472
				if (!isset($refl)) {
473
					return NULL;
474
				}
475
476
				$file = $refl->getFileName();
477
				$errorLine = $refl->getStartLine();
478
479
				return [
480
					'tab' => 'Invalid schema',
481
					'panel' => '<p><b>File:</b> ' . self::editorLink($file, $errorLine) . '</p>' .
482
						BlueScreen::highlightFile($file, $errorLine),
483
				];
484
			}
485
486
		} elseif ($e instanceof Kdyby\Doctrine\DBALException && $e->query) {
487
			return [
488
				'tab' => 'SQL',
489
				'panel' => self::highlightQuery(static::formatQuery($e->query, $e->params, [])),
490
			];
491
492
		} elseif ($e instanceof Doctrine\DBAL\Exception\DriverException) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\Exception\DriverException 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...
493
			if (($prev = $e->getPrevious()) && ($item = Helpers::findTrace($e->getTrace(), 'Doctrine\DBAL\DBALException::driverExceptionDuringQuery'))) {
494
				/** @var \Doctrine\DBAL\Driver $driver */
495
				$driver = $item['args'][0];
496
				$params = isset($item['args'][3]) ? $item['args'][3] : [];
497
498
				return [
499
					'tab' => 'SQL',
500
					'panel' => self::highlightQuery(static::formatQuery($item['args'][2], $params, [], $driver->getDatabasePlatform())),
501
				];
502
			}
503
504
		} elseif ($e instanceof Doctrine\ORM\Query\QueryException) {
0 ignored issues
show
Bug introduced by
The class Doctrine\ORM\Query\QueryException 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...
505
			if (($prev = $e->getPrevious()) && preg_match('~^(SELECT|INSERT|UPDATE|DELETE)\s+.*~i', $prev->getMessage())) {
506
				return [
507
					'tab' => 'DQL',
508
					'panel' => self::highlightQuery(static::formatQuery($prev->getMessage(), [], [])),
509
				];
510
			}
511
512
		} elseif ($e instanceof \PDOException) {
513
			$params = [];
514
515
			if (isset($e->queryString)) {
516
				$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...
517
518
			} elseif ($item = Helpers::findTrace($e->getTrace(), 'Doctrine\DBAL\Connection::executeQuery')) {
519
				$sql = $item['args'][0];
520
				$params = $item['args'][1];
521
522
			} elseif ($item = Helpers::findTrace($e->getTrace(), 'PDO::query')) {
523
				$sql = $item['args'][0];
524
525
			} elseif ($item = Helpers::findTrace($e->getTrace(), 'PDO::prepare')) {
526
				$sql = $item['args'][0];
527
			}
528
529
			return isset($sql) ? [
530
				'tab' => 'SQL',
531
				'panel' => self::highlightQuery(static::formatQuery($sql, $params, [])),
532
			] : NULL;
533
		}
534
535
		return NULL;
536
	}
537
538
539
540
	/**
541
	 * @param string $query
542
	 * @param array|Doctrine\Common\Collections\ArrayCollection $params
543
	 * @param array $types
544
	 * @param string $source
545
	 *
546
	 * @return array
547
	 */
548
	protected function dumpQuery($query, $params, array $types = [], $source = NULL)
549
	{
550
		if ($params instanceof ArrayCollection) {
0 ignored issues
show
Bug introduced by
The class Doctrine\Common\Collections\ArrayCollection 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...
551
			$tmp = [];
552
			$tmpTypes = [];
553
			foreach ($params as $key => $param) {
554
				if ($param instanceof Doctrine\ORM\Query\Parameter) {
0 ignored issues
show
Bug introduced by
The class Doctrine\ORM\Query\Parameter 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...
555
					$tmpTypes[$param->getName()] = $param->getType();
556
					$tmp[$param->getName()] = $param->getValue();
557
					continue;
558
				}
559
				$tmp[$key] = $param;
560
			}
561
			$params = $tmp;
562
			$types = $tmpTypes;
563
		}
564
565
		// query
566
		$s = '<p><b>Query</b></p><table><tr><td class="nette-Doctrine2Panel-sql">';
567
		$s .= self::highlightQuery(static::formatQuery($query, $params, $types, $this->connection ? $this->connection->getDatabasePlatform() : NULL));
568
		$s .= '</td></tr></table>';
569
570
		$e = NULL;
571
		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...
572
			list($file, $line) = $source;
573
			$e = '<p><b>File:</b> ' . self::editorLink($file, $line) . '</p>';
574
		}
575
576
		// styles and dump
577
		return $this->renderStyles() . '<div class="nette-inner tracy-inner nette-Doctrine2Panel">' . $e . $s . '</div>';
578
	}
579
580
581
582
	/**
583
	 * Returns syntax highlighted SQL command.
584
	 * This method is same as Nette\Database\Helpers::dumpSql except for parameters handling.
585
	 * @link https://github.com/nette/database/blob/667143b2d5b940f78c8dc9212f95b1bbc033c6a3/src/Database/Helpers.php#L75-L138
586
	 * @author David Grudl
587
	 * @param string $sql
588
	 * @return string
589
	 */
590
	public static function highlightQuery($sql)
591
	{
592
		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';
593
		static $keywords2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|[RI]?LIKE|REGEXP|TRUE|FALSE|WITH|INSTANCE\s+OF';
594
595
		// insert new lines
596
		$sql = " $sql ";
597
		$sql = preg_replace("#(?<=[\\s,(])($keywords1)(?=[\\s,)])#i", "\n\$1", $sql);
598
599
		// reduce spaces
600
		$sql = preg_replace('#[ \t]{2,}#', ' ', $sql);
601
602
		$sql = wordwrap($sql, 100);
603
		$sql = preg_replace('#([ \t]*\r?\n){2,}#', "\n", $sql);
604
605
		// syntax highlight
606
		$sql = htmlspecialchars($sql, ENT_IGNORE, 'UTF-8');
607
		$sql = preg_replace_callback("#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])($keywords1)(?=[\\s,)])|(?<=[\\s,(=])($keywords2)(?=[\\s,)=])#is", function ($matches) {
608
			if (!empty($matches[1])) { // comment
609
				return '<em style="color:gray">' . $matches[1] . '</em>';
610
611
			} elseif (!empty($matches[2])) { // error
612
				return '<strong style="color:red">' . $matches[2] . '</strong>';
613
614
			} elseif (!empty($matches[3])) { // most important keywords
615
				return '<strong style="color:blue">' . $matches[3] . '</strong>';
616
617
			} elseif (!empty($matches[4])) { // other keywords
618
				return '<strong style="color:green">' . $matches[4] . '</strong>';
619
			}
620
		}, $sql);
621
622
		return '<pre class="dump">' . trim($sql) . "</pre>\n";
623
	}
624
625
626
627
	/**
628
	 * @param string $query
629
	 * @param array $params
630
	 * @param array $types
631
	 * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
632
	 * @throws \Doctrine\DBAL\DBALException
633
	 * @throws \Nette\Utils\RegexpException
634
	 * @return string
635
	 */
636
	public static function formatQuery($query, $params, array $types = [], AbstractPlatform $platform = NULL)
637
	{
638
		if (!$platform) {
639
			$platform = new Doctrine\DBAL\Platforms\MySqlPlatform();
640
		}
641
642
		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...
643
			foreach ($params as $key => $param) {
644
				if (is_array($param)) {
645
					$types[$key] = Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
646
647
				} else {
648
					$types[$key] = 'string';
649
				}
650
			}
651
		}
652
653
		try {
654
			list($query, $params, $types) = \Doctrine\DBAL\SQLParserUtils::expandListParameters($query, $params, $types);
655
		} 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...
Bug introduced by
The class Doctrine\DBAL\SQLParserUtilsException 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...
656
		}
657
658
		$formattedParams = [];
659
		foreach ($params as $key => $param) {
660
			if (isset($types[$key])) {
661
				if (is_scalar($types[$key]) && array_key_exists($types[$key], Type::getTypesMap())) {
662
					$types[$key] = Type::getType($types[$key]);
663
				}
664
665
				/** @var Type[] $types */
666
				if ($types[$key] instanceof Type) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\Types\Type 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...
667
					$param = $types[$key]->convertToDatabaseValue($param, $platform);
668
				}
669
			}
670
671
			$formattedParams[] = SimpleParameterFormatter::format($param);
672
		}
673
		$params = $formattedParams;
674
675
		if (Nette\Utils\Validators::isList($params)) {
676
			$parts = explode('?', $query);
677
			if (count($params) > $parts) {
678
				throw new Kdyby\Doctrine\InvalidStateException("Too mny parameters passed to query.");
679
			}
680
681
			return implode('', Kdyby\Doctrine\Helpers::zipper($parts, $params));
682
		}
683
684
		return Strings::replace($query, '~(\\:[a-z][a-z0-9]*|\\?[0-9]*)~i', function ($m) use (&$params) {
685
			if (substr($m[0], 0, 1) === '?') {
686
				if (strlen($m[0]) > 1) {
687
					if (isset($params[$k = substr($m[0], 1)])) {
688
						return $params[$k];
689
					}
690
691
				} else {
692
					return array_shift($params);
693
				}
694
695
			} else {
696
				if (isset($params[$k = substr($m[0], 1)])) {
697
					return $params[$k];
698
				}
699
			}
700
701
			return $m[0];
702
		});
703
	}
704
705
706
707
	/**
708
	 * @param \Doctrine\Common\Annotations\AnnotationException $e
709
	 *
710
	 * @return string
711
	 */
712
	public static function highlightAnnotationLine(AnnotationException $e)
713
	{
714
		foreach ($e->getTrace() as $step) {
715
			if (@$step['class'] . @$step['type'] . @$step['function'] !== 'Doctrine\Common\Annotations\DocParser->parse') {
716
				continue;
717
			}
718
719
			$context = Strings::match($step['args'][1], '~^(?P<type>[^\s]+)\s*(?P<class>[^:]+)(?:::\$?(?P<property>[^\\(]+))?$~i');
720
			break;
721
		}
722
723
		if (!isset($context)) {
724
			return FALSE;
725
		}
726
727
		$refl = Nette\Reflection\ClassType::from($context['class']);
728
		$file = $refl->getFileName();
729
		$line = NULL;
730
731
		if ($context['type'] === 'property') {
732
			$refl = $refl->getProperty($context['property']);
733
			$line = Kdyby\Doctrine\Helpers::getPropertyLine($refl);
734
735
		} elseif ($context['type'] === 'method') {
736
			$refl = $refl->getProperty($context['method']);
737
		}
738
739
		if (($errorLine = self::calculateErrorLine($refl, $e, $line)) === NULL) {
740
			return FALSE;
741
		}
742
743
		$dump = BlueScreen::highlightFile($file, $errorLine);
744
745
		return '<p><b>File:</b> ' . self::editorLink($file, $errorLine) . '</p>' . $dump;
746
	}
747
748
749
750
	/**
751
	 * @param \Reflector|\Nette\Reflection\ClassType|\Nette\Reflection\Method $refl
752
	 * @param \Exception|\Throwable $e
753
	 * @param int $startLine
754
	 *
755
	 * @return int|string
756
	 */
757
	public static function calculateErrorLine(\Reflector $refl, $e, $startLine = NULL)
758
	{
759
		if ($startLine === NULL) {
760
			$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: 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...
761
		}
762
763
		if ($pos = Strings::match($e->getMessage(), '~position\s*(\d+)~')) {
764
			$targetLine = self::calculateAffectedLine($refl, $pos[1]);
765
766
		} elseif ($notImported = Strings::match($e->getMessage(), '~^\[Semantical Error\]\s+The annotation "([^"]*?)"~i')) {
767
			$parts = explode(self::findRenamed($refl, $notImported[1]), self::cleanedPhpDoc($refl), 2);
768
			$targetLine = self::calculateAffectedLine($refl, strlen($parts[0]));
769
770
		} elseif ($notFound = Strings::match($e->getMessage(), '~^\[Semantical Error\]\s+Couldn\'t find\s+(.*?)\s+(.*?),\s+~')) {
771
			// this is just a guess
772
			$parts = explode(self::findRenamed($refl, $notFound[2]), self::cleanedPhpDoc($refl), 2);
773
			$targetLine = self::calculateAffectedLine($refl, strlen($parts[0]));
774
775
		} else {
776
			$targetLine = self::calculateAffectedLine($refl, 1);
777
		}
778
779
		$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: 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...
780
781
		return $startLine - ($phpDocLines - ($targetLine - 1));
782
	}
783
784
785
786
	/**
787
	 * @param \Reflector|\Nette\Reflection\ClassType|\Nette\Reflection\Method $refl
788
	 * @param int $symbolPos
789
	 *
790
	 * @return int
791
	 */
792
	protected static function calculateAffectedLine(\Reflector $refl, $symbolPos)
793
	{
794
		$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: 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...
795
		$cleanedDoc = self::cleanedPhpDoc($refl, $atPos);
796
		$beforeCleanLines = count(Strings::split(substr($doc, 0, $atPos), '~[\n\r]+~'));
797
		$parsedDoc = substr($cleanedDoc, 0, $symbolPos + 1);
798
		$parsedLines = count(Strings::split($parsedDoc, '~[\n\r]+~'));
799
800
		return $parsedLines + max($beforeCleanLines - 1, 0);
801
	}
802
803
804
805
	/**
806
	 * @param \Reflector|Nette\Reflection\ClassType|Nette\Reflection\Method $refl
807
	 * @param $annotation
808
	 */
809
	private static function findRenamed(\Reflector $refl, $annotation)
810
	{
811
		$parser = new Doctrine\Common\Annotations\PhpParser();
812
		$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: 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...
813
814
		$annotationClass = ltrim($annotation, '@');
815
		foreach ($imports as $alias => $import) {
816
			if (!Strings::startsWith($annotationClass, $import)) {
817
				continue;
818
			}
819
820
			$aliased = str_replace(Strings::lower($import), $alias, Strings::lower($annotationClass));
821
			$searchFor = preg_quote(Strings::lower($aliased));
822
823
			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: 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...
824
				continue;
825
			}
826
827
			return $m['usage'];
828
		}
829
830
		return $annotation;
831
	}
832
833
834
835
	/**
836
	 * @param \Nette\Reflection\ClassType|\Nette\Reflection\Method|\Reflector $refl
837
	 * @param null $atPos
838
	 *
839
	 * @return string
840
	 */
841
	private static function cleanedPhpDoc(\Reflector $refl, &$atPos = NULL)
842
	{
843
		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: 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...
844
	}
845
846
847
848
	/**
849
	 * Returns link to editor.
850
	 * @author David Grudl
851
	 * @param string $file
852
	 * @param string $line
853
	 * @param string $text
854
	 * @return Nette\Utils\Html
855
	 */
856
	private static function editorLink($file, $line, $text = NULL)
857
	{
858
		if (Debugger::$editor && is_file($file) && $text !== NULL) {
859
			return Nette\Utils\Html::el('a')
860
				->href(strtr(Debugger::$editor, ['%file' => rawurlencode($file), '%line' => $line]))
861
				->title("$file:$line")
862
				->setHtml($text);
863
864
		} else {
865
			return Helpers::editorLink($file, $line);
866
		}
867
	}
868
869
870
871
	/****************** Registration *********************/
872
873
874
875
	public function enableLogging()
876
	{
877
		if ($this->connection === NULL) {
878
			throw new Kdyby\Doctrine\InvalidStateException("Doctrine Panel is not bound to connection.");
879
		}
880
881
		$config = $this->connection->getConfiguration();
882
		$logger = $config->getSQLLogger();
883
884
		if ($logger instanceof Doctrine\DBAL\Logging\LoggerChain) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\Logging\LoggerChain 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...
885
			$logger->addLogger($this);
886
887
		} else {
888
			$config->setSQLLogger($this);
889
		}
890
	}
891
892
893
894
	/**
895
	 * @param \Doctrine\DBAL\Connection $connection
896
	 * @return Panel
897
	 */
898
	public function bindConnection(Doctrine\DBAL\Connection $connection)
899
	{
900
		if ($this->connection !== NULL) {
901
			throw new Kdyby\Doctrine\InvalidStateException("Doctrine Panel is already bound to connection.");
902
		}
903
904
		$this->connection = $connection;
905
906
		// Tracy
907
		$this->registerBarPanel(Debugger::getBar());
908
		Debugger::getBlueScreen()->addPanel([$this, 'renderQueryException']);
909
910
		return $this;
911
	}
912
913
914
915
	/**
916
	 * @param Doctrine\ORM\EntityManager $em
917
	 * @return Panel
918
	 */
919
	public function bindEntityManager(Doctrine\ORM\EntityManager $em)
920
	{
921
		if ($this->em !== NULL) {
922
			throw new Kdyby\Doctrine\InvalidStateException("Doctrine Panel is already bound to entity manager.");
923
		}
924
925
		$this->em = $em;
926
		if ($this->em instanceof Kdyby\Doctrine\EntityManager) {
927
			$this->em->bindTracyPanel($this);
928
		}
929
930
		if ($this->connection === NULL) {
931
			$this->bindConnection($em->getConnection());
932
		}
933
934
		Debugger::getBlueScreen()->addPanel([$this, 'renderEntityManagerException']);
935
936
		return $this;
937
	}
938
939
940
941
	/**
942
	 * Registers panel to debugger
943
	 *
944
	 * @param \Tracy\Bar $bar
945
	 */
946
	public function registerBarPanel(Bar $bar)
947
	{
948
		$bar->addPanel($this);
949
	}
950
951
952
953
	/**
954
	 * Registers generic exception renderer
955
	 */
956
	public static function registerBluescreen(Nette\DI\Container $dic)
957
	{
958
		Debugger::getBlueScreen()->addPanel(function ($e) use ($dic) {
959
			return Panel::renderException($e, $dic);
960
		});
961
	}
962
963
}
964