Completed
Push — master ( bf4fc0...a3a907 )
by Tomáš
03:11
created

src/Adapter/Nette/Tracy/DoctrineSQLPanel.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Portiny\Doctrine\Adapter\Nette\Tracy;
6
7
use Doctrine\DBAL\Logging\SQLLogger;
8
use Doctrine\ORM\Cache\Logging\CacheLoggerChain;
9
use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger;
10
use Doctrine\ORM\EntityManager;
11
use Tracy\Debugger;
12
use Tracy\Dumper;
13
use Tracy\IBarPanel;
14
15
/**
16
 * Debug panel for Doctrine
17
 *
18
 * @author David Grudl
19
 * @author Patrik Votoček
20
 * @author Jan Mareš
21
 * @author Tomáš Pilař
22
 */
23
final class DoctrineSQLPanel implements IBarPanel, SQLLogger
24
{
25
	/**
26
	 * @var int
27
	 */
28
	public const DATA_INDEX_SQL = 0;
29
30
	/**
31
	 * @var int
32
	 */
33
	public const DATA_INDEX_PARAMS = 1;
34
35
	/**
36
	 * @var int
37
	 */
38
	public const DATA_INDEX_TYPES = 2;
39
40
	/**
41
	 * @var int
42
	 */
43
	public const DATA_INDEX_TIME = 3;
44
45
	/**
46
	 * @var int
47
	 */
48
	public const DATA_INDEX_TRACE = 4;
49
50
	/**
51
	 * @var int
52
	 */
53
	public const DATA_INDEX_COUNT = 5;
54
55
	/**
56
	 * @var EntityManager
57
	 */
58
	private $entityManager;
59
60
	/**
61
	 * @var bool
62
	 */
63
	private $sortQueries = FALSE;
64
65
	/**
66
	 * @var int
67
	 */
68
	private $totalTime = 0;
69
70
	/**
71
	 * @var array
72
	 */
73
	private $queries = [];
74
75 6
	public function __construct(EntityManager $entityManager)
76
	{
77 6
		$this->entityManager = $entityManager;
78 6
	}
79
80
	/**
81
	 * {@inheritdoc}
82
	 */
83 4
	public function startQuery($sql, ?array $params = NULL, ?array $types = NULL): void
84
	{
85 4
		Debugger::timer('doctrine');
86
87 4
		$this->queries[] = [
88 4
			self::DATA_INDEX_SQL => $sql,
89 4
			self::DATA_INDEX_PARAMS => $params,
90 4
			self::DATA_INDEX_TYPES => $types,
91 4
			self::DATA_INDEX_TIME => 0,
92 4
			self::DATA_INDEX_TRACE => debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : FALSE),
93
		];
94 4
	}
95
96
	/**
97
	 * {@inheritdoc}
98
	 */
99 4
	public function stopQuery(): void
100
	{
101 4
		$keys = array_keys($this->queries);
102 4
		$key = end($keys);
103 4
		$this->queries[$key][self::DATA_INDEX_TIME] = Debugger::timer('doctrine');
104 4
		$this->totalTime += $this->queries[$key][self::DATA_INDEX_TIME];
105 4
	}
106
107
	/**
108
	 * {@inheritdoc}
109
	 */
110 1
	public function getTab(): string
