123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465 |
- <?php
- /**
- * Contains debugging and dumping tools.
- *
- * @package Kohana
- * @category Base
- * @author Kohana Team
- * @copyright (c) Kohana Team
- * @license https://koseven.ga/LICENSE.md
- */
- class Kohana_Debug {
- /**
- * Returns an HTML string of debugging information about any number of
- * variables, each wrapped in a "pre" tag:
- *
- * // Displays the type and value of each variable
- * echo Debug::vars($foo, $bar, $baz);
- *
- * @param mixed $var,... variable to debug
- * @return string
- */
- public static function vars()
- {
- if (func_num_args() === 0)
- return;
- // Get all passed variables
- $variables = func_get_args();
- $output = [];
- foreach ($variables as $var)
- {
- $output[] = Debug::_dump($var, 1024);
- }
- return '<pre class="debug">'.implode("\n", $output).'</pre>';
- }
- /**
- * Returns an HTML string of information about a single variable.
- *
- * Borrows heavily on concepts from the Debug class of [Nette](http://nettephp.com/).
- *
- * @param mixed $value variable to dump
- * @param integer $length maximum length of strings
- * @param integer $level_recursion recursion limit
- * @return string
- */
- public static function dump($value, $length = 128, $level_recursion = 10)
- {
- return Debug::_dump($value, $length, $level_recursion);
- }
- /**
- * Helper for Debug::dump(), handles recursion in arrays and objects.
- *
- * @param mixed $var variable to dump
- * @param integer $length maximum length of strings
- * @param integer $limit recursion limit
- * @param integer $level current recursion level (internal usage only!)
- * @return string
- */
- protected static function _dump( & $var, $length = 128, $limit = 10, $level = 0)
- {
- if ($var === NULL)
- {
- return '<small>NULL</small>';
- }
- elseif (is_bool($var))
- {
- return '<small>bool</small> '.($var ? 'TRUE' : 'FALSE');
- }
- elseif (is_float($var))
- {
- return '<small>float</small> '.$var;
- }
- elseif (is_resource($var))
- {
- if (($type = get_resource_type($var)) === 'stream' AND $meta = stream_get_meta_data($var))
- {
- $meta = stream_get_meta_data($var);
- if (isset($meta['uri']))
- {
- $file = $meta['uri'];
- if (function_exists('stream_is_local'))
- {
- // Only exists on PHP >= 5.2.4
- if (stream_is_local($file))
- {
- $file = Debug::path($file);
- }
- }
- return '<small>resource</small><span>('.$type.')</span> '.htmlspecialchars($file, ENT_NOQUOTES, Kohana::$charset);
- }
- }
- else
- {
- return '<small>resource</small><span>('.$type.')</span>';
- }
- }
- elseif (is_string($var))
- {
- // Clean invalid multibyte characters. iconv is only invoked
- // if there are non ASCII characters in the string, so this
- // isn't too much of a hit.
- $var = UTF8::clean($var, Kohana::$charset);
- if (UTF8::strlen($var) > $length)
- {
- // Encode the truncated string
- $str = htmlspecialchars(UTF8::substr($var, 0, $length), ENT_NOQUOTES, Kohana::$charset).' …';
- }
- else
- {
- // Encode the string
- $str = htmlspecialchars($var, ENT_NOQUOTES, Kohana::$charset);
- }
- return '<small>string</small><span>('.strlen($var).')</span> "'.$str.'"';
- }
- elseif (is_array($var))
- {
- $output = [];
- // Indentation for this variable
- $space = str_repeat($s = ' ', $level);
- static $marker;
- if ($marker === NULL)
- {
- // Make a unique marker - force it to be alphanumeric so that it is always treated as a string array key
- $marker = uniqid("\x00")."x";
- }
- if (empty($var))
- {
- // Do nothing
- }
- elseif (isset($var[$marker]))
- {
- $output[] = "(\n$space$s*RECURSION*\n$space)";
- }
- elseif ($level < $limit)
- {
- $output[] = "<span>(";
- $var[$marker] = TRUE;
- foreach ($var as $key => & $val)
- {
- if ($key === $marker) continue;
- if ( ! is_int($key))
- {
- $key = '"'.htmlspecialchars($key, ENT_NOQUOTES, Kohana::$charset).'"';
- }
- $output[] = "$space$s$key => ".Debug::_dump($val, $length, $limit, $level + 1);
- }
- unset($var[$marker]);
- $output[] = "$space)</span>";
- }
- else
- {
- // Depth too great
- $output[] = "(\n$space$s...\n$space)";
- }
- return '<small>array</small><span>('.count($var).')</span> '.implode("\n", $output);
- }
- elseif (is_object($var))
- {
- // Copy the object as an array
- $array = (array) $var;
- $output = [];
- // Indentation for this variable
- $space = str_repeat($s = ' ', $level);
- $hash = spl_object_hash($var);
- // Objects that are being dumped
- static $objects = [];
- if (empty($var))
- {
- // Do nothing
- }
- elseif (isset($objects[$hash]))
- {
- $output[] = "{\n$space$s*RECURSION*\n$space}";
- }
- elseif ($level < $limit)
- {
- $output[] = "<code>{";
- $objects[$hash] = TRUE;
- foreach ($array as $key => & $val)
- {
- if (is_string($key) && $key[0] === "\x00")
- {
- // Determine if the access is protected or protected
- $access = '<small>'.(($key[1] === '*') ? 'protected' : 'private').'</small>';
- // Remove the access level from the variable name
- $key = substr($key, strrpos($key, "\x00") + 1);
- }
- else
- {
- $access = '<small>public</small>';
- }
- $output[] = "$space$s$access $key => ".Debug::_dump($val, $length, $limit, $level + 1);
- }
- unset($objects[$hash]);
- $output[] = "$space}</code>";
- }
- else
- {
- // Depth too great
- $output[] = "{\n$space$s...\n$space}";
- }
- return '<small>object</small> <span>'.get_class($var).'('.count($array).')</span> '.implode("\n", $output);
- }
- else
- {
- return '<small>'.gettype($var).'</small> '.htmlspecialchars(print_r($var, TRUE), ENT_NOQUOTES, Kohana::$charset);
- }
- }
- /**
- * Removes application, system, modpath, or docroot from a filename,
- * replacing them with the plain text equivalents. Useful for debugging
- * when you want to display a shorter path.
- *
- * // Displays SYSPATH/classes/kohana.php
- * echo Debug::path(Kohana::find_file('classes', 'kohana'));
- *
- * @param string $file path to debug
- * @return string
- */
- public static function path($file)
- {
- if (strpos($file, APPPATH) === 0)
- {
- $file = 'APPPATH'.DIRECTORY_SEPARATOR.substr($file, strlen(APPPATH));
- }
- elseif (strpos($file, SYSPATH) === 0)
- {
- $file = 'SYSPATH'.DIRECTORY_SEPARATOR.substr($file, strlen(SYSPATH));
- }
- elseif (strpos($file, MODPATH) === 0)
- {
- $file = 'MODPATH'.DIRECTORY_SEPARATOR.substr($file, strlen(MODPATH));
- }
- elseif (strpos($file, DOCROOT) === 0)
- {
- $file = 'DOCROOT'.DIRECTORY_SEPARATOR.substr($file, strlen(DOCROOT));
- }
- return $file;
- }
- /**
- * Returns an HTML string, highlighting a specific line of a file, with some
- * number of lines padded above and below.
- *
- * // Highlights the current line of the current file
- * echo Debug::source(__FILE__, __LINE__);
- *
- * @param string $file file to open
- * @param integer $line_number line number to highlight
- * @param integer $padding number of padding lines
- * @return string source of file
- * @return FALSE file is unreadable
- */
- public static function source($file, $line_number, $padding = 5)
- {
- if ( ! $file OR ! is_readable($file))
- {
- // Continuing will cause errors
- return FALSE;
- }
- // Open the file and set the line position
- $file = fopen($file, 'r');
- $line = 0;
- // Set the reading range
- $range = ['start' => $line_number - $padding, 'end' => $line_number + $padding];
- // Set the zero-padding amount for line numbers
- $format = '% '.strlen($range['end']).'d';
- $source = '';
- while (($row = fgets($file)) !== FALSE)
- {
- // Increment the line number
- if (++$line > $range['end'])
- break;
- if ($line >= $range['start'])
- {
- // Make the row safe for output
- $row = htmlspecialchars($row, ENT_NOQUOTES, Kohana::$charset);
- // Trim whitespace and sanitize the row
- $row = '<span class="number">'.sprintf($format, $line).'</span> '.$row;
- if ($line === $line_number)
- {
- // Apply highlighting to this row
- $row = '<span class="line highlight">'.$row.'</span>';
- }
- else
- {
- $row = '<span class="line">'.$row.'</span>';
- }
- // Add to the captured source
- $source .= $row;
- }
- }
- // Close the file
- fclose($file);
- return '<pre class="source"><code>'.$source.'</code></pre>';
- }
- /**
- * Returns an array of HTML strings that represent each step in the backtrace.
- *
- * // Displays the entire current backtrace
- * echo implode('<br/>', Debug::trace());
- *
- * @param array $trace
- * @return string
- */
- public static function trace(array $trace = NULL)
- {
- if ($trace === NULL)
- {
- // Start a new trace
- $trace = debug_backtrace();
- }
- // Non-standard function calls
- $statements = ['include', 'include_once', 'require', 'require_once'];
- $output = [];
- foreach ($trace as $step)
- {
- if ( ! isset($step['function']))
- {
- // Invalid trace step
- continue;
- }
- if (isset($step['file']) AND isset($step['line']))
- {
- // Include the source of this step
- $source = Debug::source($step['file'], $step['line']);
- }
- if (isset($step['file']))
- {
- $file = $step['file'];
- if (isset($step['line']))
- {
- $line = $step['line'];
- }
- }
- // function()
- $function = $step['function'];
- if (in_array($step['function'], $statements))
- {
- if (empty($step['args']))
- {
- // No arguments
- $args = [];
- }
- else
- {
- // Sanitize the file path
- $args = [$step['args'][0]];
- }
- }
- elseif (isset($step['args']))
- {
- if ( ! function_exists($step['function']) OR strpos($step['function'], '{closure}') !== FALSE)
- {
- // Introspection on closures or language constructs in a stack trace is impossible
- $params = NULL;
- }
- else
- {
- if (isset($step['class']))
- {
- if (method_exists($step['class'], $step['function']))
- {
- $reflection = new ReflectionMethod($step['class'], $step['function']);
- }
- else
- {
- $reflection = new ReflectionMethod($step['class'], '__call');
- }
- }
- else
- {
- $reflection = new ReflectionFunction($step['function']);
- }
- // Get the function parameters
- $params = $reflection->getParameters();
- }
- $args = [];
- foreach ($step['args'] as $i => $arg)
- {
- if (isset($params[$i]))
- {
- // Assign the argument by the parameter name
- $args[$params[$i]->name] = $arg;
- }
- else
- {
- // Assign the argument by number
- $args[$i] = $arg;
- }
- }
- }
- if (isset($step['class']))
- {
- // Class->method() or Class::method()
- $function = $step['class'].$step['type'].$step['function'];
- }
- $output[] = [
- 'function' => $function,
- 'args' => isset($args) ? $args : NULL,
- 'file' => isset($file) ? $file : NULL,
- 'line' => isset($line) ? $line : NULL,
- 'source' => isset($source) ? $source : NULL,
- ];
- unset($function, $args, $file, $line, $source);
- }
- return $output;
- }
- }
|