Skip to content

Commit 71bc49b

Browse files
authored
Merge pull request #18 from webman-tech/fix/17-debugbar-db-connections
fix(debugbar): 修复主动初始化未使用数据库连接
2 parents 9c7e5b3 + 6bdb996 commit 71bc49b

3 files changed

Lines changed: 180 additions & 59 deletions

File tree

packages/debugbar/src/Bootstrap/LaravelQuery.php

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,26 @@
33
namespace WebmanTech\Debugbar\Bootstrap;
44

55
use Illuminate\Database\Capsule\Manager as Capsule;
6+
use Illuminate\Container\Container;
7+
use Illuminate\Contracts\Events\Dispatcher;
68
use support\Db;
9+
use Throwable;
710
use Webman\Bootstrap;
811
use WebmanTech\Debugbar\DataCollector\LaravelQueryCollector;
912
use WebmanTech\Debugbar\DebugBar;
1013

1114
class LaravelQuery implements Bootstrap
1215
{
16+
private static bool $registered = false;
17+
1318
/**
1419
* @inheritDoc
1520
*/
1621
public static function start($worker)
1722
{
18-
if (!class_exists(Capsule::class)) {
19-
return;
20-
}
21-
$connections = array_keys((array)config('database.connections', []));
22-
if ($default = config('database.default')) {
23-
$connections[] = $default;
24-
}
25-
if (!$connections) {
23+
if (!class_exists(Capsule::class) || !class_exists(Db::class)) {
2624
return;
2725
}
28-
$connections = array_unique($connections);
2926

3027
$collectorName = (new LaravelQueryCollector())->getName();
3128
$debugBar = DebugBar::instance();
@@ -36,8 +33,30 @@ public static function start($worker)
3633
/** @var LaravelQueryCollector $collector */
3734
$collector = $debugBar->getCollector($collectorName);
3835

39-
foreach ($connections as $connection) {
40-
$collector->addListener(Db::connection((string)$connection));
36+
self::listenForQueryEvents($collector);
37+
}
38+
39+
private static function listenForQueryEvents(LaravelQueryCollector $collector): void
40+
{
41+
if (self::$registered) {
42+
return;
43+
}
44+
45+
$container = Container::getInstance();
46+
if (!$container->bound('events')) {
47+
return;
48+
}
49+
50+
$dispatcher = $container->make('events');
51+
if (!$dispatcher instanceof Dispatcher) {
52+
return;
53+
}
54+
55+
try {
56+
$collector->addEventDispatcherListener($dispatcher);
57+
self::$registered = true;
58+
} catch (Throwable) {
59+
// Ignore debug collector listener registration errors.
4160
}
4261
}
4362
}

packages/debugbar/src/DataCollector/LaravelQueryCollector.php

Lines changed: 80 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace WebmanTech\Debugbar\DataCollector;
44

55
use DebugBar\DataCollector\TimeDataCollector as DebugBarTimeDataCollector;
6+
use Illuminate\Contracts\Events\Dispatcher;
67
use Illuminate\Database\Connection;
78
use Illuminate\Database\Events\QueryExecuted;
89
use Illuminate\Database\Events\TransactionBeginning;
@@ -149,69 +150,91 @@ public function addListener(Connection $db): void
149150
return;
150151
}
151152

152-
$event->listen(function (QueryExecuted $event) use ($db): void {
153-
$connection = $event->connection;
154-
if (!$this->isEventConnectionEqual($db, $connection)) {
155-
return;
156-
}
157-
$bindings = $event->bindings;
158-
$time = $event->time;
159-
$query = $event->sql;
160-
161-
$threshold = $this->config['slow_threshold'];
162-
if (!$threshold || $time > $threshold) {
163-
if ($collector = $this->getRequestThisCollector()) {
164-
$collector->addQuery($query, $bindings, $time, $connection);
165-
}
166-
}
153+
$this->addEventListeners($event, $db);
154+
}
155+
156+
public function addEventDispatcherListener(Dispatcher $event): void
157+
{
158+
$this->addEventListeners($event);
159+
}
160+
161+
private function addEventListeners(Dispatcher $event, ?Connection $connection = null): void
162+
{
163+
$event->listen(function (QueryExecuted $event) use ($connection): void {
164+
$this->collectQueryEvent($event, $connection);
167165
});
168166

169-
$event->listen(TransactionBeginning::class, function (TransactionBeginning $transaction) use ($db): void {
170-
if (!$this->isEventConnectionEqual($db, $transaction->connection)) {
171-
return;
172-
}
173-
if ($collector = $this->getRequestThisCollector()) {
174-
$collector->collectTransactionEvent('Begin Transaction', $transaction->connection);
175-
}
167+
$event->listen(TransactionBeginning::class, function (TransactionBeginning $transaction) use ($connection): void {
168+
$this->collectTransactionConnectionEvent('Begin Transaction', $transaction->connection, $connection);
176169
});
177170

178-
$event->listen(TransactionCommitted::class, function (TransactionCommitted $transaction) use ($db): void {
179-
if (!$this->isEventConnectionEqual($db, $transaction->connection)) {
180-
return;
181-
}
182-
if ($collector = $this->getRequestThisCollector()) {
183-
$collector->collectTransactionEvent('Commit Transaction', $transaction->connection);
184-
}
171+
$event->listen(TransactionCommitted::class, function (TransactionCommitted $transaction) use ($connection): void {
172+
$this->collectTransactionConnectionEvent('Commit Transaction', $transaction->connection, $connection);
185173
});
186174

187-
$event->listen(TransactionRolledBack::class, function (TransactionRolledBack $transaction) use ($db): void {
188-
if (!$this->isEventConnectionEqual($db, $transaction->connection)) {
189-
return;
190-
}
191-
if ($collector = $this->getRequestThisCollector()) {
192-
$collector->collectTransactionEvent('Rollback Transaction', $transaction->connection);
193-
}
175+
$event->listen(TransactionRolledBack::class, function (TransactionRolledBack $transaction) use ($connection): void {
176+
$this->collectTransactionConnectionEvent('Rollback Transaction', $transaction->connection, $connection);
194177
});
195178

196-
$event->listen('connection.*.beganTransaction', function ($event, $params): void {
197-
if ($collector = $this->getRequestThisCollector()) {
198-
$collector->collectTransactionEvent('Begin Transaction', $params[0]);
199-
}
179+
$event->listen('connection.*.beganTransaction', function ($event, $params) use ($connection): void {
180+
$this->collectWildcardTransactionEvent('Begin Transaction', $params, $connection);
200181
});
201182

202-
$event->listen('connection.*.committed', function ($event, $params): void {
203-
if ($collector = $this->getRequestThisCollector()) {
204-
$collector->collectTransactionEvent('Commit Transaction', $params[0]);
205-
}
183+
$event->listen('connection.*.committed', function ($event, $params) use ($connection): void {
184+
$this->collectWildcardTransactionEvent('Commit Transaction', $params, $connection);
206185
});
207186

208-
$event->listen('connection.*.rollingBack', function ($event, $params): void {
209-
if ($collector = $this->getRequestThisCollector()) {
210-
$collector->collectTransactionEvent('Rollback Transaction', $params[0]);
211-
}
187+
$event->listen('connection.*.rollingBack', function ($event, $params) use ($connection): void {
188+
$this->collectWildcardTransactionEvent('Rollback Transaction', $params, $connection);
212189
});
213190
}
214191

192+
private function collectQueryEvent(QueryExecuted $event, ?Connection $expectedConnection = null): void
193+
{
194+
if (!$this->shouldCollectConnection($event->connection, $expectedConnection)) {
195+
return;
196+
}
197+
198+
$threshold = $this->config['slow_threshold'];
199+
if ($threshold && $event->time <= $threshold) {
200+
return;
201+
}
202+
203+
if ($collector = $this->getRequestThisCollector()) {
204+
$collector->addQuery($event->sql, $event->bindings, $event->time, $event->connection);
205+
}
206+
}
207+
208+
private function collectTransactionConnectionEvent(
209+
string $label,
210+
Connection $eventConnection,
211+
?Connection $expectedConnection = null,
212+
): void {
213+
if (!$this->shouldCollectConnection($eventConnection, $expectedConnection)) {
214+
return;
215+
}
216+
217+
if ($collector = $this->getRequestThisCollector()) {
218+
$collector->collectTransactionEvent($label, $eventConnection);
219+
}
220+
}
221+
222+
/**
223+
* @param array<mixed> $params
224+
*/
225+
private function collectWildcardTransactionEvent(
226+
string $label,
227+
array $params,
228+
?Connection $expectedConnection = null,
229+
): void {
230+
$eventConnection = $params[0] ?? null;
231+
if (!$eventConnection instanceof Connection) {
232+
return;
233+
}
234+
235+
$this->collectTransactionConnectionEvent($label, $eventConnection, $expectedConnection);
236+
}
237+
215238
/**
216239
* 获取 request 下每次新的当前 collector 对象
217240
* @return static|null
@@ -238,4 +261,13 @@ protected function isEventConnectionEqual(Connection $connection, Connection $ev
238261
{
239262
return $connection->getName() === $eventConnection->getName();
240263
}
264+
265+
private function shouldCollectConnection(Connection $eventConnection, ?Connection $expectedConnection = null): bool
266+
{
267+
if ($expectedConnection === null) {
268+
return true;
269+
}
270+
271+
return $this->isEventConnectionEqual($expectedConnection, $eventConnection);
272+
}
241273
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Illuminate\Container\Container;
6+
use Illuminate\Contracts\Events\Dispatcher;
7+
use WebmanTech\Debugbar\Bootstrap\LaravelQuery;
8+
use WebmanTech\Debugbar\DataCollector\LaravelQueryCollector;
9+
10+
function debugbar_laravel_query_listen_for_query_events(LaravelQueryCollector $collector): void
11+
{
12+
$method = new ReflectionMethod(LaravelQuery::class, 'listenForQueryEvents');
13+
$method->setAccessible(true);
14+
$method->invoke(null, $collector);
15+
}
16+
17+
beforeEach(function () {
18+
$property = new ReflectionProperty(LaravelQuery::class, 'registered');
19+
$property->setAccessible(true);
20+
$property->setValue(null, false);
21+
22+
$container = Container::getInstance();
23+
$this->originalEvents = $container->bound('events') ? $container->make('events') : null;
24+
});
25+
26+
afterEach(function () {
27+
$container = Container::getInstance();
28+
if ($this->originalEvents) {
29+
$container->instance('events', $this->originalEvents);
30+
return;
31+
}
32+
33+
$container->forgetInstance('events');
34+
});
35+
36+
it('registers query event listener only once', function () {
37+
$container = Container::getInstance();
38+
$dispatcher = $this->createMock(Dispatcher::class);
39+
$collector = $this->getMockBuilder(LaravelQueryCollector::class)
40+
->disableOriginalConstructor()
41+
->onlyMethods(['addEventDispatcherListener'])
42+
->getMock();
43+
44+
$container->instance('events', $dispatcher);
45+
46+
$collector->expects($this->once())
47+
->method('addEventDispatcherListener')
48+
->with($dispatcher);
49+
50+
debugbar_laravel_query_listen_for_query_events($collector);
51+
debugbar_laravel_query_listen_for_query_events($collector);
52+
});
53+
54+
it('ignores query event listener registration failures', function () {
55+
$container = Container::getInstance();
56+
$dispatcher = $this->createMock(Dispatcher::class);
57+
$collector = $this->getMockBuilder(LaravelQueryCollector::class)
58+
->disableOriginalConstructor()
59+
->onlyMethods(['addEventDispatcherListener'])
60+
->getMock();
61+
62+
$container->instance('events', $dispatcher);
63+
64+
$collector->expects($this->once())
65+
->method('addEventDispatcherListener')
66+
->with($dispatcher)
67+
->willThrowException(new RuntimeException('listener unavailable'));
68+
69+
debugbar_laravel_query_listen_for_query_events($collector);
70+
});

0 commit comments

Comments
 (0)