From 3f90d3fb85adb534e611ba2a7e3383a2aec043e8 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sun, 20 Aug 2023 23:29:47 +0300 Subject: [PATCH 01/38] Add var dumper collector --- config/bootstrap.php | 29 ++++++++++++ config/params.php | 2 + src/Collector/VarDumperCollector.php | 44 +++++++++++++++++++ .../VarDumperHandlerInterfaceProxy.php | 42 ++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 config/bootstrap.php create mode 100644 src/Collector/VarDumperCollector.php create mode 100644 src/Collector/VarDumperHandlerInterfaceProxy.php diff --git a/config/bootstrap.php b/config/bootstrap.php new file mode 100644 index 000000000..a78e5b210 --- /dev/null +++ b/config/bootstrap.php @@ -0,0 +1,29 @@ +has(VarDumperCollector::class)) { + return; + } + + VarDumper::setDefaultHandler( + new VarDumperHandlerInterfaceProxy( + VarDumper::getDefaultHandler(), + $container->get(VarDumperCollector::class), + ), + ); + }, +]; diff --git a/config/params.php b/config/params.php index b2f92802a..08a65b973 100644 --- a/config/params.php +++ b/config/params.php @@ -21,6 +21,7 @@ use Yiisoft\Yii\Debug\Collector\ServiceCollector; use Yiisoft\Yii\Debug\Collector\Stream\FilesystemStreamCollector; use Yiisoft\Yii\Debug\Collector\Stream\HttpStreamCollector; +use Yiisoft\Yii\Debug\Collector\VarDumperCollector; use Yiisoft\Yii\Debug\Collector\Web\MiddlewareCollector; use Yiisoft\Yii\Debug\Collector\Web\RequestCollector; use Yiisoft\Yii\Debug\Collector\Web\WebAppInfoCollector; @@ -43,6 +44,7 @@ FilesystemStreamCollector::class, HttpStreamCollector::class, ExceptionCollector::class, + VarDumperCollector::class, ], 'collectors.web' => [ WebAppInfoCollector::class, diff --git a/src/Collector/VarDumperCollector.php b/src/Collector/VarDumperCollector.php new file mode 100644 index 000000000..4575cf9e0 --- /dev/null +++ b/src/Collector/VarDumperCollector.php @@ -0,0 +1,44 @@ +vars[] = [ + 'variable' => $variable, + 'line' => $line, + ]; + } + + public function getCollected(): array + { + if (!$this->isActive()) { + return []; + } + + return [ + 'var-dumper' => $this->vars, + ]; + } + + public function getSummary(): array + { + if (!$this->isActive()) { + return []; + } + + return [ + 'var-dumper' => [ + 'total' => count($this->vars), + ], + ]; + } +} diff --git a/src/Collector/VarDumperHandlerInterfaceProxy.php b/src/Collector/VarDumperHandlerInterfaceProxy.php new file mode 100644 index 000000000..cd284881b --- /dev/null +++ b/src/Collector/VarDumperHandlerInterfaceProxy.php @@ -0,0 +1,42 @@ +collector->collectVar( + $variable, + $callStack === null ? '' : $callStack['file'] . ':' . $callStack['line'] + ); + $this->decorated->handle($variable, $depth, $highlight); + } +} From e4741d73cdb6e7d74b3c5986990b26f02da4ed4a Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 21 Aug 2023 22:35:39 +0300 Subject: [PATCH 02/38] Include bootstrap config --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 103a792a0..1535b58f0 100644 --- a/composer.json +++ b/composer.json @@ -77,6 +77,7 @@ }, "config-plugin": { "params": "params.php", + "bootstrap": "bootstrap.php", "di": "di.php", "di-console": "di-console.php", "di-web": "di-web.php", From 3701eaec4f99d4f5a071e58817a8a18f529b9385 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 21 Aug 2023 22:38:00 +0300 Subject: [PATCH 03/38] Fix composer-require-checker --- composer-require-checker.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer-require-checker.json b/composer-require-checker.json index 5571a66f9..b2a2e1537 100644 --- a/composer-require-checker.json +++ b/composer-require-checker.json @@ -15,6 +15,7 @@ "Yiisoft\\Cache\\Dependency\\Dependency", "Yiisoft\\ErrorHandler\\Event\\ApplicationError", "PHPUnit\\Framework\\TestCase", + "Yiisoft\\VarDumper\\HandlerInterface", "opcache_invalidate" ] } From 6f5bdfceb87c45214011fbac367aff99a385847b Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 2 Sep 2023 11:53:08 +0300 Subject: [PATCH 04/38] Add timeline collector --- config/params.php | 2 + src/Collector/Console/CommandCollector.php | 7 ++++ .../Console/ConsoleAppInfoCollector.php | 7 ++++ src/Collector/EventCollector.php | 12 +++--- src/Collector/ExceptionCollector.php | 5 +++ src/Collector/HttpClientCollector.php | 5 +++ src/Collector/LogCollector.php | 5 +++ src/Collector/ServiceCollector.php | 5 +++ src/Collector/TimelineCollector.php | 38 +++++++++++++++++++ src/Collector/VarDumperCollector.php | 7 +++- .../VarDumperHandlerInterfaceProxy.php | 2 +- src/Collector/Web/MiddlewareCollector.php | 6 +++ src/Collector/Web/RequestCollector.php | 10 ++++- src/Collector/Web/WebAppInfoCollector.php | 10 ++++- 14 files changed, 109 insertions(+), 12 deletions(-) create mode 100644 src/Collector/TimelineCollector.php diff --git a/config/params.php b/config/params.php index 08a65b973..f6d4d2cde 100644 --- a/config/params.php +++ b/config/params.php @@ -21,6 +21,7 @@ use Yiisoft\Yii\Debug\Collector\ServiceCollector; use Yiisoft\Yii\Debug\Collector\Stream\FilesystemStreamCollector; use Yiisoft\Yii\Debug\Collector\Stream\HttpStreamCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Collector\VarDumperCollector; use Yiisoft\Yii\Debug\Collector\Web\MiddlewareCollector; use Yiisoft\Yii\Debug\Collector\Web\RequestCollector; @@ -45,6 +46,7 @@ HttpStreamCollector::class, ExceptionCollector::class, VarDumperCollector::class, + TimelineCollector::class, ], 'collectors.web' => [ WebAppInfoCollector::class, diff --git a/src/Collector/Console/CommandCollector.php b/src/Collector/Console/CommandCollector.php index 1ec359e42..1af383602 100644 --- a/src/Collector/Console/CommandCollector.php +++ b/src/Collector/Console/CommandCollector.php @@ -13,6 +13,7 @@ use Yiisoft\Yii\Console\Output\ConsoleBufferedOutput; use Yiisoft\Yii\Debug\Collector\CollectorTrait; use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; final class CommandCollector implements SummaryCollectorInterface { @@ -24,6 +25,10 @@ final class CommandCollector implements SummaryCollectorInterface private const UNDEFINED_EXIT_CODE = -1; private array $commands = []; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { return $this->commands; @@ -35,6 +40,8 @@ public function collect(ConsoleEvent|ConsoleErrorEvent|ConsoleTerminateEvent $ev return; } + $this->timelineCollector->collect(spl_object_id($event), $this); + $command = $event->getCommand(); if ($event instanceof ConsoleErrorEvent) { diff --git a/src/Collector/Console/ConsoleAppInfoCollector.php b/src/Collector/Console/ConsoleAppInfoCollector.php index 68bac3025..fc81b478f 100644 --- a/src/Collector/Console/ConsoleAppInfoCollector.php +++ b/src/Collector/Console/ConsoleAppInfoCollector.php @@ -11,6 +11,7 @@ use Yiisoft\Yii\Console\Event\ApplicationStartup; use Yiisoft\Yii\Debug\Collector\CollectorTrait; use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; final class ConsoleAppInfoCollector implements SummaryCollectorInterface { @@ -21,6 +22,10 @@ final class ConsoleAppInfoCollector implements SummaryCollectorInterface private float $requestProcessingTimeStarted = 0; private float $requestProcessingTimeStopped = 0; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -58,6 +63,8 @@ public function collect(object $event): void } elseif ($event instanceof ApplicationShutdown) { $this->applicationProcessingTimeStopped = microtime(true); } + + $this->timelineCollector->collect(spl_object_id($event), $this); } public function getSummary(): array diff --git a/src/Collector/EventCollector.php b/src/Collector/EventCollector.php index ecc89318b..685f84953 100644 --- a/src/Collector/EventCollector.php +++ b/src/Collector/EventCollector.php @@ -8,12 +8,16 @@ use Yiisoft\Yii\Console\Event\ApplicationStartup as ConsoleApplicationStartup; use Yiisoft\Yii\Http\Event\ApplicationStartup as HttpApplicationStartup; -class EventCollector implements SummaryCollectorInterface +final class EventCollector implements SummaryCollectorInterface { use CollectorTrait; private array $events = []; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -32,11 +36,6 @@ public function collect(object $event, string $line): void return; } - $this->collectEvent($event, $line); - } - - private function collectEvent(object $event, $line): void - { $this->events[] = [ 'name' => $event::class, 'event' => $event, @@ -44,6 +43,7 @@ private function collectEvent(object $event, $line): void 'line' => $line, 'time' => microtime(true), ]; + $this->timelineCollector->collect(count($this->events), $this); } public function getSummary(): array diff --git a/src/Collector/ExceptionCollector.php b/src/Collector/ExceptionCollector.php index 8f3e7cff6..a6e27b28e 100644 --- a/src/Collector/ExceptionCollector.php +++ b/src/Collector/ExceptionCollector.php @@ -13,6 +13,10 @@ final class ExceptionCollector implements SummaryCollectorInterface private ?Throwable $exception = null; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if ($this->exception === null) { @@ -36,6 +40,7 @@ public function collect(ApplicationError $error): void } $this->exception = $error->getThrowable(); + $this->timelineCollector->collect($error::class, $this); } public function getSummary(): array diff --git a/src/Collector/HttpClientCollector.php b/src/Collector/HttpClientCollector.php index 0304ef2fe..49a14c3da 100644 --- a/src/Collector/HttpClientCollector.php +++ b/src/Collector/HttpClientCollector.php @@ -29,6 +29,10 @@ final class HttpClientCollector implements SummaryCollectorInterface */ private array $requests = []; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { return array_merge(...array_values($this->requests)); @@ -66,6 +70,7 @@ public function collect(RequestInterface $request, float|string $startTime, stri 'headers' => $request->getHeaders(), 'line' => $line, ]; + $this->timelineCollector->collect($uniqueId, $this); } public function collectTotalTime(?ResponseInterface $response, float|string $startTime, ?string $uniqueId): void diff --git a/src/Collector/LogCollector.php b/src/Collector/LogCollector.php index 5d963b47c..37e86f998 100644 --- a/src/Collector/LogCollector.php +++ b/src/Collector/LogCollector.php @@ -10,6 +10,10 @@ class LogCollector implements SummaryCollectorInterface private array $messages = []; + public function __construct(private TimelineCollector $timelineCollector,) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -31,6 +35,7 @@ public function collect(string $level, $message, array $context, string $line): 'context' => $context, 'line' => $line, ]; + $this->timelineCollector->collect(count($this->messages), $this); } private function reset(): void diff --git a/src/Collector/ServiceCollector.php b/src/Collector/ServiceCollector.php index 569888561..0c01eef34 100644 --- a/src/Collector/ServiceCollector.php +++ b/src/Collector/ServiceCollector.php @@ -10,6 +10,10 @@ final class ServiceCollector implements SummaryCollectorInterface private array $items = []; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -44,6 +48,7 @@ public function collect( 'timeStart' => $timeStart, 'timeEnd' => $timeEnd, ]; + $this->timelineCollector->collect(count($this->items), $this); } public function getSummary(): array diff --git a/src/Collector/TimelineCollector.php b/src/Collector/TimelineCollector.php new file mode 100644 index 000000000..c7900587f --- /dev/null +++ b/src/Collector/TimelineCollector.php @@ -0,0 +1,38 @@ +isActive()) { + return []; + } + return $this->events; + } + + public function collect(string|int $reference, CollectorInterface $collector): void + { + if (!$this->isActive()) { + return; + } + + $this->events[] = [ + 'reference' => $reference, + 'collector' => $collector::class, + 'time' => time() + ]; + } + + private function reset(): void + { + $this->events = []; + } +} diff --git a/src/Collector/VarDumperCollector.php b/src/Collector/VarDumperCollector.php index 4575cf9e0..70d56ba49 100644 --- a/src/Collector/VarDumperCollector.php +++ b/src/Collector/VarDumperCollector.php @@ -10,12 +10,17 @@ final class VarDumperCollector implements SummaryCollectorInterface private array $vars = []; - public function collectVar(mixed $variable, string $line): void + public function __construct(private TimelineCollector $timelineCollector) + { + } + + public function collect(mixed $variable, string $line): void { $this->vars[] = [ 'variable' => $variable, 'line' => $line, ]; + $this->timelineCollector->collect(count($this->vars), $this); } public function getCollected(): array diff --git a/src/Collector/VarDumperHandlerInterfaceProxy.php b/src/Collector/VarDumperHandlerInterfaceProxy.php index cd284881b..c76f2cd04 100644 --- a/src/Collector/VarDumperHandlerInterfaceProxy.php +++ b/src/Collector/VarDumperHandlerInterfaceProxy.php @@ -33,7 +33,7 @@ public function handle(mixed $variable, int $depth, bool $highlight = false): vo break; } - $this->collector->collectVar( + $this->collector->collect( $variable, $callStack === null ? '' : $callStack['file'] . ':' . $callStack['line'] ); diff --git a/src/Collector/Web/MiddlewareCollector.php b/src/Collector/Web/MiddlewareCollector.php index 70bd4e84c..147171c7a 100644 --- a/src/Collector/Web/MiddlewareCollector.php +++ b/src/Collector/Web/MiddlewareCollector.php @@ -9,6 +9,7 @@ use Yiisoft\Middleware\Dispatcher\Event\BeforeMiddleware; use Yiisoft\Yii\Debug\Collector\CollectorTrait; use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; final class MiddlewareCollector implements SummaryCollectorInterface { @@ -17,6 +18,10 @@ final class MiddlewareCollector implements SummaryCollectorInterface private array $beforeStack = []; private array $afterStack = []; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -73,6 +78,7 @@ public function collect(BeforeMiddleware|AfterMiddleware $event): void 'response' => $event->getResponse(), ]; } + $this->timelineCollector->collect(spl_object_id($event), $this); } private function reset(): void diff --git a/src/Collector/Web/RequestCollector.php b/src/Collector/Web/RequestCollector.php index 472ceff4b..a1094cc7d 100644 --- a/src/Collector/Web/RequestCollector.php +++ b/src/Collector/Web/RequestCollector.php @@ -8,10 +8,11 @@ use JsonException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Yiisoft\Yii\Http\Event\AfterRequest; -use Yiisoft\Yii\Http\Event\BeforeRequest; use Yiisoft\Yii\Debug\Collector\CollectorTrait; use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; +use Yiisoft\Yii\Http\Event\AfterRequest; +use Yiisoft\Yii\Http\Event\BeforeRequest; use function is_object; @@ -29,6 +30,10 @@ final class RequestCollector implements SummaryCollectorInterface private ?ServerRequestInterface $request = null; private ?ResponseInterface $response = null; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -101,6 +106,7 @@ public function collect(object $event): void $this->response = $response; $this->responseStatusCode = $response !== null ? $response->getStatusCode() : 500; } + $this->timelineCollector->collect(spl_object_id($event), $this); } public function getSummary(): array diff --git a/src/Collector/Web/WebAppInfoCollector.php b/src/Collector/Web/WebAppInfoCollector.php index 7213608f9..bae3eaf3b 100644 --- a/src/Collector/Web/WebAppInfoCollector.php +++ b/src/Collector/Web/WebAppInfoCollector.php @@ -5,11 +5,12 @@ namespace Yiisoft\Yii\Debug\Collector\Web; use Yiisoft\Yii\Console\Event\ApplicationStartup; +use Yiisoft\Yii\Debug\Collector\CollectorTrait; +use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Http\Event\AfterEmit; use Yiisoft\Yii\Http\Event\AfterRequest; use Yiisoft\Yii\Http\Event\BeforeRequest; -use Yiisoft\Yii\Debug\Collector\CollectorTrait; -use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; use function is_object; @@ -22,6 +23,10 @@ final class WebAppInfoCollector implements SummaryCollectorInterface private float $requestProcessingTimeStarted = 0; private float $requestProcessingTimeStopped = 0; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -52,6 +57,7 @@ public function collect(object $event): void } elseif ($event instanceof AfterEmit) { $this->applicationProcessingTimeStopped = microtime(true); } + $this->timelineCollector->collect(spl_object_id($event), $this); } public function getSummary(): array From 38b70b9907888eb4a3689d10db8edf1dbaeb9968 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 2 Sep 2023 12:11:14 +0300 Subject: [PATCH 05/38] Change timeline signature --- src/Collector/Console/CommandCollector.php | 2 +- src/Collector/Console/ConsoleAppInfoCollector.php | 2 +- src/Collector/EventCollector.php | 2 +- src/Collector/LogCollector.php | 2 +- src/Collector/ServiceCollector.php | 2 +- src/Collector/TimelineCollector.php | 8 ++------ src/Collector/VarDumperCollector.php | 2 +- src/Collector/Web/MiddlewareCollector.php | 2 +- src/Collector/Web/RequestCollector.php | 2 +- src/Collector/Web/WebAppInfoCollector.php | 2 +- 10 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/Collector/Console/CommandCollector.php b/src/Collector/Console/CommandCollector.php index 1af383602..b97e64f1f 100644 --- a/src/Collector/Console/CommandCollector.php +++ b/src/Collector/Console/CommandCollector.php @@ -40,7 +40,7 @@ public function collect(ConsoleEvent|ConsoleErrorEvent|ConsoleTerminateEvent $ev return; } - $this->timelineCollector->collect(spl_object_id($event), $this); + $this->timelineCollector->collect($this, spl_object_id($event)); $command = $event->getCommand(); diff --git a/src/Collector/Console/ConsoleAppInfoCollector.php b/src/Collector/Console/ConsoleAppInfoCollector.php index fc81b478f..3211ee285 100644 --- a/src/Collector/Console/ConsoleAppInfoCollector.php +++ b/src/Collector/Console/ConsoleAppInfoCollector.php @@ -64,7 +64,7 @@ public function collect(object $event): void $this->applicationProcessingTimeStopped = microtime(true); } - $this->timelineCollector->collect(spl_object_id($event), $this); + $this->timelineCollector->collect($this, spl_object_id($event)); } public function getSummary(): array diff --git a/src/Collector/EventCollector.php b/src/Collector/EventCollector.php index 685f84953..d04985ca3 100644 --- a/src/Collector/EventCollector.php +++ b/src/Collector/EventCollector.php @@ -43,7 +43,7 @@ public function collect(object $event, string $line): void 'line' => $line, 'time' => microtime(true), ]; - $this->timelineCollector->collect(count($this->events), $this); + $this->timelineCollector->collect($this, spl_object_id($event), $event::class); } public function getSummary(): array diff --git a/src/Collector/LogCollector.php b/src/Collector/LogCollector.php index 37e86f998..899c2af9f 100644 --- a/src/Collector/LogCollector.php +++ b/src/Collector/LogCollector.php @@ -35,7 +35,7 @@ public function collect(string $level, $message, array $context, string $line): 'context' => $context, 'line' => $line, ]; - $this->timelineCollector->collect(count($this->messages), $this); + $this->timelineCollector->collect($this, count($this->messages)); } private function reset(): void diff --git a/src/Collector/ServiceCollector.php b/src/Collector/ServiceCollector.php index 0c01eef34..e281bc16d 100644 --- a/src/Collector/ServiceCollector.php +++ b/src/Collector/ServiceCollector.php @@ -48,7 +48,7 @@ public function collect( 'timeStart' => $timeStart, 'timeEnd' => $timeEnd, ]; - $this->timelineCollector->collect(count($this->items), $this); + $this->timelineCollector->collect($this, count($this->items)); } public function getSummary(): array diff --git a/src/Collector/TimelineCollector.php b/src/Collector/TimelineCollector.php index c7900587f..4e12c6405 100644 --- a/src/Collector/TimelineCollector.php +++ b/src/Collector/TimelineCollector.php @@ -18,17 +18,13 @@ public function getCollected(): array return $this->events; } - public function collect(string|int $reference, CollectorInterface $collector): void + public function collect(CollectorInterface $collector, string|int $reference, ...$data): void { if (!$this->isActive()) { return; } - $this->events[] = [ - 'reference' => $reference, - 'collector' => $collector::class, - 'time' => time() - ]; + $this->events[] = [time(), $reference, $collector::class, ...$data]; } private function reset(): void diff --git a/src/Collector/VarDumperCollector.php b/src/Collector/VarDumperCollector.php index 70d56ba49..8eb9c3e4a 100644 --- a/src/Collector/VarDumperCollector.php +++ b/src/Collector/VarDumperCollector.php @@ -20,7 +20,7 @@ public function collect(mixed $variable, string $line): void 'variable' => $variable, 'line' => $line, ]; - $this->timelineCollector->collect(count($this->vars), $this); + $this->timelineCollector->collect($this, count($this->vars)); } public function getCollected(): array diff --git a/src/Collector/Web/MiddlewareCollector.php b/src/Collector/Web/MiddlewareCollector.php index 147171c7a..7dda19dd8 100644 --- a/src/Collector/Web/MiddlewareCollector.php +++ b/src/Collector/Web/MiddlewareCollector.php @@ -78,7 +78,7 @@ public function collect(BeforeMiddleware|AfterMiddleware $event): void 'response' => $event->getResponse(), ]; } - $this->timelineCollector->collect(spl_object_id($event), $this); + $this->timelineCollector->collect($this, spl_object_id($event)); } private function reset(): void diff --git a/src/Collector/Web/RequestCollector.php b/src/Collector/Web/RequestCollector.php index a1094cc7d..c1518a633 100644 --- a/src/Collector/Web/RequestCollector.php +++ b/src/Collector/Web/RequestCollector.php @@ -106,7 +106,7 @@ public function collect(object $event): void $this->response = $response; $this->responseStatusCode = $response !== null ? $response->getStatusCode() : 500; } - $this->timelineCollector->collect(spl_object_id($event), $this); + $this->timelineCollector->collect($this, spl_object_id($event)); } public function getSummary(): array diff --git a/src/Collector/Web/WebAppInfoCollector.php b/src/Collector/Web/WebAppInfoCollector.php index bae3eaf3b..6f389b161 100644 --- a/src/Collector/Web/WebAppInfoCollector.php +++ b/src/Collector/Web/WebAppInfoCollector.php @@ -57,7 +57,7 @@ public function collect(object $event): void } elseif ($event instanceof AfterEmit) { $this->applicationProcessingTimeStopped = microtime(true); } - $this->timelineCollector->collect(spl_object_id($event), $this); + $this->timelineCollector->collect($this, spl_object_id($event)); } public function getSummary(): array From 98efc081ee8d1c3ef611ac00dc0b7f8009113db7 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 2 Sep 2023 15:21:15 +0300 Subject: [PATCH 06/38] Use microtime instead of time --- src/Collector/TimelineCollector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Collector/TimelineCollector.php b/src/Collector/TimelineCollector.php index 4e12c6405..c24a3280f 100644 --- a/src/Collector/TimelineCollector.php +++ b/src/Collector/TimelineCollector.php @@ -24,7 +24,7 @@ public function collect(CollectorInterface $collector, string|int $reference, .. return; } - $this->events[] = [time(), $reference, $collector::class, ...$data]; + $this->events[] = [microtime(true), $reference, $collector::class, ...$data]; } private function reset(): void From 3426ea994033c90a7400e17e5ea6fd515c8c99ba Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 2 Sep 2023 18:00:37 +0300 Subject: [PATCH 07/38] Remove redundant property --- src/Collector/Web/RequestCollector.php | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/Collector/Web/RequestCollector.php b/src/Collector/Web/RequestCollector.php index c1518a633..3dcb9ab48 100644 --- a/src/Collector/Web/RequestCollector.php +++ b/src/Collector/Web/RequestCollector.php @@ -5,7 +5,6 @@ namespace Yiisoft\Yii\Debug\Collector\Web; use GuzzleHttp\Psr7\Message; -use JsonException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Yiisoft\Yii\Debug\Collector\CollectorTrait; @@ -39,21 +38,6 @@ public function getCollected(): array if (!$this->isActive()) { return []; } - $content = null; - if ($this->response !== null) { - $stream = $this->response->getBody(); - if ($stream->isReadable() && $stream->isSeekable()) { - $position = $stream->tell(); - $stream->rewind(); - $content = $stream->getContents(); - try { - $content = json_decode($content, associative: true, flags: JSON_THROW_ON_ERROR); - } catch (JsonException) { - // pass - } - $stream->seek($position); - } - } $requestRaw = null; if ($this->request instanceof ServerRequestInterface) { @@ -79,7 +63,6 @@ public function getCollected(): array 'requestRaw' => $requestRaw, 'response' => $this->response, 'responseRaw' => $responseRaw, - 'content' => $content, ]; } From f8bf302e9b8ac7f8be3cda7fe4623f2eceab0a2e Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sat, 2 Sep 2023 15:01:20 +0000 Subject: [PATCH 08/38] Apply fixes from StyleCI --- src/Collector/LogCollector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Collector/LogCollector.php b/src/Collector/LogCollector.php index 899c2af9f..291f44872 100644 --- a/src/Collector/LogCollector.php +++ b/src/Collector/LogCollector.php @@ -10,7 +10,7 @@ class LogCollector implements SummaryCollectorInterface private array $messages = []; - public function __construct(private TimelineCollector $timelineCollector,) + public function __construct(private TimelineCollector $timelineCollector, ) { } From 6d4db1b4fcc0ec421eae9a0bc6d91a019d9151df Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 4 Sep 2023 23:12:18 +0300 Subject: [PATCH 09/38] Fix tests & psalm errors --- src/Collector/ExceptionCollector.php | 2 +- src/Collector/HttpClientCollector.php | 2 +- src/Collector/TimelineCollector.php | 2 +- tests/Unit/Collector/CommandCollectorTest.php | 3 +- .../Collector/ConsoleAppInfoCollectorTest.php | 4 +-- .../Collector/ContainerInterfaceProxyTest.php | 20 +++++++----- .../Collector/ContainerProxyConfigTest.php | 18 +++++++---- tests/Unit/Collector/EventCollectorTest.php | 3 +- .../Collector/EventDispatcherProxyTest.php | 31 ------------------- tests/Unit/Collector/LogCollectorTest.php | 3 +- .../Collector/MiddlewareCollectorTest.php | 3 +- tests/Unit/Collector/RequestCollectorTest.php | 3 +- tests/Unit/Collector/ServiceCollectorTest.php | 3 +- .../Collector/WebAppInfoCollectorTest.php | 3 +- 14 files changed, 44 insertions(+), 56 deletions(-) delete mode 100644 tests/Unit/Collector/EventDispatcherProxyTest.php diff --git a/src/Collector/ExceptionCollector.php b/src/Collector/ExceptionCollector.php index a6e27b28e..f0ae8bfaa 100644 --- a/src/Collector/ExceptionCollector.php +++ b/src/Collector/ExceptionCollector.php @@ -40,7 +40,7 @@ public function collect(ApplicationError $error): void } $this->exception = $error->getThrowable(); - $this->timelineCollector->collect($error::class, $this); + $this->timelineCollector->collect($this, $error::class); } public function getSummary(): array diff --git a/src/Collector/HttpClientCollector.php b/src/Collector/HttpClientCollector.php index 49a14c3da..5e96aac43 100644 --- a/src/Collector/HttpClientCollector.php +++ b/src/Collector/HttpClientCollector.php @@ -70,7 +70,7 @@ public function collect(RequestInterface $request, float|string $startTime, stri 'headers' => $request->getHeaders(), 'line' => $line, ]; - $this->timelineCollector->collect($uniqueId, $this); + $this->timelineCollector->collect($this, $uniqueId); } public function collectTotalTime(?ResponseInterface $response, float|string $startTime, ?string $uniqueId): void diff --git a/src/Collector/TimelineCollector.php b/src/Collector/TimelineCollector.php index c24a3280f..068ec0704 100644 --- a/src/Collector/TimelineCollector.php +++ b/src/Collector/TimelineCollector.php @@ -24,7 +24,7 @@ public function collect(CollectorInterface $collector, string|int $reference, .. return; } - $this->events[] = [microtime(true), $reference, $collector::class, ...$data]; + $this->events[] = [microtime(true), $reference, $collector::class, array_values($data)]; } private function reset(): void diff --git a/tests/Unit/Collector/CommandCollectorTest.php b/tests/Unit/Collector/CommandCollectorTest.php index 33c71cd86..5d99fd78d 100644 --- a/tests/Unit/Collector/CommandCollectorTest.php +++ b/tests/Unit/Collector/CommandCollectorTest.php @@ -13,6 +13,7 @@ use Yiisoft\Yii\Console\Output\ConsoleBufferedOutput; use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\Console\CommandCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; final class CommandCollectorTest extends AbstractCollectorTestCase @@ -57,7 +58,7 @@ public function testCollectWithInactiveCollector(): void protected function getCollector(): CollectorInterface { - return new CommandCollector(); + return new CommandCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void diff --git a/tests/Unit/Collector/ConsoleAppInfoCollectorTest.php b/tests/Unit/Collector/ConsoleAppInfoCollectorTest.php index 6ef9bbf1d..cf632672c 100644 --- a/tests/Unit/Collector/ConsoleAppInfoCollectorTest.php +++ b/tests/Unit/Collector/ConsoleAppInfoCollectorTest.php @@ -8,8 +8,8 @@ use Yiisoft\Yii\Console\Event\ApplicationStartup; use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\Console\ConsoleAppInfoCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Collector\Web\WebAppInfoCollector; - use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; use function sleep; @@ -31,7 +31,7 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new ConsoleAppInfoCollector(); + return new ConsoleAppInfoCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void diff --git a/tests/Unit/Collector/ContainerInterfaceProxyTest.php b/tests/Unit/Collector/ContainerInterfaceProxyTest.php index 3d41a5df4..b38e7bdee 100644 --- a/tests/Unit/Collector/ContainerInterfaceProxyTest.php +++ b/tests/Unit/Collector/ContainerInterfaceProxyTest.php @@ -18,13 +18,14 @@ use Yiisoft\EventDispatcher\Provider\Provider; use Yiisoft\Files\FileHelper; use Yiisoft\Yii\Debug\Collector\CollectorInterface; -use Yiisoft\Yii\Debug\Collector\EventCollector; -use Yiisoft\Yii\Debug\Collector\LogCollector; -use Yiisoft\Yii\Debug\Collector\ServiceCollector; use Yiisoft\Yii\Debug\Collector\ContainerInterfaceProxy; use Yiisoft\Yii\Debug\Collector\ContainerProxyConfig; +use Yiisoft\Yii\Debug\Collector\EventCollector; use Yiisoft\Yii\Debug\Collector\EventDispatcherInterfaceProxy; +use Yiisoft\Yii\Debug\Collector\LogCollector; use Yiisoft\Yii\Debug\Collector\LoggerInterfaceProxy; +use Yiisoft\Yii\Debug\Collector\ServiceCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; class ContainerInterfaceProxyTest extends TestCase { @@ -72,7 +73,7 @@ public function testGetAndHasWithCallableServices(): void ], ], $dispatcherMock, - new ServiceCollector(), + $this->createServiceCollector(), $this->path, 1 ); @@ -89,7 +90,7 @@ public function testGetAndHasWithCallableServices(): void public function testGetWithArrayConfigWithStringKeys(): void { $dispatcherMock = $this->getMockBuilder(EventDispatcherInterface::class)->getMock(); - $serviceCollector = new ServiceCollector(); + $serviceCollector = $this->createServiceCollector(); $serviceCollector->startup(); // activate collector $config = new ContainerProxyConfig( @@ -127,7 +128,7 @@ public function testGetWithoutConfig(): void EventDispatcherInterface::class, ], $dispatcherMock, - new ServiceCollector(), + $this->createServiceCollector(), $this->path, 1 ); @@ -183,7 +184,7 @@ private function getConfig(): ContainerProxyConfig ], ], $dispatcherMock, - new ServiceCollector(), + $this->createServiceCollector(), $this->path, 1 ); @@ -201,4 +202,9 @@ private function getContainer(): Container ]); return new Container($config); } + + protected function createServiceCollector(): ServiceCollector + { + return new ServiceCollector(new TimelineCollector()); + } } diff --git a/tests/Unit/Collector/ContainerProxyConfigTest.php b/tests/Unit/Collector/ContainerProxyConfigTest.php index c6921f90b..203d09c7e 100644 --- a/tests/Unit/Collector/ContainerProxyConfigTest.php +++ b/tests/Unit/Collector/ContainerProxyConfigTest.php @@ -7,12 +7,13 @@ use PHPUnit\Framework\TestCase; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerInterface; -use Yiisoft\Yii\Debug\Collector\EventCollector; -use Yiisoft\Yii\Debug\Collector\LogCollector; -use Yiisoft\Yii\Debug\Collector\ServiceCollector; use Yiisoft\Yii\Debug\Collector\ContainerProxyConfig; +use Yiisoft\Yii\Debug\Collector\EventCollector; use Yiisoft\Yii\Debug\Collector\EventDispatcherInterfaceProxy; +use Yiisoft\Yii\Debug\Collector\LogCollector; use Yiisoft\Yii\Debug\Collector\LoggerInterfaceProxy; +use Yiisoft\Yii\Debug\Collector\ServiceCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; final class ContainerProxyConfigTest extends TestCase { @@ -23,13 +24,13 @@ public function testImmutability(): void $dispatcherMock = $this->getMockBuilder(EventDispatcherInterface::class)->getMock(); $this->assertNotSame($config, $config->activate()); - $this->assertNotSame($config, $config->withCollector(new ServiceCollector())); + $this->assertNotSame($config, $config->withCollector($this->createServiceCollector())); $this->assertNotSame($config, $config->withLogLevel(1)); $this->assertNotSame($config, $config->withProxyCachePath('@tests/runtime')); $this->assertNotSame( $config, $config->withDispatcher( - new EventDispatcherInterfaceProxy($dispatcherMock, new EventCollector()) + new EventDispatcherInterfaceProxy($dispatcherMock, new EventCollector(new TimelineCollector())) ) ); $this->assertNotSame( @@ -51,7 +52,7 @@ public function testGetters(): void LoggerInterface::class => [LoggerInterfaceProxy::class, LogCollector::class], ], $dispatcherMock, - new ServiceCollector(), + $this->createServiceCollector(), '@tests/runtime', 1 ); @@ -79,4 +80,9 @@ public function testGetters(): void $this->assertFalse($config->hasDecoratedServiceArrayConfigWithStringKeys(LoggerInterface::class)); $this->assertFalse($config->hasDecoratedServiceCallableConfig(LoggerInterface::class)); } + + protected function createServiceCollector(): ServiceCollector + { + return new ServiceCollector(new TimelineCollector()); + } } diff --git a/tests/Unit/Collector/EventCollectorTest.php b/tests/Unit/Collector/EventCollectorTest.php index 4d75ef803..c32d99be8 100644 --- a/tests/Unit/Collector/EventCollectorTest.php +++ b/tests/Unit/Collector/EventCollectorTest.php @@ -6,6 +6,7 @@ use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\EventCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; use Yiisoft\Yii\Debug\Tests\Unit\Support\DummyEvent; @@ -21,7 +22,7 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new EventCollector(); + return new EventCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void diff --git a/tests/Unit/Collector/EventDispatcherProxyTest.php b/tests/Unit/Collector/EventDispatcherProxyTest.php deleted file mode 100644 index 2d0e07ffa..000000000 --- a/tests/Unit/Collector/EventDispatcherProxyTest.php +++ /dev/null @@ -1,31 +0,0 @@ -createMock(EventDispatcherInterface::class); - $collector = $this->createMock(EventCollector::class); - - $eventDispatcher->method('dispatch')->willReturn($event); - $collector - ->expects($this->once()) - ->method('collect') - ->with($event, __FILE__ . ':29'); - - $proxy = new EventDispatcherInterfaceProxy($eventDispatcher, $collector); - - $proxy->dispatch($event); - } -} diff --git a/tests/Unit/Collector/LogCollectorTest.php b/tests/Unit/Collector/LogCollectorTest.php index b7e7dbc29..3221d94cd 100644 --- a/tests/Unit/Collector/LogCollectorTest.php +++ b/tests/Unit/Collector/LogCollectorTest.php @@ -7,6 +7,7 @@ use Psr\Log\LogLevel; use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\LogCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; final class LogCollectorTest extends AbstractCollectorTestCase @@ -21,6 +22,6 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new LogCollector(); + return new LogCollector(new TimelineCollector()); } } diff --git a/tests/Unit/Collector/MiddlewareCollectorTest.php b/tests/Unit/Collector/MiddlewareCollectorTest.php index aa86e86c1..81fd4efe6 100644 --- a/tests/Unit/Collector/MiddlewareCollectorTest.php +++ b/tests/Unit/Collector/MiddlewareCollectorTest.php @@ -13,6 +13,7 @@ use Yiisoft\Middleware\Dispatcher\Event\BeforeMiddleware; use Yiisoft\Middleware\Dispatcher\MiddlewareFactory; use Yiisoft\Yii\Debug\Collector\CollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Collector\Web\MiddlewareCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; use Yiisoft\Yii\Debug\Tests\Unit\Support\DummyMiddleware; @@ -34,7 +35,7 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new MiddlewareCollector(); + return new MiddlewareCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void diff --git a/tests/Unit/Collector/RequestCollectorTest.php b/tests/Unit/Collector/RequestCollectorTest.php index 5105a374f..90a7bb7e0 100644 --- a/tests/Unit/Collector/RequestCollectorTest.php +++ b/tests/Unit/Collector/RequestCollectorTest.php @@ -9,6 +9,7 @@ use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UriInterface; use Yiisoft\Yii\Debug\Collector\CollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Collector\Web\RequestCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; use Yiisoft\Yii\Http\Event\AfterRequest; @@ -59,7 +60,7 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new RequestCollector(); + return new RequestCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void diff --git a/tests/Unit/Collector/ServiceCollectorTest.php b/tests/Unit/Collector/ServiceCollectorTest.php index 1d1805e4d..9fd2d0ae7 100644 --- a/tests/Unit/Collector/ServiceCollectorTest.php +++ b/tests/Unit/Collector/ServiceCollectorTest.php @@ -7,6 +7,7 @@ use stdClass; use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\ServiceCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; final class ServiceCollectorTest extends AbstractCollectorTestCase @@ -22,6 +23,6 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new ServiceCollector(); + return new ServiceCollector(new TimelineCollector()); } } diff --git a/tests/Unit/Collector/WebAppInfoCollectorTest.php b/tests/Unit/Collector/WebAppInfoCollectorTest.php index 763a35f9c..63ce78aef 100644 --- a/tests/Unit/Collector/WebAppInfoCollectorTest.php +++ b/tests/Unit/Collector/WebAppInfoCollectorTest.php @@ -7,6 +7,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Yiisoft\Yii\Debug\Collector\CollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Collector\Web\WebAppInfoCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; use Yiisoft\Yii\Http\Event\AfterRequest; @@ -33,7 +34,7 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new WebAppInfoCollector(); + return new WebAppInfoCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void From 5c19d736866cb7ec0180f395e184c1591375bcc9 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Thu, 7 Sep 2023 19:33:32 +0300 Subject: [PATCH 10/38] Add var-dumper handler for dev-server --- src/DevServer/VarDumperHandler.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/DevServer/VarDumperHandler.php diff --git a/src/DevServer/VarDumperHandler.php b/src/DevServer/VarDumperHandler.php new file mode 100644 index 000000000..bc4247113 --- /dev/null +++ b/src/DevServer/VarDumperHandler.php @@ -0,0 +1,22 @@ +connection = Connection::create(); + } + + public function handle(mixed $variable, int $depth, bool $highlight = false): void + { + $this->connection->broadcast(json_encode($variable)); + } +} From 392a0a93e4d8e6a6cb395965bc4892c4d1ddf84e Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Thu, 7 Sep 2023 19:34:09 +0300 Subject: [PATCH 11/38] Add Connection class to control sockets --- src/DevServer/Connection.php | 102 +++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/DevServer/Connection.php diff --git a/src/DevServer/Connection.php b/src/DevServer/Connection.php new file mode 100644 index 000000000..9ab0e4558 --- /dev/null +++ b/src/DevServer/Connection.php @@ -0,0 +1,102 @@ +socket, $file)) { + $socket_last_error = socket_last_error($this->socket); + + throw new RuntimeException( + sprintf( + 'An error occurred while reading the socket. "socket_last_error" returned %d: "%s".', + $socket_last_error, + socket_strerror($socket_last_error), + ), + ); + } + } + + public function broadcast(string $data): void + { + $files = glob(self::DEFAULT_SOCKET_DIR . '-*.sock', GLOB_NOSORT); + //echo 'Files: ' . implode(', ', $files) . "\n"; + foreach ($files as $file) { + $socket = socket_create(AF_UNIX, SOCK_DGRAM, 0); + if (!@socket_connect($socket, $file)) { + @unlink($file); + continue; + } + try { + socket_send($socket, $data, strlen($data), 0); + } catch (Throwable $e) { + unlink($file); + throw $e; + } finally { + socket_close($socket); + } + } + } + + public function close(): void + { + @socket_getsockname($this->socket, $path); + @socket_close($this->socket); + @unlink($path); + } + + public function read(Closure $onSuccess, ?Closure $onError = null): \Generator + { + while (true) { + if (!socket_recvfrom($this->socket, $buffer, 32768, MSG_DONTWAIT, $ip, $port)) { + $socket_last_error = socket_last_error($this->socket); + if ($socket_last_error === 35) { + continue; + } + $this->close(); + if ($onError !== null) { + yield $onError($socket_last_error); + break; + } + throw new RuntimeException( + sprintf( + 'An error occurred while reading the socket. socket_last_error returned %d: "%s".', + $socket_last_error, + socket_strerror($socket_last_error) + ), + ); + } + yield $onSuccess($buffer, $ip, $port); + } + } +} From 65960c5fc8bc1ed5a8c6ff0ff3644a43b2775116 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Thu, 7 Sep 2023 19:35:29 +0300 Subject: [PATCH 12/38] Add dev-server command --- src/Command/DevServerCommand.php | 120 +++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/Command/DevServerCommand.php diff --git a/src/Command/DevServerCommand.php b/src/Command/DevServerCommand.php new file mode 100644 index 000000000..b661bf805 --- /dev/null +++ b/src/Command/DevServerCommand.php @@ -0,0 +1,120 @@ +setHelp( + 'In order to access server from remote machines use 0.0.0.0:8000. That is especially useful when running server in a virtual machine.' + ) + ->addOption('address', 'a', InputOption::VALUE_OPTIONAL, 'Host to serve at', $this->address) + ->addOption('port', 'p', InputOption::VALUE_OPTIONAL, 'Port to serve at', $this->port) + ->addOption('env', 'e', InputOption::VALUE_OPTIONAL, 'It is only used for testing.'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $io->title('Yii3 Debug Server'); + $io->writeln('https://yiiframework.com' . "\n"); + + $env = $input->getOption('env'); + if ($env === 'test') { + return ExitCode::OK; + } + + $socket = Connection::create(); + $socket->bind(); + + //if (socket_recvfrom($socket, $buf, 64 * 1024, 0, $source) === false) { + // echo "recv_from failed"; + //} + //var_dump($buf); + // + //return 0; + //if (!@socket_bind($socket, $address, $port)) { + // $io->error( + // sprintf( + // 'Address "%s" is already taken by another process.', + // $this->address . ':' . $this->port, + // ) + // ); + // $io->info( + // sprintf( + // 'Would you like to kill the process and rerun?' + // ) + // ); + // $result = $io->ask('Would you like to kill the process and rerun? (Y/n)'); + // + // if ($result === 'Y') { + // // todo: change to finding a process by opened port + // $pid = shell_exec( + // sprintf( + // 'ps | grep "php ./yii dev"', + // //$this->port, + // ) + // ); + // $io->info( + // sprintf( + // 'Killing the process with ID: "%s".', + // $pid, + // ) + // ); + // //shell_exec('kill ' . $pid); + // } + // //return ExitCode::IOERR; + //} + + $io->success( + sprintf( + 'Listening on "%s:%d".', + $this->address, + $this->port, + ) + ); + + if (\function_exists('pcntl_signal')) { + $io->success('Quit the server with CTRL-C or COMMAND-C.'); + + \pcntl_signal(\SIGINT, static function () use($socket) : void { + $socket->close(); + exit(1); + }); + } + + $messages = $socket->read(fn (string $buffer) => $buffer); + foreach ($messages as $message) { + $io->writeln($message); + $io->newLine(); + } + return ExitCode::OK; + } +} From 2b05fe650746cb10dce87c9616e54b344d461d6d Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Thu, 7 Sep 2023 19:38:05 +0300 Subject: [PATCH 13/38] Add command to broadcast a message --- src/Command/DevServerBroadcastCommand.php | 59 +++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/Command/DevServerBroadcastCommand.php diff --git a/src/Command/DevServerBroadcastCommand.php b/src/Command/DevServerBroadcastCommand.php new file mode 100644 index 000000000..fc5c923f2 --- /dev/null +++ b/src/Command/DevServerBroadcastCommand.php @@ -0,0 +1,59 @@ +setHelp( + 'Broadcasts a message to all connected clients.' + ) + ->addOption('message', 'm', InputOption::VALUE_OPTIONAL, 'A text to broadcast', 'Test message') + ->addOption('env', 'e', InputOption::VALUE_OPTIONAL, 'It is only used for testing.'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $io->title('Yii3 Debug Server'); + $io->writeln('https://yiiframework.com' . "\n"); + + $env = $input->getOption('env'); + if ($env === 'test') { + return ExitCode::OK; + } + + $socket = Connection::create(); + if (\function_exists('pcntl_signal')) { + $io->success('Quit the server with CTRL-C or COMMAND-C.'); + + \pcntl_signal(\SIGINT, static function () use ($socket): void { + $socket->close(); + exit(1); + }); + } + + $data = $input->getOption('message'); + $socket->broadcast($data); + + return ExitCode::OK; + } +} From 14018361c48cc8a960074962671b9a40fc201c04 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Thu, 7 Sep 2023 19:38:15 +0300 Subject: [PATCH 14/38] Register commands --- config/params.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/params.php b/config/params.php index f6d4d2cde..26c8b553b 100644 --- a/config/params.php +++ b/config/params.php @@ -29,6 +29,8 @@ use Yiisoft\Yii\Debug\Command\DebugContainerCommand; use Yiisoft\Yii\Debug\Command\DebugEventsCommand; use Yiisoft\Yii\Debug\Command\DebugResetCommand; +use Yiisoft\Yii\Debug\Command\DevServerBroadcastCommand; +use Yiisoft\Yii\Debug\Command\DevServerCommand; /** * @var $params array @@ -96,6 +98,8 @@ DebugResetCommand::COMMAND_NAME => DebugResetCommand::class, DebugContainerCommand::COMMAND_NAME => DebugContainerCommand::class, DebugEventsCommand::COMMAND_NAME => DebugEventsCommand::class, + DevServerCommand::COMMAND_NAME => DevServerCommand::class, + DevServerBroadcastCommand::COMMAND_NAME => DevServerBroadcastCommand::class, ], ], ]; From 49d3e3ef6de28d83e5be7b7f4d169de0fc4390cd Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Thu, 7 Sep 2023 19:43:57 +0300 Subject: [PATCH 15/38] Add bootstrap --- config/bootstrap.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/config/bootstrap.php b/config/bootstrap.php index a78e5b210..2c39cd91f 100644 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -2,9 +2,12 @@ declare(strict_types=1); +use Yiisoft\VarDumper\Handler\CompositeHandler; +use Yiisoft\VarDumper\Handler\EchoHandler; use Yiisoft\VarDumper\VarDumper; use Yiisoft\Yii\Debug\Collector\VarDumperCollector; use Yiisoft\Yii\Debug\Collector\VarDumperHandlerInterfaceProxy; +use Yiisoft\Yii\Debug\DevServer\VarDumperHandler; /** * @var $params array @@ -19,9 +22,14 @@ static function ($container) use ($params) { return; } + // todo: remove VarDumperHandler if dev-server is not enabled VarDumper::setDefaultHandler( new VarDumperHandlerInterfaceProxy( - VarDumper::getDefaultHandler(), + new CompositeHandler([ + VarDumper::getDefaultHandler(), + new VarDumperHandler(), + new EchoHandler(), + ]), $container->get(VarDumperCollector::class), ), ); From 619dae809a9c56b330396949e568829caf988fd7 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Thu, 7 Sep 2023 16:44:49 +0000 Subject: [PATCH 16/38] Apply fixes from StyleCI --- src/Command/DevServerCommand.php | 8 +++++--- src/DevServer/Connection.php | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Command/DevServerCommand.php b/src/Command/DevServerCommand.php index b661bf805..acfa2e156 100644 --- a/src/Command/DevServerCommand.php +++ b/src/Command/DevServerCommand.php @@ -1,4 +1,6 @@ -success('Quit the server with CTRL-C or COMMAND-C.'); - \pcntl_signal(\SIGINT, static function () use($socket) : void { + \pcntl_signal(\SIGINT, static function () use ($socket): void { $socket->close(); exit(1); }); diff --git a/src/DevServer/Connection.php b/src/DevServer/Connection.php index 9ab0e4558..a498cd723 100644 --- a/src/DevServer/Connection.php +++ b/src/DevServer/Connection.php @@ -1,4 +1,5 @@ Date: Thu, 7 Sep 2023 16:47:52 +0000 Subject: [PATCH 17/38] Apply Rector changes (CI) --- src/DevServer/VarDumperHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DevServer/VarDumperHandler.php b/src/DevServer/VarDumperHandler.php index bc4247113..a2bc46338 100644 --- a/src/DevServer/VarDumperHandler.php +++ b/src/DevServer/VarDumperHandler.php @@ -17,6 +17,6 @@ public function __construct() public function handle(mixed $variable, int $depth, bool $highlight = false): void { - $this->connection->broadcast(json_encode($variable)); + $this->connection->broadcast(json_encode($variable, JSON_THROW_ON_ERROR)); } } From a9b10a648666e259557974b3424466c3e3e190a0 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 11 Sep 2023 03:28:05 +0700 Subject: [PATCH 18/38] Rename property --- src/Collector/LoggerInterfaceProxy.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Collector/LoggerInterfaceProxy.php b/src/Collector/LoggerInterfaceProxy.php index c23a081bf..bc01556eb 100644 --- a/src/Collector/LoggerInterfaceProxy.php +++ b/src/Collector/LoggerInterfaceProxy.php @@ -10,7 +10,7 @@ final class LoggerInterfaceProxy implements LoggerInterface { - public function __construct(private LoggerInterface $logger, private LogCollector $collector) + public function __construct(private LoggerInterface $decorated, private LogCollector $collector) { } @@ -24,7 +24,7 @@ public function emergency(string|Stringable $message, array $context = []): void $context, $callStack['file'] . ':' . $callStack['line'] ); - $this->logger->emergency($message, $context); + $this->decorated->emergency($message, $context); } public function alert(string|Stringable $message, array $context = []): void @@ -32,7 +32,7 @@ public function alert(string|Stringable $message, array $context = []): void [$callStack] = debug_backtrace(); $this->collector->collect(LogLevel::ALERT, $message, $context, $callStack['file'] . ':' . $callStack['line']); - $this->logger->alert($message, $context); + $this->decorated->alert($message, $context); } public function critical(string|Stringable $message, array $context = []): void @@ -45,7 +45,7 @@ public function critical(string|Stringable $message, array $context = []): void $context, $callStack['file'] . ':' . $callStack['line'] ); - $this->logger->critical($message, $context); + $this->decorated->critical($message, $context); } public function error(string|Stringable $message, array $context = []): void @@ -53,7 +53,7 @@ public function error(string|Stringable $message, array $context = []): void [$callStack] = debug_backtrace(); $this->collector->collect(LogLevel::ERROR, $message, $context, $callStack['file'] . ':' . $callStack['line']); - $this->logger->error($message, $context); + $this->decorated->error($message, $context); } public function warning(string|Stringable $message, array $context = []): void @@ -61,7 +61,7 @@ public function warning(string|Stringable $message, array $context = []): void [$callStack] = debug_backtrace(); $this->collector->collect(LogLevel::WARNING, $message, $context, $callStack['file'] . ':' . $callStack['line']); - $this->logger->warning($message, $context); + $this->decorated->warning($message, $context); } public function notice(string|Stringable $message, array $context = []): void @@ -69,7 +69,7 @@ public function notice(string|Stringable $message, array $context = []): void [$callStack] = debug_backtrace(); $this->collector->collect(LogLevel::NOTICE, $message, $context, $callStack['file'] . ':' . $callStack['line']); - $this->logger->notice($message, $context); + $this->decorated->notice($message, $context); } public function info(string|Stringable $message, array $context = []): void @@ -77,7 +77,7 @@ public function info(string|Stringable $message, array $context = []): void [$callStack] = debug_backtrace(); $this->collector->collect(LogLevel::INFO, $message, $context, $callStack['file'] . ':' . $callStack['line']); - $this->logger->info($message, $context); + $this->decorated->info($message, $context); } public function debug(string|Stringable $message, array $context = []): void @@ -85,7 +85,7 @@ public function debug(string|Stringable $message, array $context = []): void [$callStack] = debug_backtrace(); $this->collector->collect(LogLevel::DEBUG, $message, $context, $callStack['file'] . ':' . $callStack['line']); - $this->logger->debug($message, $context); + $this->decorated->debug($message, $context); } public function log(mixed $level, string|Stringable $message, array $context = []): void @@ -93,6 +93,6 @@ public function log(mixed $level, string|Stringable $message, array $context = [ [$callStack] = debug_backtrace(); $this->collector->collect($level, $message, $context, $callStack['file'] . ':' . $callStack['line']); - $this->logger->log($level, $message, $context); + $this->decorated->log($level, $message, $context); } } From 411df8aa71ea381eb54aa713903b6cff9f3c4577 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 11 Sep 2023 03:28:27 +0700 Subject: [PATCH 19/38] Pass decorated instance to proxy callback factory --- src/Collector/ContainerInterfaceProxy.php | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/Collector/ContainerInterfaceProxy.php b/src/Collector/ContainerInterfaceProxy.php index 962833717..f5087efac 100644 --- a/src/Collector/ContainerInterfaceProxy.php +++ b/src/Collector/ContainerInterfaceProxy.php @@ -26,8 +26,6 @@ class ContainerInterfaceProxy implements ContainerInterface private ProxyManager $proxyManager; - private array $decoratedServices = []; - private array $serviceProxy = []; public function __construct(protected ContainerInterface $container, ContainerProxyConfig $config) @@ -59,10 +57,7 @@ public function get($id) $this->logProxy(ContainerInterface::class, $this->container, 'get', [$id], $instance, $timeStart); } - if ( - is_object($instance) - && (($proxy = $this->getServiceProxyCache($id)) || ($proxy = $this->getServiceProxy($id, $instance))) - ) { + if (is_object($instance) && ($proxy = $this->getServiceProxy($id, $instance))) { $this->setServiceProxyCache($id, $proxy); return $proxy; } @@ -89,19 +84,18 @@ public function isActive(): bool return $this->config->getIsActive() && $this->config->getDecoratedServices() !== []; } - private function getServiceProxyCache(string $service): ?object - { - return $this->serviceProxy[$service] ?? null; - } - private function getServiceProxy(string $service, object $instance): ?object { + if (isset($this->serviceProxy[$service])) { + return $this->serviceProxy[$service]; + } + if (!$this->isDecorated($service)) { return null; } if ($this->config->hasDecoratedServiceCallableConfig($service)) { - return $this->getServiceProxyFromCallable($this->config->getDecoratedServiceConfig($service)); + return $this->getServiceProxyFromCallable($this->config->getDecoratedServiceConfig($service), $instance); } if ($this->config->hasDecoratedServiceArrayConfigWithStringKeys($service)) { @@ -119,9 +113,9 @@ private function getServiceProxy(string $service, object $instance): ?object return null; } - private function getServiceProxyFromCallable(callable $callback): ?object + private function getServiceProxyFromCallable(callable $callback, object $instance): ?object { - return $callback($this); + return $callback($this, $instance); } private function getCommonMethodProxy(string $service, object $instance, array $callbacks): ?object From a5d1108a3f88809a90e560acf5911881d2cf2abe Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 11 Sep 2023 03:29:07 +0700 Subject: [PATCH 20/38] Use generators for reading --- src/Command/DevServerCommand.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Command/DevServerCommand.php b/src/Command/DevServerCommand.php index acfa2e156..216784479 100644 --- a/src/Command/DevServerCommand.php +++ b/src/Command/DevServerCommand.php @@ -112,10 +112,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int }); } - $messages = $socket->read(fn (string $buffer) => $buffer); - foreach ($messages as $message) { - $io->writeln($message); - $io->newLine(); + foreach ($socket->read() as $message) { + switch ($message[0]) { + case Connection::TYPE_ERROR: + $io->writeln('Connection closed with error: ' . $message[1]); + break 2; + default: + $io->writeln($message[1]); + $io->newLine(); + } } return ExitCode::OK; } From becbf1e0408b14aba58a258a8ba83e1a27023e2f Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 11 Sep 2023 03:29:57 +0700 Subject: [PATCH 21/38] Improve socket wrapper --- src/DevServer/Connection.php | 111 +++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 36 deletions(-) diff --git a/src/DevServer/Connection.php b/src/DevServer/Connection.php index a498cd723..19b85e787 100644 --- a/src/DevServer/Connection.php +++ b/src/DevServer/Connection.php @@ -6,15 +6,26 @@ namespace Yiisoft\Yii\Debug\DevServer; -use Closure; +use Generator; use RuntimeException; use Socket; use Throwable; +/** + * List of socket errors: {@see https://www.ibm.com/docs/en/zos/2.4.0?topic=calls-sockets-return-codes-errnos} + */ final class Connection { - public const DEFAULT_SOCKET_DIR = '/tmp/var-dumper'; - public const DEFAULT_SOCKET_URL = '/tmp/var-dumper-%d.sock'; + public const DEFAULT_TIMEOUT = 10 * 1000; // 10 milliseconds + public const DEFAULT_BUFFER_SIZE = 1 * 1024; // 1 kilobyte + + public const TYPE_RESULT = 0x001b; + public const TYPE_ERROR = 0x002b; + + public const MESSAGE_TYPE_VAR_DUMPER = 0x001b; + public const MESSAGE_TYPE_LOGGER = 0x002b; + + private string $uri; public function __construct( private Socket $socket, @@ -25,6 +36,18 @@ public static function create(): self { $socket = socket_create(AF_UNIX, SOCK_DGRAM, 0); + $socket_last_error = socket_last_error($socket); + + if ($socket_last_error) { + throw new RuntimeException( + sprintf( + '"socket_last_error" returned %d: "%s".', + $socket_last_error, + socket_strerror($socket_last_error), + ), + ); + } + return new self( $socket, ); @@ -33,7 +56,8 @@ public static function create(): self public function bind(): void { $n = random_int(0, PHP_INT_MAX); - $file = sprintf(self::DEFAULT_SOCKET_URL, $n); + $file = sprintf(sys_get_temp_dir() . '/yii-dev-server-%d.sock', $n); + $this->uri = $file; if (!socket_bind($this->socket, $file)) { $socket_last_error = socket_last_error($this->socket); @@ -47,56 +71,71 @@ public function bind(): void } } - public function broadcast(string $data): void + /** + * @return Generator + */ + public function read(): Generator { - $files = glob(self::DEFAULT_SOCKET_DIR . '-*.sock', GLOB_NOSORT); + while (true) { + if (!socket_recvfrom($this->socket, $buffer, self::DEFAULT_BUFFER_SIZE, MSG_DONTWAIT, $ip, $port)) { + $socket_last_error = socket_last_error($this->socket); + if ($socket_last_error === 35) { + usleep(self::DEFAULT_TIMEOUT); + continue; + } + $this->close(); + yield [self::TYPE_ERROR, $socket_last_error, socket_strerror($socket_last_error)]; + continue; + } + yield [self::TYPE_RESULT, $buffer, $ip, $port]; + } + } + + public function broadcast(int $type, string $data): void + { + $files = glob(sys_get_temp_dir() . '/yii-dev-server-*.sock', GLOB_NOSORT); //echo 'Files: ' . implode(', ', $files) . "\n"; + $uniqueErrors = []; + $payload = json_encode([$type, $data]); + $payloadLength = strlen($payload); foreach ($files as $file) { - $socket = socket_create(AF_UNIX, SOCK_DGRAM, 0); - if (!@socket_connect($socket, $file)) { + $socket = @fsockopen('udg://' . $file, -1, $errno, $errstr); + if ($errno === 61) { @unlink($file); continue; } + if ($errno !== 0) { + $uniqueErrors[$errno] = $errstr; + continue; + } try { - socket_send($socket, $data, strlen($data), 0); + if (!@fwrite($socket, $payload, $payloadLength)) { + $err = socket_last_error($socket); + $uniqueErrors[$err] = socket_strerror($err); + /** + * Connection is closed. + */ + continue; + } } catch (Throwable $e) { - unlink($file); + //@unlink($file); throw $e; } finally { - socket_close($socket); + fflush($socket); + fclose($socket); } } } + public function getUri(): string + { + return $this->uri; + } + public function close(): void { @socket_getsockname($this->socket, $path); @socket_close($this->socket); @unlink($path); } - - public function read(Closure $onSuccess, ?Closure $onError = null): \Generator - { - while (true) { - if (!socket_recvfrom($this->socket, $buffer, 32768, MSG_DONTWAIT, $ip, $port)) { - $socket_last_error = socket_last_error($this->socket); - if ($socket_last_error === 35) { - continue; - } - $this->close(); - if ($onError !== null) { - yield $onError($socket_last_error); - break; - } - throw new RuntimeException( - sprintf( - 'An error occurred while reading the socket. socket_last_error returned %d: "%s".', - $socket_last_error, - socket_strerror($socket_last_error) - ), - ); - } - yield $onSuccess($buffer, $ip, $port); - } - } } From a365faa93b5fc8cf3686e76eeddd2aec4253f630 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 11 Sep 2023 03:30:29 +0700 Subject: [PATCH 22/38] Improve VarDumperHandler --- src/DevServer/VarDumperHandler.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DevServer/VarDumperHandler.php b/src/DevServer/VarDumperHandler.php index a2bc46338..c96488ead 100644 --- a/src/DevServer/VarDumperHandler.php +++ b/src/DevServer/VarDumperHandler.php @@ -5,6 +5,7 @@ namespace Yiisoft\Yii\Debug\DevServer; use Yiisoft\VarDumper\HandlerInterface; +use Yiisoft\VarDumper\VarDumper; final class VarDumperHandler implements HandlerInterface { @@ -17,6 +18,6 @@ public function __construct() public function handle(mixed $variable, int $depth, bool $highlight = false): void { - $this->connection->broadcast(json_encode($variable, JSON_THROW_ON_ERROR)); + $this->connection->broadcast(Connection::MESSAGE_TYPE_VAR_DUMPER, VarDumper::create($variable)->asJson(false)); } } From ac0b04cf3157e7546a4d691346c44f73a52fe7a7 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 11 Sep 2023 03:30:37 +0700 Subject: [PATCH 23/38] Add LoggerDecorator --- src/DevServer/LoggerDecorator.php | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/DevServer/LoggerDecorator.php diff --git a/src/DevServer/LoggerDecorator.php b/src/DevServer/LoggerDecorator.php new file mode 100644 index 000000000..0e5ad5a37 --- /dev/null +++ b/src/DevServer/LoggerDecorator.php @@ -0,0 +1,31 @@ +connection = Connection::create(); + } + + public function log($level, Stringable|string $message, array $context = []): void + { + $this->connection->broadcast( + Connection::MESSAGE_TYPE_LOGGER, + VarDumper::create(['message' => $message, 'context' => $context])->asJson(false) + ); + $this->decorated->log($level, $message, $context); + } +} From fcfc53b742a7a7dcadbf1b77e8cd54e97abfb309 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 11 Sep 2023 03:31:00 +0700 Subject: [PATCH 24/38] Add logger wrapper --- config/params.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config/params.php b/config/params.php index 26c8b553b..3c69d03c3 100644 --- a/config/params.php +++ b/config/params.php @@ -31,6 +31,7 @@ use Yiisoft\Yii\Debug\Command\DebugResetCommand; use Yiisoft\Yii\Debug\Command\DevServerBroadcastCommand; use Yiisoft\Yii\Debug\Command\DevServerCommand; +use Yiisoft\Yii\Debug\DevServer\LoggerDecorator; /** * @var $params array @@ -61,7 +62,10 @@ ], 'trackedServices' => [ Injector::class => fn (ContainerInterface $container) => new Injector($container), - LoggerInterface::class => [LoggerInterfaceProxy::class, LogCollector::class], + LoggerInterface::class => function (ContainerInterface $container, LoggerInterface $logger) { + return new LoggerInterfaceProxy(new LoggerDecorator($logger), $container->get(LogCollector::class)); + }, + //LoggerInterface::class => [LoggerInterfaceProxy::class, LogCollector::class], EventDispatcherInterface::class => [EventDispatcherInterfaceProxy::class, EventCollector::class], ClientInterface::class => [HttpClientInterfaceProxy::class, HttpClientCollector::class], CacheInterface::class, From 939d44c6b6609aa738a6487790a08beeb21ce2bf Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 11 Sep 2023 03:31:07 +0700 Subject: [PATCH 25/38] Add types --- config/bootstrap.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/config/bootstrap.php b/config/bootstrap.php index 2c39cd91f..8d9fce471 100644 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -2,8 +2,8 @@ declare(strict_types=1); +use Psr\Container\ContainerInterface; use Yiisoft\VarDumper\Handler\CompositeHandler; -use Yiisoft\VarDumper\Handler\EchoHandler; use Yiisoft\VarDumper\VarDumper; use Yiisoft\Yii\Debug\Collector\VarDumperCollector; use Yiisoft\Yii\Debug\Collector\VarDumperHandlerInterfaceProxy; @@ -14,7 +14,7 @@ */ return [ - static function ($container) use ($params) { + static function (ContainerInterface $container) use ($params) { if (!($params['yiisoft/yii-debug']['enabled'] ?? false)) { return; } @@ -28,7 +28,6 @@ static function ($container) use ($params) { new CompositeHandler([ VarDumper::getDefaultHandler(), new VarDumperHandler(), - new EchoHandler(), ]), $container->get(VarDumperCollector::class), ), From 5b7fc1f5e8c187bd518972d7685cab56168ffe6d Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sun, 10 Sep 2023 20:32:46 +0000 Subject: [PATCH 26/38] Apply fixes from StyleCI --- src/DevServer/Connection.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/DevServer/Connection.php b/src/DevServer/Connection.php index 19b85e787..28a1abdd0 100644 --- a/src/DevServer/Connection.php +++ b/src/DevServer/Connection.php @@ -19,11 +19,11 @@ final class Connection public const DEFAULT_TIMEOUT = 10 * 1000; // 10 milliseconds public const DEFAULT_BUFFER_SIZE = 1 * 1024; // 1 kilobyte - public const TYPE_RESULT = 0x001b; - public const TYPE_ERROR = 0x002b; + public const TYPE_RESULT = 0x001B; + public const TYPE_ERROR = 0x002B; - public const MESSAGE_TYPE_VAR_DUMPER = 0x001b; - public const MESSAGE_TYPE_LOGGER = 0x002b; + public const MESSAGE_TYPE_VAR_DUMPER = 0x001B; + public const MESSAGE_TYPE_LOGGER = 0x002B; private string $uri; @@ -72,7 +72,7 @@ public function bind(): void } /** - * @return Generator + * @return Generator */ public function read(): Generator { @@ -111,7 +111,7 @@ public function broadcast(int $type, string $data): void try { if (!@fwrite($socket, $payload, $payloadLength)) { $err = socket_last_error($socket); - $uniqueErrors[$err] = socket_strerror($err); + $uniqueErrors[$err] = socket_strerror($err); /** * Connection is closed. */ From d90803d4f78289846a92d8a50d6ea32d360570f4 Mon Sep 17 00:00:00 2001 From: xepozz Date: Sun, 10 Sep 2023 20:34:21 +0000 Subject: [PATCH 27/38] Apply Rector changes (CI) --- src/DevServer/Connection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DevServer/Connection.php b/src/DevServer/Connection.php index 28a1abdd0..5aa21492f 100644 --- a/src/DevServer/Connection.php +++ b/src/DevServer/Connection.php @@ -96,7 +96,7 @@ public function broadcast(int $type, string $data): void $files = glob(sys_get_temp_dir() . '/yii-dev-server-*.sock', GLOB_NOSORT); //echo 'Files: ' . implode(', ', $files) . "\n"; $uniqueErrors = []; - $payload = json_encode([$type, $data]); + $payload = json_encode([$type, $data], JSON_THROW_ON_ERROR); $payloadLength = strlen($payload); foreach ($files as $file) { $socket = @fsockopen('udg://' . $file, -1, $errno, $errstr); From 0c5e8338242e514e5704d6433acfadb5ec6225b5 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 11 Sep 2023 14:00:53 +0700 Subject: [PATCH 28/38] Specify type of debug message --- src/Command/DevServerCommand.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Command/DevServerCommand.php b/src/Command/DevServerCommand.php index 216784479..1151cbcc0 100644 --- a/src/Command/DevServerCommand.php +++ b/src/Command/DevServerCommand.php @@ -97,9 +97,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->success( sprintf( - 'Listening on "%s:%d".', - $this->address, - $this->port, + 'Listening on "%s".', + $socket->getUri(), ) ); @@ -118,7 +117,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->writeln('Connection closed with error: ' . $message[1]); break 2; default: - $io->writeln($message[1]); + $data = \json_decode($message[1]); + if ($data[0] === Connection::MESSAGE_TYPE_VAR_DUMPER) { + $io->title('VarDumper'); + } elseif($data[0] === Connection::MESSAGE_TYPE_LOGGER) { + $io->write('Logger'); + } + $io->writeln( + sprintf( + "\033[1;37m\033[47m%s\033[0m", + $data[1] + )); $io->newLine(); } } From 177e8556e04d4b61976987692e31c26e8c0c7432 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 11 Sep 2023 07:03:18 +0000 Subject: [PATCH 29/38] Apply fixes from StyleCI --- src/Command/DevServerCommand.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Command/DevServerCommand.php b/src/Command/DevServerCommand.php index 1151cbcc0..955d818b3 100644 --- a/src/Command/DevServerCommand.php +++ b/src/Command/DevServerCommand.php @@ -120,14 +120,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int $data = \json_decode($message[1]); if ($data[0] === Connection::MESSAGE_TYPE_VAR_DUMPER) { $io->title('VarDumper'); - } elseif($data[0] === Connection::MESSAGE_TYPE_LOGGER) { + } elseif ($data[0] === Connection::MESSAGE_TYPE_LOGGER) { $io->write('Logger'); } $io->writeln( sprintf( "\033[1;37m\033[47m%s\033[0m", $data[1] - )); + ) + ); $io->newLine(); } } From b6ff4c96151d2a88a67e2101bf1db81b4c69dcf4 Mon Sep 17 00:00:00 2001 From: xepozz Date: Mon, 11 Sep 2023 07:03:55 +0000 Subject: [PATCH 30/38] Apply Rector changes (CI) --- src/Command/DevServerCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/DevServerCommand.php b/src/Command/DevServerCommand.php index 955d818b3..b5cf78524 100644 --- a/src/Command/DevServerCommand.php +++ b/src/Command/DevServerCommand.php @@ -117,7 +117,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->writeln('Connection closed with error: ' . $message[1]); break 2; default: - $data = \json_decode($message[1]); + $data = \json_decode($message[1], null, 512, JSON_THROW_ON_ERROR); if ($data[0] === Connection::MESSAGE_TYPE_VAR_DUMPER) { $io->title('VarDumper'); } elseif ($data[0] === Connection::MESSAGE_TYPE_LOGGER) { From bdc67c36276df57cd944e8a3243915c939b5fa9a Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 16 Sep 2023 15:55:39 +0300 Subject: [PATCH 31/38] Add chunking --- src/DevServer/Connection.php | 53 ++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/src/DevServer/Connection.php b/src/DevServer/Connection.php index 5aa21492f..c565588d1 100644 --- a/src/DevServer/Connection.php +++ b/src/DevServer/Connection.php @@ -77,7 +77,7 @@ public function bind(): void public function read(): Generator { while (true) { - if (!socket_recvfrom($this->socket, $buffer, self::DEFAULT_BUFFER_SIZE, MSG_DONTWAIT, $ip, $port)) { + if (!socket_recvfrom($this->socket, $header, self::DEFAULT_BUFFER_SIZE, MSG_DONTWAIT, $ip, $port)) { $socket_last_error = socket_last_error($this->socket); if ($socket_last_error === 35) { usleep(self::DEFAULT_TIMEOUT); @@ -87,16 +87,35 @@ public function read(): Generator yield [self::TYPE_ERROR, $socket_last_error, socket_strerror($socket_last_error)]; continue; } - yield [self::TYPE_RESULT, $buffer, $ip, $port]; + + $length = unpack('N', $header); + $localBuffer = ''; + $bytesToRead = $length[1]; + $bytesRead = 0; + while ($bytesRead < $bytesToRead) { + if (!$bufferLength = socket_recvfrom($this->socket, $buffer, self::DEFAULT_BUFFER_SIZE, MSG_DONTWAIT, $ip, $port)) { + $socket_last_error = socket_last_error($this->socket); + if ($socket_last_error === 35) { + usleep(self::DEFAULT_TIMEOUT); + continue; + } + $this->close(); + break; + } + + $localBuffer .= $buffer; + $bytesRead += $bufferLength; + } + yield [self::TYPE_RESULT, base64_decode($localBuffer), $ip, $port]; } } - public function broadcast(int $type, string $data): void + public function broadcast(int $type, string $data): array { $files = glob(sys_get_temp_dir() . '/yii-dev-server-*.sock', GLOB_NOSORT); //echo 'Files: ' . implode(', ', $files) . "\n"; $uniqueErrors = []; - $payload = json_encode([$type, $data], JSON_THROW_ON_ERROR); + $payload = base64_encode(json_encode([$type, $data], JSON_THROW_ON_ERROR)); $payloadLength = strlen($payload); foreach ($files as $file) { $socket = @fsockopen('udg://' . $file, -1, $errno, $errstr); @@ -109,9 +128,8 @@ public function broadcast(int $type, string $data): void continue; } try { - if (!@fwrite($socket, $payload, $payloadLength)) { - $err = socket_last_error($socket); - $uniqueErrors[$err] = socket_strerror($err); + if (!$this->fwriteStream($socket, $payload)) { + $uniqueErrors[] = error_get_last(); /** * Connection is closed. */ @@ -121,10 +139,11 @@ public function broadcast(int $type, string $data): void //@unlink($file); throw $e; } finally { - fflush($socket); + //fflush($socket); fclose($socket); } } + return $uniqueErrors; } public function getUri(): string @@ -138,4 +157,22 @@ public function close(): void @socket_close($this->socket); @unlink($path); } + + /** + * @param resource $fp + */ + private function fwriteStream($fp, string $data): int|false + { + $strlen = strlen($data); + fwrite($fp, pack('N', $strlen)); + for ($written = 0; $written < $strlen; $written += $fwrite) { + $fwrite = fwrite($fp, substr($data, $written), self::DEFAULT_BUFFER_SIZE); + //\fflush($fp); + usleep(self::DEFAULT_TIMEOUT / 2); + if ($fwrite === false) { + return $written; + } + } + return $written; + } } From 0fb8c48b4f648fcfcb2a9e3870521b1ff15a7c9a Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 16 Sep 2023 16:51:22 +0300 Subject: [PATCH 32/38] Rename dev server -> debug server --- config/bootstrap.php | 2 +- config/params.php | 10 +++++----- ...castCommand.php => DebugServerBroadcastCommand.php} | 4 ++-- .../{DevServerCommand.php => DebugServerCommand.php} | 4 ++-- src/{DevServer => DebugServer}/Connection.php | 2 +- src/{DevServer => DebugServer}/LoggerDecorator.php | 2 +- src/{DevServer => DebugServer}/VarDumperHandler.php | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) rename src/Command/{DevServerBroadcastCommand.php => DebugServerBroadcastCommand.php} (94%) rename src/Command/{DevServerCommand.php => DebugServerCommand.php} (98%) rename src/{DevServer => DebugServer}/Connection.php (99%) rename src/{DevServer => DebugServer}/LoggerDecorator.php (94%) rename src/{DevServer => DebugServer}/VarDumperHandler.php (92%) diff --git a/config/bootstrap.php b/config/bootstrap.php index 8d9fce471..f8229a9ab 100644 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -7,7 +7,7 @@ use Yiisoft\VarDumper\VarDumper; use Yiisoft\Yii\Debug\Collector\VarDumperCollector; use Yiisoft\Yii\Debug\Collector\VarDumperHandlerInterfaceProxy; -use Yiisoft\Yii\Debug\DevServer\VarDumperHandler; +use Yiisoft\Yii\Debug\DebugServer\VarDumperHandler; /** * @var $params array diff --git a/config/params.php b/config/params.php index 3c69d03c3..b3089e368 100644 --- a/config/params.php +++ b/config/params.php @@ -29,9 +29,9 @@ use Yiisoft\Yii\Debug\Command\DebugContainerCommand; use Yiisoft\Yii\Debug\Command\DebugEventsCommand; use Yiisoft\Yii\Debug\Command\DebugResetCommand; -use Yiisoft\Yii\Debug\Command\DevServerBroadcastCommand; -use Yiisoft\Yii\Debug\Command\DevServerCommand; -use Yiisoft\Yii\Debug\DevServer\LoggerDecorator; +use Yiisoft\Yii\Debug\Command\DebugServerBroadcastCommand; +use Yiisoft\Yii\Debug\Command\DebugServerCommand; +use Yiisoft\Yii\Debug\DebugServer\LoggerDecorator; /** * @var $params array @@ -102,8 +102,8 @@ DebugResetCommand::COMMAND_NAME => DebugResetCommand::class, DebugContainerCommand::COMMAND_NAME => DebugContainerCommand::class, DebugEventsCommand::COMMAND_NAME => DebugEventsCommand::class, - DevServerCommand::COMMAND_NAME => DevServerCommand::class, - DevServerBroadcastCommand::COMMAND_NAME => DevServerBroadcastCommand::class, + DebugServerCommand::COMMAND_NAME => DebugServerCommand::class, + DebugServerBroadcastCommand::COMMAND_NAME => DebugServerBroadcastCommand::class, ], ], ]; diff --git a/src/Command/DevServerBroadcastCommand.php b/src/Command/DebugServerBroadcastCommand.php similarity index 94% rename from src/Command/DevServerBroadcastCommand.php rename to src/Command/DebugServerBroadcastCommand.php index fc5c923f2..1446b5136 100644 --- a/src/Command/DevServerBroadcastCommand.php +++ b/src/Command/DebugServerBroadcastCommand.php @@ -11,9 +11,9 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Yiisoft\Yii\Console\ExitCode; -use Yiisoft\Yii\Debug\DevServer\Connection; +use Yiisoft\Yii\Debug\DebugServer\Connection; -final class DevServerBroadcastCommand extends Command +final class DebugServerBroadcastCommand extends Command { public const COMMAND_NAME = 'dev:broadcast'; protected static $defaultName = self::COMMAND_NAME; diff --git a/src/Command/DevServerCommand.php b/src/Command/DebugServerCommand.php similarity index 98% rename from src/Command/DevServerCommand.php rename to src/Command/DebugServerCommand.php index b5cf78524..6c659d14d 100644 --- a/src/Command/DevServerCommand.php +++ b/src/Command/DebugServerCommand.php @@ -14,9 +14,9 @@ use Symfony\Component\Console\SignalRegistry\SignalRegistry; use Symfony\Component\Console\Style\SymfonyStyle; use Yiisoft\Yii\Console\ExitCode; -use Yiisoft\Yii\Debug\DevServer\Connection; +use Yiisoft\Yii\Debug\DebugServer\Connection; -final class DevServerCommand extends Command +final class DebugServerCommand extends Command { public const COMMAND_NAME = 'dev'; protected static $defaultName = self::COMMAND_NAME; diff --git a/src/DevServer/Connection.php b/src/DebugServer/Connection.php similarity index 99% rename from src/DevServer/Connection.php rename to src/DebugServer/Connection.php index c565588d1..8904b2eaa 100644 --- a/src/DevServer/Connection.php +++ b/src/DebugServer/Connection.php @@ -4,7 +4,7 @@ declare(strict_types=1); -namespace Yiisoft\Yii\Debug\DevServer; +namespace Yiisoft\Yii\Debug\DebugServer; use Generator; use RuntimeException; diff --git a/src/DevServer/LoggerDecorator.php b/src/DebugServer/LoggerDecorator.php similarity index 94% rename from src/DevServer/LoggerDecorator.php rename to src/DebugServer/LoggerDecorator.php index 0e5ad5a37..f7693cb29 100644 --- a/src/DevServer/LoggerDecorator.php +++ b/src/DebugServer/LoggerDecorator.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\Yii\Debug\DevServer; +namespace Yiisoft\Yii\Debug\DebugServer; use Psr\Log\LoggerInterface; use Psr\Log\LoggerTrait; diff --git a/src/DevServer/VarDumperHandler.php b/src/DebugServer/VarDumperHandler.php similarity index 92% rename from src/DevServer/VarDumperHandler.php rename to src/DebugServer/VarDumperHandler.php index c96488ead..496186992 100644 --- a/src/DevServer/VarDumperHandler.php +++ b/src/DebugServer/VarDumperHandler.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\Yii\Debug\DevServer; +namespace Yiisoft\Yii\Debug\DebugServer; use Yiisoft\VarDumper\HandlerInterface; use Yiisoft\VarDumper\VarDumper; From c8e9248d942593be8966b5cc82cd1b10b578b0d5 Mon Sep 17 00:00:00 2001 From: Dmitriy Derepko Date: Sat, 16 Sep 2023 19:07:01 +0300 Subject: [PATCH 33/38] Timeline collector (#224) * Add timeline collector * Change timeline signature * Use microtime instead of time * Remove redundant property * Apply fixes from StyleCI * Fix tests & psalm errors --------- Co-authored-by: StyleCI Bot --- config/params.php | 2 ++ src/Collector/Console/CommandCollector.php | 7 ++++ .../Console/ConsoleAppInfoCollector.php | 7 ++++ src/Collector/EventCollector.php | 12 +++---- src/Collector/ExceptionCollector.php | 5 +++ src/Collector/HttpClientCollector.php | 5 +++ src/Collector/LogCollector.php | 5 +++ src/Collector/ServiceCollector.php | 5 +++ src/Collector/TimelineCollector.php | 34 +++++++++++++++++++ src/Collector/VarDumperCollector.php | 7 +++- .../VarDumperHandlerInterfaceProxy.php | 2 +- src/Collector/Web/MiddlewareCollector.php | 6 ++++ src/Collector/Web/RequestCollector.php | 27 +++++---------- src/Collector/Web/WebAppInfoCollector.php | 10 ++++-- tests/Unit/Collector/CommandCollectorTest.php | 3 +- .../Collector/ConsoleAppInfoCollectorTest.php | 4 +-- .../Collector/ContainerInterfaceProxyTest.php | 20 +++++++---- .../Collector/ContainerProxyConfigTest.php | 18 ++++++---- tests/Unit/Collector/EventCollectorTest.php | 3 +- .../Collector/EventDispatcherProxyTest.php | 31 ----------------- tests/Unit/Collector/LogCollectorTest.php | 3 +- .../Collector/MiddlewareCollectorTest.php | 3 +- tests/Unit/Collector/RequestCollectorTest.php | 3 +- tests/Unit/Collector/ServiceCollectorTest.php | 3 +- .../Collector/WebAppInfoCollectorTest.php | 3 +- 25 files changed, 146 insertions(+), 82 deletions(-) create mode 100644 src/Collector/TimelineCollector.php delete mode 100644 tests/Unit/Collector/EventDispatcherProxyTest.php diff --git a/config/params.php b/config/params.php index 08a65b973..f6d4d2cde 100644 --- a/config/params.php +++ b/config/params.php @@ -21,6 +21,7 @@ use Yiisoft\Yii\Debug\Collector\ServiceCollector; use Yiisoft\Yii\Debug\Collector\Stream\FilesystemStreamCollector; use Yiisoft\Yii\Debug\Collector\Stream\HttpStreamCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Collector\VarDumperCollector; use Yiisoft\Yii\Debug\Collector\Web\MiddlewareCollector; use Yiisoft\Yii\Debug\Collector\Web\RequestCollector; @@ -45,6 +46,7 @@ HttpStreamCollector::class, ExceptionCollector::class, VarDumperCollector::class, + TimelineCollector::class, ], 'collectors.web' => [ WebAppInfoCollector::class, diff --git a/src/Collector/Console/CommandCollector.php b/src/Collector/Console/CommandCollector.php index 1ec359e42..b97e64f1f 100644 --- a/src/Collector/Console/CommandCollector.php +++ b/src/Collector/Console/CommandCollector.php @@ -13,6 +13,7 @@ use Yiisoft\Yii\Console\Output\ConsoleBufferedOutput; use Yiisoft\Yii\Debug\Collector\CollectorTrait; use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; final class CommandCollector implements SummaryCollectorInterface { @@ -24,6 +25,10 @@ final class CommandCollector implements SummaryCollectorInterface private const UNDEFINED_EXIT_CODE = -1; private array $commands = []; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { return $this->commands; @@ -35,6 +40,8 @@ public function collect(ConsoleEvent|ConsoleErrorEvent|ConsoleTerminateEvent $ev return; } + $this->timelineCollector->collect($this, spl_object_id($event)); + $command = $event->getCommand(); if ($event instanceof ConsoleErrorEvent) { diff --git a/src/Collector/Console/ConsoleAppInfoCollector.php b/src/Collector/Console/ConsoleAppInfoCollector.php index 68bac3025..3211ee285 100644 --- a/src/Collector/Console/ConsoleAppInfoCollector.php +++ b/src/Collector/Console/ConsoleAppInfoCollector.php @@ -11,6 +11,7 @@ use Yiisoft\Yii\Console\Event\ApplicationStartup; use Yiisoft\Yii\Debug\Collector\CollectorTrait; use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; final class ConsoleAppInfoCollector implements SummaryCollectorInterface { @@ -21,6 +22,10 @@ final class ConsoleAppInfoCollector implements SummaryCollectorInterface private float $requestProcessingTimeStarted = 0; private float $requestProcessingTimeStopped = 0; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -58,6 +63,8 @@ public function collect(object $event): void } elseif ($event instanceof ApplicationShutdown) { $this->applicationProcessingTimeStopped = microtime(true); } + + $this->timelineCollector->collect($this, spl_object_id($event)); } public function getSummary(): array diff --git a/src/Collector/EventCollector.php b/src/Collector/EventCollector.php index ecc89318b..d04985ca3 100644 --- a/src/Collector/EventCollector.php +++ b/src/Collector/EventCollector.php @@ -8,12 +8,16 @@ use Yiisoft\Yii\Console\Event\ApplicationStartup as ConsoleApplicationStartup; use Yiisoft\Yii\Http\Event\ApplicationStartup as HttpApplicationStartup; -class EventCollector implements SummaryCollectorInterface +final class EventCollector implements SummaryCollectorInterface { use CollectorTrait; private array $events = []; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -32,11 +36,6 @@ public function collect(object $event, string $line): void return; } - $this->collectEvent($event, $line); - } - - private function collectEvent(object $event, $line): void - { $this->events[] = [ 'name' => $event::class, 'event' => $event, @@ -44,6 +43,7 @@ private function collectEvent(object $event, $line): void 'line' => $line, 'time' => microtime(true), ]; + $this->timelineCollector->collect($this, spl_object_id($event), $event::class); } public function getSummary(): array diff --git a/src/Collector/ExceptionCollector.php b/src/Collector/ExceptionCollector.php index 8f3e7cff6..f0ae8bfaa 100644 --- a/src/Collector/ExceptionCollector.php +++ b/src/Collector/ExceptionCollector.php @@ -13,6 +13,10 @@ final class ExceptionCollector implements SummaryCollectorInterface private ?Throwable $exception = null; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if ($this->exception === null) { @@ -36,6 +40,7 @@ public function collect(ApplicationError $error): void } $this->exception = $error->getThrowable(); + $this->timelineCollector->collect($this, $error::class); } public function getSummary(): array diff --git a/src/Collector/HttpClientCollector.php b/src/Collector/HttpClientCollector.php index 0304ef2fe..5e96aac43 100644 --- a/src/Collector/HttpClientCollector.php +++ b/src/Collector/HttpClientCollector.php @@ -29,6 +29,10 @@ final class HttpClientCollector implements SummaryCollectorInterface */ private array $requests = []; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { return array_merge(...array_values($this->requests)); @@ -66,6 +70,7 @@ public function collect(RequestInterface $request, float|string $startTime, stri 'headers' => $request->getHeaders(), 'line' => $line, ]; + $this->timelineCollector->collect($this, $uniqueId); } public function collectTotalTime(?ResponseInterface $response, float|string $startTime, ?string $uniqueId): void diff --git a/src/Collector/LogCollector.php b/src/Collector/LogCollector.php index 5d963b47c..291f44872 100644 --- a/src/Collector/LogCollector.php +++ b/src/Collector/LogCollector.php @@ -10,6 +10,10 @@ class LogCollector implements SummaryCollectorInterface private array $messages = []; + public function __construct(private TimelineCollector $timelineCollector, ) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -31,6 +35,7 @@ public function collect(string $level, $message, array $context, string $line): 'context' => $context, 'line' => $line, ]; + $this->timelineCollector->collect($this, count($this->messages)); } private function reset(): void diff --git a/src/Collector/ServiceCollector.php b/src/Collector/ServiceCollector.php index 569888561..e281bc16d 100644 --- a/src/Collector/ServiceCollector.php +++ b/src/Collector/ServiceCollector.php @@ -10,6 +10,10 @@ final class ServiceCollector implements SummaryCollectorInterface private array $items = []; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -44,6 +48,7 @@ public function collect( 'timeStart' => $timeStart, 'timeEnd' => $timeEnd, ]; + $this->timelineCollector->collect($this, count($this->items)); } public function getSummary(): array diff --git a/src/Collector/TimelineCollector.php b/src/Collector/TimelineCollector.php new file mode 100644 index 000000000..068ec0704 --- /dev/null +++ b/src/Collector/TimelineCollector.php @@ -0,0 +1,34 @@ +isActive()) { + return []; + } + return $this->events; + } + + public function collect(CollectorInterface $collector, string|int $reference, ...$data): void + { + if (!$this->isActive()) { + return; + } + + $this->events[] = [microtime(true), $reference, $collector::class, array_values($data)]; + } + + private function reset(): void + { + $this->events = []; + } +} diff --git a/src/Collector/VarDumperCollector.php b/src/Collector/VarDumperCollector.php index 4575cf9e0..8eb9c3e4a 100644 --- a/src/Collector/VarDumperCollector.php +++ b/src/Collector/VarDumperCollector.php @@ -10,12 +10,17 @@ final class VarDumperCollector implements SummaryCollectorInterface private array $vars = []; - public function collectVar(mixed $variable, string $line): void + public function __construct(private TimelineCollector $timelineCollector) + { + } + + public function collect(mixed $variable, string $line): void { $this->vars[] = [ 'variable' => $variable, 'line' => $line, ]; + $this->timelineCollector->collect($this, count($this->vars)); } public function getCollected(): array diff --git a/src/Collector/VarDumperHandlerInterfaceProxy.php b/src/Collector/VarDumperHandlerInterfaceProxy.php index cd284881b..c76f2cd04 100644 --- a/src/Collector/VarDumperHandlerInterfaceProxy.php +++ b/src/Collector/VarDumperHandlerInterfaceProxy.php @@ -33,7 +33,7 @@ public function handle(mixed $variable, int $depth, bool $highlight = false): vo break; } - $this->collector->collectVar( + $this->collector->collect( $variable, $callStack === null ? '' : $callStack['file'] . ':' . $callStack['line'] ); diff --git a/src/Collector/Web/MiddlewareCollector.php b/src/Collector/Web/MiddlewareCollector.php index 70bd4e84c..7dda19dd8 100644 --- a/src/Collector/Web/MiddlewareCollector.php +++ b/src/Collector/Web/MiddlewareCollector.php @@ -9,6 +9,7 @@ use Yiisoft\Middleware\Dispatcher\Event\BeforeMiddleware; use Yiisoft\Yii\Debug\Collector\CollectorTrait; use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; final class MiddlewareCollector implements SummaryCollectorInterface { @@ -17,6 +18,10 @@ final class MiddlewareCollector implements SummaryCollectorInterface private array $beforeStack = []; private array $afterStack = []; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -73,6 +78,7 @@ public function collect(BeforeMiddleware|AfterMiddleware $event): void 'response' => $event->getResponse(), ]; } + $this->timelineCollector->collect($this, spl_object_id($event)); } private function reset(): void diff --git a/src/Collector/Web/RequestCollector.php b/src/Collector/Web/RequestCollector.php index 472ceff4b..3dcb9ab48 100644 --- a/src/Collector/Web/RequestCollector.php +++ b/src/Collector/Web/RequestCollector.php @@ -5,13 +5,13 @@ namespace Yiisoft\Yii\Debug\Collector\Web; use GuzzleHttp\Psr7\Message; -use JsonException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Yiisoft\Yii\Http\Event\AfterRequest; -use Yiisoft\Yii\Http\Event\BeforeRequest; use Yiisoft\Yii\Debug\Collector\CollectorTrait; use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; +use Yiisoft\Yii\Http\Event\AfterRequest; +use Yiisoft\Yii\Http\Event\BeforeRequest; use function is_object; @@ -29,26 +29,15 @@ final class RequestCollector implements SummaryCollectorInterface private ?ServerRequestInterface $request = null; private ?ResponseInterface $response = null; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { return []; } - $content = null; - if ($this->response !== null) { - $stream = $this->response->getBody(); - if ($stream->isReadable() && $stream->isSeekable()) { - $position = $stream->tell(); - $stream->rewind(); - $content = $stream->getContents(); - try { - $content = json_decode($content, associative: true, flags: JSON_THROW_ON_ERROR); - } catch (JsonException) { - // pass - } - $stream->seek($position); - } - } $requestRaw = null; if ($this->request instanceof ServerRequestInterface) { @@ -74,7 +63,6 @@ public function getCollected(): array 'requestRaw' => $requestRaw, 'response' => $this->response, 'responseRaw' => $responseRaw, - 'content' => $content, ]; } @@ -101,6 +89,7 @@ public function collect(object $event): void $this->response = $response; $this->responseStatusCode = $response !== null ? $response->getStatusCode() : 500; } + $this->timelineCollector->collect($this, spl_object_id($event)); } public function getSummary(): array diff --git a/src/Collector/Web/WebAppInfoCollector.php b/src/Collector/Web/WebAppInfoCollector.php index 7213608f9..6f389b161 100644 --- a/src/Collector/Web/WebAppInfoCollector.php +++ b/src/Collector/Web/WebAppInfoCollector.php @@ -5,11 +5,12 @@ namespace Yiisoft\Yii\Debug\Collector\Web; use Yiisoft\Yii\Console\Event\ApplicationStartup; +use Yiisoft\Yii\Debug\Collector\CollectorTrait; +use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Http\Event\AfterEmit; use Yiisoft\Yii\Http\Event\AfterRequest; use Yiisoft\Yii\Http\Event\BeforeRequest; -use Yiisoft\Yii\Debug\Collector\CollectorTrait; -use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; use function is_object; @@ -22,6 +23,10 @@ final class WebAppInfoCollector implements SummaryCollectorInterface private float $requestProcessingTimeStarted = 0; private float $requestProcessingTimeStopped = 0; + public function __construct(private TimelineCollector $timelineCollector) + { + } + public function getCollected(): array { if (!$this->isActive()) { @@ -52,6 +57,7 @@ public function collect(object $event): void } elseif ($event instanceof AfterEmit) { $this->applicationProcessingTimeStopped = microtime(true); } + $this->timelineCollector->collect($this, spl_object_id($event)); } public function getSummary(): array diff --git a/tests/Unit/Collector/CommandCollectorTest.php b/tests/Unit/Collector/CommandCollectorTest.php index 33c71cd86..5d99fd78d 100644 --- a/tests/Unit/Collector/CommandCollectorTest.php +++ b/tests/Unit/Collector/CommandCollectorTest.php @@ -13,6 +13,7 @@ use Yiisoft\Yii\Console\Output\ConsoleBufferedOutput; use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\Console\CommandCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; final class CommandCollectorTest extends AbstractCollectorTestCase @@ -57,7 +58,7 @@ public function testCollectWithInactiveCollector(): void protected function getCollector(): CollectorInterface { - return new CommandCollector(); + return new CommandCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void diff --git a/tests/Unit/Collector/ConsoleAppInfoCollectorTest.php b/tests/Unit/Collector/ConsoleAppInfoCollectorTest.php index 6ef9bbf1d..cf632672c 100644 --- a/tests/Unit/Collector/ConsoleAppInfoCollectorTest.php +++ b/tests/Unit/Collector/ConsoleAppInfoCollectorTest.php @@ -8,8 +8,8 @@ use Yiisoft\Yii\Console\Event\ApplicationStartup; use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\Console\ConsoleAppInfoCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Collector\Web\WebAppInfoCollector; - use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; use function sleep; @@ -31,7 +31,7 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new ConsoleAppInfoCollector(); + return new ConsoleAppInfoCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void diff --git a/tests/Unit/Collector/ContainerInterfaceProxyTest.php b/tests/Unit/Collector/ContainerInterfaceProxyTest.php index 3d41a5df4..b38e7bdee 100644 --- a/tests/Unit/Collector/ContainerInterfaceProxyTest.php +++ b/tests/Unit/Collector/ContainerInterfaceProxyTest.php @@ -18,13 +18,14 @@ use Yiisoft\EventDispatcher\Provider\Provider; use Yiisoft\Files\FileHelper; use Yiisoft\Yii\Debug\Collector\CollectorInterface; -use Yiisoft\Yii\Debug\Collector\EventCollector; -use Yiisoft\Yii\Debug\Collector\LogCollector; -use Yiisoft\Yii\Debug\Collector\ServiceCollector; use Yiisoft\Yii\Debug\Collector\ContainerInterfaceProxy; use Yiisoft\Yii\Debug\Collector\ContainerProxyConfig; +use Yiisoft\Yii\Debug\Collector\EventCollector; use Yiisoft\Yii\Debug\Collector\EventDispatcherInterfaceProxy; +use Yiisoft\Yii\Debug\Collector\LogCollector; use Yiisoft\Yii\Debug\Collector\LoggerInterfaceProxy; +use Yiisoft\Yii\Debug\Collector\ServiceCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; class ContainerInterfaceProxyTest extends TestCase { @@ -72,7 +73,7 @@ public function testGetAndHasWithCallableServices(): void ], ], $dispatcherMock, - new ServiceCollector(), + $this->createServiceCollector(), $this->path, 1 ); @@ -89,7 +90,7 @@ public function testGetAndHasWithCallableServices(): void public function testGetWithArrayConfigWithStringKeys(): void { $dispatcherMock = $this->getMockBuilder(EventDispatcherInterface::class)->getMock(); - $serviceCollector = new ServiceCollector(); + $serviceCollector = $this->createServiceCollector(); $serviceCollector->startup(); // activate collector $config = new ContainerProxyConfig( @@ -127,7 +128,7 @@ public function testGetWithoutConfig(): void EventDispatcherInterface::class, ], $dispatcherMock, - new ServiceCollector(), + $this->createServiceCollector(), $this->path, 1 ); @@ -183,7 +184,7 @@ private function getConfig(): ContainerProxyConfig ], ], $dispatcherMock, - new ServiceCollector(), + $this->createServiceCollector(), $this->path, 1 ); @@ -201,4 +202,9 @@ private function getContainer(): Container ]); return new Container($config); } + + protected function createServiceCollector(): ServiceCollector + { + return new ServiceCollector(new TimelineCollector()); + } } diff --git a/tests/Unit/Collector/ContainerProxyConfigTest.php b/tests/Unit/Collector/ContainerProxyConfigTest.php index c6921f90b..203d09c7e 100644 --- a/tests/Unit/Collector/ContainerProxyConfigTest.php +++ b/tests/Unit/Collector/ContainerProxyConfigTest.php @@ -7,12 +7,13 @@ use PHPUnit\Framework\TestCase; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerInterface; -use Yiisoft\Yii\Debug\Collector\EventCollector; -use Yiisoft\Yii\Debug\Collector\LogCollector; -use Yiisoft\Yii\Debug\Collector\ServiceCollector; use Yiisoft\Yii\Debug\Collector\ContainerProxyConfig; +use Yiisoft\Yii\Debug\Collector\EventCollector; use Yiisoft\Yii\Debug\Collector\EventDispatcherInterfaceProxy; +use Yiisoft\Yii\Debug\Collector\LogCollector; use Yiisoft\Yii\Debug\Collector\LoggerInterfaceProxy; +use Yiisoft\Yii\Debug\Collector\ServiceCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; final class ContainerProxyConfigTest extends TestCase { @@ -23,13 +24,13 @@ public function testImmutability(): void $dispatcherMock = $this->getMockBuilder(EventDispatcherInterface::class)->getMock(); $this->assertNotSame($config, $config->activate()); - $this->assertNotSame($config, $config->withCollector(new ServiceCollector())); + $this->assertNotSame($config, $config->withCollector($this->createServiceCollector())); $this->assertNotSame($config, $config->withLogLevel(1)); $this->assertNotSame($config, $config->withProxyCachePath('@tests/runtime')); $this->assertNotSame( $config, $config->withDispatcher( - new EventDispatcherInterfaceProxy($dispatcherMock, new EventCollector()) + new EventDispatcherInterfaceProxy($dispatcherMock, new EventCollector(new TimelineCollector())) ) ); $this->assertNotSame( @@ -51,7 +52,7 @@ public function testGetters(): void LoggerInterface::class => [LoggerInterfaceProxy::class, LogCollector::class], ], $dispatcherMock, - new ServiceCollector(), + $this->createServiceCollector(), '@tests/runtime', 1 ); @@ -79,4 +80,9 @@ public function testGetters(): void $this->assertFalse($config->hasDecoratedServiceArrayConfigWithStringKeys(LoggerInterface::class)); $this->assertFalse($config->hasDecoratedServiceCallableConfig(LoggerInterface::class)); } + + protected function createServiceCollector(): ServiceCollector + { + return new ServiceCollector(new TimelineCollector()); + } } diff --git a/tests/Unit/Collector/EventCollectorTest.php b/tests/Unit/Collector/EventCollectorTest.php index 4d75ef803..c32d99be8 100644 --- a/tests/Unit/Collector/EventCollectorTest.php +++ b/tests/Unit/Collector/EventCollectorTest.php @@ -6,6 +6,7 @@ use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\EventCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; use Yiisoft\Yii\Debug\Tests\Unit\Support\DummyEvent; @@ -21,7 +22,7 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new EventCollector(); + return new EventCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void diff --git a/tests/Unit/Collector/EventDispatcherProxyTest.php b/tests/Unit/Collector/EventDispatcherProxyTest.php deleted file mode 100644 index 2d0e07ffa..000000000 --- a/tests/Unit/Collector/EventDispatcherProxyTest.php +++ /dev/null @@ -1,31 +0,0 @@ -createMock(EventDispatcherInterface::class); - $collector = $this->createMock(EventCollector::class); - - $eventDispatcher->method('dispatch')->willReturn($event); - $collector - ->expects($this->once()) - ->method('collect') - ->with($event, __FILE__ . ':29'); - - $proxy = new EventDispatcherInterfaceProxy($eventDispatcher, $collector); - - $proxy->dispatch($event); - } -} diff --git a/tests/Unit/Collector/LogCollectorTest.php b/tests/Unit/Collector/LogCollectorTest.php index b7e7dbc29..3221d94cd 100644 --- a/tests/Unit/Collector/LogCollectorTest.php +++ b/tests/Unit/Collector/LogCollectorTest.php @@ -7,6 +7,7 @@ use Psr\Log\LogLevel; use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\LogCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; final class LogCollectorTest extends AbstractCollectorTestCase @@ -21,6 +22,6 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new LogCollector(); + return new LogCollector(new TimelineCollector()); } } diff --git a/tests/Unit/Collector/MiddlewareCollectorTest.php b/tests/Unit/Collector/MiddlewareCollectorTest.php index aa86e86c1..81fd4efe6 100644 --- a/tests/Unit/Collector/MiddlewareCollectorTest.php +++ b/tests/Unit/Collector/MiddlewareCollectorTest.php @@ -13,6 +13,7 @@ use Yiisoft\Middleware\Dispatcher\Event\BeforeMiddleware; use Yiisoft\Middleware\Dispatcher\MiddlewareFactory; use Yiisoft\Yii\Debug\Collector\CollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Collector\Web\MiddlewareCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; use Yiisoft\Yii\Debug\Tests\Unit\Support\DummyMiddleware; @@ -34,7 +35,7 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new MiddlewareCollector(); + return new MiddlewareCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void diff --git a/tests/Unit/Collector/RequestCollectorTest.php b/tests/Unit/Collector/RequestCollectorTest.php index 5105a374f..90a7bb7e0 100644 --- a/tests/Unit/Collector/RequestCollectorTest.php +++ b/tests/Unit/Collector/RequestCollectorTest.php @@ -9,6 +9,7 @@ use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UriInterface; use Yiisoft\Yii\Debug\Collector\CollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Collector\Web\RequestCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; use Yiisoft\Yii\Http\Event\AfterRequest; @@ -59,7 +60,7 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new RequestCollector(); + return new RequestCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void diff --git a/tests/Unit/Collector/ServiceCollectorTest.php b/tests/Unit/Collector/ServiceCollectorTest.php index 1d1805e4d..9fd2d0ae7 100644 --- a/tests/Unit/Collector/ServiceCollectorTest.php +++ b/tests/Unit/Collector/ServiceCollectorTest.php @@ -7,6 +7,7 @@ use stdClass; use Yiisoft\Yii\Debug\Collector\CollectorInterface; use Yiisoft\Yii\Debug\Collector\ServiceCollector; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; final class ServiceCollectorTest extends AbstractCollectorTestCase @@ -22,6 +23,6 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new ServiceCollector(); + return new ServiceCollector(new TimelineCollector()); } } diff --git a/tests/Unit/Collector/WebAppInfoCollectorTest.php b/tests/Unit/Collector/WebAppInfoCollectorTest.php index 763a35f9c..63ce78aef 100644 --- a/tests/Unit/Collector/WebAppInfoCollectorTest.php +++ b/tests/Unit/Collector/WebAppInfoCollectorTest.php @@ -7,6 +7,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Yiisoft\Yii\Debug\Collector\CollectorInterface; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Collector\Web\WebAppInfoCollector; use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase; use Yiisoft\Yii\Http\Event\AfterRequest; @@ -33,7 +34,7 @@ protected function collectTestData(CollectorInterface $collector): void protected function getCollector(): CollectorInterface { - return new WebAppInfoCollector(); + return new WebAppInfoCollector(new TimelineCollector()); } protected function checkCollectedData(array $data): void From 55788fe5fb86dfa6d68c9be4e5b066c4585fbd4b Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 6 Jul 2024 22:35:51 +0300 Subject: [PATCH 34/38] Refactor --- config/bootstrap.php | 12 ++-- config/di.php | 18 ++++- config/params.php | 9 ++- src/Collector/ContainerInterfaceProxy.php | 8 +-- src/Command/DebugServerBroadcastCommand.php | 4 +- src/Command/DebugServerCommand.php | 74 ++++----------------- src/DebugServer/LoggerDecorator.php | 2 +- 7 files changed, 45 insertions(+), 82 deletions(-) diff --git a/config/bootstrap.php b/config/bootstrap.php index f8229a9ab..c5b4e96f1 100644 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -22,13 +22,15 @@ static function (ContainerInterface $container) use ($params) { return; } - // todo: remove VarDumperHandler if dev-server is not enabled + $decorated = VarDumper::getDefaultHandler(); + + if ($params['yiisoft/yii-debug']['devServer']['enabled'] ?? false) { + $decorated = new CompositeHandler([$decorated, new VarDumperHandler()]); + } + VarDumper::setDefaultHandler( new VarDumperHandlerInterfaceProxy( - new CompositeHandler([ - VarDumper::getDefaultHandler(), - new VarDumperHandler(), - ]), + $decorated, $container->get(VarDumperCollector::class), ), ); diff --git a/config/di.php b/config/di.php index 7d34518b3..3a6500a3e 100644 --- a/config/di.php +++ b/config/di.php @@ -5,14 +5,18 @@ use Composer\Autoload\ClassLoader; use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; +use Psr\Log\LoggerInterface; use Yiisoft\Aliases\Aliases; use Yiisoft\VarDumper\ClosureExporter; use Yiisoft\VarDumper\UseStatementParser; use Yiisoft\Yii\Debug\Collector\ContainerInterfaceProxy; use Yiisoft\Yii\Debug\Collector\ContainerProxyConfig; +use Yiisoft\Yii\Debug\Collector\LogCollector; +use Yiisoft\Yii\Debug\Collector\LoggerInterfaceProxy; use Yiisoft\Yii\Debug\Collector\ServiceCollector; use Yiisoft\Yii\Debug\Collector\Stream\FilesystemStreamCollector; use Yiisoft\Yii\Debug\DebuggerIdGenerator; +use Yiisoft\Yii\Debug\DebugServer\LoggerDecorator; use Yiisoft\Yii\Debug\Storage\FileStorage; use Yiisoft\Yii\Debug\Storage\StorageInterface; @@ -44,13 +48,23 @@ $params = $params['yiisoft/yii-debug']; $collector = $container->get(ServiceCollector::class); $dispatcher = $container->get(EventDispatcherInterface::class); - $debuggerEnabled = (bool) ($params['enabled'] ?? false); + $isDebuggerEnabled = (bool) ($params['enabled'] ?? false); + $isDevServerEnabled = (bool) ($params['devServer']['enabled'] ?? false); + $trackedServices = (array) ($params['trackedServices'] ?? []); + + if ($isDevServerEnabled) { + $trackedServices[LoggerInterface::class] = static fn ( + ContainerInterface $container, + LoggerInterface $logger, + ) => new LoggerInterfaceProxy(new LoggerDecorator($logger), $container->get(LogCollector::class)); + } + $path = $container->get(Aliases::class)->get('@runtime/cache/container-proxy'); $logLevel = $params['logLevel'] ?? ContainerInterfaceProxy::LOG_NOTHING; return new ContainerProxyConfig( - $debuggerEnabled, + $isDebuggerEnabled, $trackedServices, $dispatcher, $collector, diff --git a/config/params.php b/config/params.php index c2a66fc33..ba4ea7e28 100644 --- a/config/params.php +++ b/config/params.php @@ -30,7 +30,6 @@ use Yiisoft\Yii\Debug\Command\DebugResetCommand; use Yiisoft\Yii\Debug\Command\DebugServerBroadcastCommand; use Yiisoft\Yii\Debug\Command\DebugServerCommand; -use Yiisoft\Yii\Debug\DebugServer\LoggerDecorator; /** * @var $params array @@ -39,6 +38,9 @@ return [ 'yiisoft/yii-debug' => [ 'enabled' => true, + 'devServer' => [ + 'enabled' => true, + ], 'collectors' => [ LogCollector::class, EventCollector::class, @@ -61,10 +63,7 @@ ], 'trackedServices' => [ Injector::class => fn (ContainerInterface $container) => new Injector($container), - LoggerInterface::class => function (ContainerInterface $container, LoggerInterface $logger) { - return new LoggerInterfaceProxy(new LoggerDecorator($logger), $container->get(LogCollector::class)); - }, - //LoggerInterface::class => [LoggerInterfaceProxy::class, LogCollector::class], + LoggerInterface::class => [LoggerInterfaceProxy::class, LogCollector::class], EventDispatcherInterface::class => [EventDispatcherInterfaceProxy::class, EventCollector::class], ClientInterface::class => [HttpClientInterfaceProxy::class, HttpClientCollector::class], ], diff --git a/src/Collector/ContainerInterfaceProxy.php b/src/Collector/ContainerInterfaceProxy.php index 57eb1efb9..3437aadea 100644 --- a/src/Collector/ContainerInterfaceProxy.php +++ b/src/Collector/ContainerInterfaceProxy.php @@ -54,13 +54,7 @@ public function get($id): mixed $this->logProxy(ContainerInterface::class, $this->container, 'get', [$id], $instance, $timeStart); } - if ( - is_object($instance) - && ( - ($proxy = $this->getServiceProxyCache($id)) || - ($proxy = $this->getServiceProxy($id, $instance)) - ) - ) { + if (is_object($instance) && ($proxy = $this->getServiceProxy($id, $instance))) { $this->setServiceProxyCache($id, $proxy); return $proxy; } diff --git a/src/Command/DebugServerBroadcastCommand.php b/src/Command/DebugServerBroadcastCommand.php index 1446b5136..47b01f6d7 100644 --- a/src/Command/DebugServerBroadcastCommand.php +++ b/src/Command/DebugServerBroadcastCommand.php @@ -10,6 +10,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Yiisoft\VarDumper\VarDumper; use Yiisoft\Yii\Console\ExitCode; use Yiisoft\Yii\Debug\DebugServer\Connection; @@ -52,7 +53,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $data = $input->getOption('message'); - $socket->broadcast($data); + $socket->broadcast(Connection::MESSAGE_TYPE_LOGGER, $data); + $socket->broadcast(Connection::MESSAGE_TYPE_VAR_DUMPER, VarDumper::create(['$data' => $data])->asJson(false)); return ExitCode::OK; } diff --git a/src/Command/DebugServerCommand.php b/src/Command/DebugServerCommand.php index 6c659d14d..39a0ce1a0 100644 --- a/src/Command/DebugServerCommand.php +++ b/src/Command/DebugServerCommand.php @@ -1,7 +1,5 @@ bind(); - //if (socket_recvfrom($socket, $buf, 64 * 1024, 0, $source) === false) { - // echo "recv_from failed"; - //} - //var_dump($buf); - // - //return 0; - //if (!@socket_bind($socket, $address, $port)) { - // $io->error( - // sprintf( - // 'Address "%s" is already taken by another process.', - // $this->address . ':' . $this->port, - // ) - // ); - // $io->info( - // sprintf( - // 'Would you like to kill the process and rerun?' - // ) - // ); - // $result = $io->ask('Would you like to kill the process and rerun? (Y/n)'); - // - // if ($result === 'Y') { - // // todo: change to finding a process by opened port - // $pid = shell_exec( - // sprintf( - // 'ps | grep "php ./yii dev"', - // //$this->port, - // ) - // ); - // $io->info( - // sprintf( - // 'Killing the process with ID: "%s".', - // $pid, - // ) - // ); - // //shell_exec('kill ' . $pid); - // } - // //return ExitCode::IOERR; - //} - $io->success( sprintf( 'Listening on "%s".', @@ -112,26 +69,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int } foreach ($socket->read() as $message) { - switch ($message[0]) { - case Connection::TYPE_ERROR: - $io->writeln('Connection closed with error: ' . $message[1]); - break 2; - default: - $data = \json_decode($message[1], null, 512, JSON_THROW_ON_ERROR); - if ($data[0] === Connection::MESSAGE_TYPE_VAR_DUMPER) { - $io->title('VarDumper'); - } elseif ($data[0] === Connection::MESSAGE_TYPE_LOGGER) { - $io->write('Logger'); - } - $io->writeln( - sprintf( - "\033[1;37m\033[47m%s\033[0m", - $data[1] - ) - ); - $io->newLine(); + if ($message[0] === Connection::TYPE_ERROR) { + $io->error('Connection closed with error: ' . $message[1]); + break; } + + $data = \json_decode($message[1], null, 512, JSON_THROW_ON_ERROR); + $type = match ($data[0]) { + Connection::MESSAGE_TYPE_VAR_DUMPER => 'VarDumper', + Connection::MESSAGE_TYPE_LOGGER => 'Logger', + default => 'Plain text', + }; + + $io->block($data[1], $type); } + return ExitCode::OK; } } diff --git a/src/DebugServer/LoggerDecorator.php b/src/DebugServer/LoggerDecorator.php index f7693cb29..5a6f1f2eb 100644 --- a/src/DebugServer/LoggerDecorator.php +++ b/src/DebugServer/LoggerDecorator.php @@ -24,7 +24,7 @@ public function log($level, Stringable|string $message, array $context = []): vo { $this->connection->broadcast( Connection::MESSAGE_TYPE_LOGGER, - VarDumper::create(['message' => $message, 'context' => $context])->asJson(false) + VarDumper::create(['message' => $message, 'context' => $context])->asJson(false, 1) ); $this->decorated->log($level, $message, $context); } From 8b66603fbcaa59b1f008f0580436ea959c53060e Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sat, 6 Jul 2024 19:36:33 +0000 Subject: [PATCH 35/38] Apply fixes from StyleCI --- src/Command/DebugServerBroadcastCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/DebugServerBroadcastCommand.php b/src/Command/DebugServerBroadcastCommand.php index 47b01f6d7..0d55de749 100644 --- a/src/Command/DebugServerBroadcastCommand.php +++ b/src/Command/DebugServerBroadcastCommand.php @@ -54,7 +54,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $data = $input->getOption('message'); $socket->broadcast(Connection::MESSAGE_TYPE_LOGGER, $data); - $socket->broadcast(Connection::MESSAGE_TYPE_VAR_DUMPER, VarDumper::create(['$data' => $data])->asJson(false)); + $socket->broadcast(Connection::MESSAGE_TYPE_VAR_DUMPER, VarDumper::create(['$data' => $data])->asJson(false)); return ExitCode::OK; } From 3c41327987c8345f463eae00b3ac5f5f04f42520 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sun, 7 Jul 2024 07:36:45 +0300 Subject: [PATCH 36/38] Research more --- src/DebugServer/Connection.php | 48 +++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/src/DebugServer/Connection.php b/src/DebugServer/Connection.php index 8904b2eaa..b8f6dc796 100644 --- a/src/DebugServer/Connection.php +++ b/src/DebugServer/Connection.php @@ -21,6 +21,7 @@ final class Connection public const TYPE_RESULT = 0x001B; public const TYPE_ERROR = 0x002B; + public const TYPE_RELEASE = 0x003B; public const MESSAGE_TYPE_VAR_DUMPER = 0x001B; public const MESSAGE_TYPE_LOGGER = 0x002B; @@ -72,13 +73,29 @@ public function bind(): void } /** - * @return Generator + * @return Generator */ public function read(): Generator { + $sndbuf = socket_get_option($this->socket,SOL_SOCKET,SO_SNDBUF); + $rcvbuf = socket_get_option($this->socket,SOL_SOCKET,SO_RCVBUF); + + socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ["sec" => 2, "usec" => 0]); + socket_set_option($this->socket, SOL_SOCKET, SO_RCVBUF, 1024*10); + socket_set_option($this->socket, SOL_SOCKET, SO_SNDBUF, 1024*10); + + $newFrameAwaitRepeat = 0; + $maxFrameAwaitRepeats = 10; + $maxRepeats = 10; + while (true) { - if (!socket_recvfrom($this->socket, $header, self::DEFAULT_BUFFER_SIZE, MSG_DONTWAIT, $ip, $port)) { + if (!socket_recv($this->socket, $header, 8, MSG_WAITALL)) { $socket_last_error = socket_last_error($this->socket); + $newFrameAwaitRepeat++; + if ($newFrameAwaitRepeat === $maxFrameAwaitRepeats) { + $newFrameAwaitRepeat = 0; + yield [self::TYPE_RELEASE, $socket_last_error, socket_strerror($socket_last_error)]; + } if ($socket_last_error === 35) { usleep(self::DEFAULT_TIMEOUT); continue; @@ -88,15 +105,26 @@ public function read(): Generator continue; } - $length = unpack('N', $header); + $length = unpack('P', $header); $localBuffer = ''; $bytesToRead = $length[1]; $bytesRead = 0; + //$value = 2 ** ((int) ($bytesToRead / 2)); + //socket_set_option($this->socket, SOL_SOCKET, SO_RCVBUF, $value); + $repeat = 0; while ($bytesRead < $bytesToRead) { - if (!$bufferLength = socket_recvfrom($this->socket, $buffer, self::DEFAULT_BUFFER_SIZE, MSG_DONTWAIT, $ip, $port)) { + //$buffer = socket_read($this->socket, $bytesToRead - $bytesRead); + //$bufferLength = strlen($buffer); + $bufferLength = socket_recv($this->socket, $buffer, min($bytesToRead - $bytesRead, self::DEFAULT_BUFFER_SIZE), MSG_DONTWAIT); + if ($bufferLength === false) { + if ($repeat === $maxRepeats) { + break; + } + //if ($bufferLength === false) { $socket_last_error = socket_last_error($this->socket); if ($socket_last_error === 35) { - usleep(self::DEFAULT_TIMEOUT); + $repeat++; + usleep(self::DEFAULT_TIMEOUT * 5); continue; } $this->close(); @@ -106,7 +134,7 @@ public function read(): Generator $localBuffer .= $buffer; $bytesRead += $bufferLength; } - yield [self::TYPE_RESULT, base64_decode($localBuffer), $ip, $port]; + yield [self::TYPE_RESULT, base64_decode($localBuffer)]; } } @@ -115,8 +143,7 @@ public function broadcast(int $type, string $data): array $files = glob(sys_get_temp_dir() . '/yii-dev-server-*.sock', GLOB_NOSORT); //echo 'Files: ' . implode(', ', $files) . "\n"; $uniqueErrors = []; - $payload = base64_encode(json_encode([$type, $data], JSON_THROW_ON_ERROR)); - $payloadLength = strlen($payload); + $payload = (json_encode([$type, $data], JSON_THROW_ON_ERROR)); foreach ($files as $file) { $socket = @fsockopen('udg://' . $file, -1, $errno, $errstr); if ($errno === 61) { @@ -163,12 +190,13 @@ public function close(): void */ private function fwriteStream($fp, string $data): int|false { + $data = base64_encode($data); $strlen = strlen($data); - fwrite($fp, pack('N', $strlen)); + fwrite($fp, pack('P', $strlen)); for ($written = 0; $written < $strlen; $written += $fwrite) { $fwrite = fwrite($fp, substr($data, $written), self::DEFAULT_BUFFER_SIZE); //\fflush($fp); - usleep(self::DEFAULT_TIMEOUT / 2); + usleep(self::DEFAULT_TIMEOUT * 5); if ($fwrite === false) { return $written; } From 92e63139c1327692e0b579192858476b6311c859 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sun, 7 Jul 2024 04:37:51 +0000 Subject: [PATCH 37/38] Apply fixes from StyleCI --- src/DebugServer/Connection.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/DebugServer/Connection.php b/src/DebugServer/Connection.php index b8f6dc796..ffdd2fbfa 100644 --- a/src/DebugServer/Connection.php +++ b/src/DebugServer/Connection.php @@ -77,12 +77,12 @@ public function bind(): void */ public function read(): Generator { - $sndbuf = socket_get_option($this->socket,SOL_SOCKET,SO_SNDBUF); - $rcvbuf = socket_get_option($this->socket,SOL_SOCKET,SO_RCVBUF); + $sndbuf = socket_get_option($this->socket, SOL_SOCKET, SO_SNDBUF); + $rcvbuf = socket_get_option($this->socket, SOL_SOCKET, SO_RCVBUF); - socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ["sec" => 2, "usec" => 0]); - socket_set_option($this->socket, SOL_SOCKET, SO_RCVBUF, 1024*10); - socket_set_option($this->socket, SOL_SOCKET, SO_SNDBUF, 1024*10); + socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => 2, 'usec' => 0]); + socket_set_option($this->socket, SOL_SOCKET, SO_RCVBUF, 1024 * 10); + socket_set_option($this->socket, SOL_SOCKET, SO_SNDBUF, 1024 * 10); $newFrameAwaitRepeat = 0; $maxFrameAwaitRepeats = 10; @@ -120,7 +120,7 @@ public function read(): Generator if ($repeat === $maxRepeats) { break; } - //if ($bufferLength === false) { + //if ($bufferLength === false) { $socket_last_error = socket_last_error($this->socket); if ($socket_last_error === 35) { $repeat++; @@ -143,7 +143,7 @@ public function broadcast(int $type, string $data): array $files = glob(sys_get_temp_dir() . '/yii-dev-server-*.sock', GLOB_NOSORT); //echo 'Files: ' . implode(', ', $files) . "\n"; $uniqueErrors = []; - $payload = (json_encode([$type, $data], JSON_THROW_ON_ERROR)); + $payload = json_encode([$type, $data], JSON_THROW_ON_ERROR); foreach ($files as $file) { $socket = @fsockopen('udg://' . $file, -1, $errno, $errstr); if ($errno === 61) { From fde266eeae7730d1222e16651d2826f092175576 Mon Sep 17 00:00:00 2001 From: xepozz Date: Sun, 24 Nov 2024 16:41:00 +0000 Subject: [PATCH 38/38] Apply Rector changes (CI) --- src/Command/DebugServerCommand.php | 4 ++-- src/DebugServer/Connection.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Command/DebugServerCommand.php b/src/Command/DebugServerCommand.php index 39a0ce1a0..d09e8c564 100644 --- a/src/Command/DebugServerCommand.php +++ b/src/Command/DebugServerCommand.php @@ -21,8 +21,8 @@ final class DebugServerCommand extends Command protected static $defaultDescription = 'Runs PHP built-in web server'; public function __construct( - private string $address = '0.0.0.0', - private int $port = 8890, + private readonly string $address = '0.0.0.0', + private readonly int $port = 8890, ) { parent::__construct(); } diff --git a/src/DebugServer/Connection.php b/src/DebugServer/Connection.php index ffdd2fbfa..503a9d662 100644 --- a/src/DebugServer/Connection.php +++ b/src/DebugServer/Connection.php @@ -29,7 +29,7 @@ final class Connection private string $uri; public function __construct( - private Socket $socket, + private readonly Socket $socket, ) { } @@ -105,7 +105,7 @@ public function read(): Generator continue; } - $length = unpack('P', $header); + $length = unpack('P', (string) $header); $localBuffer = ''; $bytesToRead = $length[1]; $bytesRead = 0;