class Kint in Devel 8
Same name in this branch
- 8 kint/kint/Kint.class.php \Kint
- 8 kint/src/Plugin/Devel/Dumper/Kint.php \Drupal\kint\Plugin\Devel\Dumper\Kint
Same name and namespace in other branches
Hierarchy
- class \Kint
Expanded class hierarchy of Kint
2 string references to 'Kint'
- Kint.class.php in kint/
kint/ Kint.class.php - kint_trace in kint/
kint.module - Alias of Kint::trace().
File
- kint/
kint/ Kint.class.php, line 37
View source
class Kint {
// these are all public and 1:1 config array keys so you can switch them easily
private static $_enabledMode;
# stores mode and active statuses
public static $returnOutput;
public static $fileLinkFormat;
public static $displayCalledFrom;
public static $charEncodings;
public static $maxStrLength;
public static $appRootDirs;
public static $maxLevels;
public static $theme;
public static $expandedByDefault;
public static $cliDetection;
public static $cliColors;
const MODE_RICH = 'r';
const MODE_WHITESPACE = 'w';
const MODE_CLI = 'c';
const MODE_PLAIN = 'p';
public static $aliases = array(
'methods' => array(
array(
'kint',
'dump',
),
array(
'kint',
'trace',
),
),
'functions' => array(
'd',
'dd',
'ddd',
's',
'sd',
),
);
private static $_firstRun = true;
/**
* Enables or disables Kint, can globally enforce the rendering mode. If called without parameters, returns the
* current mode.
*
* @param mixed $forceMode
* null or void - return current mode
* false - disable (no output)
* true - enable and detect cli automatically
* Kint::MODE_* - enable and force selected mode disregarding detection and function
* shorthand (s()/d()), note that you can still override this
* with the "~" modifier
*
* @return mixed previously set value if a new one is passed
*/
public static function enabled($forceMode = null) {
# act both as a setter...
if (isset($forceMode)) {
$before = self::$_enabledMode;
self::$_enabledMode = $forceMode;
return $before;
}
# ...and a getter
return self::$_enabledMode;
}
/**
* Prints a debug backtrace, same as Kint::dump(1)
*
* @param array $trace [OPTIONAL] you can pass your own trace, otherwise, `debug_backtrace` will be called
*
* @return mixed
*/
public static function trace($trace = null) {
if (!self::enabled()) {
return '';
}
return self::dump(isset($trace) ? $trace : debug_backtrace(true));
}
/**
* Dump information about variables, accepts any number of parameters, supports modifiers:
*
* clean up any output before kint and place the dump at the top of page:
* - Kint::dump()
* *****
* expand all nodes on display:
* ! Kint::dump()
* *****
* dump variables disregarding their depth:
* + Kint::dump()
* *****
* return output instead of displaying it:
* @ Kint::dump()
* *****
* force output as plain text
* ~ Kint::dump()
*
* Modifiers are supported by all dump wrapper functions, including Kint::trace(). Space is optional.
*
*
* You can also use the following shorthand to display debug_backtrace():
* Kint::dump( 1 );
*
* Passing the result from debug_backtrace() to kint::dump() as a single parameter will display it as trace too:
* $trace = debug_backtrace( true );
* Kint::dump( $trace );
* Or simply:
* Kint::dump( debug_backtrace() );
*
*
* @param mixed $data
*
* @return void|string
*/
public static function dump($data = null) {
if (!self::enabled()) {
return '';
}
list($names, $modifiers, $callee, $previousCaller, $miniTrace) = self::_getCalleeInfo(defined('DEBUG_BACKTRACE_IGNORE_ARGS') ? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) : debug_backtrace());
$modeOldValue = self::enabled();
$firstRunOldValue = self::$_firstRun;
# process modifiers: @, +, !, ~ and -
if (strpos($modifiers, '-') !== false) {
self::$_firstRun = true;
while (ob_get_level()) {
ob_end_clean();
}
}
if (strpos($modifiers, '!') !== false) {
$expandedByDefaultOldValue = self::$expandedByDefault;
self::$expandedByDefault = true;
}
if (strpos($modifiers, '+') !== false) {
$maxLevelsOldValue = self::$maxLevels;
self::$maxLevels = false;
}
if (strpos($modifiers, '@') !== false) {
$returnOldValue = self::$returnOutput;
self::$returnOutput = true;
self::$_firstRun = true;
}
if (strpos($modifiers, '~') !== false) {
self::enabled(self::MODE_WHITESPACE);
}
# set mode for current run
$mode = self::enabled();
if ($mode === true) {
$mode = PHP_SAPI === 'cli' ? self::MODE_CLI : self::MODE_RICH;
}
self::enabled($mode);
$decorator = self::enabled() === self::MODE_RICH ? 'Kint_Decorators_Rich' : 'Kint_Decorators_Plain';
$output = '';
if (self::$_firstRun) {
$output .= call_user_func(array(
$decorator,
'init',
));
}
$trace = false;
if ($names === array(
null,
) && func_num_args() === 1 && $data === 1) {
# Kint::dump(1) shorthand
$trace = KINT_PHP53 ? debug_backtrace(true) : debug_backtrace();
}
elseif (func_num_args() === 1 && is_array($data)) {
$trace = $data;
# test if the single parameter is result of debug_backtrace()
}
$trace and $trace = self::_parseTrace($trace);
$output .= call_user_func(array(
$decorator,
'wrapStart',
));
if ($trace) {
$output .= call_user_func(array(
$decorator,
'decorateTrace',
), $trace);
}
else {
$data = func_num_args() === 0 ? array(
"[[no arguments passed]]",
) : func_get_args();
foreach ($data as $k => $argument) {
kintParser::reset();
# when the dump arguments take long to generate output, user might have changed the file and
# Kint might not parse the arguments correctly, so check if names are set and while the
# displayed names might be wrong, at least don't throw an error
$output .= call_user_func(array(
$decorator,
'decorate',
), kintParser::factory($argument, isset($names[$k]) ? $names[$k] : ''));
}
}
$output .= call_user_func(array(
$decorator,
'wrapEnd',
), $callee, $miniTrace, $previousCaller);
self::enabled($modeOldValue);
self::$_firstRun = false;
if (strpos($modifiers, '~') !== false) {
self::$_firstRun = $firstRunOldValue;
}
else {
self::enabled($modeOldValue);
}
if (strpos($modifiers, '!') !== false) {
self::$expandedByDefault = $expandedByDefaultOldValue;
}
if (strpos($modifiers, '+') !== false) {
self::$maxLevels = $maxLevelsOldValue;
}
if (strpos($modifiers, '@') !== false) {
self::$returnOutput = $returnOldValue;
self::$_firstRun = $firstRunOldValue;
return $output;
}
if (self::$returnOutput) {
return $output;
}
echo $output;
return '';
}
/**
* generic path display callback, can be configured in the settings; purpose is to show relevant path info and hide
* as much of the path as possible.
*
* @param string $file
*
* @return string
*/
public static function shortenPath($file) {
$file = str_replace('\\', '/', $file);
$shortenedName = $file;
$replaced = false;
if (is_array(self::$appRootDirs)) {
foreach (self::$appRootDirs as $path => $replaceString) {
if (empty($path)) {
continue;
}
$path = str_replace('\\', '/', $path);
if (strpos($file, $path) === 0) {
$shortenedName = $replaceString . substr($file, strlen($path));
$replaced = true;
break;
}
}
}
# fallback to find common path with Kint dir
if (!$replaced) {
$pathParts = explode('/', str_replace('\\', '/', KINT_DIR));
$fileParts = explode('/', $file);
$i = 0;
foreach ($fileParts as $i => $filePart) {
if (!isset($pathParts[$i]) || $pathParts[$i] !== $filePart) {
break;
}
}
$shortenedName = ($i ? '.../' : '') . implode('/', array_slice($fileParts, $i));
}
return $shortenedName;
}
public static function getIdeLink($file, $line) {
return str_replace(array(
'%f',
'%l',
), array(
$file,
$line,
), self::$fileLinkFormat);
}
/**
* trace helper, shows the place in code inline
*
* @param string $file full path to file
* @param int $lineNumber the line to display
* @param int $padding surrounding lines to show besides the main one
*
* @return bool|string
*/
private static function _showSource($file, $lineNumber, $padding = 7) {
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 = array(
'start' => $lineNumber - $padding,
'end' => $lineNumber + $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, 'UTF-8');
# trim whitespace and sanitize the row
$row = '<span>' . sprintf($format, $line) . '</span> ' . $row;
if ($line === $lineNumber) {
# apply highlighting to this row
$row = '<div class="kint-highlight">' . $row . '</div>';
}
else {
$row = '<div>' . $row . '</div>';
}
# add to the captured source
$source .= $row;
}
}
# close the file
fclose($file);
return $source;
}
/**
* returns parameter names that the function was passed, as well as any predefined symbols before function
* call (modifiers)
*
* @param array $trace
*
* @return array( $parameters, $modifier, $callee, $previousCaller )
*/
private static function _getCalleeInfo($trace) {
$previousCaller = array();
$miniTrace = array();
$prevStep = array();
# go from back of trace to find first occurrence of call to Kint or its wrappers
while ($step = array_pop($trace)) {
if (self::_stepIsInternal($step)) {
$previousCaller = $prevStep;
break;
}
elseif (isset($step['file'], $step['line'])) {
unset($step['object'], $step['args']);
array_unshift($miniTrace, $step);
}
$prevStep = $step;
}
$callee = $step;
if (!isset($callee['file']) || !is_readable($callee['file'])) {
return false;
}
# open the file and read it up to the position where the function call expression ended
$file = fopen($callee['file'], 'r');
$line = 0;
$source = '';
while (($row = fgets($file)) !== false) {
if (++$line > $callee['line']) {
break;
}
$source .= $row;
}
fclose($file);
$source = self::_removeAllButCode($source);
if (empty($callee['class'])) {
$codePattern = $callee['function'];
}
else {
if ($callee['type'] === '::') {
$codePattern = $callee['class'] . "\7*" . $callee['type'] . "\7*" . $callee['function'];
}
else {
/*if ( $callee['type'] === '->' )*/
$codePattern = ".*\7*" . $callee['type'] . "\7*" . $callee['function'];
}
}
// todo if more than one call in one line - not possible to determine variable names
// todo does not recognize string concat
# get the position of the last call to the function
preg_match_all("\n [\n # beginning of statement\n [\7{(]\n\n # search for modifiers (group 1)\n ([-+!@~]*)?\n\n # spaces\n \7*\n\n # check if output is assigned to a variable (group 2) todo: does not detect concat\n (\n \\\$[a-z0-9_]+ # variable\n \7*\\.?=\7* # assignment\n )?\n\n # possibly a namespace symbol\n \\\\?\n\n\t\t\t# spaces again\n \7*\n\n # main call to Kint\n {$codePattern}\n\n\t\t\t# spaces everywhere\n \7*\n\n # find the character where kint's opening bracket resides (group 3)\n (\\()\n\n ]ix", $source, $matches, PREG_OFFSET_CAPTURE);
$modifiers = end($matches[1]);
$assignment = end($matches[2]);
$bracket = end($matches[3]);
$modifiers = $modifiers[0];
if ($assignment[1] !== -1) {
$modifiers .= '@';
}
$paramsString = preg_replace("[\7+]", ' ', substr($source, $bracket[1] + 1));
# we now have a string like this:
# <parameters passed>); <the rest of the last read line>
# remove everything in brackets and quotes, we don't need nested statements nor literal strings which would
# only complicate separating individual arguments
$c = strlen($paramsString);
$inString = $escaped = $openedBracket = $closingBracket = false;
$i = 0;
$inBrackets = 0;
$openedBrackets = array();
while ($i < $c) {
$letter = $paramsString[$i];
if (!$inString) {
if ($letter === '\'' || $letter === '"') {
$inString = $letter;
}
elseif ($letter === '(' || $letter === '[') {
$inBrackets++;
$openedBrackets[] = $openedBracket = $letter;
$closingBracket = $openedBracket === '(' ? ')' : ']';
}
elseif ($inBrackets && $letter === $closingBracket) {
$inBrackets--;
array_pop($openedBrackets);
$openedBracket = end($openedBrackets);
$closingBracket = $openedBracket === '(' ? ')' : ']';
}
elseif (!$inBrackets && $letter === ')') {
$paramsString = substr($paramsString, 0, $i);
break;
}
}
elseif ($letter === $inString && !$escaped) {
$inString = false;
}
# replace whatever was inside quotes or brackets with untypeable characters, we don't
# need that info. We'll later replace the whole string with '...'
if ($inBrackets > 0) {
if ($inBrackets > 1 || $letter !== $openedBracket) {
$paramsString[$i] = "\7";
}
}
if ($inString) {
if ($letter !== $inString || $escaped) {
$paramsString[$i] = "\7";
}
}
$escaped = !$escaped && $letter === '\\';
$i++;
}
# by now we have an un-nested arguments list, lets make it to an array for processing further
$arguments = explode(',', preg_replace("[\7+]", '...', $paramsString));
# test each argument whether it was passed literary or was it an expression or a variable name
$parameters = array();
$blacklist = array(
'null',
'true',
'false',
'array(...)',
'array()',
'"..."',
'[...]',
'b"..."',
);
foreach ($arguments as $argument) {
$argument = trim($argument);
if (is_numeric($argument) || in_array(str_replace("'", '"', strtolower($argument)), $blacklist, true)) {
$parameters[] = null;
}
else {
$parameters[] = $argument;
}
}
return array(
$parameters,
$modifiers,
$callee,
$previousCaller,
$miniTrace,
);
}
/**
* removes comments and zaps whitespace & <?php tags from php code, makes for easier further parsing
*
* @param string $source
*
* @return string
*/
private static function _removeAllButCode($source) {
$commentTokens = array(
T_COMMENT => true,
T_INLINE_HTML => true,
T_DOC_COMMENT => true,
);
$whiteSpaceTokens = array(
T_WHITESPACE => true,
T_CLOSE_TAG => true,
T_OPEN_TAG => true,
T_OPEN_TAG_WITH_ECHO => true,
);
$cleanedSource = '';
foreach (token_get_all($source) as $token) {
if (is_array($token)) {
if (isset($commentTokens[$token[0]])) {
continue;
}
if (isset($whiteSpaceTokens[$token[0]])) {
$token = "\7";
}
else {
$token = $token[1];
}
}
elseif ($token === ';') {
$token = "\7";
}
$cleanedSource .= $token;
}
return $cleanedSource;
}
/**
* returns whether current trace step belongs to Kint or its wrappers
*
* @param $step
*
* @return array
*/
private static function _stepIsInternal($step) {
if (isset($step['class'])) {
foreach (self::$aliases['methods'] as $alias) {
if ($alias[0] === strtolower($step['class']) && $alias[1] === strtolower($step['function'])) {
return true;
}
}
return false;
}
else {
return in_array(strtolower($step['function']), self::$aliases['functions'], true);
}
}
private static function _parseTrace(array $data) {
$trace = array();
$traceFields = array(
'file',
'line',
'args',
'class',
);
$fileFound = false;
# file element must exist in one of the steps
# validate whether a trace was indeed passed
while ($step = array_pop($data)) {
if (!is_array($step) || !isset($step['function'])) {
return false;
}
if (!$fileFound && isset($step['file']) && file_exists($step['file'])) {
$fileFound = true;
}
$valid = false;
foreach ($traceFields as $element) {
if (isset($step[$element])) {
$valid = true;
break;
}
}
if (!$valid) {
return false;
}
if (self::_stepIsInternal($step)) {
$step = array(
'file' => $step['file'],
'line' => $step['line'],
'function' => '',
);
array_unshift($trace, $step);
break;
}
if ($step['function'] !== 'spl_autoload_call') {
# meaningless
array_unshift($trace, $step);
}
}
if (!$fileFound) {
return false;
}
$output = array();
foreach ($trace as $step) {
if (isset($step['file'])) {
$file = $step['file'];
if (isset($step['line'])) {
$line = $step['line'];
# include the source of this step
if (self::enabled() === self::MODE_RICH) {
$source = self::_showSource($file, $line);
}
}
}
$function = $step['function'];
if (in_array($function, array(
'include',
'include_once',
'require',
'require_once',
))) {
if (empty($step['args'])) {
# no arguments
$args = array();
}
else {
# sanitize the included file path
$args = array(
'file' => self::shortenPath($step['args'][0]),
);
}
}
elseif (isset($step['args'])) {
if (empty($step['class']) && !function_exists($function)) {
# introspection on closures or language constructs in a stack trace is impossible before PHP 5.3
$params = null;
}
else {
try {
if (isset($step['class'])) {
if (method_exists($step['class'], $function)) {
$reflection = new ReflectionMethod($step['class'], $function);
}
else {
if (isset($step['type']) && $step['type'] == '::') {
$reflection = new ReflectionMethod($step['class'], '__callStatic');
}
else {
$reflection = new ReflectionMethod($step['class'], '__call');
}
}
}
else {
$reflection = new ReflectionFunction($function);
}
# get the function parameters
$params = $reflection
->getParameters();
} catch (Exception $e) {
# avoid various PHP version incompatibilities
$params = null;
}
}
$args = array();
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 + 1)] = $arg;
}
}
}
if (isset($step['class'])) {
# Class->method() or Class::method()
$function = $step['class'] . $step['type'] . $function;
}
// todo it's possible to parse the object name out from the source!
$output[] = array(
'function' => $function,
'args' => isset($args) ? $args : null,
'file' => isset($file) ? $file : null,
'line' => isset($line) ? $line : null,
'source' => isset($source) ? $source : null,
'object' => isset($step['object']) ? $step['object'] : null,
);
unset($function, $args, $file, $line, $source);
}
return $output;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
Kint:: |
public static | property | ||
Kint:: |
public static | property | ||
Kint:: |
public static | property | ||
Kint:: |
public static | property | ||
Kint:: |
public static | property | ||
Kint:: |
public static | property | ||
Kint:: |
public static | property | ||
Kint:: |
public static | property | ||
Kint:: |
public static | property | ||
Kint:: |
public static | property | ||
Kint:: |
public static | property | ||
Kint:: |
public static | property | ||
Kint:: |
private static | property | ||
Kint:: |
private static | property | ||
Kint:: |
public static | function | * Dump information about variables, accepts any number of parameters, supports modifiers: * * clean up any output before kint and place the dump at the top of page: * - Kint::dump() * ***** * expand all nodes on display: * !… | |
Kint:: |
public static | function | * Enables or disables Kint, can globally enforce the rendering mode. If called without parameters, returns the * current mode. * * | |
Kint:: |
public static | function | ||
Kint:: |
constant | |||
Kint:: |
constant | |||
Kint:: |
constant | |||
Kint:: |
constant | |||
Kint:: |
public static | function | * generic path display callback, can be configured in the settings; purpose is to show relevant path info and hide * as much of the path as possible. * * | |
Kint:: |
public static | function | * Prints a debug backtrace, same as Kint::dump(1) * * | |
Kint:: |
private static | function | * returns parameter names that the function was passed, as well as any predefined symbols before function * call (modifiers) * * | |
Kint:: |
private static | function | ||
Kint:: |
private static | function | * removes comments and zaps whitespace & <?php tags from php code, makes for easier further parsing * * | |
Kint:: |
private static | function | * trace helper, shows the place in code inline * * | |
Kint:: |
private static | function | * returns whether current trace step belongs to Kint or its wrappers * * |