diff --git a/config/events-console.php b/config/events-console.php index 4fd01cf8..20e3db4f 100644 --- a/config/events-console.php +++ b/config/events-console.php @@ -17,12 +17,12 @@ return [ ApplicationStartup::class => [ - [Debugger::class, 'startup'], + [Debugger::class, 'start'], [ConsoleAppInfoCollector::class, 'collect'], ], ApplicationShutdown::class => [ [ConsoleAppInfoCollector::class, 'collect'], - [Debugger::class, 'shutdown'], + [Debugger::class, 'stop'], ], ConsoleCommandEvent::class => [ [ConsoleAppInfoCollector::class, 'collect'], diff --git a/config/events-web.php b/config/events-web.php index 2da79a9b..d67b268c 100644 --- a/config/events-web.php +++ b/config/events-web.php @@ -20,14 +20,14 @@ return [ ApplicationStartup::class => [ - [Debugger::class, 'startup'], + [Debugger::class, 'start'], [WebAppInfoCollector::class, 'collect'], ], ApplicationShutdown::class => [ [WebAppInfoCollector::class, 'collect'], ], BeforeRequest::class => [ - [Debugger::class, 'startup'], + [Debugger::class, 'start'], [WebAppInfoCollector::class, 'collect'], [RequestCollector::class, 'collect'], ], @@ -38,7 +38,7 @@ AfterEmit::class => [ [ProfilerInterface::class, 'flush'], [WebAppInfoCollector::class, 'collect'], - [Debugger::class, 'shutdown'], + [Debugger::class, 'stop'], ], ApplicationError::class => [ [ExceptionCollector::class, 'collect'], diff --git a/src/Collector/CollectorInterface.php b/src/Collector/CollectorInterface.php index 12dc034c..f9f60c10 100644 --- a/src/Collector/CollectorInterface.php +++ b/src/Collector/CollectorInterface.php @@ -22,7 +22,7 @@ public function startup(): void; /** * Called once at application shutdown. - * Cleanup could be done here. + * Cleanup could be done here. Implementation must be idempotent. */ public function shutdown(): void; diff --git a/src/Command/DebugResetCommand.php b/src/Command/DebugResetCommand.php index 67c96ee8..b2a98a4c 100644 --- a/src/Command/DebugResetCommand.php +++ b/src/Command/DebugResetCommand.php @@ -33,7 +33,7 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - $this->debugger->stop(); + $this->debugger->kill(); $this->storage->clear(); return ExitCode::OK; diff --git a/src/Debugger.php b/src/Debugger.php index f9c0795c..cd3d93a5 100644 --- a/src/Debugger.php +++ b/src/Debugger.php @@ -13,21 +13,31 @@ use Yiisoft\Yii\Debug\StartupPolicy\Debugger\DebuggerStartupPolicyInterface; use Yiisoft\Yii\Debug\Storage\StorageInterface; +/** + * Debugger collects data from collectors and stores it in a storage. + */ final class Debugger { /** + * @var CollectorInterface[] Collectors, indexed by their names. + * * @psalm-var array */ private readonly array $collectors; - private readonly DataNormalizer $dataNormalizer; /** - * @var string|null ID of the current request. Null if debugger is not active. + * @var DataNormalizer Data normalizer that prepares data for storage. */ - private ?string $id = null; + private readonly DataNormalizer $dataNormalizer; /** - * @param CollectorInterface[] $collectors + * @param StorageInterface $storage The storage to store collected data. + * @param CollectorInterface[] $collectors Collectors to be used. + * @param DebuggerStartupPolicyInterface $debuggerStartupPolicy Policy to decide whether debugger should be started. + * Default {@see AlwaysOnDebuggerPolicy} that always allows to startup debugger. + * @param CollectorStartupPolicyInterface $collectorStartupPolicy Policy to decide whether collector should be + * started. Default {@see AllowAllCollectorPolicy} that always allows to use all collectors. + * @param array $excludedClasses List of classes to be excluded from collected data before storing. */ public function __construct( private readonly StorageInterface $storage, @@ -44,20 +54,42 @@ public function __construct( $this->dataNormalizer = new DataNormalizer($excludedClasses); - register_shutdown_function([$this, 'shutdown']); + register_shutdown_function([$this, 'stop']); } + /** + * @var string|null ID of the current request. `null` if debugger is not active. + */ + private ?string $id = null; + + /** + * Returns whether debugger is active. + * + * @return bool Whether debugger is active. + */ public function isActive(): bool { return $this->id !== null; } + /** + * Returns ID of the current request. + * + * Throws `LogicException` if debugger is not started. Use {@see isActive()} to check if debugger is active. + * + * @return string ID of the current request. + */ public function getId(): string { return $this->id ?? throw new LogicException('Debugger is not started.'); } - public function startup(object $event): void + /** + * Starts debugger and collectors. + * + * @param object $event Event that triggered debugger startup. + */ + public function start(object $event): void { if (!$this->debuggerStartupPolicy->satisfies($event)) { return; @@ -72,39 +104,58 @@ public function startup(object $event): void } } - public function shutdown(): void + /** + * Stops the debugger for listening. Collected data will be flushed to storage. + */ + public function stop(): void { if (!$this->isActive()) { return; } try { - $collectedData = array_map( - static fn (CollectorInterface $collector) => $collector->getCollected(), - $this->collectors - ); - - /** @var array[] $data */ - [$data, $objectsMap] = $this->dataNormalizer->prepareDataAndObjectsMap($collectedData, 30); - - /** @var array $summary */ - $summary = $this->dataNormalizer->prepareData($this->collectSummaryData(), 30); - - $this->storage->write($this->getId(), $data, $objectsMap, $summary); + $this->flush(); } finally { - foreach ($this->collectors as $collector) { - $collector->shutdown(); - } - $this->id = null; + $this->deactivate(); } } - public function stop(): void + /** + * Stops the debugger from listening. Collected data will not be flushed to storage. + */ + public function kill(): void { if (!$this->isActive()) { return; } + $this->deactivate(); + } + + /** + * Collects data from collectors and stores it in a storage. + */ + private function flush(): void + { + $collectedData = array_map( + static fn (CollectorInterface $collector) => $collector->getCollected(), + $this->collectors + ); + + /** @var array[] $data */ + [$data, $objectsMap] = $this->dataNormalizer->prepareDataAndObjectsMap($collectedData, 30); + + /** @var array $summary */ + $summary = $this->dataNormalizer->prepareData($this->collectSummaryData(), 30); + + $this->storage->write($this->getId(), $data, $objectsMap, $summary); + } + + /** + * Stops debugger and collectors. + */ + private function deactivate(): void + { foreach ($this->collectors as $collector) { $collector->shutdown(); } diff --git a/tests/Unit/DebuggerTest.php b/tests/Unit/DebuggerTest.php index d0ee1c3e..c62f7c8a 100644 --- a/tests/Unit/DebuggerTest.php +++ b/tests/Unit/DebuggerTest.php @@ -16,17 +16,17 @@ final class DebuggerTest extends TestCase { - public function testStartup(): void + public function testStart(): void { $collector = $this->getMockBuilder(CollectorInterface::class)->getMock(); $collector->expects($this->once())->method('startup'); $storage = new MemoryStorage(); $debugger = new Debugger($storage, [$collector]); - $debugger->startup(new stdClass()); + $debugger->start(new stdClass()); } - public function testShutdown(): void + public function testStop(): void { $collector = $this->getMockBuilder(CollectorInterface::class)->getMock(); $collector->expects($this->once())->method('shutdown'); @@ -34,13 +34,13 @@ public function testShutdown(): void $storage->expects($this->once())->method('write'); $debugger = new Debugger($storage, [$collector]); - $debugger->startup(new BeforeRequest(new ServerRequest('GET', '/test'))); - $debugger->shutdown(); - $debugger->shutdown(); - $debugger->shutdown(); + $debugger->start(new BeforeRequest(new ServerRequest('GET', '/test'))); + $debugger->stop(); + $debugger->stop(); + $debugger->stop(); } - public function testShutdownWithStartupPrevention(): void + public function testStopWithStartupPrevention(): void { $collector = $this->getMockBuilder(CollectorInterface::class)->getMock(); $collector->expects($this->never())->method('startup'); @@ -50,8 +50,8 @@ public function testShutdownWithStartupPrevention(): void $storage->expects($this->never())->method('write'); $debugger = new Debugger($storage, [$collector], new AllowDebuggerPolicy()); - $debugger->startup(new BeforeRequest(new ServerRequest('GET', '/test'))); - $debugger->shutdown(); + $debugger->start(new BeforeRequest(new ServerRequest('GET', '/test'))); + $debugger->stop(); } public function testStopSkipped(): void @@ -63,8 +63,8 @@ public function testStopSkipped(): void $storage->expects($this->never())->method('write'); $debugger = new Debugger($storage, [$collector]); - $debugger->startup(new BeforeRequest(new ServerRequest('GET', '/test'))); - $debugger->stop(); - $debugger->stop(); + $debugger->start(new BeforeRequest(new ServerRequest('GET', '/test'))); + $debugger->kill(); + $debugger->kill(); } }