From ba6c10eb305fcbb87273d6bbd9c4d9a78e6eec40 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 6 Jul 2013 21:20:49 -0400 Subject: [PATCH] Yii debugger WIP --- framework/yii/base/Action.php | 5 +- framework/yii/base/Application.php | 12 +++ framework/yii/base/Controller.php | 3 + framework/yii/base/InlineAction.php | 5 +- framework/yii/console/Application.php | 1 + framework/yii/debug/LogTarget.php | 10 ++- framework/yii/debug/Module.php | 21 ++++-- framework/yii/debug/Panel.php | 5 +- .../yii/debug/controllers/DefaultController.php | 4 +- framework/yii/debug/panels/ConfigPanel.php | 1 - framework/yii/debug/panels/DbPanel.php | 21 ++++++ framework/yii/debug/panels/LogPanel.php | 17 ++++- framework/yii/debug/panels/ProfilingPanel.php | 85 +++++++++++++++++++++ framework/yii/debug/panels/RequestPanel.php | 88 +++++++++++++++++----- framework/yii/debug/views/default/index.php | 4 +- framework/yii/log/Target.php | 18 ++--- framework/yii/web/Application.php | 1 + framework/yii/web/UrlManager.php | 3 + tests/unit/framework/web/UrlManagerTest.php | 6 ++ 19 files changed, 266 insertions(+), 44 deletions(-) create mode 100644 framework/yii/debug/panels/DbPanel.php create mode 100644 framework/yii/debug/panels/ProfilingPanel.php diff --git a/framework/yii/base/Action.php b/framework/yii/base/Action.php index 1b3efbb..429d074 100644 --- a/framework/yii/base/Action.php +++ b/framework/yii/base/Action.php @@ -77,7 +77,10 @@ class Action extends Component throw new InvalidConfigException(get_class($this) . ' must define a "run()" method.'); } $args = $this->controller->bindActionParams($this, $params); - Yii::trace('Running "' . get_class($this) . '::run()" with parameters: ' . var_export($args, true), __METHOD__); + Yii::info('Running "' . get_class($this) . '::run()" with parameters: ' . var_export($args, true), __METHOD__); + if (Yii::$app->requestedParams === null) { + Yii::$app->requestedParams = $args; + } return call_user_func_array(array($this, 'run'), $args); } } diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index 55f1ab0..ae12244 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -78,6 +78,18 @@ abstract class Application extends Module * Defaults to 256KB. */ public $memoryReserveSize = 262144; + /** + * @var string the requested route + */ + public $requestedRoute; + /** + * @var Action the requested Action. If null, it means the request cannot be resolved into an action. + */ + public $requestedAction; + /** + * @var array the parameters supplied to the requested action. + */ + public $requestedParams; /** * @var string Used to reserve memory for fatal error handler. diff --git a/framework/yii/base/Controller.php b/framework/yii/base/Controller.php index 3f924a3..c0c5f2f 100644 --- a/framework/yii/base/Controller.php +++ b/framework/yii/base/Controller.php @@ -108,6 +108,9 @@ class Controller extends Component $action = $this->createAction($id); if ($action !== null) { Yii::trace("Route to run: " . $action->getUniqueId(), __METHOD__); + if (Yii::$app->requestedAction === null) { + Yii::$app->requestedAction = $action; + } $oldAction = $this->action; $this->action = $action; $result = null; diff --git a/framework/yii/base/InlineAction.php b/framework/yii/base/InlineAction.php index 8a9da85..3a12bc8 100644 --- a/framework/yii/base/InlineAction.php +++ b/framework/yii/base/InlineAction.php @@ -46,7 +46,10 @@ class InlineAction extends Action public function runWithParams($params) { $args = $this->controller->bindActionParams($this, $params); - Yii::trace("Running '" . get_class($this->controller) . '::' . $this->actionMethod . "()' with parameters: " . var_export($args, true), __METHOD__); + Yii::info("Running '" . get_class($this->controller) . '::' . $this->actionMethod . "()' with parameters: " . var_export($args, true), __METHOD__); + if (Yii::$app->requestedParams === null) { + Yii::$app->requestedParams = $args; + } return call_user_func_array(array($this->controller, $this->actionMethod), $args); } } diff --git a/framework/yii/console/Application.php b/framework/yii/console/Application.php index c5e22ab..a69abe0 100644 --- a/framework/yii/console/Application.php +++ b/framework/yii/console/Application.php @@ -92,6 +92,7 @@ class Application extends \yii\base\Application public function handleRequest($request) { list ($route, $params) = $request->resolve(); + $this->requestedRoute = $route; $result = $this->runAction($route, $params); if ($result instanceof Response) { return $result; diff --git a/framework/yii/debug/LogTarget.php b/framework/yii/debug/LogTarget.php index 71478ce..c5d0162 100644 --- a/framework/yii/debug/LogTarget.php +++ b/framework/yii/debug/LogTarget.php @@ -23,6 +23,10 @@ class LogTarget extends Target public $tag; public $historySize = 20; + /** + * @param \yii\debug\Module $module + * @param array $config + */ public function __construct($module, $config = array()) { parent::__construct($config); @@ -42,8 +46,8 @@ class LogTarget extends Target } $file = "$path/{$this->tag}.log"; $data = array(); - foreach ($this->module->panels as $panel) { - $data[$panel->id] = $panel->save(); + foreach ($this->module->panels as $id => $panel) { + $data[$id] = $panel->save(); } file_put_contents($file, json_encode($data)); } @@ -58,7 +62,7 @@ class LogTarget extends Target */ public function collect($messages, $final) { - $this->messages = array_merge($this->messages, $this->filterMessages($messages)); + $this->messages = array_merge($this->messages, $messages); if ($final) { $this->export($this->messages); $this->gc(); diff --git a/framework/yii/debug/Module.php b/framework/yii/debug/Module.php index c084fad..2fe0cf2 100644 --- a/framework/yii/debug/Module.php +++ b/framework/yii/debug/Module.php @@ -28,6 +28,10 @@ class Module extends \yii\base\Module public $controllerNamespace = 'yii\debug\controllers'; /** + * @var LogTarget + */ + public $logTarget; + /** * @var array|Panel[] */ public $panels = array(); @@ -36,19 +40,20 @@ class Module extends \yii\base\Module { parent::init(); + $this->logTarget = Yii::$app->getLog()->targets['debug'] = new LogTarget($this); + Yii::$app->getView()->on(View::EVENT_END_BODY, array($this, 'renderToolbar')); + foreach (array_merge($this->corePanels(), $this->panels) as $id => $config) { - $config['id'] = $id; + $config['module'] = $this; $this->panels[$id] = Yii::createObject($config); } - - Yii::$app->getLog()->targets['debug'] = new LogTarget($this); - Yii::$app->getView()->on(View::EVENT_END_BODY, array($this, 'renderToolbar')); } public function beforeAction($action) { Yii::$app->getView()->off(View::EVENT_END_BODY, array($this, 'renderToolbar')); unset(Yii::$app->getLog()->targets['debug']); + $this->logTarget = null; $ip = Yii::$app->getRequest()->getUserIP(); foreach ($this->allowedIPs as $filter) { @@ -63,7 +68,7 @@ class Module extends \yii\base\Module { /** @var View $view */ $id = 'yii-debug-toolbar'; - $tag = Yii::$app->getLog()->targets['debug']->tag; + $tag = $this->logTarget->tag; $url = Yii::$app->getUrlManager()->createUrl('debug/default/toolbar', array( 'tag' => $tag, )); @@ -88,6 +93,12 @@ class Module extends \yii\base\Module 'log' => array( 'class' => 'yii\debug\panels\LogPanel', ), + 'profiling' => array( + 'class' => 'yii\debug\panels\ProfilingPanel', + ), + 'db' => array( + 'class' => 'yii\debug\panels\DbPanel', + ), ); } } diff --git a/framework/yii/debug/Panel.php b/framework/yii/debug/Panel.php index 4db7f62..70ddc92 100644 --- a/framework/yii/debug/Panel.php +++ b/framework/yii/debug/Panel.php @@ -15,7 +15,10 @@ use yii\base\Component; */ class Panel extends Component { - public $id; + /** + * @var Module + */ + public $module; public $data; public function getName() diff --git a/framework/yii/debug/controllers/DefaultController.php b/framework/yii/debug/controllers/DefaultController.php index b63bc4b..9ad1c8c 100644 --- a/framework/yii/debug/controllers/DefaultController.php +++ b/framework/yii/debug/controllers/DefaultController.php @@ -51,8 +51,8 @@ class DefaultController extends Controller if (preg_match('/^[\w\-]+$/', $tag) && is_file($file)) { $data = json_decode(file_get_contents($file), true); foreach ($this->module->panels as $id => $panel) { - if (isset($data[$panel->id])) { - $panel->load($data[$panel->id]); + if (isset($data[$id])) { + $panel->load($data[$id]); } else { // remove the panel since it has not received any data unset($this->module->panels[$id]); diff --git a/framework/yii/debug/panels/ConfigPanel.php b/framework/yii/debug/panels/ConfigPanel.php index 9e19c90..f9eccc0 100644 --- a/framework/yii/debug/panels/ConfigPanel.php +++ b/framework/yii/debug/panels/ConfigPanel.php @@ -9,7 +9,6 @@ namespace yii\debug\panels; use Yii; use yii\debug\Panel; -use yii\helpers\Html; /** * @author Qiang Xue diff --git a/framework/yii/debug/panels/DbPanel.php b/framework/yii/debug/panels/DbPanel.php new file mode 100644 index 0000000..57cd4d9 --- /dev/null +++ b/framework/yii/debug/panels/DbPanel.php @@ -0,0 +1,21 @@ + + * @since 2.0 + */ +class DbPanel extends Panel +{ + public function getName() + { + return 'Database'; + } +} diff --git a/framework/yii/debug/panels/LogPanel.php b/framework/yii/debug/panels/LogPanel.php index e9de619..4d2f028 100644 --- a/framework/yii/debug/panels/LogPanel.php +++ b/framework/yii/debug/panels/LogPanel.php @@ -40,10 +40,21 @@ EOD; $time = date('H:i:s.', $log[3]) . sprintf('%03d', (int)(($log[3] - (int)$log[3]) * 1000)); $level = Logger::getLevelName($log[1]); $message = Html::encode(wordwrap($log[0])); - $rows[] = "$time$level{$log[2]}$message"; + if ($log[1] == Logger::LEVEL_ERROR) { + $class = ' class="error"'; + } elseif ($log[1] == Logger::LEVEL_WARNING) { + $class = ' class="warning"'; + } elseif ($log[1] == Logger::LEVEL_INFO) { + $class = ' class="info"'; + } else { + $class = ''; + } + $rows[] = "$time$level{$log[2]}$message"; } $rows = implode("\n", $rows); return <<Log Messages + @@ -62,8 +73,10 @@ EOD; public function save() { + $target = $this->module->logTarget; + $messages = $target->filterMessages($target->messages, Logger::LEVEL_ERROR | Logger::LEVEL_INFO | Logger::LEVEL_WARNING | Logger::LEVEL_TRACE); return array( - 'messages' => Yii::$app->getLog()->targets['debug']->messages, + 'messages' => $messages, ); } } diff --git a/framework/yii/debug/panels/ProfilingPanel.php b/framework/yii/debug/panels/ProfilingPanel.php new file mode 100644 index 0000000..643ca5f --- /dev/null +++ b/framework/yii/debug/panels/ProfilingPanel.php @@ -0,0 +1,85 @@ + + * @since 2.0 + */ +class ProfilingPanel extends Panel +{ + public function getName() + { + return 'Profiling'; + } + + public function getDetail() + { + $messages = $this->data['messages']; + $timings = array(); + $stack = array(); + foreach ($messages as $i => $log) { + list($token, $level, $category, $timestamp) = $log; + $log[4] = $i; + if ($level == Logger::LEVEL_PROFILE_BEGIN) { + $stack[] = $log; + } elseif ($level == Logger::LEVEL_PROFILE_END) { + if (($last = array_pop($stack)) !== null && $last[0] === $token) { + $timings[$last[4]] = array(count($stack), $token, $category, $timestamp - $last[3]); + } + } + } + + $now = microtime(true); + while (($last = array_pop($stack)) !== null) { + $delta = $now - $last[3]; + $timings[$last[4]] = array(count($stack), $last[0], $last[2], $delta); + } + ksort($timings); + + $rows = array(); + foreach ($timings as $timing) { + $time = sprintf('%0.5f', $timing[3]); + $procedure = str_repeat(' ', $timing[0] * 4) . Html::encode($timing[1]); + $category = Html::encode($timing[2]); + $rows[] = ""; + } + $rows = implode("\n", $rows); + + return <<Performance Profiling + +
$category$procedure{$time}s
+ + + + + + + + +$rows + +
CategoryProcedureTime
+EOD; + } + + public function save() + { + $target = $this->module->logTarget; + $messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE); + return array( + 'messages' => $messages, + ); + } +} diff --git a/framework/yii/debug/panels/RequestPanel.php b/framework/yii/debug/panels/RequestPanel.php index a587c59..905a375 100644 --- a/framework/yii/debug/panels/RequestPanel.php +++ b/framework/yii/debug/panels/RequestPanel.php @@ -7,6 +7,8 @@ namespace yii\debug\panels; +use Yii; +use yii\base\InlineAction; use yii\debug\Panel; use yii\helpers\Html; @@ -39,37 +41,92 @@ EOD; public function getDetail() { - return "

\$_GET

\n" . $this->renderTable($this->data['GET']) . "\n" - . "

\$_POST

\n" . $this->renderTable($this->data['POST']) . "\n" - . "

\$_COOKIE

\n" . $this->renderTable($this->data['COOKIE']) . "\n" - . "

\$_FILES

\n" . $this->renderTable($this->data['FILES']) . "\n" - . "

\$_SESSION

\n" . $this->renderTable($this->data['SESSION']) . "\n" - . "

\$_SERVER

\n" . $this->renderTable($this->data['SERVER']); + $data = array( + 'Route' => $this->data['route'], + 'Action' => $this->data['action'], + 'Parameters' => $this->data['actionParams'], + ); + return "

Request Information

\n" + . $this->renderData('Routing', $data) . "\n" + . $this->renderData('Flashes', $this->data['flashes']) . "\n" + . $this->renderData('$_GET', $this->data['GET']) . "\n" + . $this->renderData('$_POST', $this->data['POST']) . "\n" + . $this->renderData('$_COOKIE', $this->data['COOKIE']) . "\n" + . $this->renderData('$_FILES', $this->data['FILES']) . "\n" + . $this->renderData('$_SESSION', $this->data['SESSION']) . "\n" + . $this->renderData('$_SERVER', $this->data['SERVER']) . "\n" + . $this->renderData('Request Headers', $this->data['requestHeaders']) . "\n" + . $this->renderData('Response Headers', $this->data['responseHeaders']); } public function save() { + if (function_exists('apache_request_headers')) { + $requestHeaders = apache_request_headers(); + } elseif (function_exists('http_get_request_headers')) { + $requestHeaders = http_get_request_headers(); + } else { + $requestHeaders = array(); + } + $responseHeaders = array(); + foreach (headers_list() as $header) { + if (($pos = strpos($header, ':')) !== false) { + $name = substr($header, 0, $pos); + $value = trim(substr($header, $pos + 1)); + if (isset($responseHeaders[$name])) { + if (!is_array($responseHeaders[$name])) { + $responseHeaders[$name] = array($responseHeaders[$name], $value); + } else { + $responseHeaders[$name][] = $value; + } + } else { + $responseHeaders[$name] = $value; + } + } else { + $responseHeaders[] = $header; + } + } + if (Yii::$app->requestedAction) { + if (Yii::$app->requestedAction instanceof InlineAction) { + $action = get_class(Yii::$app->requestedAction->controller) . '::' . Yii::$app->requestedAction->actionMethod . '()'; + } else { + $action = get_class(Yii::$app->requestedAction) . '::run()'; + } + } else { + $action = null; + } + /** @var \yii\web\Session $session */ + $session = Yii::$app->getComponent('session', false); return array( 'memory' => memory_get_peak_usage(), 'time' => microtime(true) - YII_BEGIN_TIME, - 'SERVER' => $_SERVER, - 'GET' => $_GET, - 'POST' => $_POST, - 'COOKIE' => $_COOKIE, + 'flashes' => $session ? $session->getAllFlashes() : array(), + 'requestHeaders' => $requestHeaders, + 'responseHeaders' => $responseHeaders, + 'route' => Yii::$app->requestedAction->getUniqueId(), + 'action' => $action, + 'actionParams' => Yii::$app->requestedParams, + 'SERVER' => empty($_SERVER) ? array() : $_SERVER, + 'GET' => empty($_GET) ? array() : $_GET, + 'POST' => empty($_POST) ? array() : $_POST, + 'COOKIE' => empty($_COOKIE) ? array() : $_COOKIE, 'FILES' => empty($_FILES) ? array() : $_FILES, 'SESSION' => empty($_SESSION) ? array() : $_SESSION, ); } - protected function renderTable($values) + protected function renderData($caption, $values) { + if (empty($values)) { + return "

$caption

\n

Empty.

"; + } $rows = array(); foreach ($values as $name => $value) { $rows[] = '' . Html::encode($name) . '
' . Html::encode(var_export($value, true)) . '
'; } - if (!empty($rows)) { - $rows = implode("\n", $rows); - return <<$caption @@ -77,8 +134,5 @@ $rows
NameValue
EOD; - } else { - return 'Empty.'; - } } } diff --git a/framework/yii/debug/views/default/index.php b/framework/yii/debug/views/default/index.php index 79caf02..3fdf37a 100644 --- a/framework/yii/debug/views/default/index.php +++ b/framework/yii/debug/views/default/index.php @@ -24,8 +24,8 @@ use yii\helpers\Html;