111
	{
112
		return '<span title="Doctrine 2">'
113
			. '<img  src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAKT2lDQ1BQaG90b3Nob3A'
114
			. 'gSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIo'
115
			. 'K2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDw'
116
			. 'rIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl'
117
			. '7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABR'
118
			. 'G8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8'
119
			. 't6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcp'
120
			. 'Xff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcU'
121
			. 'l0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAA'
122
			. 'ARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLy'
123
			. 'BCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLV'
124
			. 'Duag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQ'
125
			. 'SgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkm'
126
			. 'xpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2'
127
			. 'ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl'
128
			. '3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61M'
129
			. 'bU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvO'
130
			. 'UZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9l'
131
			. 'T3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcN'
132
			. 'AQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2B'
133
			. 'aeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8W'
134
			. 'uw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc'
135
			. '+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrde'
136
			. 'wt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1'
137
			. 'BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU8'
138
			. '5ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJT'
139
			. 'wQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZB'
140
			. 'xQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsI'
141
			. 'S4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+'
142
			. '1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7Z'
143
			. 'DuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXH'
144
			. 'txwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRpt'
145
			. 'TmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK'
146
			. '4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu'
147
			. '72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6'
148
			. 'i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8'
149
			. 'EA5jz/GMzLdsAAAAJcEhZcwAALiMAAC4jAXilP3YAAAAHdElNRQffCxUPDinoxmA5AAABO1BMVEUAAADjcTnqakDwgzrxgz7ziDv'
150
			. '1gz72iUD5hz35iD75iT7/gAD/gED/gEf/gID/iz25VS33hDv3iUjngzf/hT7xgTz3hj31gTz8iTv4hj7tfTr5hj77ij78iT73hj3'
151
			. '6iD37hkD8iT35hz77hz77iT73hj36hz76hz/6hz76iD79iD/2hTz7iD75hz37iD76iD77iT75hz77iT75iD78ij/6iT/6iT74hz7'
152
			. '3hj37hz33hj30hD31gDb1gjj1gjn1hDr1hDv1hTz1hT31iEP1iUP2hT32hT72hj32i0b2lVX3hTr3hj33hz73k1L3mFz3mV33ml7'
153
			. '3m1/3nGP4hjz4hz74n2X4oGf4qXf4sYL5hz76iD781bz83cr849H96dz969/+7uT+8+v+8+3+9O7+9/H++PT//Pv//v7///+xeeL'
154
			. 'XAAAAO3RSTlMAAAAAAAAAAAAAAAAAAAAAAgUGBw0QEBERFBUZHR4fKSotP0RKW15lc32GlaWsydvo6err6+z2+vv8/YuXaogAAAA'
155
			. 'BYktHRGjLbPQiAAAAuklEQVQYGQXBhyIVABQA0FNkr6zsGTKzZT83e5Vsks39/y/oHFD7ZbC+HADMLU82qgRQaSpivsUHAGVtCxE'
156
			. '37aoBRRrGI+K6B1BhZGntVyHisB8wtBOX+bq7EWfdxfjUfBPx8yHf9wrxHfTF6vHvk5fMP4UfdTAafzMzM3NzqwsG4ur56fEt837'
157
			. 'loAk6D6Kwfp75by1moMpExEXmXcTOMPB5No7yNmJ7TA3g2+Lp/vX0VyWAUjp6W/mI/zsxJP3EcQMdAAAAAElFTkSuQmCC" />'
158 1
			. count($this->queries) . ' queries'
159 1
			. ($this->totalTime ? ' / ' . sprintf('%0.1f', $this->totalTime * 1000) . 'ms' : '')
160 1
			. '</span>';
161
	}
162
163
	/**
164
	 * {@inheritdoc}
165
	 */
166 1
	public function getPanel(): string
167
	{
168 1
		$s = '';
169
170 1
		if ($this->sortQueries) {
171
			$this->queries = $this->sortQueries($this->queries, self::DATA_INDEX_TIME);
172
		}
173
174 1
		foreach ($this->queries as $query) {
175 1
			$s .= $this->processQuery($query);
176
		}
177
178 1
		return $this->renderStyles() .
179 1
			'<h1>Queries: ' . count($this->queries) .
180 1
			($this->totalTime ? ', time: ' . sprintf('%0.3f', $this->totalTime * 1000) . ' ms' : '') .
181 1
			'</h1>
182
			<div class="tracy-inner nette-Doctrine2Panel">
183 1
			' . $this->renderPanelCacheStatistics() . '
184
			<h2>Queries</h2>
185
			<table>
186 1
			<tr><th>Time&nbsp;ms</th><th>SQL</th><th>Params</th><th>Trace</th></tr>' . $s .
187 1
			'</table>
188
			</div>';
189
	}
190
191
	/**
192
	 * Binds panel to debug bar.
193
	 */
194 6
	public function bindToBar(): void
195
	{
196 6
		if (! $this->isTracyEnabled()) {
197 6
			return;
198
		}
199
200
		$this->entityManager->getConfiguration()->setSQLLogger($this);
201
		Debugger::getBar()->addPanel($this);
202
	}
203
204
	/**
205
	 * @param bool $sortQueries
206
	 */
207
	public function setSortQueries($sortQueries): void
208
	{
209
		$this->sortQueries = $sortQueries;
210
	}
211
212 2
	public function getQueries(): array
213
	{
214 2
		return $this->queries;
215
	}
216
217 6
	private function isTracyEnabled(): bool
218
	{
219 6
		return PHP_SAPI !== 'cli' && Debugger::isEnabled() && ! Debugger::$productionMode;
220
	}
