Completed
Push — master ( 69a6cf...e97670 )
by recca
05:12 queued 02:01
created

DatabasePanel::hightlight()   C

Complexity

Conditions 16
Paths 1

Size

Total Lines 62
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 16.044

Importance

Changes 0
Metric Value
cc 16
eloc 38
c 0
b 0
f 0
nc 1
nop 3
dl 0
loc 62
ccs 34
cts 36
cp 0.9444
crap 16.044
rs 6.1338

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Recca0120\LaravelTracy\Panels;
4
5
use PDO;
6
use DateTime;
7
use Exception;
8
9
class DatabasePanel extends AbstractSubscriablePanel implements IAjaxPanel
10
{
11
    /**
12
     * $queries.
13
     *
14
     * @var array
15
     */
16
    protected $queries = [];
17
18
    /**
19
     * $totalTime.
20
     *
21
     * @var float
22
     */
23
    protected $totalTime = 0.0;
24
25
    /**
26
     * $counter.
27
     *
28
     * @var int
29
     */
30
    protected $counter = 0;
31
32
    /**
33
     * KEYWORDS1.
34
     *
35
     * @var string
36
     */
37
    const KEYWORDS1 = 'SELECT|(?:ON\s+DUPLICATE\s+KEY)?UPDATE|INSERT(?:\s+INTO)?|REPLACE(?:\s+INTO)?|DELETE|CALL|UNION|FROM|WHERE|HAVING|GROUP\s+BY|ORDER\s+BY|LIMIT|OFFSET|SET|VALUES|LEFT\s+JOIN|INNER\s+JOIN|TRUNCATE';
38
39
    /**
40
     * KEYWORDS2.
41
     *
42
     * @var string
43
     */
44
    const KEYWORDS2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|[RI]?LIKE|REGEXP|TRUE|FALSE';
45
46
    /**
47
     * explain sql.
48
     *
49
     * @param PDO $pdo
50
     * @param string $sql
51
     * @param array $bindings
52
     * @return array
53
     */
54 1
    public static function explain(PDO $pdo, $sql, $bindings = [])
55
    {
56 1
        $explains = [];
57 1
        if (preg_match('#\s*\(?\s*SELECT\s#iA', $sql)) {
58 1
            $statement = $pdo->prepare('EXPLAIN '.$sql);
59 1
            $statement->execute($bindings);
60 1
            $explains = $statement->fetchAll(PDO::FETCH_CLASS);
61 1
        }
62
63 1
        return $explains;
64
    }
65
66
    /**
67
     * Returns syntax highlighted SQL command.
68
     *
69
     *
70
     * @param string $sql
71
     * @param array $bindings
72
     * @param \PDO $pdo
73
     * @return string
74
     */
75 4
    public static function hightlight($sql, array $bindings = [], PDO $pdo = null)
76
    {
77
        // insert new lines
78 4
        $sql = " $sql ";
79 4
        $sql = preg_replace('#(?<=[\\s,(])('.static::KEYWORDS1.')(?=[\\s,)])#i', "\n\$1", $sql);
80
81
        // reduce spaces
82 4
        $sql = preg_replace('#[ \t]{2,}#', ' ', $sql);
83
84
        // syntax highlight
85 4
        $sql = htmlspecialchars($sql, ENT_IGNORE, 'UTF-8');
86
        $sql = preg_replace_callback('#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])('.static::KEYWORDS1.')(?=[\\s,)])|(?<=[\\s,(=])('.static::KEYWORDS2.')(?=[\\s,)=])#is', function ($matches) {
87 4
            if (! empty($matches[1])) { // comment
88 1
                return '<em style="color:gray">'.$matches[1].'</em>';
89 4
            } elseif (! empty($matches[2])) { // error
90 1
                return '<strong style="color:red">'.$matches[2].'</strong>';
91 4
            } elseif (! empty($matches[3])) { // most important keywords
92 4
                return '<strong style="color:blue; text-transform: uppercase;">'.$matches[3].'</strong>';
93 1
            } elseif (! empty($matches[4])) { // other keywords
94 1
                return '<strong style="color:green">'.$matches[4].'</strong>';
95
            }
96 4
        }, $sql);
97
98
        $bindings = array_map(function ($binding) use ($pdo) {
99 3
            if (is_array($binding) === true) {
100
                $binding = implode(', ', array_map(function ($value) {
101 1
                    return is_string($value) === true ? htmlspecialchars('\''.$value.'\'', ENT_NOQUOTES, 'UTF-8') : $value;
102 1
                }, $binding));
103
104 1
                return htmlspecialchars('('.$binding.')', ENT_NOQUOTES, 'UTF-8');
105
            }
106
107 3
            if (is_string($binding) === true && (preg_match('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u', $binding) || preg_last_error())) {
108
                return '<i title="Length '.strlen($binding).' bytes">&lt;binary&gt;</i>';
109
            }
110
111 3
            if (is_string($binding) === true) {
112 3
                $text = htmlspecialchars($pdo ? $pdo->quote($binding) : '\''.$binding.'\'', ENT_NOQUOTES, 'UTF-8');
113
114 3
                return '<span title="Length '.strlen($text).' characters">'.$text.'</span>';
115
            }
116
117 1
            if (is_resource($binding) === true) {
118 1
                $type = get_resource_type($binding);
119 1
                if ($type === 'stream') {
120 1
                    $info = stream_get_meta_data($binding);
121 1
                }
122
123 1
                return '<i'.(isset($info['uri']) ? ' title="'.htmlspecialchars($info['uri'], ENT_NOQUOTES, 'UTF-8').'"' : null)
124 1
                    .'>&lt;'.htmlspecialchars($type, ENT_NOQUOTES, 'UTF-8').' resource&gt;</i>';
125
            }
126
127 1
            if ($binding instanceof DateTime) {
128 1
                return htmlspecialchars('\''.$binding->format('Y-m-d H:i:s').'\'', ENT_NOQUOTES, 'UTF-8');
129
            }
130
131
            return htmlspecialchars($binding, ENT_NOQUOTES, 'UTF-8');
132 4
        }, $bindings);
133 4
        $sql = str_replace(['%', '?'], ['%%', '%s'], $sql);
134
135 4
        return '<div><code>'.nl2br(trim(vsprintf($sql, $bindings))).'</code></div>';
136
    }
137
138
    /**
139
     * perform quer analysis hint.
140
     *
141
     * @param string $sql
142
     * @param string $version
143
     * @param float $driver
144
     * @return array
145
     */
146 2
    public static function performQueryAnalysis($sql, $version = null, $driver = null)
147
    {
148 2
        $hints = [];
149 2
        if (preg_match('/^\\s*SELECT\\s*`?[a-zA-Z0-9]*`?\\.?\\*/i', $sql)) {
150 2
            $hints[] = 'Use <code>SELECT *</code> only if you need all columns from table';
151 2
        }
152 2
        if (preg_match('/ORDER BY RAND()/i', $sql)) {
153 1
            $hints[] = '<code>ORDER BY RAND()</code> is slow, try to avoid if you can.
154
                You can <a href="http://stackoverflow.com/questions/2663710/how-does-mysqls-order-by-rand-work">read this</a>
155
                or <a href="http://stackoverflow.com/questions/1244555/how-can-i-optimize-mysqls-order-by-rand-function">this</a>';
156 1
        }
157 2
        if (strpos($sql, '!=') !== false) {
158 1
            $hints[] = 'The <code>!=</code> operator is not standard. Use the <code>&lt;&gt;</code> operator to test for inequality instead.';
159 1
        }
160 2
        if (stripos($sql, 'WHERE') === false) {
161 1
            $hints[] = 'The <code>SELECT</code> statement has no <code>WHERE</code> clause and could examine many more rows than intended';
162 1
        }
163 2
        if (preg_match('/LIMIT\\s/i', $sql) && stripos($sql, 'ORDER BY') === false) {
164 1
            $hints[] = '<code>LIMIT</code> without <code>ORDER BY</code> causes non-deterministic results, depending on the query execution plan';
165 1
        }
166 2
        if (preg_match('/LIKE\\s[\'"](%.*?)[\'"]/i', $sql, $matches)) {
167 1
            $hints[] = 'An argument has a leading wildcard character: <code>'.$matches[1].'</code>.
168 1
                The predicate with this argument is not sargable and cannot use an index if one exists.';
169 1
        }
170 2
        if ($version < 5.5 && $driver === 'mysql') {
171 2
            if (preg_match('/\\sIN\\s*\\(\\s*SELECT/i', $sql)) {
172 1
                $hints[] = '<code>IN()</code> and <code>NOT IN()</code> subqueries are poorly optimized in that MySQL version : '.$version.
173 1
                    '. MySQL executes the subquery as a dependent subquery for each row in the outer query';
174 1
            }
175 2
        }
176
177 2
        return $hints;
178
    }
179
180
    /**
181
     * subscribe.
182
     */
183 2
    protected function subscribe()
184
    {
185 2
        $events = $this->laravel['events'];
186 2
        if (version_compare($this->laravel->version(), 5.2, '>=') === true) {
187
            $events->listen('Illuminate\Database\Events\QueryExecuted', function ($event) {
188 1
                $this->logQuery(
189 1
                    $event->sql,
190 1
                    $event->bindings,
191 1
                    $event->time,
192 1
                    $event->connectionName,
193 1
                    $event->connection->getPdo()
194 1
                );
195 1
            });
196 1
        } else {
197 1
            $events->listen('illuminate.query', function ($sql, $bindings, $time, $connectionName) {
198 1
                $this->logQuery(
199 1
                    $sql,
200 1
                    $bindings,
201 1
                    $time,
202 1
                    $connectionName,
203 1
                    $this->laravel['db']->connection($connectionName)->getPdo()
204 1
                );
205 1
            });
206
        }
207 2
    }
208
209
    /**
210
     * logQuery.
211
     *
212
     * @param string $sql
213
     * @param array $bindings
214
     * @param int $time
215
     * @param string $name
216
     * @param PDO $pdo
217
     * @param string $driver
218
     * @return $this
219
     */
220 3
    public function logQuery($sql, $bindings = [], $time = 0, $name = null, PDO $pdo = null, $driver = 'mysql')
221
    {
222 3
        ++$this->counter;
223 3
        $this->totalTime += $time;
224 3
        $source = self::findSource();
225 3
        $editorLink = self::editorLink($source);
226 3
        $this->queries[] = [
227 3
            'sql' => $sql,
228 3
            'bindings' => $bindings,
229 3
            'time' => $time,
230 3
            'name' => $name,
231 3
            'pdo' => $pdo,
232 3
            'driver' => $driver,
233 3
            'source' => $source,
234 3
            'editorLink' => $editorLink,
235 3
            'formattedSql' => null,
236 3
            'fullSql' => null,
237
        ];
238
239 3
        return $this;
240
    }
241
242
    /**
243
     * getAttributes.
244
     *
245
     * @return array
246
     */
247 3
    protected function getAttributes()
248
    {
249 3
        $queries = [];
250 3
        foreach ($this->queries as $query) {
251 3
            $sql = $query['sql'];
252 3
            $bindings = $query['bindings'];
253 3
            $pdo = $query['pdo'];
254 3
            $driver = $query['driver'];
255 3
            $version = 0;
256
257 3
            $hightlight = static::hightlight($sql, $bindings, $pdo);
258 3
            $explains = [];
259 3
            $hints = [];
260 3
            if ($pdo instanceof PDO) {
261 2
                $driver = $this->getDatabaseDriver($pdo);
262 2
                if ($driver === 'mysql') {
263 1
                    $version = $this->getDatabaseVersion($pdo);
264 1
                    $explains = static::explain($pdo, $sql, $bindings);
265 1
                    $hints = static::performQueryAnalysis($sql, $version, $driver);
266 1
                }
267 2
            }
268
269 3
            $queries[] = array_merge($query, compact('hightlight', 'explains', 'hints', 'driver', 'version'));
270 3
        }
271
272
        return [
273 3
            'counter' => $this->counter,
274 3
            'totalTime' => $this->totalTime,
275 3
            'queries' => $queries,
276 3
        ];
277
    }
278
279
    /**
280
     * getDatabaseDriver.
281
     *
282
     * @param \PDO $pdo
283
     * @return string
284
     */
285 2
    protected function getDatabaseDriver(PDO $pdo)
286
    {
287
        try {
288 2
            $driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
289 2
        } catch (Exception $e) {
290 1
            $driver = null;
291
        }
292
293 2
        return $driver;
294
    }
295
296
    /**
297
     * getDatabaseVersion.
298
     *
299
     * @param \PDO $pdo
300
     * @return string
301
     */
302 1
    protected function getDatabaseVersion(PDO $pdo)
303
    {
304
        try {
305 1
            $version = $pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
306 1
        } catch (Exception $e) {
307 1
            $version = 0;
308
        }
309
310 1
        return $version;
311
    }
312
}
313