Skip to content

Instantly share code, notes, and snippets.

@WinterSilence
Created October 20, 2021 12:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save WinterSilence/6f743f646315c363779df286f9a663cc to your computer and use it in GitHub Desktop.
Save WinterSilence/6f743f646315c363779df286f9a663cc to your computer and use it in GitHub Desktop.
Extended Yii 2 class `VarDumper`
<?php
namespace yii\helpers;
use Closure;
use ReflectionFunction;
use Throwable;
use Traversable;
use yii\base\Arrayable;
use function array_keys;
use function array_shift;
use function array_slice;
use function count;
use function debug_backtrace;
use function defined;
use function file;
use function get_class;
use function gettype;
use function implode;
use function iterator_to_array;
use function ltrim;
use function method_exists;
use function serialize;
use function str_repeat;
use function strlen;
use function substr;
use function token_get_all;
use function var_export;
use const DEBUG_BACKTRACE_IGNORE_ARGS;
use const T_FUNCTION;
/**
* Helper methods for explore PHP variables.
*/
class VarDumper extends BaseVarDumper
{
/**
* @var string The left indent for internal code
*/
public static string $indent = ' ';
/**
* @var int The max. size of array to display in line
*/
public static int $inlineArrayMaxSize = 3;
/**
* @inheritdoc
* @param string|null $leftOffset line offset at left as string, NULL - auto detect offset
*/
public static function export($var, string $leftOffset = null): string
{
if ($leftOffset === null) {
// calculate left offset
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0];
$line = file($trace['file'])[$trace['line'] - 1];
$leftOffset = substr($line, 0, -strlen(ltrim($line)));
}
return static::exportInternal($var, 0, $leftOffset);
}
/**
* Recursive export.
*
* @param mixed $var the variable to be exported
* @param int $level the depth level
* @param string|null $leftOffset line offset at left as string, NULL - auto detect offset
* @return string
*/
protected static function exportInternal($var, int $level = 0, string $leftOffset = null): string
{
switch (gettype($var)) {
case 'NULL':
$result = 'null';
break;
case 'array':
if (empty($var)) {
$result = '[]';
} else {
$keys = array_keys($var);
$outputKeys = $keys !== array_keys($keys);
$spaces = str_repeat(static::$indent, $level);
$multiline = count($var) > static::$inlineArrayMaxSize;
$result = '[';
foreach ($var as $key => $value) {
if ($multiline) {
$result .= PHP_EOL . $spaces . static::$indent;
}
if ($outputKeys && !isset($keys[$key])) {
$result .= var_export($key, true) . ' => ';
}
$result .= static::exportInternal($value, $level + 1, $leftOffset) . ',';
}
if ($multiline) {
$result .= PHP_EOL . $spaces;
}
$result .= ']';
}
break;
case 'object':
if ($var instanceof Closure) {
$result = static::exportClosure($var);
} else {
try {
$result = 'unserialize(' . var_export(serialize($var), true) . ')';
} catch (Throwable $e) {
// serialize may fail, for example: if object contains a Closure instance so we use a fallback
if ($var instanceof Arrayable) {
$result = static::exportInternal($var->toArray(), $level, $leftOffset);
} elseif ($var instanceof Traversable) {
$result = static::exportInternal(iterator_to_array($var), $level, $leftOffset);
} elseif ('__PHP_Incomplete_Class' !== get_class($var) && method_exists($var, '__toString')) {
$result = var_export($var->__toString(), true);
} else {
$result = var_export(static::dumpAsString($var), true);
}
}
}
break;
case 'string':
if (
StringHelper::endsWith($var, '::class')
|| StringHelper::endsWith($var, '::className()')
|| StringHelper::startsWith($var, 'Yii::t(')
) {
$result = $var;
break;
}
default:
$result = var_export($var, true);
}
return $result;
}
/**
* Exports a [[Closure]] instance.
*
* @param Closure $closure the closure instance.
* @return string
*/
protected static function exportClosure(Closure $closure): string
{
$reflection = new ReflectionFunction($closure);
$fileName = $reflection->getFileName();
$start = $reflection->getStartLine();
$end = $reflection->getEndLine();
if ($fileName === false || $start === false || $end === false) {
return 'function () {/** Error: unable to determine Closure source */}';
}
--$start;
$source = implode(PHP_EOL, array_slice(file($fileName), $start, $end - $start));
$tokens = token_get_all('<?php ' . $source);
array_shift($tokens);
$closureTokens = [];
$pendingParenthesisCount = 0;
foreach ($tokens as $token) {
if (isset($token[0]) && ($token[0] === T_FUNCTION || (defined('T_FN') && $token[0] === T_FN))) {
$closureTokens[] = $token[1];
continue;
}
if ($closureTokens !== []) {
$closureTokens[] = $token[1] ?? $token;
if ($token === '}') {
$pendingParenthesisCount--;
if ($pendingParenthesisCount === 0) {
break;
}
} elseif ($token === '{') {
$pendingParenthesisCount++;
}
}
}
return implode('', $closureTokens);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment