Completed
Push — master ( 06cb54...79190c )
by Tomáš
08:10
created

DoctrineSQLPanel::bindToBar()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.5

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 3
cts 6
cp 0.5
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 0
crap 2.5
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
Bug introduced by
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()
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()
167
	{
168 1
		$s = '';
169
170 1
		if ($this->sortQueries) {
171
			$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 empty($this->queries) ? '' :
179 1
			$this->renderStyles() .
180 1
			'<h1>Queries: ' . count($this->queries) .
181 1
			($this->totalTime ? ', time: ' . sprintf('%0.3f', $this->totalTime * 1000) . ' ms' : '') .
182 1
			'</h1>
183
			<div class="tracy-inner nette-Doctrine2Panel">
184 1
			' . $this->renderPanelCacheStatistics() . '
185
			<h2>Queries</h2>
186
			<table>
187 1
			<tr><th>Time&nbsp;ms</th><th>SQL</th><th>Params</th><th>Trace</th></tr>' . $s .
188 1
			'</table>
189
			</div>';
190
	}
191
192
	/**
193
	 * Binds panel to debug bar.
194
	 */
195 6
	public function bindToBar(): void
196
	{
197 6
		if (! $this->isTracyEnabled()) {
198 6
			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 2
	public function getQueries(): array
214
	{
215 2
		return $this->queries;
216
	}
217
218
	/**
219
	 * @return bool
220
	 */
221 6
	private function isTracyEnabled()
222
	{
223 6
		return php_sapi_name() !== 'cli' && Debugger::isEnabled() && ! Debugger::$productionMode;
224
	}
225
226
	/**
227
	 * @return string
228
	 */
229 1
	private function processQuery(array $query)
230
	{
231 1
		$s = '<tr>';
232 1
		$s .= '<td>' . sprintf('%0.3f', $query[self::DATA_INDEX_TIME] * 1000);
233
234 1
		if (isset($query[self::DATA_INDEX_COUNT])) {
235
			$s .= '/' . sprintf('%d', $query[self::DATA_INDEX_COUNT]);
236
		}
237
238 1
		$s .= '</td>';
239
240
		$s .= '<td class="nette-Doctrine2Panel-sql" style="min-width: 400px">' .
241 1
			Helper::dumpSql($query[self::DATA_INDEX_SQL]);
242
243 1
		$s .= '</td>';
244 1
		$s .= '<td>' . Dumper::toHtml($query[self::DATA_INDEX_PARAMS]) . '</td>';
245 1
		$s .= '<td>' . Dumper::toHtml($query[self::DATA_INDEX_TRACE]) . '</td>';
246 1
		$s .= '</tr>';
247
248 1
		return $s;
249
	}
250
251 1
	private function renderStyles()
252
	{
253 1
		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 1
	private function renderPanelCacheStatistics()
265
	{
266 1
		if (empty($this->entityManager)) {
267
			return '';
268
		}
269
270 1
		$config = $this->entityManager->getConfiguration();
271 1
		if (! $config->isSecondLevelCacheEnabled()) {
272 1
			return '';
273
		}
274
275
		$cacheLogger = $config->getSecondLevelCacheConfiguration()->getCacheLogger();
276
277
		if (! $cacheLogger instanceof CacheLoggerChain) {
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