Completed
Push — master ( 4e8223...162bbd )
by Tomáš
07:39
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 int
57
	 */
58
	private $totalTime = 0;
59
60
	/**
61
	 * @var bool
62
	 */
63
	private $sortQueries = FALSE;
64
65
	/**
66
	 * @var array
67
	 */
68
	private $queries = [];
69
70
	/**
71
	 * @var EntityManager
72
	 */
73
	private $entityManager;
74
75 6
	public function __construct(EntityManager $entityManager)
0 ignored issues
show
You have injected the EntityManager via parameter $entityManager. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
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
	private function sortQueries(array $queries, int $key): array
218
	{
219
		uasort(
220
			$queries,
221
			function ($a, $b) use ($key) {
222
				if ($a[$key] === $b[$key]) {
223
					return 0;
224
				}
225
226
				return $a[$key] > $b[$key] ? -1 : 1;
227
			}
228
		);
229
230
		return $queries;
231
	}
232
233 1
	private function processQuery(array $query): string
234
	{
235 1
		$s = '<tr>';
236 1
		$s .= '<td>' . sprintf('%0.3f', $query[self::DATA_INDEX_TIME] * 1000);
237
238 1
		if (isset($query[self::DATA_INDEX_COUNT])) {
239
			$s .= '/' . sprintf('%d', $query[self::DATA_INDEX_COUNT]);
240
		}
241
242 1
		$s .= '</td>';
243
244
		$s .= '<td class="nette-Doctrine2Panel-sql" style="min-width: 400px">' .
245 1
			Helper::dumpSql($query[self::DATA_INDEX_SQL]);
246
247 1
		$s .= '</td>';
248 1
		$s .= '<td>' . Dumper::toHtml($query[self::DATA_INDEX_PARAMS]) . '</td>';
249 1
		$s .= '<td>' . Dumper::toHtml($query[self::DATA_INDEX_TRACE]) . '</td>';
250 1
		$s .= '</tr>';
251
252 1
		return $s;
253
	}
254
255 1
	private function renderStyles(): string
256
	{
257 1
		return '<style>
258
			#tracy-debug td.nette-Doctrine2Panel-sql { background: white !important }
259
			#tracy-debug .nette-Doctrine2Panel-source { color: #BBB !important }
260
			#tracy-debug div.tracy-inner.nette-Doctrine2Panel { max-width: 1000px }
261
			#tracy-debug .nette-Doctrine2Panel tr table { margin: 8px 0; max-height: 150px; overflow:auto }
262
			#tracy-debug .nette-Doctrine2Panel-cache-green { color: green !important; font-weight: bold }
263
			#tracy-debug .nette-Doctrine2Panel-cache-red { color: red !important; font-weight: bold }
264
			#tracy-debug .nette-Doctrine2Panel h2 { font-size: 23px; }
265
			</style>';
266
	}
267
268 1
	private function renderPanelCacheStatistics(): string
269
	{
270 1
		if (empty($this->entityManager)) {
271
			return '';
272
		}
273
274 1
		$config = $this->entityManager->getConfiguration();
275 1
		if (! $config->isSecondLevelCacheEnabled()) {
276 1
			return '';
277
		}
278
279
		$cacheConfiguration = $config->getSecondLevelCacheConfiguration();
280
		if (! $cacheConfiguration) {
281
			return '';
282
		}
283
284
		$cacheLogger = $cacheConfiguration->getCacheLogger();
285
		if (! $cacheLogger instanceof CacheLoggerChain) {
286
			return '';
287
		}
288
289
		/** @var StatisticsCacheLogger|null $statistics */
290
		$statistics = $cacheLogger->getLogger('statistics');
291
		if (! $statistics) {
292
			return '';
293
		}
294
295
		$cacheDriver = $this->entityManager->getConfiguration()->getMetadataCacheImpl();
296
		$driverInformation = '';
297
		if ($cacheDriver) {
298
			$driverInformation = get_class($cacheDriver);
299
		}
300
301
		return '<h2>Second Level Cache</h2>
302
			<table>
303
				<tr>
304
					<td>Driver</td>
305
					<td><strong>' . $driverInformation . '</strong></td>
306
				</tr>
307
				<tr>
308
					<td>Cache hits</td>
309
					<td>
310
						<strong class="nette-Doctrine2Panel-cache-green">' . $statistics->getHitCount() . '</strong>
311
					</td>
312
				</tr>
313
				<tr>
314
					<td>Cache misses</td>
315
					<td>
316
						<strong class="nette-Doctrine2Panel-cache-red">' . $statistics->getMissCount() . '</strong>
317
					</td>
318
				</tr>
319
				<tr>
320
					<td>Cache puts</td>
321
					<td>
322
						<strong class="nette-Doctrine2Panel-cache-red">' . $statistics->getPutCount() . '</strong>
323
					</td>
324
				</tr>
325
			</table>';
326
	}
327
328 6
	private function isTracyEnabled(): bool
329
	{
330 6
		return PHP_SAPI !== 'cli' && Debugger::isEnabled() && ! Debugger::$productionMode;
331
	}
332
}
333