221
222 1
	private function processQuery(array $query): string
223
	{
224 1
		$s = '<tr>';
225 1
		$s .= '<td>' . sprintf('%0.3f', $query[self::DATA_INDEX_TIME] * 1000);
226
227 1
		if (isset($query[self::DATA_INDEX_COUNT])) {
228
			$s .= '/' . sprintf('%d', $query[self::DATA_INDEX_COUNT]);
229
		}
230
231 1
		$s .= '</td>';
232
233
		$s .= '<td class="nette-Doctrine2Panel-sql" style="min-width: 400px">' .
234 1
			Helper::dumpSql($query[self::DATA_INDEX_SQL]);
235
236 1
		$s .= '</td>';
237 1
		$s .= '<td>' . Dumper::toHtml($query[self::DATA_INDEX_PARAMS]) . '</td>';
238 1
		$s .= '<td>' . Dumper::toHtml($query[self::DATA_INDEX_TRACE]) . '</td>';
239 1
		$s .= '</tr>';
240
241 1
		return $s;
242
	}
243
244 1
	private function renderStyles(): string
245
	{
246 1
		return '<style>
247
			#tracy-debug td.nette-Doctrine2Panel-sql { background: white !important }
248
			#tracy-debug .nette-Doctrine2Panel-source { color: #BBB !important }
249
			#tracy-debug div.tracy-inner.nette-Doctrine2Panel { max-width: 1000px }
250
			#tracy-debug .nette-Doctrine2Panel tr table { margin: 8px 0; max-height: 150px; overflow:auto }
251
			#tracy-debug .nette-Doctrine2Panel-cache-green { color: green !important; font-weight: bold }
252
			#tracy-debug .nette-Doctrine2Panel-cache-red { color: red !important; font-weight: bold }
253
			#tracy-debug .nette-Doctrine2Panel h2 { font-size: 23px; }
254
			</style>';
255
	}
256
257 1
	private function renderPanelCacheStatistics(): string
258
	{
259 1
		if (empty($this->entityManager)) {
260
			return '';
261
		}
262
263 1
		$config = $this->entityManager->getConfiguration();
264 1
		if (! $config->isSecondLevelCacheEnabled()) {
265 1
			return '';
266
		}
267
268
		$cacheConfiguration = $config->getSecondLevelCacheConfiguration();
269
		if (! $cacheConfiguration) {
270
			return '';
271
		}
272
273
		$cacheLogger = $cacheConfiguration->getCacheLogger();
274
		if (! $cacheLogger instanceof CacheLoggerChain) {
0 ignored issues
show
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...
275
			return '';
276
		}
277
278
		/** @var StatisticsCacheLogger|null $statistics */
279
		$statistics = $cacheLogger->getLogger('statistics');
280
		if (! $statistics) {
281
			return '';
282
		}
283
284
		$cacheDriver = $this->entityManager->getConfiguration()->getMetadataCacheImpl();
285
		$driverInformation = '';
286
		if ($cacheDriver) {
287
			$driverInformation = get_class($cacheDriver);
288
		}
289
290
		return '<h2>Second Level Cache</h2>
291
			<table>
292
				<tr>
293
					<td>Driver</td>
294
					<td><strong>' . $driverInformation . '</strong></td>
295
				</tr>
296
				<tr>
297
					<td>Cache hits</td>
298
					<td>
299
						<strong class="nette-Doctrine2Panel-cache-green">' . $statistics->getHitCount() . '</strong>
300
					</td>
301
				</tr>
302
				<tr>
303
					<td>Cache misses</td>
304
					<td>
305
						<strong class="nette-Doctrine2Panel-cache-red">' . $statistics->getMissCount() . '</strong>
306
					</td>
307
				</tr>
308
				<tr>
309
					<td>Cache puts</td>
310
					<td>
311
						<strong class="nette-Doctrine2Panel-cache-red">' . $statistics->getPutCount() . '</strong>
312
					</td>
313
				</tr>
314
			</table>';
315
	}
316
317
	private function sortQueries(array $queries, int $key): array
318
	{
319
		uasort(
320
			$queries,
321
			function ($a, $b) use ($key) {
322
				if ($a[$key] === $b[$key]) {
323
					return 0;
324
				}
325
326
				return ($a[$key] > $b[$key]) ? -1 : 1;
327
			}
328
		);
329
330
		return $queries;
331
	}
332
}
333