Issues (28)

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

Check for loose comparison of booleans.

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

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
324
	}
325
326
}
327