Commit 266a3e1b authored by tufengqi's avatar tufengqi

fix

parent f0058098
......@@ -8,7 +8,6 @@ $baseDir = dirname($vendorDir);
return array(
'yii\\swiftmailer\\' => array($vendorDir . '/yiisoft/yii2-swiftmailer'),
'yii\\redis\\' => array($vendorDir . '/yiisoft/yii2-redis/src'),
'yii\\debug\\' => array($vendorDir . '/yiisoft/yii2-debug'),
'yii\\composer\\' => array($vendorDir . '/yiisoft/yii2-composer'),
'yii\\bootstrap\\' => array($vendorDir . '/yiisoft/yii2-bootstrap/src'),
'yii\\' => array($vendorDir . '/yiisoft/yii2'),
......
......@@ -16,7 +16,6 @@ class ComposerStaticInitbdb6249a02d7626d1497a483c8a891d8
array (
'yii\\swiftmailer\\' => 16,
'yii\\redis\\' => 10,
'yii\\debug\\' => 10,
'yii\\composer\\' => 13,
'yii\\bootstrap\\' => 14,
'yii\\' => 4,
......@@ -36,10 +35,6 @@ class ComposerStaticInitbdb6249a02d7626d1497a483c8a891d8
array (
0 => __DIR__ . '/..' . '/yiisoft/yii2-redis/src',
),
'yii\\debug\\' =>
array (
0 => __DIR__ . '/..' . '/yiisoft/yii2-debug',
),
'yii\\composer\\' =>
array (
0 => __DIR__ . '/..' . '/yiisoft/yii2-composer',
......
......@@ -498,55 +498,6 @@
]
},
{
"name": "yiisoft/yii2-debug",
"version": "2.0.13",
"version_normalized": "2.0.13.0",
"source": {
"type": "git",
"url": "https://github.com/yiisoft/yii2-debug.git",
"reference": "b37f414959c2fafefb332020b42037cd17c1cb7f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yiisoft/yii2-debug/zipball/b37f414959c2fafefb332020b42037cd17c1cb7f",
"reference": "b37f414959c2fafefb332020b42037cd17c1cb7f",
"shasum": ""
},
"require": {
"yiisoft/yii2": "~2.0.13",
"yiisoft/yii2-bootstrap": "~2.0.0"
},
"time": "2017-12-05T07:36:23+00:00",
"type": "yii2-extension",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"yii\\debug\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com"
}
],
"description": "The debugger extension for the Yii framework",
"keywords": [
"debug",
"debugger",
"yii2"
]
},
{
"name": "yiisoft/yii2-mns",
"version": "dev-master",
"version_normalized": "9999999-dev",
......
......@@ -30,13 +30,4 @@ return array (
'@yii/bootstrap' => $vendorDir . '/yiisoft/yii2-bootstrap/src',
),
),
'yiisoft/yii2-debug' =>
array (
'name' => 'yiisoft/yii2-debug',
'version' => '2.0.13.0',
'alias' =>
array (
'@yii/debug' => $vendorDir . '/yiisoft/yii2-debug',
),
),
);
This diff is collapsed.
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug;
use yii\web\AssetBundle;
/**
* Debugger asset bundle
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class DebugAsset extends AssetBundle
{
public $sourcePath = '@yii/debug/assets';
public $css = [
'main.css',
'toolbar.css',
];
public $depends = [
'yii\web\YiiAsset',
'yii\bootstrap\BootstrapAsset',
];
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug;
/**
* FlattenException wraps a PHP Exception to be able to serialize it.
* Implements the Throwable interface
* Basically, this class removes all objects from the trace.
* Ported from Symfony components @link https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Debug/Exception/FlattenException.php
*
* @author Dmitry Bashkarev <dmitry@bashkarev.com>
* @since 2.0.10
*/
class FlattenException
{
/**
* @var string
*/
protected $message;
/**
* @var mixed|int
*/
protected $code;
/**
* @var string
*/
protected $file;
/**
* @var int
*/
protected $line;
/**
* @var FlattenException|null
*/
private $_previous;
/**
* @var array
*/
private $_trace;
/**
* @var string
*/
private $_toString;
/**
* @var string
*/
private $_class;
/**
* FlattenException constructor.
* @param \Exception $exception
*/
public function __construct(\Exception $exception)
{
$this->setMessage($exception->getMessage());
$this->setCode($exception->getCode());
$this->setFile($exception->getFile());
$this->setLine($exception->getLine());
$this->setTrace($exception->getTrace());
$this->setToString($exception->__toString());
$this->setClass(get_class($exception));
$previous = $exception->getPrevious();
if ($previous instanceof \Exception) {
$this->setPrevious(new self($previous));
}
}
/**
* Gets the Exception message
* @return string the Exception message as a string.
*/
public function getMessage()
{
return $this->message;
}
/**
* Gets the Exception code
* @return mixed|int the exception code as integer.
*/
public function getCode()
{
return $this->code;
}
/**
* Gets the file in which the exception occurred
* @return string the filename in which the exception was created.
*/
public function getFile()
{
return $this->file;
}
/**
* Gets the line in which the exception occurred
* @return int the line number where the exception was created.
*/
public function getLine()
{
return $this->line;
}
/**
* Gets the stack trace
* @return array the Exception stack trace as an array.
*/
public function getTrace()
{
return $this->_trace;
}
/**
* Returns previous Exception
* @return FlattenException the previous `FlattenException` if available or null otherwise.
*/
public function getPrevious()
{
return $this->_previous;
}
/**
* Gets the stack trace as a string
* @return string the Exception stack trace as a string.
*/
public function getTraceAsString()
{
$remove = "Stack trace:\n";
$len = strpos($this->_toString, $remove);
if ($len === false) {
return '';
}
return substr($this->_toString, $len + strlen($remove));
}
/**
* String representation of the exception
* @return string the string representation of the exception.
*/
public function __toString()
{
return $this->_toString;
}
/**
* @return string the name of the class in which the exception was created.
*/
public function getClass()
{
return $this->_class;
}
/**
* @param string $message the Exception message as a string.
*/
protected function setMessage($message)
{
$this->message = $message;
}
/**
* @param mixed|int $code the exception code as integer.
*/
protected function setCode($code)
{
$this->code = $code;
}
/**
* @param string $file the filename in which the exception was created.
*/
protected function setFile($file)
{
$this->file = $file;
}
/**
* @param int $line the line number where the exception was created.
*/
protected function setLine($line)
{
$this->line = $line;
}
/**
* @param array $trace the Exception stack trace as an array.
*/
protected function setTrace($trace)
{
$this->_trace = [];
foreach ($trace as $entry) {
$class = '';
$namespace = '';
if (isset($entry['class'])) {
$parts = explode('\\', $entry['class']);
$class = array_pop($parts);
$namespace = implode('\\', $parts);
}
$this->_trace[] = [
'namespace' => $namespace,
'short_class' => $class,
'class' => isset($entry['class']) ? $entry['class'] : '',
'type' => isset($entry['type']) ? $entry['type'] : '',
'function' => isset($entry['function']) ? $entry['function'] : null,
'file' => isset($entry['file']) ? $entry['file'] : null,
'line' => isset($entry['line']) ? $entry['line'] : null,
'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : [],
];
}
}
/**
* @param string $string the string representation of the thrown object.
*/
protected function setToString($string)
{
$this->_toString = $string;
}
/**
* @param FlattenException $previous previous Exception.
*/
protected function setPrevious(FlattenException $previous)
{
$this->_previous = $previous;
}
/**
* @param string $class the name of the class in which the exception was created.
*/
protected function setClass($class)
{
$this->_class = $class;
}
/**
* Allows you to sterilize the Exception trace arguments
* @param array $args
* @param int $level recursion level
* @param int $count number of records counter
* @return array arguments tracing.
*/
private function flattenArgs($args, $level = 0, &$count = 0)
{
$result = [];
foreach ($args as $key => $value) {
if (++$count > 10000) {
return ['array', '*SKIPPED over 10000 entries*'];
}
if ($value instanceof \__PHP_Incomplete_Class) {
// is_object() returns false on PHP<=7.1
$result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)];
} elseif (is_object($value)) {
$result[$key] = ['object', get_class($value)];
} elseif (is_array($value)) {
if ($level > 10) {
$result[$key] = ['array', '*DEEP NESTED ARRAY*'];
} else {
$result[$key] = ['array', $this->flattenArgs($value, $level + 1, $count)];
}
} elseif (null === $value) {
$result[$key] = ['null', null];
} elseif (is_bool($value)) {
$result[$key] = ['boolean', $value];
} elseif (is_int($value)) {
$result[$key] = ['integer', $value];
} elseif (is_float($value)) {
$result[$key] = ['float', $value];
} elseif (is_resource($value)) {
$result[$key] = ['resource', get_resource_type($value)];
} else {
$result[$key] = ['string', (string)$value];
}
}
return $result;
}
/**
* @param \__PHP_Incomplete_Class $value
* @return string the real class name of an incomplete class
*/
private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value)
{
$array = new \ArrayObject($value);
return $array['__PHP_Incomplete_Class_Name'];
}
}
\ No newline at end of file
The Yii framework is free software. It is released under the terms of
the following BSD License.
Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Yii Software LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug;
use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\FileHelper;
use yii\log\Target;
/**
* The debug LogTarget is used to store logs for later use in the debugger tool
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class LogTarget extends Target
{
/**
* @var Module
*/
public $module;
public $tag;
/**
* @param \yii\debug\Module $module
* @param array $config
*/
public function __construct($module, $config = [])
{
parent::__construct($config);
$this->module = $module;
$this->tag = uniqid();
}
/**
* Exports log messages to a specific destination.
* Child classes must implement this method.
*/
public function export()
{
$path = $this->module->dataPath;
FileHelper::createDirectory($path, $this->module->dirMode);
$summary = $this->collectSummary();
$dataFile = "$path/{$this->tag}.data";
$data = [];
$exceptions = [];
foreach ($this->module->panels as $id => $panel) {
try {
$data[$id] = serialize($panel->save());
} catch (\Exception $exception) {
$exceptions[$id] = new FlattenException($exception);
}
}
$data['summary'] = $summary;
$data['exceptions'] = $exceptions;
file_put_contents($dataFile, serialize($data));
if ($this->module->fileMode !== null) {
@chmod($dataFile, $this->module->fileMode);
}
$indexFile = "$path/index.data";
$this->updateIndexFile($indexFile, $summary);
}
/**
* Updates index file with summary log data
*
* @param string $indexFile path to index file
* @param array $summary summary log data
* @throws \yii\base\InvalidConfigException
*/
private function updateIndexFile($indexFile, $summary)
{
touch($indexFile);
if (($fp = @fopen($indexFile, 'r+')) === false) {
throw new InvalidConfigException("Unable to open debug data index file: $indexFile");
}
@flock($fp, LOCK_EX);
$manifest = '';
while (($buffer = fgets($fp)) !== false) {
$manifest .= $buffer;
}
if (!feof($fp) || empty($manifest)) {
// error while reading index data, ignore and create new
$manifest = [];
} else {
$manifest = unserialize($manifest);
}
$manifest[$this->tag] = $summary;
$this->gc($manifest);
ftruncate($fp, 0);
rewind($fp);
fwrite($fp, serialize($manifest));
@flock($fp, LOCK_UN);
@fclose($fp);
if ($this->module->fileMode !== null) {
@chmod($indexFile, $this->module->fileMode);
}
}
/**
* Processes the given log messages.
* This method will filter the given messages with [[levels]] and [[categories]].
* And if requested, it will also export the filtering result to specific medium (e.g. email).
* @param array $messages log messages to be processed. See [[\yii\log\Logger::messages]] for the structure
* of each message.
* @param bool $final whether this method is called at the end of the current application
*/
public function collect($messages, $final)
{
$this->messages = array_merge($this->messages, $messages);
if ($final) {
$this->export();
}
}
/**
* Removes obsolete data files
* @param array $manifest
*/
protected function gc(&$manifest)
{
if (count($manifest) > $this->module->historySize + 10) {
$n = count($manifest) - $this->module->historySize;
foreach (array_keys($manifest) as $tag) {
$file = $this->module->dataPath . "/$tag.data";
@unlink($file);
unset($manifest[$tag]);
if (--$n <= 0) {
break;
}
}
}
}
/**
* Collects summary data of current request.
* @return array
*/
protected function collectSummary()
{
if (Yii::$app === null) {
return '';
}
$request = Yii::$app->getRequest();
$response = Yii::$app->getResponse();
$summary = [
'tag' => $this->tag,
'url' => $request->getAbsoluteUrl(),
'ajax' => (int) $request->getIsAjax(),
'method' => $request->getMethod(),
'ip' => $request->getUserIP(),
'time' => $_SERVER['REQUEST_TIME_FLOAT'],
'statusCode' => $response->statusCode,
'sqlCount' => $this->getSqlTotalCount(),
];
if (isset($this->module->panels['mail'])) {
$summary['mailCount'] = count($this->module->panels['mail']->getMessages());
}
return $summary;
}
/**
* Returns total sql count executed in current request. If database panel is not configured
* returns 0.
* @return int
*/
protected function getSqlTotalCount()
{
if (!isset($this->module->panels['db'])) {
return 0;
}
$profileLogs = $this->module->panels['db']->getProfileLogs();
# / 2 because messages are in couple (begin/end)
return count($profileLogs) / 2;
}
}
This diff is collapsed.
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug;
use Yii;
use yii\base\Component;
use yii\helpers\ArrayHelper;
use yii\helpers\Url;
/**
* Panel is a base class for debugger panel classes. It defines how data should be collected,
* what should be displayed at debug toolbar and on debugger details view.
*
* @property string $detail Content that is displayed in debugger detail view. This property is read-only.
* @property string $name Name of the panel. This property is read-only.
* @property string $summary Content that is displayed at debug toolbar. This property is read-only.
* @property string $url URL pointing to panel detail view. This property is read-only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Panel extends Component
{
/**
* @var string panel unique identifier.
* It is set automatically by the container module.
*/
public $id;
/**
* @var string request data set identifier.
*/
public $tag;
/**
* @var Module
*/
public $module;
/**
* @var mixed data associated with panel
*/
public $data;
/**
* @var array array of actions to add to the debug modules default controller.
* This array will be merged with all other panels actions property.
* See [[\yii\base\Controller::actions()]] for the format.
*/
public $actions = [];
/**
* @var FlattenException|null Error while saving the panel
* @since 2.0.10
*/
protected $error;
/**
* @return string name of the panel
*/
public function getName()
{
return '';
}
/**
* @return string content that is displayed at debug toolbar
*/
public function getSummary()
{
return '';
}
/**
* @return string content that is displayed in debugger detail view
*/
public function getDetail()
{
return '';
}
/**
* Saves data to be later used in debugger detail view.
* This method is called on every page where debugger is enabled.
*
* @return mixed data to be saved
*/
public function save()
{
return null;
}
/**
* Loads data into the panel
*
* @param mixed $data
*/
public function load($data)
{
$this->data = $data;
}
/**
* @param null|array $additionalParams Optional additional parameters to add to the route
* @return string URL pointing to panel detail view
*/
public function getUrl($additionalParams = null)
{
$route = [
'/' . $this->module->id . '/default/view',
'panel' => $this->id,
'tag' => $this->tag,
];
if (is_array($additionalParams)){
$route = ArrayHelper::merge($route, $additionalParams);
}
return Url::toRoute($route);
}
/**
* Returns a trace line
* @param array $options The array with trace
* @return string the trace line
* @since 2.0.7
*/
public function getTraceLine($options)
{
if (!isset($options['text'])) {
$options['text'] = "{$options['file']}:{$options['line']}";
}
$traceLine = $this->module->traceLine;
if ($traceLine === false) {
return $options['text'];
}
$options['file'] = str_replace('\\', '/', $options['file']);
$rawLink = $traceLine instanceof \Closure ? $traceLine($options, $this) : $traceLine;
return strtr($rawLink, ['{file}' => $options['file'], '{line}' => $options['line'], '{text}' => $options['text']]);
}
/**
* @param FlattenException $error
* @since 2.0.10
*/
public function setError(FlattenException $error)
{
$this->error = $error;
}
/**
* @return FlattenException|null
* @since 2.0.10
*/
public function getError()
{
return $this->error;
}
/**
* @return bool
* @since 2.0.10
*/
public function hasError()
{
return $this->error !== null;
}
/**
* Is the panel enabled?
* @return bool
* @since 2.0.10
*/
public function isEnabled()
{
return true;
}
}
<p align="center">
<a href="https://github.com/yiisoft" target="_blank">
<img src="https://avatars0.githubusercontent.com/u/993323" height="100px">
</a>
<h1 align="center">Debug Extension for Yii 2</h1>
<br>
</p>
This extension provides a debugger for [Yii framework 2.0](http://www.yiiframework.com) applications. When this extension is used,
a debugger toolbar will appear at the bottom of every page. The extension also provides
a set of standalone pages to display more detailed debug information.
For license information check the [LICENSE](LICENSE.md)-file.
Documentation is at [docs/guide/README.md](docs/guide/README.md).
[![Latest Stable Version](https://poser.pugx.org/yiisoft/yii2-debug/v/stable.png)](https://packagist.org/packages/yiisoft/yii2-debug)
[![Total Downloads](https://poser.pugx.org/yiisoft/yii2-debug/downloads.png)](https://packagist.org/packages/yiisoft/yii2-debug)
[![Build Status](https://travis-ci.org/yiisoft/yii2-debug.svg?branch=master)](https://travis-ci.org/yiisoft/yii2-debug)
Installation
------------
The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
Either run
```
php composer.phar require --prefer-dist yiisoft/yii2-debug
```
or add
```
"yiisoft/yii2-debug": "~2.0.0"
```
to the require section of your `composer.json` file.
Usage
-----
Once the extension is installed, simply modify your application configuration as follows:
```php
return [
'bootstrap' => ['debug'],
'modules' => [
'debug' => [
'class' => 'yii\debug\Module',
// uncomment and adjust the following to add your IP if you are not connecting from localhost.
//'allowedIPs' => ['127.0.0.1', '::1'],
],
// ...
],
...
];
```
You will see a debugger toolbar showing at the bottom of every page of your application.
You can click on the toolbar to see more detailed debug information.
Open Files in IDE
-----
You can create a link to open files in your favorite IDE with this configuration:
```php
return [
'bootstrap' => ['debug'],
'modules' => [
'debug' => [
'class' => 'yii\debug\Module',
'traceLine' => '<a href="phpstorm://open?url={file}&line={line}">{file}:{line}</a>',
// uncomment and adjust the following to add your IP if you are not connecting from localhost.
//'allowedIPs' => ['127.0.0.1', '::1'],
],
// ...
],
...
];
```
You must make some changes to your OS. See these examples:
- PHPStorm: https://github.com/aik099/PhpStormProtocol
- Sublime Text 3 on Windows or Linux: https://packagecontrol.io/packages/subl%20protocol
- Sublime Text 3 on Mac: https://github.com/inopinatus/sublime_url
#### Virtualized or dockerized
If your application is run under a virtualized or dockerized environment, it is often the case that the application's base path is different inside of the virtual machine or container than on your host machine. For the links work in those situations, you can configure `traceLine` like this (change the path to your app):
```php
'traceLine' => function($options, $panel) {
$filePath = str_replace(Yii::$app->basePath, '~/path/to/your/app', $options['file']);
return strtr('<a href="ide://open?url=file://{file}&line={line}">{text}</a>', ['{file}' => $filePath]);
},
```
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug;
use yii\web\AssetBundle;
/**
* Timeline asset bundle
*
* @author Dmitriy Bashkarev <dmitriy@bashkarev.com>
* @since 2.0.7
*/
class TimelineAsset extends AssetBundle
{
public $sourcePath = '@yii/debug/assets';
public $css = [
'timeline.css',
];
public $js = [
'timeline.js',
];
}
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug;
use yii\web\AssetBundle;
/**
* Userswitch asset bundle
*
* @author Semen Dubina <yii2debug@sam002.net>
* @since 2.0.10
*/
class UserswitchAsset extends AssetBundle
{
public $sourcePath = '@yii/debug/assets';
public $js = [
'userswitch.js',
];
}
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\actions\db;
use yii\base\Action;
use yii\debug\panels\DbPanel;
use yii\web\HttpException;
/**
* ExplainAction provides EXPLAIN information for SQL queries
*
* @author Laszlo <github@lvlconsultancy.nl>
* @since 2.0.6
*/
class ExplainAction extends Action
{
/**
* @var DbPanel
*/
public $panel;
public function run($seq, $tag)
{
$this->controller->loadData($tag);
$timings = $this->panel->calculateTimings();
if (!isset($timings[$seq])) {
throw new HttpException(404, 'Log message not found.');
}
$query = $timings[$seq]['info'];
$results = $this->panel->getDb()->createCommand('EXPLAIN ' . $query)->queryAll();
$output[] = '<table class="table"><thead><tr>' . implode(array_map(function($key) {
return '<th>' . $key . '</th>';
}, array_keys($results[0]))) . '</tr></thead><tbody>';
foreach ($results as $result) {
$output[] = '<tr>' . implode(array_map(function($value) {
return '<td>' . (empty($value) ? 'NULL' : htmlspecialchars($value)) . '</td>';
}, $result)) . '</tr>';
}
$output[] = '</tbody></table>';
return implode($output);
}
}
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 50 50"><path fill="#444" d="M15.563 40.836a.997.997 0 0 0 1.414 0l15-15a1 1 0 0 0 0-1.414l-15-15a1 1 0 0 0-1.414 1.414L29.856 25.13 15.563 39.42a1 1 0 0 0 0 1.414z"/></svg>
.main-container {
width: auto;
}
span.indent {
color: #ccc;
}
ul.trace {
font-size: 12px;
color: #999;
margin: 2px 0 0 0;
padding: 0;
list-style: none;
white-space: normal;
}
#db-panel-detailed-grid table tbody tr td {
position: relative;
}
.db-explain {
position: absolute;
bottom: 4px;
right: 4px;
font-size: 10px;
}
.db-explain-text {
display: none;
margin: 10px 0 0px 0;
font-size: 13px;
width: 100%;
word-break: break-all;
}
#db-explain-all {
position: absolute;
bottom: 0;
right: 0;
font-size: 12px;
margin-right: 15px;
}
ul.assets {
margin: 2px 0 0 0;
padding: 0;
list-style: none;
white-space: normal;
}
.callout {
margin: 0 0 10px 0;
padding: 5px;
border: solid 1px #eee;
border-radius: 3px;
}
.callout-important {
background-color: rgba(185, 74, 72, 0.2);
border-color: rgba(185, 74, 72, 0.4);
}
.callout-success {
background-color: rgba(70, 136, 71, 0.2);
border-color: rgba(70, 136, 71, 0.4);
}
.callout-info {
background-color: rgba(58, 135, 173, 0.2);
border-color: rgba(58, 135, 173, 0.4);
}
.list-group .glyphicon {
float: right;
}
td, th {
white-space: pre-wrap;
word-wrap: break-word;
}
.request-table td {
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
word-break: break-all;
}
.request-table tr > th:first-child {
width: 25%;
}
.config-php-info-table td.v {
word-break: break-all;
}
.not-set {
color: #c55;
font-style: italic;
}
.detail-grid-view th {
white-space: nowrap;
}
/* add sorting icons to gridview sort links */
a.asc:after, a.desc:after {
position: relative;
top: 1px;
display: inline-block;
font-family: 'Glyphicons Halflings';
font-style: normal;
font-weight: normal;
line-height: 1;
padding-left: 5px;
}
a.asc:after {
content: /*"\e113"*/ "\e151";
}
a.desc:after {
content: /*"\e114"*/ "\e152";
}
.sort-numerical a.asc:after {
content: "\e153";
}
.sort-numerical a.desc:after {
content: "\e154";
}
.sort-ordinal a.asc:after {
content: "\e155";
}
.sort-ordinal a.desc:after {
content: "\e156";
}
.mail-sorter {
margin-top: 7px;
}
.mail-sorter li {
list-style: none;
float: left;
width: 12%;
font-weight: bold;
}
.nowrap {
white-space: nowrap;
}
.table-pointer tbody tr {
cursor: pointer;
}
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 50 50"><path fill="#444" d="M39.642 9.722a1.01 1.01 0 0 0-.382-.077H28.103a1 1 0 0 0 0 2h8.743L21.7 26.79a1 1 0 0 0 1.414 1.415L38.26 13.06v8.743a1 1 0 0 0 2 0V10.646a1.005 1.005 0 0 0-.618-.924z"/><path d="M39.26 27.985a1 1 0 0 0-1 1v10.66h-28v-28h10.683a1 1 0 0 0 0-2H9.26a1 1 0 0 0-1 1v30a1 1 0 0 0 1 1h30a1 1 0 0 0 1-1v-11.66a1 1 0 0 0-1-1z"/></svg>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 50 50" version="1.1"><path d="m41.1 23c-0.6 0-1 0.4-1 1v10.7l-25.6-0.1c0 0 0-2 0-2.8 0-0.8-0.7-1-1-0.6l-3.5 3.5c-0.6 0.6-0.6 1.3 0 2l3.4 3.4c0.4 0.4 1.1 0.2 1-0.6l0-2.9c0 0 20.8 0.1 26.6 0 0.6 0 1-0.4 1-1v-11.7c0-0.6-0.4-1-1-1zM9 26.9 9 26.9 9 26.9 9 26.9"/><path d="m9 26.9c0.6 0 1-0.4 1-1v-10.7l25.6 0.1c0 0 0 2 0 2.8 0 0.8 0.7 1 1 0.6l3.5-3.5c0.6-0.6 0.6-1.3 0-2l-3.4-3.4c-0.4-0.4-1.1-0.2-1 0.6l0 2.9c0 0-20.8-0.1-26.6 0-0.6 0-1 0.4-1 1v11.7c0 0.6 0.4 1 1 1z"/></svg>
\ No newline at end of file
.debug-timeline-panel {
border: 1px solid #ddd;
position: relative;
margin-bottom: 20px;
}
.debug-timeline-panel.inline .debug-timeline-panel__item {
height: 20px;
margin-top: -20px;
border-bottom: 0;
}
.debug-timeline-panel.inline .debug-timeline-panel__item:first-child {
margin: 0;
}
.debug-timeline-panel.inline .debug-timeline-panel__item:not(.empty):hover {
background-color: transparent;
}
.debug-timeline-panel.inline .debug-timeline-panel__items .time {
box-shadow: inset 0px 0 3px -1px rgba(255, 255, 255, 0.7);
}
.debug-timeline-panel.inline .debug-timeline-panel__items .category {
display: none;
}
.debug-timeline-panel.inline .ruler.ruler-start,
.debug-timeline-panel.inline .ruler.ruler-end{
display: none;
}
.debug-timeline-panel:not(.inline) .debug-timeline-panel__item a:focus{
outline: none;
}
.debug-timeline-panel.affix .ruler b {
z-index: 2;
position: fixed;
top: 0;
}
.debug-timeline-panel .category {
opacity: 1;
font-size: 10px;
position: absolute;
line-height: 20px;
padding: 0 10px;
color: #222;
white-space: nowrap;
cursor: pointer;
}
.debug-timeline-panel .category span {
color: #7d7d7d;
}
.debug-timeline-panel .category span.memory[title] {
cursor: help;
border-bottom: 1px dotted #777;
}
.debug-timeline-panel .right > .category {
right: 100%;
}
.debug-timeline-panel .left > .category {
left: 100%;
}
.debug-timeline-panel .ruler {
position: absolute;
content: '';
font-size: 10px;
padding-left: 2px;
top: 0;
height: 100%;
border-left: 1px solid #ddd;
}
.debug-timeline-panel__header .ruler:first-child{
border-left: none;
}
.debug-timeline-panel .ruler.ruler-start {
top: auto;
margin-top: 20px;
}
.debug-timeline-panel .ruler.ruler-end {
left: -1px;
top: auto;
}
.debug-timeline-panel .ruler b {
position: absolute;
z-index: 2;
color: black;
font-weight: bold;
white-space: nowrap;
background-color: rgba(255,255,255,.4);
min-width: 40px;
line-height: 19px;
display: block;
text-align: center;
}
.debug-timeline-panel .time {
position: relative;
min-height: 20px;
display: block;
min-width: 1px;
padding: 0;
background-color: #989898;
}
.debug-timeline-panel .time + .tooltip .tooltip-inner{
max-width: 300px;
max-height: 180px;
overflow: auto;
word-wrap: break-word;
overflow-wrap: break-word;
}
.debug-timeline-panel__header {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
.debug-timeline-panel__header,
.debug-timeline-panel__item {
min-height: 20px;
border-bottom: 1px solid #ddd;
overflow: hidden;
}
.debug-timeline-panel__header .control {
position: absolute;
margin-left: -20px;
top:0;
}
.debug-timeline-panel__header .control button {
display: none;
padding: 0;
}
.debug-timeline-panel__header .control button:focus{
outline: none;
}
.debug-timeline-panel__header .control button:hover{
fill: #337ab7;
}
.debug-timeline-panel:not(.inline) .debug-timeline-panel__header .control button.inline,
.debug-timeline-panel.inline .debug-timeline-panel__header .control button.open{
display: block;
}
.debug-timeline-panel.affix .debug-timeline-panel__header .control{
position: fixed;
}
.debug-timeline-panel__item:last-child {
border-bottom: 0;
}
.debug-timeline-panel__item:nth-child(2n) {
background-color: #f9f9f9;
}
.debug-timeline-panel__item:hover {
background-color: rgba(51, 122, 183, 0.16);
}
.debug-timeline-panel__item.empty {
background-color: #f9f9f9;
line-height: 20px;
padding-left: 10px;
}
.debug-timeline-panel__item.empty span {
position: absolute;
background-color: inherit;
}
.debug-timeline-panel__search {
background-color: #f9f9f9;
padding: 10px 10px 0px 10px;
margin-bottom: 10px;
font-size: 16px;
}
.debug-timeline-panel__search > div {
display: inline-block;
margin-bottom: 10px;
}
.debug-timeline-panel__search .duration {
margin-right: 20px;
}
.debug-timeline-panel__search label {
width: 80px;
}
.debug-timeline-panel__search input {
font-size: 16px;
padding: 4px;
}
.debug-timeline-panel__search input#timeline-duration {
width: 55px;
text-align: right;
}
.debug-timeline-panel__search input#timeline-category {
min-width: 185px;
}
.debug-timeline-panel__memory {
position: relative;
margin-top: 18px;
box-sizing: content-box;
border-bottom: 1px solid #ddd;
}
.debug-timeline-panel__memory svg{
width: 100%;
}
.debug-timeline-panel__memory .scale {
font-size: 12px;
line-height: 16px;
position: absolute;
border-bottom: 1px dashed #000;
width: 100%;
padding-left: 6px;
transition: bottom 0.2s ease;
}
@media (max-width:767px) {
.debug-timeline-panel .ruler:nth-child(2n) b{
display: none;
}
}
@media (max-width: 991px) {
.debug-timeline-panel__header .control{
margin-left: -17px;
}
}
(function () {
'use strict';
var Timeline = function (options) {
this.options = options;
var self = this;
this.init = function () {
if (this.options.$focus) {
this.options.$focus.focus();
delete this.options.$focus;
}
self.options.$timeline.find('.debug-timeline-panel__item a')
.on('show.bs.tooltip', function () {
var data = $(this).data('memory');
if (data) {
self.options.$memory.text(data[0]).css({'bottom': data[1]+'%'});
}
})
.tooltip();
return self;
};
this.setFocus = function ($elem) {
this.options.$focus = $elem;
return $elem;
};
this.affixTop = function (refresh) {
if (!this.options.affixTop || refresh) {
this.options.affixTop = self.options.$header.offset().top;
}
return this.options.affixTop;
};
$(document).on('pjax:success', function () {
self.init()
});
$(window).on('resize', function () {
self.affixTop(true);
});
self.options.$header
.on('dblclick', function () {
self.options.$timeline.toggleClass('inline');
})
.on('click', 'button', function () {
self.options.$timeline.toggleClass('inline');
});
self.options.$search.on('change', function () {
self.setFocus($(this)).submit();
});
self.options.$timeline.affix({
offset: {
top: function () {
return self.affixTop()
}
}
});
this.init();
};
(new Timeline({
'$timeline': $('.debug-timeline-panel'),
'$header': $('.debug-timeline-panel__header'),
'$search': $('.debug-timeline-panel__search input'),
'$memory': $('.debug-timeline-panel__memory .scale')
}));
})();
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
(function () {
'use strict';
var sendSetIdentity = function(e) {
var form = $(this);
var formData = form.serialize();
$.ajax({
url: form.attr("action"),
type: form.attr("method"),
data: formData,
success: function (data) {
window.top.location.reload();
},
error: function (data) {
form.yiiActiveForm('updateMessages', data.responseJSON, true);
}
});
};
$('#debug-userswitch__set-identity').on('beforeSubmit', sendSetIdentity)
.on('submit', function(e){
e.preventDefault();
});
$('#debug-userswitch__reset-identity').on('beforeSubmit', sendSetIdentity)
.on('submit', function(e){
e.preventDefault();
});
$('#debug-userswitch__filter').on("click", "tbody tr", function(event) {
$('#debug-userswitch__set-identity #user_id').val($(this).data('key'));
$('#debug-userswitch__set-identity').submit();
event.stopPropagation();
});
})();
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\components\search;
use yii\base\Component;
use yii\debug\components\search\matchers\MatcherInterface;
/**
* Provides array filtering capabilities.
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class Filter extends Component
{
/**
* @var array rules for matching filters in the way: [:fieldName => [rule1, rule2,..]]
*/
protected $rules = [];
/**
* Adds data filtering rule.
*
* @param string $name attribute name
* @param MatcherInterface $rule
*/
public function addMatcher($name, MatcherInterface $rule)
{
if ($rule->hasValue()) {
$this->rules[$name][] = $rule;
}
}
/**
* Applies filter on a given array and returns filtered data.
*
* @param array $data data to filter
* @return array filtered data
*/
public function filter(array $data)
{
$filtered = [];
foreach ($data as $row) {
if ($this->passesFilter($row)) {
$filtered[] = $row;
}
}
return $filtered;
}
/**
* Checks if the given data satisfies filters.
*
* @param array $row data
* @return bool if data passed filtering
*/
private function passesFilter(array $row)
{
foreach ($row as $name => $value) {
if (isset($this->rules[$name])) {
// check all rules for a given attribute
foreach ($this->rules[$name] as $rule) {
/* @var $rule MatcherInterface */
if (!$rule->match($value)) {
return false;
}
}
}
}
return true;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\components\search\matchers;
use yii\base\Component;
/**
* Base class for matchers that are used in a filter.
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
abstract class Base extends Component implements MatcherInterface
{
/**
* @var mixed base value to check
*/
protected $baseValue;
/**
* @inheritdoc
*/
public function setValue($value)
{
$this->baseValue = $value;
}
/**
* @inheritdoc
*/
public function hasValue()
{
return !empty($this->baseValue) || ($this->baseValue === '0');
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\components\search\matchers;
/**
* Checks if the given value is greater than the base one.
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class GreaterThan extends Base
{
/**
* @inheritdoc
*/
public function match($value)
{
return ($value > $this->baseValue);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\components\search\matchers;
/**
* Checks if the given value is greater than or equal the base one.
*
* @author Dmitriy Bashkarev <dmitriy@bashkarev.com>
* @since 2.0.7
*/
class GreaterThanOrEqual extends Base
{
/**
* @inheritdoc
*/
public function match($value)
{
return $value >= $this->baseValue;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\components\search\matchers;
/**
* Checks if the given value is lower than the base one.
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class LowerThan extends Base
{
/**
* @inheritdoc
*/
public function match($value)
{
return ($value < $this->baseValue);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\components\search\matchers;
/**
* MatcherInterface should be implemented by all matchers that are used in a filter.
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
interface MatcherInterface
{
/**
* Checks if the value passed matches base value.
*
* @param mixed $value value to be matched
* @return bool if there is a match
*/
public function match($value);
/**
* Sets base value to match against
*
* @param mixed $value
*/
public function setValue($value);
/**
* Checks if base value is set
*
* @return bool if base value is set
*/
public function hasValue();
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\components\search\matchers;
use yii\helpers\VarDumper;
/**
* Checks if the given value is exactly or partially same as the base one.
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class SameAs extends Base
{
/**
* @var boolean if partial match should be used.
*/
public $partial = false;
/**
* @inheritdoc
*/
public function match($value)
{
if (!is_scalar($value)) {
$value = VarDumper::export($value);
}
if ($this->partial) {
return mb_stripos($value, $this->baseValue, 0, \Yii::$app->charset) !== false;
}
return strcmp(mb_strtoupper($this->baseValue, \Yii::$app->charset), mb_strtoupper($value, \Yii::$app->charset)) === 0;
}
}
{
"name": "yiisoft/yii2-debug",
"description": "The debugger extension for the Yii framework",
"keywords": ["yii2", "debug", "debugger"],
"type": "yii2-extension",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2-debug/issues",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2-debug"
},
"authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com"
}
],
"minimum-stability": "dev",
"require": {
"yiisoft/yii2": "~2.0.13",
"yiisoft/yii2-bootstrap": "~2.0.0"
},
"autoload": {
"psr-4": {
"yii\\debug\\": ""
}
},
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"repositories": [
{
"type": "composer",
"url": "https://asset-packagist.org"
}
]
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\controllers;
use Yii;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\debug\models\search\Debug;
use yii\web\Response;
/**
* Debugger controller
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class DefaultController extends Controller
{
/**
* @inheritdoc
*/
public $layout = 'main';
/**
* @var \yii\debug\Module
*/
public $module;
/**
* @var array the summary data (e.g. URL, time)
*/
public $summary;
/**
* @inheritdoc
*/
public function actions()
{
$actions = [];
foreach ($this->module->panels as $panel) {
$actions = array_merge($actions, $panel->actions);
}
return $actions;
}
public function beforeAction($action)
{
Yii::$app->response->format = Response::FORMAT_HTML;
return parent::beforeAction($action);
}
public function actionIndex()
{
$searchModel = new Debug();
$dataProvider = $searchModel->search($_GET, $this->getManifest());
// load latest request
$tags = array_keys($this->getManifest());
$tag = reset($tags);
$this->loadData($tag);
return $this->render('index', [
'panels' => $this->module->panels,
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
'manifest' => $this->getManifest(),
]);
}
public function actionView($tag = null, $panel = null)
{
if ($tag === null) {
$tags = array_keys($this->getManifest());
$tag = reset($tags);
}
$this->loadData($tag);
if (isset($this->module->panels[$panel])) {
$activePanel = $this->module->panels[$panel];
} else {
$activePanel = $this->module->panels[$this->module->defaultPanel];
}
if ($activePanel->hasError()) {
\Yii::$app->errorHandler->handleException($activePanel->getError());
}
return $this->render('view', [
'tag' => $tag,
'summary' => $this->summary,
'manifest' => $this->getManifest(),
'panels' => $this->module->panels,
'activePanel' => $activePanel,
]);
}
public function actionToolbar($tag)
{
$this->loadData($tag, 5);
return $this->renderPartial('toolbar', [
'tag' => $tag,
'panels' => $this->module->panels,
'position' => 'bottom',
]);
}
public function actionDownloadMail($file)
{
$filePath = Yii::getAlias($this->module->panels['mail']->mailPath) . '/' . basename($file);
if ((mb_strpos($file, '\\') !== false || mb_strpos($file, '/') !== false) || !is_file($filePath)) {
throw new NotFoundHttpException('Mail file not found');
}
return Yii::$app->response->sendFile($filePath);
}
private $_manifest;
protected function getManifest($forceReload = false)
{
if ($this->_manifest === null || $forceReload) {
if ($forceReload) {
clearstatcache();
}
$indexFile = $this->module->dataPath . '/index.data';
$content = '';
$fp = @fopen($indexFile, 'r');
if ($fp !== false) {
@flock($fp, LOCK_SH);
$content = fread($fp, filesize($indexFile));
@flock($fp, LOCK_UN);
fclose($fp);
}
if ($content !== '') {
$this->_manifest = array_reverse(unserialize($content), true);
} else {
$this->_manifest = [];
}
}
return $this->_manifest;
}
public function loadData($tag, $maxRetry = 0)
{
// retry loading debug data because the debug data is logged in shutdown function
// which may be delayed in some environment if xdebug is enabled.
// See: https://github.com/yiisoft/yii2/issues/1504
for ($retry = 0; $retry <= $maxRetry; ++$retry) {
$manifest = $this->getManifest($retry > 0);
if (isset($manifest[$tag])) {
$dataFile = $this->module->dataPath . "/$tag.data";
$data = unserialize(file_get_contents($dataFile));
$exceptions = $data['exceptions'];
foreach ($this->module->panels as $id => $panel) {
if (isset($data[$id])) {
$panel->tag = $tag;
$panel->load(unserialize($data[$id]));
}
if (isset($exceptions[$id])) {
$panel->setError($exceptions[$id]);
}
}
$this->summary = $data['summary'];
return;
}
sleep(1);
}
throw new NotFoundHttpException("Unable to find debug data tagged with '$tag'.");
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\controllers;
use Yii;
use yii\debug\models\UserSwitch;
use yii\web\BadRequestHttpException;
use yii\web\Controller;
use yii\web\Response;
/**
* User controller
*
* @author Semen Dubina <yii2debug@sam002.net>
* @since 2.0.10
*/
class UserController extends Controller
{
/**
* @inheritdoc
*/
public function beforeAction($action)
{
Yii::$app->response->format = Response::FORMAT_JSON;
if (!Yii::$app->session->hasSessionId) {
throw new BadRequestHttpException('Need an active session');
}
return parent::beforeAction($action);
}
/**
* Set new identity, switch user
* @return \yii\web\User
*/
public function actionSetIdentity()
{
$user_id = Yii::$app->request->post('user_id');
$userSwitch = new UserSwitch();
$newIdentity = Yii::$app->user->identity->findIdentity($user_id);
$userSwitch->setUserByIdentity($newIdentity);
return Yii::$app->user;
}
/**
* Reset identity, switch to main user
* @return \yii\web\User
*/
public function actionResetIdentity()
{
$userSwitch = new UserSwitch();
$userSwitch->reset();
return Yii::$app->user;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models;
use yii\base\Model;
use yii\log\Logger;
/**
* Router model
*
* @author Dmitriy Bashkarev <dmitriy@bashkarev.com>
* @since 2.0.8
*/
class Router extends Model
{
/**
* @var array logged messages.
*/
public $messages = [];
/**
* @var string|null info message.
*/
public $message;
/**
* @var array logged rules.
* ```php
* [
* [
* 'rule' => (string),
* 'match' => (bool),
* 'parent'=> parent class (string)
* ]
* ]
* ```
*/
public $logs = [];
/**
* @var int count, before match.
*/
public $count = 0;
/**
* @var bool
*/
public $hasMatch = false;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
$last = null;
foreach ($this->messages as $message) {
if ($message[1] === Logger::LEVEL_TRACE && is_string($message[0])) {
$this->message = $message[0];
} elseif (isset($message[0]['rule'], $message[0]['match'])) {
if (!empty($last['parent']) && $last['parent'] === $message[0]['rule']) {
continue;
}
$this->logs[] = $message[0];
++$this->count;
if ($message[0]['match']) {
$this->hasMatch = true;
}
$last = $message[0];
}
}
}
}
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models;
use Yii;
use yii\base\Model;
use yii\web\IdentityInterface;
use yii\web\User;
/**
* UserSwitch is a model used to temporary logging in another user
*
* @property User $mainUser This property is read-only.
* @property null|User $user This property is read-only.
*
* @author Semen Dubina <yii2debug@sam002.net>
* @since 2.0.10
*/
class UserSwitch extends Model
{
/**
* @var User user which we are currently switched to
*/
private $_user;
/**
* @var User the main user who was originally logged in before switching.
*/
private $_mainUser;
/**
* @var string|User ID of the user component or a user object
* @since 2.0.13
*/
public $userComponent = 'user';
/**
* @inheritdoc
*/
public function rules()
{
return [
[['user', 'mainUser'], 'safe']
];
}
/**
* @return array customized attribute labels
*/
public function attributeLabels()
{
return [
'user' => 'Current User',
'mainUser' => 'frontend', 'Main User',
];
}
/**
* Get current user
* @return null|User
*/
public function getUser()
{
if ($this->_user === null) {
/* @var $user User */
$this->_user = is_string($this->userComponent) ? Yii::$app->get($this->userComponent, false) : $this->userComponent;
}
return $this->_user;
}
/**
* Get main user
* @return User
*/
public function getMainUser()
{
$currentUser = $this->getUser();
if ($this->_mainUser === null && $currentUser->getIsGuest() === false) {
$session = Yii::$app->getSession();
if ($session->has('main_user')) {
$mainUserId = $session->get('main_user');
$mainIdentity = call_user_func([$currentUser->identityClass, 'findIdentity'], $mainUserId);
} else {
$mainIdentity = $currentUser->identity;
}
$mainUser = clone $currentUser;
$mainUser->setIdentity($mainIdentity);
$this->_mainUser = $mainUser;
}
return $this->_mainUser;
}
/**
* Switch user
* @param User $user
*/
public function setUser(User $user)
{
// Check if user is currently active one
$isCurrent = ($user->getId() === $this->getMainUser()->getId());
// Switch identity
$this->getUser()->switchIdentity($user->identity);
if (!$isCurrent) {
Yii::$app->getSession()->set('main_user', $this->getMainUser()->getId());
} else {
Yii::$app->getSession()->remove('main_user');
}
}
/**
* Switch to user by identity
* @param IdentityInterface $identity
*/
public function setUserByIdentity(IdentityInterface $identity)
{
$user = clone $this->getUser();
$user->setIdentity($identity);
$this->setUser($user);
}
/**
* Reset to main user
*/
public function reset()
{
$this->setUser($this->getMainUser());
}
/**
* Checks if current user is main or not.
* @return bool
*/
public function isMainUser()
{
$user = $this->getUser();
if ($user->getIsGuest()) {
return true;
}
return ($user->getId() === $this->getMainUser()->getId());
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
use yii\base\Model;
use yii\debug\components\search\Filter;
use yii\debug\components\search\matchers;
/**
* Base search model
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class Base extends Model
{
/**
* Adds filtering condition for a given attribute
*
* @param Filter $filter filter instance
* @param string $attribute attribute to filter
* @param bool $partial if partial match should be used
*/
public function addCondition(Filter $filter, $attribute, $partial = false)
{
$value = $this->$attribute;
if (mb_strpos($value, '>') !== false) {
$value = (int) str_replace('>', '', $value);
$filter->addMatcher($attribute, new matchers\GreaterThan(['value' => $value]));
} elseif (mb_strpos($value, '<') !== false) {
$value = (int) str_replace('<', '', $value);
$filter->addMatcher($attribute, new matchers\LowerThan(['value' => $value]));
} else {
$filter->addMatcher($attribute, new matchers\SameAs(['value' => $value, 'partial' => $partial]));
}
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
use yii\data\ArrayDataProvider;
use yii\debug\components\search\Filter;
/**
* Search model for current request database queries.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class Db extends Base
{
/**
* @var string type of the input search value
*/
public $type;
/**
* @var integer query attribute input search value
*/
public $query;
/**
* @inheritdoc
*/
public function rules()
{
return [
[['type', 'query'], 'safe'],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'type' => 'Type',
'query' => 'Query',
];
}
/**
* Returns data provider with filled models. Filter applied if needed.
*
* @param array $models data to return provider for
* @return \yii\data\ArrayDataProvider
*/
public function search($models)
{
$dataProvider = new ArrayDataProvider([
'allModels' => $models,
'pagination' => false,
'sort' => [
'attributes' => ['duration', 'seq', 'type', 'query', 'duplicate'],
],
]);
if (!$this->validate()) {
return $dataProvider;
}
$filter = new Filter();
$this->addCondition($filter, 'type', true);
$this->addCondition($filter, 'query', true);
$dataProvider->allModels = $filter->filter($models);
return $dataProvider;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
use yii\data\ArrayDataProvider;
use yii\debug\components\search\Filter;
/**
* Search model for requests manifest data.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class Debug extends Base
{
/**
* @var string tag attribute input search value
*/
public $tag;
/**
* @var string ip attribute input search value
*/
public $ip;
/**
* @var string method attribute input search value
*/
public $method;
/**
* @var integer ajax attribute input search value
*/
public $ajax;
/**
* @var string url attribute input search value
*/
public $url;
/**
* @var string status code attribute input search value
*/
public $statusCode;
/**
* @var integer sql count attribute input search value
*/
public $sqlCount;
/**
* @var integer total mail count attribute input search value
*/
public $mailCount;
/**
* @var array critical codes, used to determine grid row options.
*/
public $criticalCodes = [400, 404, 500];
/**
* @inheritdoc
*/
public function rules()
{
return [
[['tag', 'ip', 'method', 'ajax', 'url', 'statusCode', 'sqlCount', 'mailCount'], 'safe'],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'tag' => 'Tag',
'ip' => 'Ip',
'method' => 'Method',
'ajax' => 'Ajax',
'url' => 'url',
'statusCode' => 'Status code',
'sqlCount' => 'Query Count',
'mailCount' => 'Mail Count',
];
}
/**
* Returns data provider with filled models. Filter applied if needed.
* @param array $params an array of parameter values indexed by parameter names
* @param array $models data to return provider for
* @return \yii\data\ArrayDataProvider
*/
public function search($params, $models)
{
$dataProvider = new ArrayDataProvider([
'allModels' => $models,
'sort' => [
'attributes' => ['method', 'ip', 'tag', 'time', 'statusCode', 'sqlCount', 'mailCount'],
],
'pagination' => [
'pageSize' => 50,
],
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$filter = new Filter();
$this->addCondition($filter, 'tag', true);
$this->addCondition($filter, 'ip', true);
$this->addCondition($filter, 'method');
$this->addCondition($filter, 'ajax');
$this->addCondition($filter, 'url', true);
$this->addCondition($filter, 'statusCode');
$this->addCondition($filter, 'sqlCount');
$this->addCondition($filter, 'mailCount');
$dataProvider->allModels = $filter->filter($models);
return $dataProvider;
}
/**
* Checks if code is critical.
*
* @param int $code
* @return bool
*/
public function isCodeCritical($code)
{
return in_array($code, $this->criticalCodes, false);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
use yii\data\ArrayDataProvider;
use yii\debug\components\search\Filter;
/**
* Search model for current request log.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class Log extends Base
{
/**
* @var string ip attribute input search value
*/
public $level;
/**
* @var string method attribute input search value
*/
public $category;
/**
* @var integer message attribute input search value
*/
public $message;
/**
* @inheritdoc
*/
public function rules()
{
return [
[['level', 'message', 'category'], 'safe'],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'level' => 'Level',
'category' => 'Category',
'message' => 'Message',
];
}
/**
* Returns data provider with filled models. Filter applied if needed.
*
* @param array $params an array of parameter values indexed by parameter names
* @param array $models data to return provider for
* @return \yii\data\ArrayDataProvider
*/
public function search($params, $models)
{
$dataProvider = new ArrayDataProvider([
'allModels' => $models,
'pagination' => false,
'sort' => [
'attributes' => ['time', 'level', 'category', 'message'],
'defaultOrder' => [
'time' => SORT_ASC,
],
],
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$filter = new Filter();
$this->addCondition($filter, 'level');
$this->addCondition($filter, 'category', true);
$this->addCondition($filter, 'message', true);
$dataProvider->allModels = $filter->filter($models);
return $dataProvider;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
use yii\data\ArrayDataProvider;
use yii\debug\components\search\Filter;
/**
* Mail represents the model behind the search form about current send emails.
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class Mail extends Base
{
/**
* @var string from attribute input search value
*/
public $from;
/**
* @var string to attribute input search value
*/
public $to;
/**
* @var string reply attribute input search value
*/
public $reply;
/**
* @var string cc attribute input search value
*/
public $cc;
/**
* @var string bcc attribute input search value
*/
public $bcc;
/**
* @var string subject attribute input search value
*/
public $subject;
/**
* @var string body attribute input search value
*/
public $body;
/**
* @var string charset attribute input search value
*/
public $charset;
/**
* @var string headers attribute input search value
*/
public $headers;
/**
* @var string file attribute input search value
*/
public $file;
/**
* @inheritdoc
*/
public function rules()
{
return [
[['from', 'to', 'reply', 'cc', 'bcc', 'subject', 'body', 'charset'], 'safe'],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'from' => 'From',
'to' => 'To',
'reply' => 'Reply',
'cc' => 'Copy receiver',
'bcc' => 'Hidden copy receiver',
'subject' => 'Subject',
'charset' => 'Charset'
];
}
/**
* Returns data provider with filled models. Filter applied if needed.
* @param array $params
* @param array $models
* @return \yii\data\ArrayDataProvider
*/
public function search($params, $models)
{
$dataProvider = new ArrayDataProvider([
'allModels' => $models,
'pagination' => [
'pageSize' => 20,
],
'sort' => [
'attributes' => ['from', 'to', 'reply', 'cc', 'bcc', 'subject', 'body', 'charset'],
],
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$filter = new Filter();
$this->addCondition($filter, 'from', true);
$this->addCondition($filter, 'to', true);
$this->addCondition($filter, 'reply', true);
$this->addCondition($filter, 'cc', true);
$this->addCondition($filter, 'bcc', true);
$this->addCondition($filter, 'subject', true);
$this->addCondition($filter, 'body', true);
$this->addCondition($filter, 'charset', true);
$dataProvider->allModels = $filter->filter($models);
return $dataProvider;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
use yii\data\ArrayDataProvider;
use yii\debug\components\search\Filter;
/**
* Search model for current request profiling log.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class Profile extends Base
{
/**
* @var string method attribute input search value
*/
public $category;
/**
* @var integer info attribute input search value
*/
public $info;
/**
* @inheritdoc
*/
public function rules()
{
return [
[['category', 'info'], 'safe'],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'category' => 'Category',
'info' => 'Info',
];
}
/**
* Returns data provider with filled models. Filter applied if needed.
*
* @param array $params an array of parameter values indexed by parameter names
* @param array $models data to return provider for
* @return \yii\data\ArrayDataProvider
*/
public function search($params, $models)
{
$dataProvider = new ArrayDataProvider([
'allModels' => $models,
'pagination' => false,
'sort' => [
'attributes' => ['category', 'seq', 'duration', 'info'],
'defaultOrder' => [
'duration' => SORT_DESC,
],
],
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$filter = new Filter();
$this->addCondition($filter, 'category', true);
$this->addCondition($filter, 'info', true);
$dataProvider->allModels = $filter->filter($models);
return $dataProvider;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use yii\db\ActiveRecord;
/**
* Search model for implementation of IdentityInterface
*
* @author Semen Dubina <yii2debug@sam002.net>
* @since 2.0.10
*/
class User extends Model
{
/**
* @var Model implementation of IdentityInterface
*/
public $identityImplement = null;
/**
* @inheritdoc
*/
public function init()
{
if (\Yii::$app->user && \Yii::$app->user->identityClass) {
$identityImplementation = new \Yii::$app->user->identityClass();
if ($identityImplementation instanceof Model) {
$this->identityImplement = $identityImplementation;
}
}
parent::init();
}
/**
* @inheritdoc
*/
public function __get($name)
{
return $this->identityImplement->__get($name);
}
/**
* @inheritdoc
*/
public function __set($name, $value)
{
return $this->identityImplement->__set($name, $value);
}
/**
* @inheritdoc
*/
public function rules()
{
return [[array_keys($this->identityImplement->getAttributes()), 'safe']];
}
/**
* @inheritdoc
*/
public function attributes()
{
return $this->identityImplement->attributes();
}
/**
* @inheritdoc
*/
public function search($params)
{
if ($this->identityImplement instanceof ActiveRecord) {
return $this->serachActiveDataProvider($params);
}
return null;
}
/**
* Search method for ActiveRecord
* @param array $params the data array to load model.
* @return ActiveDataProvider
*/
private function serachActiveDataProvider($params)
{
/** @var ActiveRecord $model */
$model = $this->identityImplement;
$query = $model::find();
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
foreach ($model::getTableSchema()->columns as $attribute => $column) {
if ($column->phpType === 'string') {
$query->andFilterWhere(['like', $attribute, $model->getAttribute($attribute)]);
} else {
$query->andFilterWhere([$attribute => $model->getAttribute($attribute)]);
}
}
return $dataProvider;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\search;
use yii\data\DataProviderInterface;
use yii\web\IdentityInterface;
/**
* UserSearchInterface is the interface that should be implemented by a class
* providing identity information and search method.
*
* @author Semen Dubina <yii2debug@sam002.net>
* @since 2.0.10
*/
interface UserSearchInterface extends IdentityInterface
{
/**
* Creates data provider instance with search query applied.
* @param array $params the data array to load model.
* @return DataProviderInterface
*/
public function search($params);
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\timeline;
use yii\data\ArrayDataProvider;
use yii\debug\panels\TimelinePanel;
/**
* DataProvider implements a data provider based on a data array.
*
* @property array $rulers This property is read-only.
*
* @author Dmitriy Bashkarev <dmitriy@bashkarev.com>
* @since 2.0.8
*/
class DataProvider extends ArrayDataProvider
{
/**
* @var TimelinePanel
*/
protected $panel;
/**
* DataProvider constructor.
* @param TimelinePanel $panel
* @param array $config
*/
public function __construct(TimelinePanel $panel, $config = [])
{
$this->panel = $panel;
parent::__construct($config);
}
/**
* @inheritdoc
*/
protected function prepareModels()
{
if (($models = $this->allModels) === null) {
return [];
}
$child = [];
foreach ($models as $key => &$model) {
$model['timestamp'] *= 1000;
$model['duration'] *= 1000;
$model['child'] = 0;
$model['css']['width'] = $this->getWidth($model);
$model['css']['left'] = $this->getLeft($model);
$model['css']['color'] = $this->getColor($model);
foreach ($child as $id => $timestamp) {
if ($timestamp > $model['timestamp']) {
++$models[$id]['child'];
} else {
unset($child[$id]);
}
}
$child[$key] = $model['timestamp'] + $model['duration'];
}
return $models;
}
/**
* Getting HEX color based on model duration
* @param array $model
* @return string
*/
public function getColor($model)
{
$width = isset($model['css']['width']) ? $model['css']['width'] : $this->getWidth($model);
foreach ($this->panel->colors as $percent => $color) {
if ($width >= $percent) {
return $color;
}
}
return '#d6e685';
}
/**
* Returns the offset left item, percentage of the total width
* @param array $model
* @return float
*/
public function getLeft($model)
{
return $this->getTime($model) / ($this->panel->duration / 100);
}
/**
* Returns item duration, milliseconds
* @param array $model
* @return float
*/
public function getTime($model)
{
return $model['timestamp'] - $this->panel->start;
}
/**
* Returns item width percent of the total width
* @param array $model
* @return float
*/
public function getWidth($model)
{
return $model['duration'] / ($this->panel->duration / 100);
}
/**
* Returns item, css class
* @param array $model
* @return string
*/
public function getCssClass($model)
{
$class = 'time';
$class .= (($model['css']['left'] > 15) && ($model['css']['left'] + $model['css']['width'] > 50)) ? ' right' : ' left';
return $class;
}
/**
* ruler items, key milliseconds, value offset left
* @param int $line number of columns
* @return array
*/
public function getRulers($line = 10)
{
if ($line == 0) {
return [];
}
$data = [0];
$percent = ($this->panel->duration / 100);
$row = $this->panel->duration / $line;
$precision = $row > 100 ? -2 : -1;
for ($i = 1; $i < $line; $i++) {
$ms = round($i * $row, $precision);
$data[$ms] = $ms / $percent;
}
return $data;
}
/**
* ```php
* [
* 0 => string, memory usage (MB)
* 1 => float, Y position (percent)
* ]
* @param array $model
* @return array|null
*/
public function getMemory($model)
{
if (empty($model['memory'])) {
return null;
}
return [
sprintf('%.2f MB', $model['memory'] / 1048576),
$model['memory'] / ($this->panel->memory / 100)
];
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\timeline;
use yii\debug\components\search\Filter;
use yii\debug\components\search\matchers\GreaterThanOrEqual;
use yii\debug\models\search\Base;
use yii\debug\panels\TimelinePanel;
/**
* Search model for timeline data.
*
* @author Dmitriy Bashkarev <dmitriy@bashkarev.com>
* @since 2.0.8
*/
class Search extends Base
{
/**
* @var string attribute search
*/
public $category;
/**
* @var integer attribute search
*/
public $duration = 0;
/**
* @inheritdoc
*/
public function rules()
{
return [
[['category', 'duration'], 'safe'],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'duration' => 'Duration ≥'
];
}
/**
* Returns data provider with filled models. Filter applied if needed.
*
* @param array $params $params an array of parameter values indexed by parameter names
* @param TimeLinePanel $panel
* @return DataProvider
*/
public function search($params, $panel)
{
$models = $panel->models;
$dataProvider = new DataProvider($panel, [
'allModels' => $models,
'sort' => [
'attributes' => ['category', 'timestamp']
],
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$filter = new Filter();
$this->addCondition($filter, 'category', true);
if ($this->duration > 0) {
$filter->addMatcher('duration', new GreaterThanOrEqual(['value' => $this->duration / 1000]));
}
$dataProvider->allModels = $filter->filter($models);
return $dataProvider;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\models\timeline;
use yii\base\BaseObject;
use yii\debug\panels\TimelinePanel;
use yii\helpers\StringHelper;
/**
* Svg is used to draw a graph using SVG
*
* @author Dmitriy Bashkarev <dmitriy@bashkarev.com>
* @since 2.0.8
*/
class Svg extends BaseObject
{
/**
* @var int Max X coordinate
*/
public $x = 1920;
/**
* @var int Max Y coordinate
*/
public $y = 40;
/**
* @var string Stroke color
*/
public $stroke = '#1e6823';
/**
* @var array Listen messages panels
*/
public $listenMessages = ['log', 'profiling'];
/**
* @var array Color indicators svg graph.
*/
public $gradient = [
10 => '#d6e685',
60 => '#8cc665',
90 => '#44a340',
100 => '#1e6823'
];
/**
* @var string Svg template
*/
public $template = '<svg width="{x}" height="{y}" viewBox="0 0 {x} {y}" preserveAspectRatio="none"><defs>{linearGradient}</defs><g><polygon points="{polygon}" fill="url(#gradient)"/><polyline points="{polyline}" fill="none" stroke="{stroke}" stroke-width="1"/></g></svg>';
/**
* ```php
* [
* [x, y]
* ]
* ```
* @var array Each point is define by a X and a Y coordinate.
*/
protected $points = [];
/**
* @var TimelinePanel
*/
protected $panel;
/**
* @inheritdoc
*/
public function __construct(TimelinePanel $panel, $config = [])
{
parent::__construct($config);
$this->panel = $panel;
foreach ($this->listenMessages as $panel) {
if (isset($this->panel->module->panels[$panel]->data['messages'])) {
$this->addPoints($this->panel->module->panels[$panel]->data['messages']);
}
}
}
/**
* @return string
*/
public function __toString()
{
if ($this->points === []) {
return '';
}
return strtr($this->template, [
'{x}' => StringHelper::normalizeNumber($this->x),
'{y}' => StringHelper::normalizeNumber($this->y),
'{stroke}' => $this->stroke,
'{polygon}' => $this->polygon(),
'{polyline}' => $this->polyline(),
'{linearGradient}' => $this->linearGradient()
]);
}
/**
* @return bool Has points
*/
public function hasPoints()
{
return ($this->points !== []);
}
/**
* @param array $messages log messages. See [[Logger::messages]] for the structure
* @return int added points
*/
protected function addPoints($messages)
{
$hasPoints = $this->hasPoints();
$memory = $this->panel->memory / 100; // 1 percent memory
$yOne = $this->y / 100; // 1 percent Y coordinate
$xOne = $this->panel->duration / $this->x; // 1 percent X coordinate
$i = 0;
foreach ($messages as $message) {
if (empty($message[5])) {
break;
}
++$i;
$this->points[] = [
($message[3] * 1000 - $this->panel->start) / $xOne,
$this->y - ($message[5] / $memory * $yOne),
];
}
if ($hasPoints && $i) {
usort($this->points, function ($a, $b) {
return ($a[0] < $b[0]) ? -1 : 1;
});
}
return $i;
}
/**
* @return string Points attribute for polygon path
*/
protected function polygon()
{
$str = "0 $this->y ";
foreach ($this->points as $point) {
list($x, $y) = $point;
$str .= "{$x} {$y} ";
}
$str .= $this->x - 0.001 . " {$y} {$this->x} {$this->y}";
return StringHelper::normalizeNumber($str);
}
/**
* @return string Points attribute for polyline path
*/
protected function polyline()
{
$str = "0 $this->y ";
foreach ($this->points as $point) {
list($x, $y) = $point;
$str .= "{$x} {$y} ";
}
$str .= "$this->x {$y}";
return StringHelper::normalizeNumber($str);
}
/**
* @return string
*/
protected function linearGradient()
{
$gradient = '<linearGradient id="gradient" x1="0" x2="0" y1="1" y2="0">';
foreach ($this->gradient as $percent => $color) {
$gradient .= '<stop offset="' . StringHelper::normalizeNumber($percent) . '%" stop-color="' . $color . '"></stop>';
}
return $gradient . '</linearGradient>';
}
}
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\Html;
use yii\debug\Panel;
use yii\web\AssetBundle;
use yii\web\AssetManager;
/**
* Debugger panel that collects and displays asset bundles data.
*
* @author Artur Fursa <arturfursa@gmail.com>
* @since 2.0
*/
class AssetPanel extends Panel
{
/**
* @inheritdoc
*/
public function getName()
{
return 'Asset Bundles';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('panels/assets/summary', ['panel' => $this]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
return Yii::$app->view->render('panels/assets/detail', ['panel' => $this]);
}
/**
* @inheritdoc
*/
public function save()
{
$bundles = Yii::$app->view->assetManager->bundles;
if (empty($bundles)) { // bundles can be false
return [];
}
$data = [];
foreach ($bundles as $name => $bundle) {
if ($bundle instanceof AssetBundle) {
$bundleData = (array) $bundle;
if (isset($bundleData['publishOptions']['beforeCopy']) && $bundleData['publishOptions']['beforeCopy'] instanceof \Closure) {
$bundleData['publishOptions']['beforeCopy'] = '\Closure';
}
if (isset($bundleData['publishOptions']['afterCopy']) && $bundleData['publishOptions']['afterCopy'] instanceof \Closure) {
$bundleData['publishOptions']['afterCopy'] = '\Closure';
}
$data[$name] = $bundleData;
}
}
return $data;
}
/**
* @inheritdoc
*/
public function isEnabled()
{
try {
Yii::$app->view->assetManager;
} catch (InvalidConfigException $exception) {
return false;
}
return true;
}
/**
* Additional formatting for view.
*
* @param AssetBundle[] $bundles Array of bundles to formatting.
*
* @return AssetBundle[]
*/
protected function format(array $bundles)
{
foreach ($bundles as $bundle) {
$this->cssCount += count($bundle->css);
$this->jsCount += count($bundle->js);
array_walk($bundle->css, function(&$file, $key, $userdata) {
$file = Html::a($file, $userdata->baseUrl . '/' . $file, ['target' => '_blank']);
}, $bundle);
array_walk($bundle->js, function(&$file, $key, $userdata) {
$file = Html::a($file, $userdata->baseUrl . '/' . $file, ['target' => '_blank']);
}, $bundle);
array_walk($bundle->depends, function(&$depend) {
$depend = Html::a($depend, '#' . $depend);
});
$this->formatOptions($bundle->publishOptions);
$this->formatOptions($bundle->jsOptions);
$this->formatOptions($bundle->cssOptions);
}
return $bundles;
}
/**
* Format associative array of params to simple value.
*
* @param array $params
*
* @return array
*/
protected function formatOptions(array &$params)
{
if (!is_array($params)) {
return $params;
}
foreach ($params as $param => $value) {
$params[$param] = Html::tag('strong', '\'' . $param . '\' => ') . (string) $value;
}
return $params;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\debug\Panel;
/**
* Debugger panel that collects and displays application configuration and environment.
*
* @property array $extensions This property is read-only.
* @property array $phpInfo This property is read-only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ConfigPanel extends Panel
{
/**
* @inheritdoc
*/
public function getName()
{
return 'Configuration';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('panels/config/summary', ['panel' => $this]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
return Yii::$app->view->render('panels/config/detail', ['panel' => $this]);
}
/**
* Returns data about extensions
*
* @return array
*/
public function getExtensions()
{
$data = [];
foreach ($this->data['extensions'] as $extension) {
$data[$extension['name']] = $extension['version'];
}
ksort($data);
return $data;
}
/**
* Returns the BODY contents of the phpinfo() output
*
* @return array
*/
public function getPhpInfo()
{
ob_start();
phpinfo();
$pinfo = ob_get_contents();
ob_end_clean();
$phpinfo = preg_replace('%^.*<body>(.*)</body>.*$%ms', '$1', $pinfo);
$phpinfo = str_replace('<table', '<div class="table-responsive"><table class="table table-condensed table-bordered table-striped table-hover config-php-info-table" ', $phpinfo);
$phpinfo = str_replace('</table>', '</table></div>', $phpinfo);
return $phpinfo;
}
/**
* @inheritdoc
*/
public function save()
{
return [
'phpVersion' => PHP_VERSION,
'yiiVersion' => Yii::getVersion(),
'application' => [
'yii' => Yii::getVersion(),
'name' => Yii::$app->name,
'version' => Yii::$app->version,
'language' => Yii::$app->language,
'sourceLanguage' => Yii::$app->sourceLanguage,
'charset' => Yii::$app->charset,
'env' => YII_ENV,
'debug' => YII_DEBUG,
],
'php' => [
'version' => PHP_VERSION,
'xdebug' => extension_loaded('xdebug'),
'apc' => extension_loaded('apc'),
'memcache' => extension_loaded('memcache'),
'memcached' => extension_loaded('memcached'),
],
'extensions' => Yii::$app->extensions,
];
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\base\InvalidConfigException;
use yii\debug\Panel;
use yii\helpers\ArrayHelper;
use yii\log\Logger;
use yii\debug\models\search\Db;
/**
* Debugger panel that collects and displays database queries performed.
*
* @property array $profileLogs This property is read-only.
* @property string $summaryName Short name of the panel, which will be use in summary. This property is
* read-only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class DbPanel extends Panel
{
/**
* @var integer the threshold for determining whether the request has involved
* critical number of DB queries. If the number of queries exceeds this number,
* the execution is considered taking critical number of DB queries.
*/
public $criticalQueryThreshold;
/**
* @var string the name of the database component to use for executing (explain) queries
*/
public $db = 'db';
/**
* @var array the default ordering of the database queries. In the format of
* [ property => sort direction ], for example: [ 'duration' => SORT_DESC ]
* @since 2.0.7
*/
public $defaultOrder = [
'seq' => SORT_ASC
];
/**
* @var array the default filter to apply to the database queries. In the format
* of [ property => value ], for example: [ 'type' => 'SELECT' ]
* @since 2.0.7
*/
public $defaultFilter = [];
/**
* @var array db queries info extracted to array as models, to use with data provider.
*/
private $_models;
/**
* @var array current database request timings
*/
private $_timings;
/**
* @inheritdoc
*/
public function init()
{
$this->actions['db-explain'] = [
'class' => 'yii\\debug\\actions\\db\\ExplainAction',
'panel' => $this,
];
}
/**
* @inheritdoc
*/
public function getName()
{
return 'Database';
}
/**
* @return string short name of the panel, which will be use in summary.
*/
public function getSummaryName()
{
return 'DB';
}
/**
* @inheritdoc
*/
public function getSummary()
{
$timings = $this->calculateTimings();
$queryCount = count($timings);
$queryTime = number_format($this->getTotalQueryTime($timings) * 1000) . ' ms';
return Yii::$app->view->render('panels/db/summary', [
'timings' => $this->calculateTimings(),
'panel' => $this,
'queryCount' => $queryCount,
'queryTime' => $queryTime,
]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
$searchModel = new Db();
if (!$searchModel->load(Yii::$app->request->getQueryParams())) {
$searchModel->load($this->defaultFilter, '');
}
$models = $this->getModels();
$dataProvider = $searchModel->search($models);
$dataProvider->getSort()->defaultOrder = $this->defaultOrder;
$sumDuplicates = $this->sumDuplicateQueries($models);
return Yii::$app->view->render('panels/db/detail', [
'panel' => $this,
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
'hasExplain' => $this->hasExplain(),
'sumDuplicates' => $sumDuplicates,
]);
}
/**
* Calculates given request profile timings.
*
* @return array timings [token, category, timestamp, traces, nesting level, elapsed time]
*/
public function calculateTimings()
{
if ($this->_timings === null) {
$this->_timings = Yii::getLogger()->calculateTimings(isset($this->data['messages']) ? $this->data['messages'] : []);
}
return $this->_timings;
}
/**
* @inheritdoc
*/
public function save()
{
return ['messages' => $this->getProfileLogs()];
}
/**
* Returns all profile logs of the current request for this panel. It includes categories such as:
* 'yii\db\Command::query', 'yii\db\Command::execute'.
* @return array
*/
public function getProfileLogs()
{
$target = $this->module->logTarget;
return $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\db\Command::query', 'yii\db\Command::execute']);
}
/**
* Returns total query time.
*
* @param array $timings
* @return int total time
*/
protected function getTotalQueryTime($timings)
{
$queryTime = 0;
foreach ($timings as $timing) {
$queryTime += $timing['duration'];
}
return $queryTime;
}
/**
* Returns an array of models that represents logs of the current request.
* Can be used with data providers such as \yii\data\ArrayDataProvider.
* @return array models
*/
protected function getModels()
{
if ($this->_models === null) {
$this->_models = [];
$timings = $this->calculateTimings();
$duplicates = $this->countDuplicateQuery($timings);
foreach ($timings as $seq => $dbTiming) {
$this->_models[] = [
'type' => $this->getQueryType($dbTiming['info']),
'query' => $dbTiming['info'],
'duration' => ($dbTiming['duration'] * 1000), // in milliseconds
'trace' => $dbTiming['trace'],
'timestamp' => ($dbTiming['timestamp'] * 1000), // in milliseconds
'seq' => $seq,
'duplicate' => $duplicates[$dbTiming['info']],
];
}
}
return $this->_models;
}
/**
* Return associative array, where key is query string
* and value is number of occurrences the same query in array.
*
* @param $timings
* @return array
* @since 2.0.13
*/
public function countDuplicateQuery($timings)
{
$query = ArrayHelper::getColumn($timings, 'info');
return array_count_values($query);
}
/**
* Returns sum of all duplicated queries
*
* @param $modelData
* @return int
* @since 2.0.13
*/
public function sumDuplicateQueries($modelData)
{
$numDuplicates = 0;
$duplicates = ArrayHelper::getColumn($modelData, 'duplicate');
foreach ($duplicates as $duplicate) {
if ($duplicate > 1) {
$numDuplicates++;
}
}
return $numDuplicates;
}
/**
* Returns database query type.
*
* @param string $timing timing procedure string
* @return string query type such as select, insert, delete, etc.
*/
protected function getQueryType($timing)
{
$timing = ltrim($timing);
preg_match('/^([a-zA-z]*)/', $timing, $matches);
return count($matches) ? mb_strtoupper($matches[0], 'utf8') : '';
}
/**
* Check if given queries count is critical according settings.
*
* @param int $count queries count
* @return bool
*/
public function isQueryCountCritical($count)
{
return (($this->criticalQueryThreshold !== null) && ($count > $this->criticalQueryThreshold));
}
/**
* Returns array query types
*
* @return array
* @since 2.0.3
*/
public function getTypes()
{
return array_reduce(
$this->_models,
function ($result, $item) {
$result[$item['type']] = $item['type'];
return $result;
},
[]
);
}
/**
* @inheritdoc
*/
public function isEnabled()
{
try {
$this->getDb();
} catch (InvalidConfigException $exception) {
return false;
}
return true;
}
/**
* @return bool Whether the DB component has support for EXPLAIN queries
* @since 2.0.5
*/
protected function hasExplain()
{
$db = $this->getDb();
if (!($db instanceof \yii\db\Connection)) {
return false;
}
switch ($db->getDriverName()) {
case 'mysql':
case 'sqlite':
case 'pgsql':
case 'cubrid':
return true;
default:
return false;
}
}
/**
* Check if given query type can be explained.
*
* @param string $type query type
* @return bool
*
* @since 2.0.5
*/
public static function canBeExplained($type)
{
return $type !== 'SHOW';
}
/**
* Returns a reference to the DB component associated with the panel
*
* @return \yii\db\Connection
* @since 2.0.5
*/
public function getDb()
{
return Yii::$app->get($this->db);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\debug\Panel;
use yii\helpers\VarDumper;
use yii\log\Logger;
use yii\debug\models\search\Log;
/**
* Debugger panel that collects and displays logs.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class LogPanel extends Panel
{
/**
* @var array log messages extracted to array as models, to use with data provider.
*/
private $_models;
/**
* @inheritdoc
*/
public function getName()
{
return 'Logs';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('panels/log/summary', ['data' => $this->data, 'panel' => $this]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
$searchModel = new Log();
$dataProvider = $searchModel->search(Yii::$app->request->getQueryParams(), $this->getModels());
return Yii::$app->view->render('panels/log/detail', [
'dataProvider' => $dataProvider,
'panel' => $this,
'searchModel' => $searchModel,
]);
}
/**
* @inheritdoc
*/
public function save()
{
$target = $this->module->logTarget;
$except = [];
if (isset($this->module->panels['router'])) {
$except = $this->module->panels['router']->getCategories();
}
$messages = $target->filterMessages($target->messages, Logger::LEVEL_ERROR | Logger::LEVEL_INFO | Logger::LEVEL_WARNING | Logger::LEVEL_TRACE, [], $except);
foreach ($messages as &$message) {
if (!is_string($message[0])) {
// exceptions may not be serializable if in the call stack somewhere is a Closure
if ($message[0] instanceof \Throwable || $message[0] instanceof \Exception) {
$message[0] = (string) $message[0];
} else {
$message[0] = VarDumper::export($message[0]);
}
}
}
return ['messages' => $messages];
}
/**
* Returns an array of models that represents logs of the current request.
* Can be used with data providers, such as \yii\data\ArrayDataProvider.
*
* @param bool $refresh if need to build models from log messages and refresh them.
* @return array models
*/
protected function getModels($refresh = false)
{
if ($this->_models === null || $refresh) {
$this->_models = [];
foreach ($this->data['messages'] as $message) {
$this->_models[] = [
'message' => $message[0],
'level' => $message[1],
'category' => $message[2],
'time' => $message[3] * 1000, // time in milliseconds
'trace' => $message[4]
];
}
}
return $this->_models;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\base\Event;
use yii\debug\models\search\Mail;
use yii\debug\Panel;
use yii\mail\BaseMailer;
use yii\helpers\FileHelper;
use yii\mail\MessageInterface;
/**
* Debugger panel that collects and displays the generated emails.
*
* @property array $messages Messages. This property is read-only.
*
* @author Mark Jebri <mark.github@yandex.ru>
* @since 2.0
*/
class MailPanel extends Panel
{
/**
* @var string path where all emails will be saved. should be an alias.
*/
public $mailPath = '@runtime/debug/mail';
/**
* @var array current request sent messages
*/
private $_messages = [];
/**
* @inheritdoc
*/
public function init()
{
parent::init();
Event::on(BaseMailer::className(), BaseMailer::EVENT_AFTER_SEND, function ($event) {
/* @var $message MessageInterface */
$message = $event->message;
$messageData = [
'isSuccessful' => $event->isSuccessful,
'from' => $this->convertParams($message->getFrom()),
'to' => $this->convertParams($message->getTo()),
'reply' => $this->convertParams($message->getReplyTo()),
'cc' => $this->convertParams($message->getCc()),
'bcc' => $this->convertParams($message->getBcc()),
'subject' => $message->getSubject(),
'charset' => $message->getCharset(),
];
// add more information when message is a SwiftMailer message
if ($message instanceof \yii\swiftmailer\Message) {
/* @var $swiftMessage \Swift_Message */
$swiftMessage = $message->getSwiftMessage();
$body = $swiftMessage->getBody();
if (empty($body)) {
$parts = $swiftMessage->getChildren();
foreach ($parts as $part) {
if (!($part instanceof \Swift_Mime_Attachment)) {
/* @var $part \Swift_Mime_MimePart */
if ($part->getContentType() === 'text/plain') {
$messageData['charset'] = $part->getCharset();
$body = $part->getBody();
break;
}
}
}
}
$messageData['body'] = $body;
$messageData['time'] = $swiftMessage->getDate();
$messageData['headers'] = $swiftMessage->getHeaders();
}
// store message as file
$fileName = $event->sender->generateMessageFileName();
FileHelper::createDirectory(Yii::getAlias($this->mailPath));
file_put_contents(Yii::getAlias($this->mailPath) . '/' . $fileName, $message->toString());
$messageData['file'] = $fileName;
$this->_messages[] = $messageData;
});
}
/**
* @inheritdoc
*/
public function getName()
{
return 'Mail';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('panels/mail/summary', ['panel' => $this, 'mailCount' => count($this->data)]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
$searchModel = new Mail();
$dataProvider = $searchModel->search(Yii::$app->request->get(), $this->data);
return Yii::$app->view->render('panels/mail/detail', [
'panel' => $this,
'dataProvider' => $dataProvider,
'searchModel' => $searchModel
]);
}
/**
* @inheritdoc
*/
public function save()
{
return $this->getMessages();
}
/**
* Returns info about messages of current request. Each element is array holding
* message info, such as: time, reply, bc, cc, from, to and other.
* @return array messages
*/
public function getMessages()
{
return $this->_messages;
}
/**
* @param mixed $attr
* @return string
*/
private function convertParams($attr)
{
if (is_array($attr)) {
$attr = implode(', ', array_keys($attr));
}
return $attr;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\debug\Panel;
use yii\log\Logger;
use yii\debug\models\search\Profile;
/**
* Debugger panel that collects and displays performance profiling info.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ProfilingPanel extends Panel
{
/**
* @var array current request profile timings
*/
private $_models;
/**
* @inheritdoc
*/
public function getName()
{
return 'Profiling';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('panels/profile/summary', [
'memory' => sprintf('%.3f MB', $this->data['memory'] / 1048576),
'time' => number_format($this->data['time'] * 1000) . ' ms',
'panel' => $this
]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
$searchModel = new Profile();
$dataProvider = $searchModel->search(Yii::$app->request->getQueryParams(), $this->getModels());
return Yii::$app->view->render('panels/profile/detail', [
'panel' => $this,
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
'memory' => sprintf('%.3f MB', $this->data['memory'] / 1048576),
'time' => number_format($this->data['time'] * 1000) . ' ms',
]);
}
/**
* @inheritdoc
*/
public function save()
{
$target = $this->module->logTarget;
$messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE);
return [
'memory' => memory_get_peak_usage(),
'time' => microtime(true) - YII_BEGIN_TIME,
'messages' => $messages,
];
}
/**
* Returns array of profiling models that can be used in a data provider.
* @return array models
*/
protected function getModels()
{
if ($this->_models === null) {
$this->_models = [];
$timings = Yii::getLogger()->calculateTimings(isset($this->data['messages']) ? $this->data['messages'] : []);
foreach ($timings as $seq => $profileTiming) {
$this->_models[] = [
'duration' => $profileTiming['duration'] * 1000, // in milliseconds
'category' => $profileTiming['category'],
'info' => $profileTiming['info'],
'level' => $profileTiming['level'],
'timestamp' => $profileTiming['timestamp'] * 1000, //in milliseconds
'seq' => $seq,
];
}
}
return $this->_models;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\base\InlineAction;
use yii\debug\Panel;
/**
* Debugger panel that collects and displays request data.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class RequestPanel extends Panel
{
/**
* @var array list of the PHP predefined variables that are allowed to be displayed in the request panel.
* Note that a variable must be accessible via `$GLOBALS`. Otherwise it won't be displayed.
* @since 2.0.10
*/
public $displayVars = ['_SERVER', '_GET', '_POST', '_COOKIE', '_FILES', '_SESSION'];
/**
* @inheritdoc
*/
public function getName()
{
return 'Request';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('panels/request/summary', ['panel' => $this]);
}
/**
* @inheritdoc
*/
public function getDetail()
{
return Yii::$app->view->render('panels/request/detail', ['panel' => $this]);
}
/**
* @inheritdoc
*/
public function save()
{
$headers = Yii::$app->getRequest()->getHeaders();
$requestHeaders = [];
foreach ($headers as $name => $value) {
if (is_array($value) && count($value) == 1) {
$requestHeaders[$name] = current($value);
} else {
$requestHeaders[$name] = $value;
}
}
$responseHeaders = [];
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] = [$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;
}
$data = [
'flashes' => $this->getFlashes(),
'statusCode' => Yii::$app->getResponse()->getStatusCode(),
'requestHeaders' => $requestHeaders,
'responseHeaders' => $responseHeaders,
'route' => Yii::$app->requestedAction ? Yii::$app->requestedAction->getUniqueId() : Yii::$app->requestedRoute,
'action' => $action,
'actionParams' => Yii::$app->requestedParams,
'general' => [
'method' => Yii::$app->getRequest()->getMethod(),
'isAjax' => Yii::$app->getRequest()->getIsAjax(),
'isPjax' => Yii::$app->getRequest()->getIsPjax(),
'isFlash' => Yii::$app->getRequest()->getIsFlash(),
'isSecureConnection' => Yii::$app->getRequest()->getIsSecureConnection(),
],
'requestBody' => Yii::$app->getRequest()->getRawBody() == '' ? [] : [
'Content Type' => Yii::$app->getRequest()->getContentType(),
'Raw' => Yii::$app->getRequest()->getRawBody(),
'Decoded to Params' => Yii::$app->getRequest()->getBodyParams(),
],
];
foreach ($this->displayVars as $name) {
$data[trim($name, '_')] = empty($GLOBALS[$name]) ? [] : $GLOBALS[$name];
}
return $data;
}
/**
* Getting flash messages without deleting them or touching deletion counters
*
* @return array flash messages (key => message).
*/
protected function getFlashes()
{
/* @var $session \yii\web\Session */
$session = Yii::$app->has('session', true) ? Yii::$app->get('session') : null;
if ($session === null || !$session->getIsActive()) {
return [];
}
$counters = $session->get($session->flashParam, []);
$flashes = [];
foreach (array_keys($counters) as $key) {
if (array_key_exists($key, $_SESSION)) {
$flashes[$key] = $_SESSION[$key];
}
}
return $flashes;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\debug\models\Router;
use yii\debug\Panel;
use yii\log\Logger;
/**
* RouterPanel provides a panel which displays information about routing process.
*
* @property array $categories Note that the type of this property differs in getter and setter. See
* [[getCategories()]] and [[setCategories()]] for details.
*
* @author Dmitriy Bashkarev <dmitriy@bashkarev.com>
* @since 2.0.8
*/
class RouterPanel extends Panel
{
/**
* @var array
*/
private $_categories = [
'yii\web\UrlManager::parseRequest',
'yii\web\UrlRule::parseRequest',
'yii\web\CompositeUrlRule::parseRequest',
'yii\rest\UrlRule::parseRequest'
];
/**
* @param string|array $values
*/
public function setCategories($values)
{
if (!is_array($values)) {
$values = [$values];
}
$this->_categories = array_merge($this->_categories, $values);
}
/**
* Listens categories of the messages.
* @return array
*/
public function getCategories()
{
return $this->_categories;
}
/**
* @inheritdoc
*/
public function getName()
{
return 'Router';
}
/**
* @inheritdoc
*/
public function getDetail()
{
return Yii::$app->view->render('panels/router/detail', ['model' => new Router($this->data)]);
}
/**
* @inheritdoc
*/
public function save()
{
$target = $this->module->logTarget;
return [
'messages' => $target::filterMessages($target->messages, Logger::LEVEL_TRACE, $this->_categories)
];
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\debug\panels;
use Yii;
use yii\debug\Panel;
use yii\debug\models\timeline\Search;
use yii\debug\models\timeline\Svg;
use yii\base\InvalidConfigException;
/**
* Debugger panel that collects and displays timeline data.
*
* @property array $colors
* @property float $duration This property is read-only.
* @property float $start This property is read-only.
* @property array $svgOptions
*
* @author Dmitriy Bashkarev <dmitriy@bashkarev.com>
* @since 2.0.7
*/
class TimelinePanel extends Panel
{
/**
* @var array Color indicators item profile.
*
* - keys: percentages of time request
* - values: hex color
*/
private $_colors = [
20 => '#1e6823',
10 => '#44a340',
1 => '#8cc665'
];
/**
* @var array log messages extracted to array as models, to use with data provider.
*/
private $_models;
/**
* @var float Start request, timestamp (obtained by microtime(true))
*/
private $_start;
/**
* @var float End request, timestamp (obtained by microtime(true))
*/
private $_end;
/**
* @var float Request duration, milliseconds
*/
private $_duration;
/**
* @var Svg|null
*/
private $_svg;
/**
* @var array
*/
private $_svgOptions = [
'class' => 'yii\debug\models\timeline\Svg'
];
/**
* @var int Used memory in request
*/
private $_memory;
/**
* @inheritdoc
*/
public function init()
{
if (!isset($this->module->panels['profiling'])) {
throw new InvalidConfigException('Unable to determine the profiling panel');
}
parent::init();
}
/**
* @inheritdoc
*/
public function getName()
{
return 'Timeline';
}
/**
* @inheritdoc
*/
public function getDetail()
{
$searchModel = new Search();
$dataProvider = $searchModel->search(Yii::$app->request->getQueryParams(), $this);
return Yii::$app->view->render('panels/timeline/detail', [
'panel' => $this,
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
]);
}
/**
* @inheritdoc
*/
public function load($data)
{
if (!isset($data['start']) || empty($data['start'])) {
throw new \RuntimeException('Unable to determine request start time');
}
$this->_start = $data['start'] * 1000;
if (!isset($data['end']) || empty($data['end'])) {
throw new \RuntimeException('Unable to determine request end time');
}
$this->_end = $data['end'] * 1000;
if (isset($this->module->panels['profiling']->data['time'])) {
$this->_duration = $this->module->panels['profiling']->data['time'] * 1000;
} else {
$this->_duration = $this->_end - $this->_start;
}
if ($this->_duration <= 0) {
throw new \RuntimeException('Duration cannot be zero');
}
if (!isset($data['memory']) || empty($data['memory'])) {
throw new \RuntimeException('Unable to determine used memory in request');
}
$this->_memory = $data['memory'];
}
/**
* @inheritdoc
*/
public function save()
{
return [
'start' => YII_BEGIN_TIME,
'end' => microtime(true),
'memory' => memory_get_peak_usage(),
];
}
/**
* Sets color indicators.
* key: percentages of time request, value: hex color
* @param array $colors
*/
public function setColors($colors)
{
krsort($colors);
$this->_colors = $colors;
}
/**
* Color indicators item profile,
* key: percentages of time request, value: hex color
* @return array
*/
public function getColors()
{
return $this->_colors;
}
/**
* @param array $options
*/
public function setSvgOptions($options)
{
if ($this->_svg !== null) {
$this->_svg = null;
}
$this->_svgOptions = array_merge($this->_svgOptions, $options);
}
/**
* @return array
*/
public function getSvgOptions()
{
return $this->_svgOptions;
}
/**
* Start request, timestamp (obtained by microtime(true))
* @return float
*/
public function getStart()
{
return $this->_start;
}
/**
* Request duration, milliseconds
* @return float
*/
public function getDuration()
{
return $this->_duration;
}
/**
* Memory peak in request, bytes. (obtained by memory_get_peak_usage())
* @return int
* @since 2.0.8
*/
public function getMemory()
{
return $this->_memory;
}
/**
* @return Svg
* @since 2.0.8
*/
public function getSvg()
{
if ($this->_svg === null) {
$this->_svg = Yii::createObject($this->_svgOptions,[$this]);
}
return $this->_svg;
}
/**
* Returns an array of models that represents logs of the current request.
* Can be used with data providers, such as \yii\data\ArrayDataProvider.
*
* @param bool $refresh if need to build models from log messages and refresh them.
* @return array models
*/
protected function getModels($refresh = false)
{
if ($this->_models === null || $refresh) {
$this->_models = [];
if (isset($this->module->panels['profiling']->data['messages'])) {
$this->_models = Yii::getLogger()->calculateTimings($this->module->panels['profiling']->data['messages']);
}
}
return $this->_models;
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment