Created
October 20, 2021 12:04
-
-
Save WinterSilence/6f743f646315c363779df286f9a663cc to your computer and use it in GitHub Desktop.
Extended Yii 2 class `VarDumper`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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