From 7e93b1719334c4531512dc9103ad44de5fbab1af Mon Sep 17 00:00:00 2001 From: Dmitriy Derepko Date: Mon, 3 Mar 2025 17:51:10 +0300 Subject: [PATCH 1/2] adapt yii3 debugger --- composer.json | 11 ++- src/LogTarget.php | 10 +-- src/Module.php | 87 ++++++++++++++++++------ src/controllers/ApiController.php | 96 +++++++++++++++++++++++++++ src/controllers/DefaultController.php | 27 ++++++++ 5 files changed, 201 insertions(+), 30 deletions(-) create mode 100644 src/controllers/ApiController.php diff --git a/composer.json b/composer.json index edd25823d..017a2a397 100644 --- a/composer.json +++ b/composer.json @@ -30,13 +30,17 @@ "require": { "php": ">=5.4", "ext-mbstring": "*", - "yiisoft/yii2": "~2.0.13" + "yiisoft/yii2": "~2.0.13", + "samdark/yii2-psr-log-target": "dev-master" }, "require-dev": { "yiisoft/yii2-swiftmailer": "*", "yiisoft/yii2-coding-standards": "~2.0", "cweagans/composer-patches": "^1.7", - "phpunit/phpunit": "4.8.34" + "phpunit/phpunit": "4.8.34", + "yiisoft/yii-debug": "dev-master", + "yiisoft/router-fastroute": "^3.0", + "yiisoft/yii-debug-api": "dev-master" }, "autoload": { "psr-4": { @@ -73,7 +77,8 @@ "config": { "allow-plugins": { "cweagans/composer-patches": true, - "yiisoft/yii2-composer": true + "yiisoft/yii2-composer": true, + "yiisoft/config": false } } } diff --git a/src/LogTarget.php b/src/LogTarget.php index 5d94fdda3..850b29686 100644 --- a/src/LogTarget.php +++ b/src/LogTarget.php @@ -39,7 +39,7 @@ public function __construct($module, $config = []) { parent::__construct($config); $this->module = $module; - $this->tag = uniqid(); + //$this->tag = uniqid(); } /** @@ -53,7 +53,7 @@ public function export() FileHelper::createDirectory($path, $this->module->dirMode); $summary = $this->collectSummary(); - $dataFile = "$path/{$this->tag}.data"; + $dataFile = "$path/{$this->module->debugger->getId()}.data"; $data = []; $exceptions = []; foreach ($this->module->panels as $id => $panel) { @@ -115,7 +115,7 @@ public function loadTagToPanels($tag) $exceptions = $data['exceptions']; foreach ($this->module->panels as $id => $panel) { if (isset($data[$id])) { - $panel->tag = $tag; + $panel->tag = $this->module->debugger->getId(); $panel->load(unserialize($data[$id])); } else { unset($this->module->panels[$id]); @@ -152,7 +152,7 @@ private function updateIndexFile($indexFile, $summary) $manifest = unserialize($manifest); } - $manifest[$this->tag] = $summary; + $manifest[$this->module->debugger->getId()] = $summary; $this->gc($manifest); ftruncate($fp, 0); @@ -245,7 +245,7 @@ protected function collectSummary() $request = Yii::$app->getRequest(); $response = Yii::$app->getResponse(); $summary = [ - 'tag' => $this->tag, + 'tag' => $this->module->debugger->getId(), 'url' => $request instanceof yii\console\Request ? "php yii " . implode(' ', $request->getParams()): $request->getAbsoluteUrl(), 'ajax' => $request instanceof yii\console\Request ? 0 : (int) $request->getIsAjax(), 'method' => $request instanceof yii\console\Request ? 'COMMAND' : $request->getMethod(), diff --git a/src/Module.php b/src/Module.php index ee21a022b..5518b7641 100644 --- a/src/Module.php +++ b/src/Module.php @@ -7,6 +7,9 @@ namespace yii\debug; +use Psr\Log\NullLogger; +use samdark\log\PsrTarget; +use stdClass; use Yii; use yii\base\Application; use yii\base\BootstrapInterface; @@ -14,9 +17,19 @@ use yii\helpers\IpHelper; use yii\helpers\Json; use yii\helpers\Url; +use yii\log\Target; use yii\web\ForbiddenHttpException; use yii\web\Response; use yii\web\View; +use Yiisoft\VarDumper\VarDumper; +use Yiisoft\Yii\Debug\Collector\LogCollector; +use Yiisoft\Yii\Debug\Collector\LoggerInterfaceProxy; +use Yiisoft\Yii\Debug\Collector\TimelineCollector; +use Yiisoft\Yii\Debug\Collector\VarDumperCollector; +use Yiisoft\Yii\Debug\Collector\VarDumperHandlerInterfaceProxy; +use Yiisoft\Yii\Debug\Collector\Web\WebAppInfoCollector; +use Yiisoft\Yii\Debug\Debugger; +use Yiisoft\Yii\Debug\Storage\FileStorage; /** * The Yii Debug Module provides the debug toolbar and debugger @@ -50,7 +63,8 @@ class Module extends \yii\base\Module implements BootstrapInterface * * The signature is the following: * - * function (Action|null $action) The action can be null when called from a non action context (like set debug header) + * function (Action|null $action) The action can be null when called from a non action context (like set debug + * header) * * @since 2.1.0 */ @@ -63,14 +77,14 @@ class Module extends \yii\base\Module implements BootstrapInterface * @var LogTarget|array|string the logTarget object, or the configuration for creating the logTarget object. */ public $logTarget = 'yii\debug\LogTarget'; - + /** - * @var \yii\rbac\BaseManager|string|array the RBAC access checker [[BaseManager]] object or the application + * @var \yii\rbac\BaseManager|string|array the RBAC access checker [[BaseManager]] object or the application * component ID of the AuthManager [[BaseManager]]. * @since 2.1.19 */ public $authManager = 'authManager'; - + /** * @var array|Panel[] list of debug panels. The array keys are the panel IDs, and values are the corresponding * panel class names or configuration arrays. This will be merged with [[corePanels()]]. @@ -136,8 +150,8 @@ class Module extends \yii\base\Module implements BootstrapInterface */ public $disableCallbackRestrictionWarning = false; /** - * @var mixed the string with placeholders to be be substituted or an anonymous function that returns the trace line string. - * The placeholders are {file}, {line} and {text} and the string should be as follows: + * @var mixed the string with placeholders to be be substituted or an anonymous function that returns the trace + * line string. The placeholders are {file}, {line} and {text} and the string should be as follows: * * `File: {file} - Line: {line} - Text: {text}` * @@ -207,6 +221,9 @@ class Module extends \yii\base\Module implements BootstrapInterface */ public $skipAjaxRequestUrl = []; + + public Debugger|null $debugger = null; + /** * Returns the logo URL to be used in `=5.4", "ext-mbstring": "*", "yiisoft/yii2": "~2.0.13", - "samdark/yii2-psr-log-target": "dev-master" + "samdark/yii2-psr-log-target": "dev-master", + "yiisoft/yii-http": "dev-master" }, "require-dev": { "yiisoft/yii2-swiftmailer": "*", diff --git a/src/Module.php b/src/Module.php index 5518b7641..0e2ef7f7e 100644 --- a/src/Module.php +++ b/src/Module.php @@ -7,29 +7,42 @@ namespace yii\debug; +use HttpSoft\Message\ServerRequest; +use HttpSoft\Message\Stream; +use HttpSoft\Message\Uri; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; use Psr\Log\NullLogger; use samdark\log\PsrTarget; use stdClass; +use Symfony\Component\Console\Event\ConsoleEvent; use Yii; use yii\base\Application; use yii\base\BootstrapInterface; +use yii\base\Event; use yii\helpers\Html; use yii\helpers\IpHelper; use yii\helpers\Json; use yii\helpers\Url; use yii\log\Target; use yii\web\ForbiddenHttpException; +use yii\web\Request; use yii\web\Response; use yii\web\View; use Yiisoft\VarDumper\VarDumper; +use Yiisoft\Yii\Debug\Collector\Console\CommandCollector; use Yiisoft\Yii\Debug\Collector\LogCollector; use Yiisoft\Yii\Debug\Collector\LoggerInterfaceProxy; use Yiisoft\Yii\Debug\Collector\TimelineCollector; use Yiisoft\Yii\Debug\Collector\VarDumperCollector; use Yiisoft\Yii\Debug\Collector\VarDumperHandlerInterfaceProxy; +use Yiisoft\Yii\Debug\Collector\Web\RequestCollector; use Yiisoft\Yii\Debug\Collector\Web\WebAppInfoCollector; use Yiisoft\Yii\Debug\Debugger; use Yiisoft\Yii\Debug\Storage\FileStorage; +use Yiisoft\Yii\Debug\Storage\StorageInterface; +use Yiisoft\Yii\Http\Event\AfterRequest; +use Yiisoft\Yii\Http\Event\BeforeRequest; /** * The Yii Debug Module provides the debug toolbar and debugger @@ -293,6 +306,12 @@ protected function initPanels() */ public function bootstrap($app) { + // temporary disable debug for console + if ($app instanceof \yii\console\Application) { + return; + } + + $timelineCollector = new TimelineCollector(); $logCollector = new LogCollector($timelineCollector); $logger = new LoggerInterfaceProxy(new NullLogger(), $logCollector); @@ -304,10 +323,17 @@ public function bootstrap($app) $varDumper = new VarDumperHandlerInterfaceProxy(VarDumper::getDefaultHandler(), $varDumperCollector); VarDumper::setDefaultHandler($varDumper); + $requestCollector = new RequestCollector($timelineCollector); + $commandCollector = new CommandCollector($timelineCollector); + + $fileStorage = new FileStorage($this->dataPath); + $this->debugger = new Debugger( - new FileStorage($this->dataPath), + $fileStorage, [ - //$logCollector, + $logCollector, + $requestCollector, + $commandCollector, new WebAppInfoCollector($timelineCollector), $varDumperCollector, ], @@ -315,12 +341,25 @@ public function bootstrap($app) $this->logTarget = $psrTarget; $app->getLog()->targets['debug'] = $this->logTarget; + Yii::$container->setSingleton(Debugger::class, $this->debugger); + Yii::$container->setSingleton(StorageInterface::class, $fileStorage); + $this->debugger->start(new stdClass()); // delay attaching event handler to the view component after it is fully configured $app->on(Application::EVENT_BEFORE_REQUEST, function () use ($app) { $app->getResponse()->on(Response::EVENT_AFTER_PREPARE, [$this, 'setDebugHeaders']); }); + $app->on(Application::EVENT_BEFORE_REQUEST, function (Event $event) use ($app, $requestCollector) { + $requestCollector->collect( + new BeforeRequest($this->createPsr7Request($app->request)) + ); + }); + $app->on(Application::EVENT_AFTER_REQUEST, function (Event $event) use ($app, $requestCollector) { + $requestCollector->collect( + new AfterRequest($this->createPsr7Response($app->response)) + ); + }); $app->on(Application::EVENT_BEFORE_ACTION, function () use ($app) { $app->getView()->on(View::EVENT_END_BODY, [$this, 'renderToolbar']); $app->getView()->on(View::EVENT_END_BODY, [$this, 'stopDebugger']); @@ -334,6 +373,13 @@ public function bootstrap($app) 'normalizer' => false, 'suffix' => false, ], + [ + 'class' => $this->urlRuleClass, + 'route' => $this->getUniqueId() . '/api/view', + 'pattern' => $this->getUniqueId() . '/api/view/', + 'normalizer' => false, + 'suffix' => false, + ], [ 'class' => $this->urlRuleClass, 'route' => $this->getUniqueId() . '//', @@ -344,6 +390,34 @@ public function bootstrap($app) ], false); } + + public function createPsr7Request(Request $request): ServerRequestInterface + { + $request = new ServerRequest( + serverParams: $_SERVER, + uploadedFiles: $_FILES, + cookieParams: $request->getCookies()->toArray(), + queryParams: $request->getQueryParams(), + parsedBody: $request->getRawBody(), + method: $request->method, + uri: new Uri($request->getAbsoluteUrl()), + headers: $request->getHeaders()->toArray() + ); + + return $request; + } + + public function createPsr7Response(Response $response): ResponseInterface + { + $result = new \HttpSoft\Message\Response( + statusCode: $response->getStatusCode(), + headers: $response->getHeaders()->toArray(), + body: $response->content, + ); + + return $result; + } + /** * {@inheritdoc} * @throws \yii\base\InvalidConfigException diff --git a/src/controllers/ApiController.php b/src/controllers/ApiController.php index 6e6aadaef..26eeaf1d4 100644 --- a/src/controllers/ApiController.php +++ b/src/controllers/ApiController.php @@ -17,6 +17,9 @@ use Yiisoft\DataResponse\DataResponseFactory; use Yiisoft\DataResponse\Formatter\JsonDataResponseFormatter; use Yiisoft\Yii\Debug\Api\Debug\Controller\DebugController; +use Yiisoft\Yii\Debug\Api\Debug\Exception\NotFoundException; +use Yiisoft\Yii\Debug\Api\Debug\HtmlViewProviderInterface; +use Yiisoft\Yii\Debug\Api\Debug\ModuleFederationProviderInterface; use Yiisoft\Yii\Debug\Api\Debug\Repository\CollectorRepository; use Yiisoft\Yii\Debug\Storage\FileStorage; @@ -78,9 +81,7 @@ public function actionIndex() 'responseFactory' => Yii::createObject(ResponseFactory::class, []), 'streamFactory' => Yii::createObject(StreamFactory::class, []), ]); - $collectorRepository = Yii::createObject(CollectorRepository::class, [ - 'storage' => new FileStorage(Yii::getAlias('@runtime/debug')), - ]); + $collectorRepository = Yii::$container->get(CollectorRepository::class); $controller = Yii::createObject(DebugController::class, [ 'responseFactory' => $dataResponseFactory, 'collectorRepository' => $collectorRepository, @@ -93,4 +94,28 @@ public function actionIndex() ]) )->getBody(); } + + public function actionView(string $id, string $collector) + { + $dataResponseFactory = Yii::createObject(DataResponseFactory::class, [ + 'responseFactory' => Yii::createObject(ResponseFactory::class, []), + 'streamFactory' => Yii::createObject(StreamFactory::class, []), + ]); + $collectorRepository = Yii::$container->get(CollectorRepository::class); + $data = $collectorRepository->getDetail($id); + + $collectorClass = $collector; + if ($collectorClass !== null) { + $data = $data[$collectorClass] ?? throw new NotFoundException( + sprintf("Requested collector doesn't exist: %s.", $collectorClass) + ); + } + + return (new JsonDataResponseFormatter()) + ->format( + $dataResponseFactory->createResponse([ + 'data' => $data, + ]) + )->getBody(); + } }