Issues (6)

src/QueryDiagnosisImpl.php (6 issues)

1
<?php
2
3
namespace SamAsEnd\QueryDiagnosis;
4
5
use Illuminate\Database\Events\QueryExecuted;
6
use Illuminate\Support\Collection;
7
use Illuminate\Support\Facades\DB;
8
use Illuminate\Support\Str;
9
use SamAsEnd\QueryDiagnosis\Enums\JoinType;
10
use SamAsEnd\QueryDiagnosis\Exceptions\UnsupportedDatabaseDriverException;
11
use SamAsEnd\QueryDiagnosis\Strategies\JoinTypeQueryDiagnosis;
12
use SamAsEnd\QueryDiagnosis\Strategies\QueryDiagnosisContract;
13
use SamAsEnd\QueryDiagnosis\Strategies\SelectTypeContainsQueryDiagnosis;
14
15
class QueryDiagnosisImpl
16
{
17
    private const SELECT = 'select ';
18
19
    private const EXPLAIN = 'explain ';
20
21
    private const UNION = 'UNION';
22
23
    protected readonly Collection $queryDiagnoses;
24
25
    protected readonly Collection $queriesCache;
26
27
    protected bool $booted = false;
28
29 5
    public function __construct()
30
    {
31 5
        $this->queryDiagnoses = Collection::make();
0 ignored issues
show
The property queryDiagnoses is declared read-only in SamAsEnd\QueryDiagnosis\QueryDiagnosisImpl.
Loading history...
32 5
        $this->queriesCache = Collection::make();
0 ignored issues
show
The property queriesCache is declared read-only in SamAsEnd\QueryDiagnosis\QueryDiagnosisImpl.
Loading history...
33
34 5
        $this->booted = false;
35
    }
36
37 1
    public function preventUnionSelectTypes(): void
38
    {
39 1
        $this->customQueryDiagnosis(new SelectTypeContainsQueryDiagnosis(self::UNION));
40
    }
41
42 4
    public function preventFullDatabaseScanQueries(): void
43
    {
44 4
        $this->customQueryDiagnosis(new JoinTypeQueryDiagnosis(JoinType::ALL));
45
    }
46
47
    public function preventFullIndexScanQueries(): void
48
    {
49
        $this->customQueryDiagnosis(new JoinTypeQueryDiagnosis(JoinType::INDEX));
50
    }
51
52 5
    public function customQueryDiagnosis(QueryDiagnosisContract $queryDiagnosis): void
53
    {
54 5
        $this->boot();
55
56 5
        $this->queryDiagnoses->push($queryDiagnosis);
57
    }
58
59 5
    protected function boot(): void
60
    {
61 5
        if (! $this->booted) {
62 5
            $this->booted = true;
63
64 5
            DB::listen(function (QueryExecuted $executedQuery) {
65 5
                $query = Str::of($executedQuery->sql)
66 5
                    ->lower()
67 5
                    ->trim('()') // laravel sometime group the queries as (select...)
68 5
                    ->trim()
69 5
                    ->value();
70
71 5
                if (static::shouldBeDiagnosed($query)) {
0 ignored issues
show
Bug Best Practice introduced by
The method SamAsEnd\QueryDiagnosis\...pl::shouldBeDiagnosed() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

71
                if (static::/** @scrutinizer ignore-call */ shouldBeDiagnosed($query)) {
Loading history...
72 5
                    $this->queriesCache->push($query);
0 ignored issues
show
$query of type string is incompatible with the type Illuminate\Support\TValue expected by parameter $values of Illuminate\Support\Collection::push(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

72
                    $this->queriesCache->push(/** @scrutinizer ignore-type */ $query);
Loading history...
73 5
                    static::diagnoseQuery($executedQuery);
0 ignored issues
show
Bug Best Practice introduced by
The method SamAsEnd\QueryDiagnosis\...isImpl::diagnoseQuery() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

73
                    static::/** @scrutinizer ignore-call */ 
74
                            diagnoseQuery($executedQuery);
Loading history...
74
                }
75
            });
76
        }
77
    }
78
79 5
    protected function shouldBeDiagnosed(string $query): bool
80
    {
81 5
        return Str::startsWith($query, static::SELECT)
82 5
            && $this->queriesCache->doesntContain($query);
83
    }
84
85 5
    protected function diagnoseQuery(QueryExecuted $queryExecuted): void
86
    {
87 5
        if ('mysql' !== ($driver = $queryExecuted->connection->getDriverName())) {
88 3
            throw new UnsupportedDatabaseDriverException($driver);
89
        }
90
91 2
        $explainResults = collect($queryExecuted->connection->select(
0 ignored issues
show
$queryExecuted->connecti...ueryExecuted->bindings) of type array is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $value of collect(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

91
        $explainResults = collect(/** @scrutinizer ignore-type */ $queryExecuted->connection->select(
Loading history...
92 2
            self::EXPLAIN.$queryExecuted->sql,
93 2
            $queryExecuted->bindings
94
        ));
95
96
        /** @var QueryDiagnosisContract $queryDiagnosis */
97 2
        foreach ($this->queryDiagnoses as $queryDiagnosis) {
98 2
            if ($queryDiagnosis->match($queryExecuted, $explainResults)) {
99 2
                $queryDiagnosis->report($queryExecuted, $explainResults);
100
            }
101
        }
102
    }
103
}
104