DatabaseCollector::collectData()   F
last analyzed

Complexity

Conditions 19
Paths 648

Size

Total Lines 117
Code Lines 79

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 19
eloc 79
c 3
b 0
f 0
nc 648
nop 1
dl 0
loc 117
rs 0.8388

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
        if (isset($data['show_db']) && $data['show_db']) {
48
            $showDbWarning = DebugBar::config()->get('show_db_warning');
49
            $messages = DebugBar::getMessageCollector();
50
            if ($messages && $showDbWarning) {
51
                $messages->addMessage(
52
                    "Consider enabling `optimistic_connect` to avoid an extra query. You can turn this warning off by setting show_db_warning = false",
53
                    'warning',
54
                    true
55
                );
56
            }
57
        }
58
59
        if (isset($data['has_unclosed_transaction']) && $data['has_unclosed_transaction']) {
60
            $messages = DebugBar::getMessageCollector();
61
            if ($messages) {
62
                $messages->addMessage(
63
                    "There is an unclosed transaction on your page and some statement may not be persisted to the database",
64
                    'warning',
65
                    true
66
                );
67
            }
68
        }
69
70
        return $data;
71
    }
72
73
    /**
74
     * Explode comma separated elements not within parenthesis or quotes
75
     *
76
     * @param string $str
77
     * @return array
78
     */
79
    protected static function explodeFields($str)
80
    {
81
        return preg_split("/(?![^(]*\)),/", $str);
82
    }
83
84
    /**
85
     * Collects data
86
     *
87
     * @param TimeDataCollector $timeCollector
88
     * @return array
89
     */
90
    protected function collectData(?TimeDataCollector $timeCollector = null)
91
    {
92
        $stmts = [];
93
94
        $total_duration = 0;
95
        $total_mem = 0;
96
97
        $failed = 0;
98
99
        $i = 0;
100
101
        // Get queries gathered by proxy
102
        $queries = ProxyDBExtension::getQueries();
103
104
        $limit = DebugBar::config()->get('query_limit');
105
        $warnDurationThreshold = DebugBar::config()->get('warn_dbqueries_threshold_seconds');
106
107
        // only show db if there is more than one database in use
108
        $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...
109
            return $stmt['database'];
110
        }, $queries)))) > 1;
111
112
        $showDb = false;
113
        $hasUnclosedTransaction = false;
114
        foreach ($queries as $stmt) {
115
            $i++;
116
117
            $total_duration += $stmt['duration'];
118
            $total_mem += $stmt['memory'];
119
120
            if (!$stmt['success']) {
121
                $failed++;
122
            }
123
124
            if (str_starts_with((string) $stmt['short_query'], 'SHOW DATABASES LIKE')) {
125
                $showDb = true;
126
            }
127
128
            if (str_contains((string) $stmt['short_query'], 'START TRANSACTION')) {
129
                $hasUnclosedTransaction = true;
130
            }
131
            if (str_contains((string) $stmt['short_query'], 'END TRANSACTION')) {
132
                $hasUnclosedTransaction = false;
133
            }
134
135
            if ($limit && $i > $limit) {
136
                $stmts[] = [
137
                    'sql' => "Only the first $limit queries are shown"
138
                ];
139
                break;
140
            }
141
142
            $stmts[] = [
143
                'sql' => $stmt['short_query'],
144
                'row_count' => $stmt['rows'],
145
                'params' => $stmt['select'] ? $stmt['select'] : null,
146
                'duration' => $stmt['duration'],
147
                'duration_str' => $this->getDataFormatter()->formatDuration($stmt['duration']),
148
                'memory' => $stmt['memory'],
149
                'memory_str' => $this->getDataFormatter()->formatBytes($stmt['memory']),
150
                'is_success' => $stmt['success'],
151
                'database' => $showDb ? $stmt['database'] : null,
152
                'source' => $stmt['source'],
153
                'warn' => $stmt['duration'] > $warnDurationThreshold
154
            ];
155
156
            if ($timeCollector !== null) {
157
                $timeCollector->addMeasure(
158
                    $stmt['short_query'],
159
                    $stmt['start_time'],
160
                    $stmt['end_time']
161
                );
162
            }
163
        }
164
165
        // Save as CSV
166
        $db_save_csv = DebugBar::config()->get('db_save_csv');
167
        if ($db_save_csv && !empty($queries)) {
168
            $filename = date('Ymd_His') . '_' . count($queries) . '_' . uniqid() . '.csv';
169
            $isOutput = false;
170
            if (isset($_REQUEST['downloadqueries']) && Director::isDev()) {
171
                $isOutput = true;
172
                if (headers_sent()) {
173
                    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...
174
                }
175
                header('Content-Type: text/csv');
176
                header('Content-Disposition: attachment;filename=' . $filename);
177
                $fp = fopen('php://output', 'w');
178
            } else {
179
                $tempFolder = TEMP_FOLDER . '/debugbar/db';
180
                if (!is_dir($tempFolder)) {
181
                    mkdir($tempFolder, 0755, true);
182
                }
183
                $fp = fopen($tempFolder . '/' . $filename, 'w');
184
            }
185
            $headers = array_keys($queries[0]);
186
            fputcsv($fp, $headers);
187
            foreach ($queries as $query) {
188
                fputcsv($fp, $query);
189
            }
190
            fclose($fp);
191
192
            if ($isOutput) {
193
                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...
194
            }
195
        }
196
197
        return [
198
            'nb_statements' => count($queries),
199
            'nb_failed_statements' => $failed,
200
            'show_db' => $showDb,
201
            'has_unclosed_transaction' => $hasUnclosedTransaction,
202
            'statements' => $stmts,
203
            'accumulated_duration' => $total_duration,
204
            'accumulated_duration_str' => $this->getDataFormatter()->formatDuration($total_duration),
205
            'memory_usage' => $total_mem,
206
            'memory_usage_str' => $this->getDataFormatter()->formatBytes($total_mem),
207
        ];
208
    }
209
210
    /**
211
     * @return string
212
     */
213
    public function getName()
214
    {
215
        return 'db';
216
    }
217
218
    /**
219
     * @return array
220
     */
221
    public function getWidgets()
222
    {
223
        return [
224
            "database" => [
225
                "icon" => "database",
226
                "widget" => "PhpDebugBar.Widgets.SQLQueriesWidget",
227
                "map" => "db",
228
                "default" => "[]"
229
            ],
230
            "database:badge" => [
231
                "map" => "db.nb_statements",
232
                "default" => 0
233
            ]
234
        ];
235
    }
236
237
    /**
238
     * @return array
239
     */
240
    public function getAssets()
241
    {
242
        return [
243
            'base_path' => '/' . DebugBar::moduleResource('javascript')->getRelativePath(),
244
            'base_url' => Director::makeRelative(DebugBar::moduleResource('javascript')->getURL()),
245
            'css' => 'sqlqueries/widget.css',
246
            'js' => 'sqlqueries/widget.js'
247
        ];
248
    }
249
}
250