1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This file is part of the DTForce Nette-Doctrine extension (http://www.dtforce.com/). |
5
|
|
|
* |
6
|
|
|
* This source file is subject to the GNU Lesser General Public License. |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace DTForce\DoctrineExtension\Debug; |
10
|
|
|
|
11
|
|
|
use Doctrine\DBAL\Connection; |
12
|
|
|
use Doctrine\DBAL\Logging\SQLLogger; |
13
|
|
|
use Doctrine\ORM\Cache\Logging\CacheLoggerChain; |
14
|
|
|
use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger; |
15
|
|
|
use Doctrine\ORM\EntityManager; |
16
|
|
|
use Lekarna\Application\Debug\DebugHelper; |
17
|
|
|
use Lekarna\Doctrine\Cache\RedisCache; |
18
|
|
|
use Nette\Database\Helpers; |
19
|
|
|
use Nette\InvalidStateException; |
20
|
|
|
use Nette\Utils\Strings; |
21
|
|
|
use Tracy\Debugger; |
22
|
|
|
use Tracy\Dumper; |
23
|
|
|
use Tracy\IBarPanel; |
24
|
|
|
|
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* Debug panel for Doctrine |
28
|
|
|
* |
29
|
|
|
* @author David Grudl |
30
|
|
|
* @author Patrik Votoček |
31
|
|
|
* @author Jan Mareš |
32
|
|
|
* @author Tomáš Pilař |
33
|
|
|
*/ |
34
|
|
|
final class DoctrineSQLPanel implements IBarPanel, SQLLogger |
35
|
|
|
{ |
36
|
|
|
|
37
|
|
|
const DATA_INDEX_SQL = 0; |
38
|
|
|
const DATA_INDEX_PARAMS = 1; |
39
|
|
|
const DATA_INDEX_TYPES = 2; |
40
|
|
|
const DATA_INDEX_TIME = 3; |
41
|
|
|
const DATA_INDEX_EXPLAIN = 4; |
42
|
|
|
const DATA_INDEX_TRACE = 5; |
43
|
|
|
const DATA_INDEX_COUNT = 6; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var bool |
47
|
|
|
*/ |
48
|
|
|
private $sortQueries = FALSE; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @var bool |
52
|
|
|
*/ |
53
|
|
|
private $groupQueries = FALSE; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* whether to do explain queries for selects or not |
57
|
|
|
* @var bool |
58
|
|
|
*/ |
59
|
|
|
private $doExplains = FALSE; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* @var bool |
63
|
|
|
*/ |
64
|
|
|
private $explainRunning = FALSE; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* @var Connection|NULL |
68
|
|
|
*/ |
69
|
|
|
private $connection; |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* @var int |
73
|
|
|
*/ |
74
|
|
|
private $totalTime = 0; |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @var array |
78
|
|
|
*/ |
79
|
|
|
private $queries = []; |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* @var EntityManager |
83
|
|
|
*/ |
84
|
|
|
private $entityManager; |
85
|
|
|
|
86
|
|
|
|
87
|
|
|
public function __construct(EntityManager $entityManager) |
|
|
|
|
88
|
|
|
{ |
89
|
|
|
$this->entityManager = $entityManager; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* {@inheritdoc} |
95
|
|
|
*/ |
96
|
|
|
public function startQuery($sql, array $params = NULL, array $types = NULL) |
97
|
|
|
{ |
98
|
|
|
if ($this->explainRunning) { |
99
|
|
|
return; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
Debugger::timer('doctrine'); |
103
|
|
|
|
104
|
|
|
$this->queries[] = [ |
105
|
|
|
self::DATA_INDEX_SQL => $sql, |
106
|
|
|
self::DATA_INDEX_PARAMS => $params, |
107
|
|
|
self::DATA_INDEX_TYPES => $types, |
108
|
|
|
self::DATA_INDEX_TIME => 0, |
109
|
|
|
self::DATA_INDEX_EXPLAIN => NULL, |
110
|
|
|
self::DATA_INDEX_TRACE => debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : FALSE) |
111
|
|
|
]; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* {@inheritdoc} |
117
|
|
|
*/ |
118
|
|
|
public function stopQuery() |
119
|
|
|
{ |
120
|
|
|
if ($this->explainRunning) { |
121
|
|
|
return; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
$keys = array_keys($this->queries); |
125
|
|
|
$key = end($keys); |
126
|
|
|
$this->queries[$key][self::DATA_INDEX_TIME] = Debugger::timer('doctrine'); |
127
|
|
|
$this->totalTime += $this->queries[$key][self::DATA_INDEX_TIME]; |
128
|
|
|
|
129
|
|
|
// get EXPLAIN for SELECT queries |
130
|
|
|
if ($this->doExplains) { |
131
|
|
|
if ($this->connection === NULL) { |
132
|
|
|
throw new InvalidStateException( |
133
|
|
|
'You must set a Doctrine\DBAL\Connection to get EXPLAIN.' |
134
|
|
|
); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
$query = $this->queries[$key][self::DATA_INDEX_SQL]; |
138
|
|
|
|
139
|
|
|
if ( ! Strings::startsWith($query, 'SELECT')) { // only SELECTs are supported |
140
|
|
|
return; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
// prevent logging explains & infinite recursion |
144
|
|
|
$this->explainRunning = TRUE; |
145
|
|
|
|
146
|
|
|
$params = $this->queries[$key][self::DATA_INDEX_PARAMS]; |
147
|
|
|
$types = $this->queries[$key][self::DATA_INDEX_TYPES]; |
148
|
|
|
|
149
|
|
|
$stmt = $this->connection->executeQuery('EXPLAIN ' . $query, $params, $types); |
|
|
|
|
150
|
|
|
|
151
|
|
|
$this->queries[$key][self::DATA_INDEX_EXPLAIN] = $stmt->fetchAll(); |
152
|
|
|
|
153
|
|
|
$this->explainRunning = FALSE; |
154
|
|
|
} |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* {@inheritdoc} |
160
|
|
|
*/ |
161
|
|
|
public function getTab() |
162
|
|
|
{ |
163
|
|
|
return '<span title="Doctrine 2">' |
164
|
|
|
// @codingStandardsIgnoreStart |
165
|
|
|
. '<img src="data:image/png;base64,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AXilP3YAAAAHdElNRQffCxUPDinoxmA5AAABO1BMVEUAAADjcTnqakDwgzrxgz7ziDv1gz72iUD5hz35iD75iT7/gAD/gED/gEf/gID/iz25VS33hDv3iUjngzf/hT7xgTz3hj31gTz8iTv4hj7tfTr5hj77ij78iT73hj36iD37hkD8iT35hz77hz77iT73hj36hz76hz/6hz76iD79iD/2hTz7iD75hz37iD76iD77iT75hz77iT75iD78ij/6iT/6iT74hz73hj37hz33hj30hD31gDb1gjj1gjn1hDr1hDv1hTz1hT31iEP1iUP2hT32hT72hj32i0b2lVX3hTr3hj33hz73k1L3mFz3mV33ml73m1/3nGP4hjz4hz74n2X4oGf4qXf4sYL5hz76iD781bz83cr849H96dz969/+7uT+8+v+8+3+9O7+9/H++PT//Pv//v7///+xeeLXAAAAO3RSTlMAAAAAAAAAAAAAAAAAAAAAAgUGBw0QEBERFBUZHR4fKSotP0RKW15lc32GlaWsydvo6err6+z2+vv8/YuXaogAAAABYktHRGjLbPQiAAAAuklEQVQYGQXBhyIVABQA0FNkr6zsGTKzZT83e5Vsks39/y/oHFD7ZbC+HADMLU82qgRQaSpivsUHAGVtCxE37aoBRRrGI+K6B1BhZGntVyHisB8wtBOX+bq7EWfdxfjUfBPx8yHf9wrxHfTF6vHvk5fMP4UfdTAafzMzM3NzqwsG4ur56fEt837loAk6D6Kwfp75by1moMpExEXmXcTOMPB5No7yNmJ7TA3g2+Lp/vX0VyWAUjp6W/mI/zsxJP3EcQMdAAAAAElFTkSuQmCC" />' |
166
|
|
|
// @codingStandardsIgnoreEnd |
167
|
|
|
. count($this->queries) . ' queries' |
168
|
|
|
. ($this->totalTime ? ' / ' . sprintf('%0.1f', $this->totalTime * 1000) . 'ms' : '') |
169
|
|
|
. '</span>'; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* {@inheritdoc} |
175
|
|
|
*/ |
176
|
|
|
public function getPanel() |
177
|
|
|
{ |
178
|
|
|
$s = ''; |
179
|
|
|
|
180
|
|
|
if ($this->groupQueries) { |
181
|
|
|
$this->queries = $this->groupQueries($this->queries); |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
if ($this->sortQueries) { |
185
|
|
|
$this->sortQueries($this->queries, self::DATA_INDEX_TIME); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
foreach ($this->queries as $query) { |
189
|
|
|
$s .= $this->processQuery($query); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
return empty($this->queries) ? '' : |
193
|
|
|
$this->renderStyles() . |
194
|
|
|
'<h1>Queries: ' . count($this->queries) . |
195
|
|
|
($this->totalTime ? ', time: ' . sprintf('%0.3f', $this->totalTime * 1000) . ' ms' : '') . |
196
|
|
|
'</h1> |
197
|
|
|
<div class="tracy-inner nette-Doctrine2Panel"> |
198
|
|
|
' . $this->renderPanelCacheStatistics() . ' |
199
|
|
|
<h2>Queries</h2> |
200
|
|
|
<table> |
201
|
|
|
<tr><th>Time ms</th><th>SQL</th><th>Params</th><th>Trace</th></tr>' . $s . |
202
|
|
|
'</table> |
203
|
|
|
</div>'; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Binds panel to debug bar. |
209
|
|
|
*/ |
210
|
|
|
public function bindToBar() |
211
|
|
|
{ |
212
|
|
|
if ( ! $this->isTracyEnabled()) { |
213
|
|
|
return; |
214
|
|
|
} |
215
|
|
|
$this->entityManager->getConfiguration()->setSQLLogger($this); |
216
|
|
|
$this->connection = $this->entityManager->getConnection(); |
217
|
|
|
Debugger::getBar()->addPanel($this); |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
|
221
|
|
|
/** |
222
|
|
|
* @return bool |
223
|
|
|
*/ |
224
|
|
|
private function isTracyEnabled() |
225
|
|
|
{ |
226
|
|
|
return ( ! defined('IS_CLI') || ! IS_CLI) && Debugger::isEnabled() && ! Debugger::$productionMode; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* @param bool $sortQueries |
232
|
|
|
*/ |
233
|
|
|
public function setSortQueries($sortQueries) |
234
|
|
|
{ |
235
|
|
|
$this->sortQueries = $sortQueries; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* @param bool $groupQueries |
241
|
|
|
*/ |
242
|
|
|
public function setGroupQueries($groupQueries) |
243
|
|
|
{ |
244
|
|
|
$this->groupQueries = $groupQueries; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* @param array |
250
|
|
|
* @return string |
251
|
|
|
*/ |
252
|
|
|
protected function processQuery(array $query) |
253
|
|
|
{ |
254
|
|
|
$s = '<tr>'; |
255
|
|
|
$s .= '<td>' . sprintf('%0.3f', $query[self::DATA_INDEX_TIME] * 1000); |
256
|
|
|
|
257
|
|
|
if ($this->doExplains && isset($query[self::DATA_INDEX_EXPLAIN])) { |
258
|
|
|
static $counter; |
259
|
|
|
$counter++; |
260
|
|
|
$s .= "<br /><a href='#' class='nette-toggler' rel='#nette-Doctrine2Panel-row-$counter'>" . |
261
|
|
|
"explain ►</a>"; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
if (isset($query[self::DATA_INDEX_COUNT])) { |
265
|
|
|
$s .= '/' . sprintf('%d', $query[self::DATA_INDEX_COUNT]); |
266
|
|
|
} |
267
|
|
|
$s .= '</td>'; |
268
|
|
|
|
269
|
|
|
$s .= '<td class="nette-Doctrine2Panel-sql" style="min-width: 400px">' . |
270
|
|
|
Helpers::dumpSql($query[self::DATA_INDEX_SQL]); |
271
|
|
|
|
272
|
|
|
if ($this->doExplains && isset($query[self::DATA_INDEX_EXPLAIN])) { |
273
|
|
|
$s .= "<table id='nette-Doctrine2Panel-row-$counter' class='nette-collapsed'><tr>"; |
|
|
|
|
274
|
|
|
foreach ($query[self::DATA_INDEX_EXPLAIN][0] as $col => $foo) { |
275
|
|
|
$s .= '<th>' . htmlSpecialChars($col) . '</th>'; |
276
|
|
|
} |
277
|
|
|
$s .= '</tr>'; |
278
|
|
|
foreach ($query[self::DATA_INDEX_EXPLAIN] as $row) { |
|
|
|
|
279
|
|
|
$s .= '<tr>'; |
280
|
|
|
foreach ($row as $col) { |
281
|
|
|
$s .= '<td>' . htmlSpecialChars($col) . '</td>'; |
282
|
|
|
} |
283
|
|
|
$s .= '</tr>'; |
284
|
|
|
} |
285
|
|
|
$s .= '</table>'; |
286
|
|
|
} |
287
|
|
|
$s .= '</td>'; |
288
|
|
|
$s .= '<td>' . Dumper::toHtml($query[self::DATA_INDEX_PARAMS]) . '</td>'; |
289
|
|
|
$s .= '<td>' . Dumper::toHtml($query[self::DATA_INDEX_TRACE]) . '</td>'; |
290
|
|
|
$s .= '</tr>'; |
291
|
|
|
|
292
|
|
|
return $s; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
|
296
|
|
|
protected function renderStyles() |
297
|
|
|
{ |
298
|
|
|
return '<style> |
299
|
|
|
#tracy-debug td.nette-Doctrine2Panel-sql { background: white !important } |
300
|
|
|
#tracy-debug .nette-Doctrine2Panel-source { color: #BBB !important } |
301
|
|
|
#tracy-debug div.tracy-inner.nette-Doctrine2Panel { max-width: 1000px } |
302
|
|
|
#tracy-debug .nette-Doctrine2Panel tr table { margin: 8px 0; max-height: 150px; overflow:auto } |
303
|
|
|
#tracy-debug .nette-Doctrine2Panel-cache-green { color: green !important; font-weight: bold } |
304
|
|
|
#tracy-debug .nette-Doctrine2Panel-cache-red { color: red !important; font-weight: bold } |
305
|
|
|
#tracy-debug .nette-Doctrine2Panel h2 { font-size: 23px; } |
306
|
|
|
</style>'; |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
|
310
|
|
|
private function renderPanelCacheStatistics() |
311
|
|
|
{ |
312
|
|
|
if (empty($this->entityManager)) { |
313
|
|
|
return ''; |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
$config = $this->entityManager->getConfiguration(); |
317
|
|
|
if ( ! $config->isSecondLevelCacheEnabled()) { |
318
|
|
|
return ''; |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
$cacheLogger = $config->getSecondLevelCacheConfiguration()->getCacheLogger(); |
322
|
|
|
|
323
|
|
|
if ( ! $cacheLogger instanceof CacheLoggerChain) { |
324
|
|
|
return ''; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
/** @var StatisticsCacheLogger $statistics */ |
328
|
|
|
$statistics = $cacheLogger->getLogger('statistics'); |
329
|
|
|
if ( ! ($statistics)) { |
330
|
|
|
return ''; |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
$cacheDriver = $this->entityManager->getConfiguration()->getMetadataCacheImpl(); |
334
|
|
|
$driverInformation = get_class($cacheDriver); |
335
|
|
|
if ($cacheDriver instanceof RedisCache) { |
|
|
|
|
336
|
|
|
$driverInformation .= ', database: ' . $cacheDriver->getCurrentDatabase(); |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
return '<h2>Second Level Cache</h2> |
340
|
|
|
<table> |
341
|
|
|
<tr> |
342
|
|
|
<td>Driver</td> |
343
|
|
|
<td><strong>' . $driverInformation . '</strong></td> |
344
|
|
|
</tr> |
345
|
|
|
<tr> |
346
|
|
|
<td>Cache hits</td> |
347
|
|
|
<td> |
348
|
|
|
<strong class="nette-Doctrine2Panel-cache-green">' . $statistics->getHitCount() . '</strong> |
349
|
|
|
</td> |
350
|
|
|
</tr> |
351
|
|
|
<tr> |
352
|
|
|
<td>Cache misses</td> |
353
|
|
|
<td> |
354
|
|
|
<strong class="nette-Doctrine2Panel-cache-red">' . $statistics->getMissCount() . '</strong> |
355
|
|
|
</td> |
356
|
|
|
</tr> |
357
|
|
|
<tr> |
358
|
|
|
<td>Cache puts</td> |
359
|
|
|
<td> |
360
|
|
|
<strong class="nette-Doctrine2Panel-cache-red">' . $statistics->getPutCount() . '</strong> |
361
|
|
|
</td> |
362
|
|
|
</tr> |
363
|
|
|
</table>'; |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
|
367
|
|
|
/** |
368
|
|
|
* @return array |
369
|
|
|
*/ |
370
|
|
|
protected function groupQueries(array $queries) |
371
|
|
|
{ |
372
|
|
|
$indexed = []; |
373
|
|
|
foreach ($queries as $query) { |
374
|
|
|
$indexed[$query[self::DATA_INDEX_SQL]][] = $query; |
375
|
|
|
} |
376
|
|
|
$grouped = []; |
377
|
|
|
foreach ($indexed as $item) { |
378
|
|
|
if (count($item) === 1) { |
379
|
|
|
$grouped[] = $item[0]; |
380
|
|
|
continue; |
381
|
|
|
} |
382
|
|
|
$groupedItem = $item[0]; |
383
|
|
|
$times = $params = $traces = []; |
384
|
|
|
foreach ($item as $subItem) { |
385
|
|
|
$times[] = $subItem[self::DATA_INDEX_TIME]; |
386
|
|
|
$params[] = $subItem[self::DATA_INDEX_PARAMS]; |
387
|
|
|
$traces[] = $subItem[self::DATA_INDEX_TRACE]; |
388
|
|
|
} |
389
|
|
|
$groupedItem[self::DATA_INDEX_TIME] = array_sum($times); |
390
|
|
|
$groupedItem[self::DATA_INDEX_PARAMS] = $params; |
391
|
|
|
$groupedItem[self::DATA_INDEX_TRACE] = $traces; |
392
|
|
|
$groupedItem[self::DATA_INDEX_COUNT] = count($times); |
393
|
|
|
$grouped[] = $groupedItem; |
394
|
|
|
} |
395
|
|
|
return $grouped; |
396
|
|
|
} |
397
|
|
|
|
398
|
|
|
|
399
|
|
|
/** |
400
|
|
|
* @param array $queries |
401
|
|
|
* @param string $key |
402
|
|
|
*/ |
403
|
|
|
protected function sortQueries(array &$queries, $key) |
404
|
|
|
{ |
405
|
|
|
uasort( |
406
|
|
|
$queries, |
407
|
|
|
function ($a, $b) use ($key) { |
408
|
|
|
if ($a[$key] === $b[$key]) { |
409
|
|
|
return 0; |
410
|
|
|
} |
411
|
|
|
return ($a[$key] > $b[$key]) ? -1 : 1; |
412
|
|
|
} |
413
|
|
|
); |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
} |
417
|
|
|
|
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:If that code throws an exception and the
EntityManager
is closed. Any other code which depends on the same instance of theEntityManager
during this request will fail.On the other hand, if you instead inject the
ManagerRegistry
, thegetManager()
method guarantees that you will always get a usable manager instance.