This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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); |
||
0 ignored issues
–
show
|
|||
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 |
Instead of embedding dynamic parameters in SQL, Doctrine also allows you to pass them separately and insert a placeholder instead: