ApiDebugger::checkNPlusOne()   A
last analyzed

Complexity

Conditions 5
Paths 9

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 14
c 1
b 0
f 0
nc 9
nop 1
dl 0
loc 24
rs 9.4888
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ka4ivan\ApiDebugger\Support;
6
7
use Illuminate\Http\Request;
0 ignored issues
show
Bug introduced by
The type Illuminate\Http\Request was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Facades\DB;
10
11
class ApiDebugger
12
{
13
    /**
14
     * Check if the debugger is active based on APP_DEBUG environment variable.
15
     *
16
     * @return bool
17
     */
18
    public function isActive(): bool
19
    {
20
        return (bool) env('APP_DEBUG');
21
    }
22
23
    /**
24
     * Start debugging by enabling query logging if the debugger is active.
25
     *
26
     * @return void
27
     */
28
    public function startDebug(): void
29
    {
30
        if ($this->isActive()) {
31
            DB::enableQueryLog();
32
        }
33
    }
34
35
    /**
36
     * Get the debugging information including request data and queries executed.
37
     *
38
     * @param Request $request
39
     * @return array
40
     */
41
    public function getDebug(Request $request): array
42
    {
43
        return [
44
            'debugger' => [
45
                'queries' => $this->getQueriesInfo(),
46
                'request' => $this->getRequestInfo($request),
47
            ],
48
        ];
49
    }
50
51
    /**
52
     * Retrieve information about the request such as body and headers.
53
     *
54
     * @param Request $request
55
     * @return array
56
     */
57
    protected function getRequestInfo(Request $request): array
58
    {
59
        return [
60
            'body' => Arr::except($request->input(), ['password', 'confirm_password', 'password_confirmation', '_destination', '_method', '_token', '_modal', 'destination']),
61
            'headers' => $request->header(),
62
        ];
63
    }
64
65
    /**
66
     * Retrieve the query log information, including count, executed queries, and checks for N+1 and long queries.
67
     *
68
     * @return array
69
     */
70
    protected function getQueriesInfo(): array
71
    {
72
        $queries = DB::getQueryLog();
73
        $totalTime = round(array_reduce($queries, fn ($carry, $query) => $carry + $query['time'], 0), 2);
74
75
        $longQueries = $this->checkLongQueries($queries);
76
        $nPlusOneIssues = $this->checkNPlusOne($queries);
77
78
        return [
79
            'count' => count($queries),
80
            'time' => $totalTime,
81
            'data' => $queries,
82
            'long_queries' => $longQueries,
83
            'n_plus_one' => $nPlusOneIssues,
84
        ];
85
    }
86
87
    /**
88
     * Check for long queries (taking longer than a specified threshold).
89
     *
90
     * @param array $queries
91
     * @param float $threshold
92
     * @return array
93
     */
94
    protected function checkLongQueries(array $queries, float $threshold = 10.0): array
95
    {
96
        $longQueries = [];
97
98
        foreach ($queries as $query) {
99
            if ($query['time'] > $threshold) {
100
                $longQueries[] = $query;
101
            }
102
        }
103
104
        return $longQueries;
105
    }
106
107
    /**
108
     * Check for N+1 query issues.
109
     *
110
     * This method compares queries to detect repeating queries with similar structures.
111
     *
112
     * @param array $queries
113
     * @return array
114
     */
115
    protected function checkNPlusOne(array $queries): array
116
    {
117
        $queryCounts = [];
118
        $nPlusOneIssues = [];
119
120
        foreach ($queries as $query) {
121
            $queryKey = $query['query'];
122
            if (isset($queryCounts[$queryKey])) {
123
                $queryCounts[$queryKey]++;
124
            } else {
125
                $queryCounts[$queryKey] = 1;
126
            }
127
        }
128
129
        foreach ($queryCounts as $queryKey => $count) {
130
            if ($count > 1) {
131
                $nPlusOneIssues[] = [
132
                    'query' => $queryKey,
133
                    'count' => $count,
134
                ];
135
            }
136
        }
137
138
        return $nPlusOneIssues;
139
    }
140
}
141