Passed
Push — master ( f9bd37...d5f0ab )
by Thomas
02:37
created

DatabaseCollector::collectData()   D

Complexity

Conditions 17
Paths 168

Size

Total Lines 108
Code Lines 73

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 17
eloc 73
c 2
b 0
f 0
nc 168
nop 1
dl 0
loc 108
rs 4.65

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 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) {
0 ignored issues
show
Unused Code introduced by
The assignment to $showDb is dead and can be removed.
Loading history...
86
            return $stmt['database'];
87
        }, $queries)))) > 1;
88
89
        $showDb = false;
90
        foreach ($queries as $stmt) {
91
            $i++;
92
93
            $total_duration += $stmt['duration'];
94
            $total_mem += $stmt['memory'];
95
96
            if (!$stmt['success']) {
97
                $failed++;
98
            }
99
100
            if (str_starts_with($stmt['short_query'], 'SHOW DATABASES LIKE')) {
101
                $showDb = true;
102
            }
103
104
            if ($limit && $i > $limit) {
105
                $stmts[] = array(
106
                    'sql' => "Only the first $limit queries are shown"
107
                );
108
                break;
109
            }
110
111
            $stmts[] = array(
112
                'sql' => $stmt['short_query'],
113
                'row_count' => $stmt['rows'],
114
                'params' => $stmt['select'] ? $stmt['select'] : null,
115
                'duration' => $stmt['duration'],
116
                'duration_str' => $this->getDataFormatter()->formatDuration($stmt['duration']),
117
                'memory' => $stmt['memory'],
118
                'memory_str' => $this->getDataFormatter()->formatBytes($stmt['memory']),
119
                'is_success' => $stmt['success'],
120
                'database' => $showDb ? $stmt['database'] : null,
121
                'source' => $stmt['source'],
122
                'warn' => $stmt['duration'] > $warnDurationThreshold
123
            );
124
125
            if ($timeCollector !== null) {
126
                $timeCollector->addMeasure(
127
                    $stmt['short_query'],
128
                    $stmt['start_time'],
129
                    $stmt['end_time']
130
                );
131
            }
132
        }
133
134
        // Save as CSV
135
        $db_save_csv = DebugBar::config()->get('db_save_csv');
136
        if ($db_save_csv && !empty($queries)) {
137
            $filename = date('Ymd_His') . '_' . count($queries) . '_' . uniqid() . '.csv';
138
            $isOutput = false;
139
            if (isset($_REQUEST['downloadqueries']) && Director::isDev()) {
140
                $isOutput = true;
141
                if (headers_sent()) {
142
                    die('Cannot download queries, headers are already sent');
0 ignored issues
show
Best Practice introduced by
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...
143
                }
144
                header('Content-Type: text/csv');
145
                header('Content-Disposition: attachment;filename=' . $filename);
146
                $fp = fopen('php://output', 'w');
147
            } else {
148
                $tempFolder = TEMP_FOLDER . '/debugbar/db';
149
                if (!is_dir($tempFolder)) {
150
                    mkdir($tempFolder, 0755, true);
151
                }
152
                $fp = fopen($tempFolder . '/' . $filename, 'w');
153
            }
154
            $headers = array_keys($queries[0]);
155
            fputcsv($fp, $headers);
156
            foreach ($queries as $query) {
157
                fputcsv($fp, $query);
158
            }
159
            fclose($fp);
160
161
            if ($isOutput) {
162
                die();
0 ignored issues
show
Best Practice introduced by
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...
163
            }
164
        }
165
166
        return array(
167
            'nb_statements' => count($queries),
168
            'nb_failed_statements' => $failed,
169
            'show_db' => $showDb,
170
            'statements' => $stmts,
171
            'accumulated_duration' => $total_duration,
172
            'accumulated_duration_str' => $this->getDataFormatter()->formatDuration($total_duration),
173
            'memory_usage' => $total_mem,
174
            'memory_usage_str' => $this->getDataFormatter()->formatBytes($total_mem),
175
        );
176
    }
177
178
    /**
179
     * @return string
180
     */
181
    public function getName()
182
    {
183
        return 'db';
184
    }
185
186
    /**
187
     * @return array
188
     */
189
    public function getWidgets()
190
    {
191
        return array(
192
            "database" => array(
193
                "icon" => "database",
194
                "widget" => "PhpDebugBar.Widgets.SQLQueriesWidget",
195
                "map" => "db",
196
                "default" => "[]"
197
            ),
198
            "database:badge" => array(
199
                "map" => "db.nb_statements",
200
                "default" => 0
201
            )
202
        );
203
    }
204
205
    /**
206
     * @return array
207
     */
208
    public function getAssets()
209
    {
210
        return array(
211
            'base_path' => '/' . DebugBar::moduleResource('javascript')->getRelativePath(),
212
            'base_url' => Director::makeRelative(DebugBar::moduleResource('javascript')->getURL()),
213
            'css' => 'sqlqueries/widget.css',
214
            'js' => 'sqlqueries/widget.js'
215
        );
216
    }
217
}
218