Completed
Push — master ( d4201d...06cb54 )
by Tomáš
03:43
created

DoctrineSQLPanel::getQueries()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
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
	public function __construct(EntityManager $entityManager)
76
	{
77
		$this->entityManager = $entityManager;
78
	}
79
80
	/**
81
	 * {@inheritdoc}
82
	 */
83
	public function startQuery($sql, ?array $params = NULL, ?array $types = NULL): void
84
	{
85
		Debugger::timer('doctrine');
86
87
		$this->queries[] = [
88
			self::DATA_INDEX_SQL => $sql,
89
			self::DATA_INDEX_PARAMS => $params,
90
			self::DATA_INDEX_TYPES => $types,
91
			self::DATA_INDEX_TIME => 0,
92
			self::DATA_INDEX_TRACE => debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : FALSE),
93
		];
94
	}
95
96
	/**
97
	 * {@inheritdoc}
98
	 */
99
	public function stopQuery(): void
100
	{
101
		$keys = array_keys($this->queries);
102
		$key = end($keys);
103
		$this->queries[$key][self::DATA_INDEX_TIME] = Debugger::timer('doctrine');
104
		$this->totalTime += $this->queries[$key][self::DATA_INDEX_TIME];
105
	}
106
107
	/**
108
	 * {@inheritdoc}
109
	 */
110
	public function getTab()
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
			. count($this->queries) . ' queries'
159
			. ($this->totalTime ? ' / ' . sprintf('%0.1f', $this->totalTime * 1000) . 'ms' : '')
160
			. '</span>';
161
	}
162
163
	/**
164
	 * {@inheritdoc}
165
	 */
166
	public function getPanel()
167
	{
168
		$s = '';
169
170
		if ($this->sortQueries) {
171
			$this->sortQueries($this->queries, self::DATA_INDEX_TIME);
172
		}
173
174
		foreach ($this->queries as $query) {
175
			$s .= $this->processQuery($query);
176
		}
177
178
		return empty($this->queries) ? '' :
179
			$this->renderStyles() .
180
			'<h1>Queries: ' . count($this->queries) .
181
			($this->totalTime ? ', time: ' . sprintf('%0.3f', $this->totalTime * 1000) . ' ms' : '') .
182
			'</h1>
183
			<div class="tracy-inner nette-Doctrine2Panel">
184
			' . $this->renderPanelCacheStatistics() . '
185
			<h2>Queries</h2>
186
			<table>
187
			<tr><th>Time&nbsp;ms</th><th>SQL</th><th>Params</th><th>Trace</th></tr>' . $s .
188
			'</table>
189
			</div>';
190
	}
191
192
	/**
193
	 * Binds panel to debug bar.
194
	 */
195
	public function bindToBar(): void
196
	{
197
		if (! $this->isTracyEnabled()) {
198
			return;
199
		}
200
201
		$this->entityManager->getConfiguration()->setSQLLogger($this);
202
		Debugger::getBar()->addPanel($this);
203
	}
204
205
	/**
206
	 * @param bool $sortQueries
207
	 */
208
	public function setSortQueries($sortQueries): void
209
	{
210
		$this->sortQueries = $sortQueries;
211
	}
212
213
	public function getQueries(): array
214
	{
215
		return $this->queries;
216
	}
217
218
	/**
219
	 * @return bool
220
	 */
221
	private function isTracyEnabled()
222
	{
223
		return php_sapi_name() !== 'cli' && Debugger::isEnabled() && ! Debugger::$productionMode;
224
	}
225
226
	/**
227
	 * @return string
228
	 */
229
	private function processQuery(array $query)
230
	{
231
		$s = '<tr>';
232
		$s .= '<td>' . sprintf('%0.3f', $query[self::DATA_INDEX_TIME] * 1000);
233
234
		if (isset($query[self::DATA_INDEX_COUNT])) {
235
			$s .= '/' . sprintf('%d', $query[self::DATA_INDEX_COUNT]);
236
		}
237
238
		$s .= '</td>';
239
240
		$s .= '<td class="nette-Doctrine2Panel-sql" style="min-width: 400px">' .
241
			Helper::dumpSql($query[self::DATA_INDEX_SQL]);
242
243
		$s .= '</td>';
244
		$s .= '<td>' . Dumper::toHtml($query[self::DATA_INDEX_PARAMS]) . '</td>';
245
		$s .= '<td>' . Dumper::toHtml($query[self::DATA_INDEX_TRACE]) . '</td>';
246
		$s .= '</tr>';
247
248
		return $s;
249
	}
250
251
	private function renderStyles()
252
	{
253
		return '<style>
254
			#tracy-debug td.nette-Doctrine2Panel-sql { background: white !important }
255
			#tracy-debug .nette-Doctrine2Panel-source { color: #BBB !important }
256
			#tracy-debug div.tracy-inner.nette-Doctrine2Panel { max-width: 1000px }
257
			#tracy-debug .nette-Doctrine2Panel tr table { margin: 8px 0; max-height: 150px; overflow:auto }
258
			#tracy-debug .nette-Doctrine2Panel-cache-green { color: green !important; font-weight: bold }
259
			#tracy-debug .nette-Doctrine2Panel-cache-red { color: red !important; font-weight: bold }
260
			#tracy-debug .nette-Doctrine2Panel h2 { font-size: 23px; }
261
			</style>';
262
	}
263
264
	private function renderPanelCacheStatistics()
265
	{
266
		if (empty($this->entityManager)) {
267
			return '';
268
		}
269
270
		$config = $this->entityManager->getConfiguration();
271
		if (! $config->isSecondLevelCacheEnabled()) {
272
			return '';
273
		}
274
275
		$cacheLogger = $config->getSecondLevelCacheConfiguration()->getCacheLogger();
276
277
		if (! $cacheLogger instanceof 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...
278
			return '';
279
		}
280
281
		/** @var StatisticsCacheLogger $statistics */
282
		$statistics = $cacheLogger->getLogger('statistics');
283
		if (! ($statistics)) {
284
			return '';
285
		}
286
287
		$cacheDriver = $this->entityManager->getConfiguration()->getMetadataCacheImpl();
288
		$driverInformation = get_class($cacheDriver);
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): void
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
}
331