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 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
|
|||
324 | } |
||
325 | |||
326 | } |
||
327 |
If an expression can have both
false
, andnull
as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.