Issues (49)

code/Collector/DatabaseCollector.php (2 issues)

Severity
1
<?php
2
3
namespace LeKoala\DebugBar\Collector;
4
5
use LeKoala\DebugBar\DebugBar;
6
use SilverStripe\Control\Director;
7
use DebugBar\DataCollector\Renderable;
8
use DebugBar\DataCollector\AssetProvider;
9
use DebugBar\DataCollector\DataCollector;
10
use LeKoala\DebugBar\Extension\ProxyDBExtension;
11
12
/**
13
 * Collects data about SQL statements executed through the proxied behaviour
14
 */
15
class DatabaseCollector extends DataCollector implements Renderable, AssetProvider
16
{
17
    /**
18
     * @var TimeDataCollector
19
     */
20
    protected $timeCollector;
21
22
    /**
23
     * @return array
24
     */
25
    public function collect()
26
    {
27
        $this->timeCollector = DebugBar::getDebugBar()->getCollector('time');
28
29
        $data = $this->collectData($this->timeCollector);
30
31
        // Check for excessive number of queries
32
        $dbQueryWarningLevel = DebugBar::config()->get('warn_query_limit');
33
        if ($dbQueryWarningLevel && $data['nb_statements'] > $dbQueryWarningLevel) {
34
            $helpLink = DebugBar::config()->get('performance_guide_link');
35
            $messages = DebugBar::getMessageCollector();
36
            if ($messages) {
37
                $messages->addMessage(
38
                    "This page ran more than $dbQueryWarningLevel database queries." .
39
                        "\nYou could reduce this by implementing caching." .
40
                        "\nYou can find more info here: $helpLink",
41
                    'warning',
42
                    true
43
                );
44
            }
45
        }
46
47
        return $data;
48
    }
49
50
    /**
51
     * Explode comma separated elements not within parenthesis or quotes
52
     *
53
     * @param string $str
54
     * @return array
55
     */
56
    protected static function explodeFields($str)
57
    {
58
        return preg_split("/(?![^(]*\)),/", $str);
59
    }
60
61
    /**
62
     * Collects data
63
     *
64
     * @param TimeDataCollector $timeCollector
65
     * @return array
66
     */
67
    protected function collectData(TimeDataCollector $timeCollector = null)
68
    {
69
        $stmts = array();
70
71
        $total_duration = 0;
72
        $total_mem = 0;
73
74
        $failed = 0;
75
76
        $i = 0;
77
78
        // Get queries gathered by proxy
79
        $queries = ProxyDBExtension::getQueries();
80
81
        $limit = DebugBar::config()->get('query_limit');
82
        $warnDurationThreshold = DebugBar::config()->get('warn_dbqueries_threshold_seconds');
83
84
        // only show db if there is more than one database in use
85
        $showDb = count(array_filter(array_unique(array_map(function ($stmt) {
86
            return $stmt['database'];
87
        }, $queries)))) > 1;
88
89
        foreach ($queries as $stmt) {
90
            $i++;
91
92
            $total_duration += $stmt['duration'];
93
            $total_mem += $stmt['memory'];
94
95
            if (!$stmt['success']) {
96
                $failed++;
97
            }
98
99
            if ($limit && $i > $limit) {
100
                $stmts[] = array(
101
                    'sql' => "Only the first $limit queries are shown"
102
                );
103
                break;
104
            }
105
106
            $stmts[] = array(
107
                'sql' => $stmt['short_query'],
108
                'row_count' => $stmt['rows'],
109
                'params' => $stmt['select'] ? $stmt['select'] : null,
110
                'duration' => $stmt['duration'],
111
                'duration_str' => $this->getDataFormatter()->formatDuration($stmt['duration']),
112
                'memory' => $stmt['memory'],
113
                'memory_str' => $this->getDataFormatter()->formatBytes($stmt['memory']),
114
                'is_success' => $stmt['success'],
115
                'database' => $showDb ? $stmt['database'] : null,
116
                'source' => $stmt['source'],
117
                'warn' => $stmt['duration'] > $warnDurationThreshold
118
            );
119
120
            if ($timeCollector !== null) {
121
                $timeCollector->addMeasure(
122
                    $stmt['short_query'],
123
                    $stmt['start_time'],
124
                    $stmt['end_time']
125
                );
126
            }
127
        }
128
129
        // Save as CSV
130
        $db_save_csv = DebugBar::config()->get('db_save_csv');
131
        if ($db_save_csv && !empty($queries)) {
132
            $filename = date('Ymd_His') . '_' . count($queries) . '_' . uniqid() . '.csv';
133
            $isOutput = false;
134
            if (isset($_REQUEST['downloadqueries']) && Director::isDev()) {
135
                $isOutput = true;
136
                if (headers_sent()) {
137
                    die('Cannot download queries, headers are already sent');
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
138
                }
139
                header('Content-Type: text/csv');
140
                header('Content-Disposition: attachment;filename=' . $filename);
141
                $fp = fopen('php://output', 'w');
142
            } else {
143
                $tempFolder = TEMP_FOLDER . '/debugbar/db';
144
                if (!is_dir($tempFolder)) {
145
                    mkdir($tempFolder, 0755, true);
146
                }
147
                $fp = fopen($tempFolder . '/' . $filename, 'w');
148
            }
149
            $headers = array_keys($queries[0]);
150
            fputcsv($fp, $headers);
151
            foreach ($queries as $query) {
152
                fputcsv($fp, $query);
153
            }
154
            fclose($fp);
155
156
            if ($isOutput) {
157
                die();
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
158
            }
159
        }
160
161
        return array(
162
            'nb_statements' => count($queries),
163
            'nb_failed_statements' => $failed,
164
            'statements' => $stmts,
165
            'accumulated_duration' => $total_duration,
166
            'accumulated_duration_str' => $this->getDataFormatter()->formatDuration($total_duration),
167
            'memory_usage' => $total_mem,
168
            'memory_usage_str' => $this->getDataFormatter()->formatBytes($total_mem),
169
        );
170
    }
171
172
    /**
173
     * @return string
174
     */
175
    public function getName()
176
    {
177
        return 'db';
178
    }
179
180
    /**
181
     * @return array
182
     */
183
    public function getWidgets()
184
    {
185
        return array(
186
            "database" => array(
187
                "icon" => "database",
188
                "widget" => "PhpDebugBar.Widgets.SQLQueriesWidget",
189
                "map" => "db",
190
                "default" => "[]"
191
            ),
192
            "database:badge" => array(
193
                "map" => "db.nb_statements",
194
                "default" => 0
195
            )
196
        );
197
    }
198
199
    /**
200
     * @return array
201
     */
202
    public function getAssets()
203
    {
204
        return array(
205
            'base_path' => '/' . DebugBar::moduleResource('javascript')->getRelativePath(),
206
            'base_url' => Director::makeRelative(DebugBar::moduleResource('javascript')->getURL()),
207
            'css' => 'sqlqueries/widget.css',
208
            'js' => 'sqlqueries/widget.js'
209
        );
210
    }
211
}
212