1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Database\Diagnostics;
9:
10: use Nette;
11: use Nette\Database\Helpers;
12:
13:
14: 15: 16: 17: 18:
19: class ConnectionPanel extends Nette\Object implements Nette\Diagnostics\IBarPanel
20: {
21:
22: static public $maxLength;
23:
24:
25: public $maxQueries = 100;
26:
27:
28: private $totalTime = 0;
29:
30:
31: private $count = 0;
32:
33:
34: private $queries = array();
35:
36:
37: public $name;
38:
39:
40: public $explain = TRUE;
41:
42:
43: public $disabled = FALSE;
44:
45:
46: public function __construct(Nette\Database\Connection $connection)
47: {
48: $connection->onQuery[] = array($this, 'logQuery');
49: }
50:
51:
52: public function logQuery(Nette\Database\Connection $connection, $result)
53: {
54: if ($this->disabled) {
55: return;
56: }
57: $this->count++;
58:
59: $source = NULL;
60: $trace = $result instanceof \PDOException ? $result->getTrace() : debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : FALSE);
61: foreach ($trace as $row) {
62: if (isset($row['file']) && is_file($row['file']) && !Nette\Diagnostics\Debugger::getBluescreen()->isCollapsed($row['file'])) {
63: if ((isset($row['function']) && strpos($row['function'], 'call_user_func') === 0)
64: || (isset($row['class']) && is_subclass_of($row['class'], '\\Nette\\Database\\Connection'))
65: ) {
66: continue;
67: }
68: $source = array($row['file'], (int) $row['line']);
69: break;
70: }
71: }
72: if ($result instanceof Nette\Database\ResultSet) {
73: $this->totalTime += $result->getTime();
74: if ($this->count < $this->maxQueries) {
75: $this->queries[] = array($connection, $result->getQueryString(), $result->getParameters(), $source, $result->getTime(), $result->getRowCount(), NULL);
76: }
77:
78: } elseif ($result instanceof \PDOException && $this->count < $this->maxQueries) {
79: $this->queries[] = array($connection, $result->queryString, NULL, $source, NULL, NULL, $result->getMessage());
80: }
81: }
82:
83:
84: public static function renderException($e)
85: {
86: if (!$e instanceof \PDOException) {
87: return;
88: }
89: if (isset($e->queryString)) {
90: $sql = $e->queryString;
91:
92: } elseif ($item = Nette\Diagnostics\Helpers::findTrace($e->getTrace(), 'PDO::prepare')) {
93: $sql = $item['args'][0];
94: }
95: return isset($sql) ? array(
96: 'tab' => 'SQL',
97: 'panel' => Helpers::dumpSql($sql),
98: ) : NULL;
99: }
100:
101:
102: public function getTab()
103: {
104: return '<span title="Nette\\Database ' . htmlSpecialChars($this->name, ENT_QUOTES, 'UTF-8') . '">'
105: . '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAEYSURBVBgZBcHPio5hGAfg6/2+R980k6wmJgsJ5U/ZOAqbSc2GnXOwUg7BESgLUeIQ1GSjLFnMwsKGGg1qxJRmPM97/1zXFAAAAEADdlfZzr26miup2svnelq7d2aYgt3rebl585wN6+K3I1/9fJe7O/uIePP2SypJkiRJ0vMhr55FLCA3zgIAOK9uQ4MS361ZOSX+OrTvkgINSjS/HIvhjxNNFGgQsbSmabohKDNoUGLohsls6BaiQIMSs2FYmnXdUsygQYmumy3Nhi6igwalDEOJEjPKP7CA2aFNK8Bkyy3fdNCg7r9/fW3jgpVJbDmy5+PB2IYp4MXFelQ7izPrhkPHB+P5/PjhD5gCgCenx+VR/dODEwD+A3T7nqbxwf1HAAAAAElFTkSuQmCC" />'
106: . $this->count . ' ' . ($this->count === 1 ? 'query' : 'queries')
107: . ($this->totalTime ? ' / ' . sprintf('%0.1f', $this->totalTime * 1000) . ' ms' : '')
108: . '</span>';
109: }
110:
111:
112: public function getPanel()
113: {
114: $this->disabled = TRUE;
115: $s = '';
116: foreach ($this->queries as $query) {
117: list($connection, $sql, $params, $source, $time, $rows, $error) = $query;
118:
119: $explain = NULL;
120: if (!$error && $this->explain && preg_match('#\s*\(?\s*SELECT\s#iA', $sql)) {
121: try {
122: $cmd = is_string($this->explain) ? $this->explain : 'EXPLAIN';
123: $explain = $connection->queryArgs("$cmd $sql", $params)->fetchAll();
124: } catch (\PDOException $e) {}
125: }
126:
127: $s .= '<tr><td>';
128: if ($error) {
129: $s .= '<span title="' . htmlSpecialChars($error, ENT_IGNORE | ENT_QUOTES, 'UTF-8') . '">ERROR</span>';
130: } elseif ($time !== NULL) {
131: $s .= sprintf('%0.3f', $time * 1000);
132: }
133: if ($explain) {
134: static $counter;
135: $counter++;
136: $s .= "<br /><a class='nette-toggle-collapsed' href='#nette-DbConnectionPanel-row-$counter'>explain</a>";
137: }
138:
139: $s .= '</td><td class="nette-DbConnectionPanel-sql">' . Helpers::dumpSql($sql, $params);
140: if ($explain) {
141: $s .= "<table id='nette-DbConnectionPanel-row-$counter' class='nette-collapsed'><tr>";
142: foreach ($explain[0] as $col => $foo) {
143: $s .= '<th>' . htmlSpecialChars($col, ENT_NOQUOTES, 'UTF-8') . '</th>';
144: }
145: $s .= "</tr>";
146: foreach ($explain as $row) {
147: $s .= "<tr>";
148: foreach ($row as $col) {
149: $s .= '<td>' . htmlSpecialChars($col, ENT_NOQUOTES, 'UTF-8') . '</td>';
150: }
151: $s .= "</tr>";
152: }
153: $s .= "</table>";
154: }
155: if ($source) {
156: $s .= Nette\Diagnostics\Helpers::editorLink($source[0], $source[1])->class('nette-DbConnectionPanel-source');
157: }
158:
159: $s .= '</td><td>' . $rows . '</td></tr>';
160: }
161:
162: return $this->count ?
163: '<style class="nette-debug"> #nette-debug td.nette-DbConnectionPanel-sql { background: white !important }
164: #nette-debug .nette-DbConnectionPanel-source { color: #BBB !important } </style>
165: <h1 title="' . htmlSpecialChars($connection->getDsn(), ENT_QUOTES, 'UTF-8') . '">Queries: ' . $this->count
166: . ($this->totalTime ? ', time: ' . sprintf('%0.3f', $this->totalTime * 1000) . ' ms' : '') . ', ' . htmlSpecialChars($this->name) . '</h1>
167: <div class="nette-inner nette-DbConnectionPanel">
168: <table>
169: <tr><th>Time ms</th><th>SQL Query</th><th>Rows</th></tr>' . $s . '
170: </table>'
171: . (count($this->queries) < $this->count ? '<p>...and more</p>' : '')
172: . '</div>' : '';
173: }
174:
175: }
176: