SqlDumper::default()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
namespace Cerbero\SqlDumper;
4
5
use Cerbero\SqlDumper\Dumpers\ConsoleDumper;
6
use Cerbero\SqlDumper\Dumpers\DumperInterface;
7
use Cerbero\SqlDumper\Dumpers\EmailDumper;
8
use Cerbero\SqlDumper\Dumpers\HtmlDumper;
9
use Cerbero\SqlDumper\Dumpers\LogDumper;
10
use Cerbero\SqlDumper\Dumpers\MarkdownDumper;
11
use Illuminate\Container\Container;
12
use Illuminate\Contracts\Events\Dispatcher;
13
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
14
use Illuminate\Database\Eloquent\Model;
15
use Illuminate\Database\Events\QueryExecuted;
16
use Illuminate\Database\Query\Builder;
17
use Illuminate\Support\Facades\DB;
18
use Illuminate\Support\Str;
19
20
/**
21
 * The SQL queries dumper.
22
 *
23
 */
24
class SqlDumper
25
{
26
    /**
27
     * The dumper.
28
     *
29
     * @var DumperInterface
30
     */
31
    protected $dumper;
32
33
    /**
34
     * The total time spent to run queries.
35
     *
36
     * @var int
37
     */
38
    protected $totalTimeInMs = 0;
39
40
    /**
41
     * Set the dependencies.
42
     *
43
     * @param DumperInterface $dumper
44
     */
45 30
    public function __construct(DumperInterface $dumper)
46
    {
47 30
        $this->dumper = $dumper;
48 30
    }
49
50
    /**
51
     * Retrieve the dumper
52
     *
53
     * @return DumperInterface
54
     */
55 3
    public function getDumper(): DumperInterface
56
    {
57 3
        return $this->dumper;
58
    }
59
60
    /**
61
     * Dump all queries run in the callback
62
     *
63
     * @param callable $callback
64
     * @return mixed
65
     */
66 27
    public function dump(callable $callback)
67
    {
68 27
        $dispatcher = DB::getEventDispatcher();
69
70 27
        DB::setEventDispatcher($this->getListeningDispatcher());
71
72 27
        $result = $callback();
73
74 27
        DB::setEventDispatcher($dispatcher);
75
76 27
        $this->dumper->addTotalTime($this->totalTimeInMs)->dump();
77 24
        $this->totalTimeInMs = 0;
78
79 24
        return $result;
80
    }
81
82
    /**
83
     * Retrieve the event dispatcher with a listener for SQL queries
84
     *
85
     * @return Dispatcher
86
     */
87 27
    protected function getListeningDispatcher(): Dispatcher
88
    {
89 27
        $dispatcher = clone DB::getEventDispatcher();
90
91
        $dispatcher->listen(QueryExecuted::class, function (QueryExecuted $event) {
92 27
            $frame = $this->getCallerFrame();
93 27
            $this->totalTimeInMs += $event->time;
94 27
            $query = $this->getQueryFromEvent($event);
95 27
            $dispatcher = $event->connection->getEventDispatcher();
96
97 27
            $event->connection->unsetEventDispatcher();
98 27
            $explanationRows = $event->connection->select('EXPLAIN ' . $event->sql, $event->bindings);
99 27
            $event->connection->setEventDispatcher($dispatcher);
100
101 27
            $this->dumper->addQuery($query)->addTime($event->time);
102
103 27
            if (isset($frame['file'], $frame['line'])) {
104 27
                $this->dumper->addCaller($frame['file'], $frame['line']);
105
            }
106
107 27
            $this->dumper->addExplanations($explanationRows)->addSeparator();
108 27
        });
109
110 27
        return $dispatcher;
111
    }
112
113
    /**
114
     * Retrieve the query caller frame from the backtrace
115
     *
116
     * @return array
117
     */
118 27
    protected function getCallerFrame(): array
119
    {
120 27
        $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
121 27
        $queryCallers = [Model::class, Builder::class, EloquentBuilder::class];
122
123 27
        for ($i = count($backtrace) - 1; $i > 0; $i--) {
124 27
            if (in_array($backtrace[$i]['class'] ?? null, $queryCallers)) {
125 27
                return $backtrace[$i];
126
            }
127
        }
128
    }
129
130
    /**
131
     * Retrieve the SQL query from the given event
132
     *
133
     * @param QueryExecuted $event
134
     * @return string
135
     */
136 27
    protected function getQueryFromEvent(QueryExecuted $event): string
137
    {
138
        $bindings = array_map(function ($binding) {
139 6
            return is_numeric($binding) ? $binding : "\"{$binding}\"";
140 27
        }, $event->bindings);
141
142 27
        return Str::replaceArray('?', $bindings, $event->sql);
143
    }
144
145
    /**
146
     * Dump SQL queries via the default dumper
147
     *
148
     * @param callable $callback
149
     * @return mixed
150
     */
151 6
    public static function default(callable $callback)
152
    {
153 6
        return Container::getInstance()->make(static::class)->dump($callback);
154
    }
155
156
    /**
157
     * Dump SQL queries via the console dumper
158
     *
159
     * @param callable $callback
160
     * @return mixed
161
     */
162 3
    public static function console(callable $callback)
163
    {
164 3
        return (new static(new ConsoleDumper()))->dump($callback);
165
    }
166
167
    /**
168
     * Dump SQL queries via the email dumper
169
     *
170
     * @param callable $callback
171
     * @return mixed
172
     */
173 6
    public static function email(callable $callback)
174
    {
175 6
        return (new static(new EmailDumper(new HtmlDumper())))->dump($callback);
176
    }
177
178
    /**
179
     * Dump SQL queries via the html dumper
180
     *
181
     * @param callable $callback
182
     * @return mixed
183
     */
184 3
    public static function html(callable $callback)
185
    {
186 3
        return (new static(new HtmlDumper()))->dump($callback);
187
    }
188
189
    /**
190
     * Dump SQL queries via the log dumper
191
     *
192
     * @param callable $callback
193
     * @return mixed
194
     */
195 6
    public static function log(callable $callback)
196
    {
197 6
        return (new static(new LogDumper()))->dump($callback);
198
    }
199
200
    /**
201
     * Dump SQL queries via the markdown dumper
202
     *
203
     * @param callable $callback
204
     * @return mixed
205
     */
206 3
    public static function markdown(callable $callback)
207
    {
208 3
        return (new static(new MarkdownDumper()))->dump($callback);
209
    }
210
}
211