Completed
Push — master ( 27b921...bfb6e5 )
by Tomáš
04:49
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)
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 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) {
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