Skip to content

Commit

Permalink
Debugger: minor refactoring and phpdoc (#305)
Browse files Browse the repository at this point in the history
Co-authored-by: Dmitriy Derepko <[email protected]>
  • Loading branch information
vjik and xepozz authored Jan 24, 2025
1 parent c578957 commit e29bcf3
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 44 deletions.
4 changes: 2 additions & 2 deletions config/events-console.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
6 changes: 3 additions & 3 deletions config/events-web.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
],
Expand All @@ -38,7 +38,7 @@
AfterEmit::class => [
[ProfilerInterface::class, 'flush'],
[WebAppInfoCollector::class, 'collect'],
[Debugger::class, 'shutdown'],
[Debugger::class, 'stop'],
],
ApplicationError::class => [
[ExceptionCollector::class, 'collect'],
Expand Down
2 changes: 1 addition & 1 deletion src/Collector/CollectorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion src/Command/DebugResetCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
99 changes: 75 additions & 24 deletions src/Debugger.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, CollectorInterface>
*/
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,
Expand All @@ -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;
Expand All @@ -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();
}
Expand Down
26 changes: 13 additions & 13 deletions tests/Unit/DebuggerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,31 @@

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');
$storage = $this->getMockBuilder(StorageInterface::class)->getMock();
$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');
Expand All @@ -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
Expand All @@ -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();
}
}

0 comments on commit e29bcf3

Please sign in to comment.