'; echo ''; echo ''; $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); $file = isset($trace[0]['file']) ? $trace[0]['file'] : '未知文件'; $line = isset($trace[0]['line']) ? $trace[0]['line'] : '未知行号'; echo '
调试输出 - ' . $file . ' (第 ' . $line . ' 行)
'; foreach ($args as $index => $arg) { echo '
'; echo '

变量 #' . ($index + 1) . '

'; echo '
' . self::formatVar($arg) . '
'; echo '
'; } // 打印调用栈 echo '

调用栈

'; echo '
'; $traces = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT); foreach ($traces as $i => $t) { if ($i === 0) continue; // 跳过当前函数 $class = isset($t['class']) ? $t['class'] : ''; $type = isset($t['type']) ? $t['type'] : ''; $function = isset($t['function']) ? $t['function'] : ''; $file = isset($t['file']) ? $t['file'] : '未知文件'; $line = isset($t['line']) ? $t['line'] : '未知行号'; echo '
'; echo '#' . $i . ' '; echo $file . ' (' . $line . '): '; if ($class) { echo $class . $type . $function . '()'; } else { echo $function . '()'; } echo '
'; } echo '
'; echo ''; } else { // CLI环境,使用文本格式输出 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); $file = isset($trace[0]['file']) ? $trace[0]['file'] : '未知文件'; $line = isset($trace[0]['line']) ? $trace[0]['line'] : '未知行号'; echo "\n\033[1;36m调试输出 - {$file} (第 {$line} 行)\033[0m\n\n"; foreach ($args as $index => $arg) { echo "\033[1;33m变量 #" . ($index + 1) . "\033[0m\n"; echo self::formatVarCli($arg) . "\n\n"; } // 打印调用栈 echo "\033[1;33m调用栈:\033[0m\n"; $traces = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT); foreach ($traces as $i => $t) { if ($i === 0) continue; // 跳过当前函数 $class = isset($t['class']) ? $t['class'] : ''; $type = isset($t['type']) ? $t['type'] : ''; $function = isset($t['function']) ? $t['function'] : ''; $file = isset($t['file']) ? $t['file'] : '未知文件'; $line = isset($t['line']) ? $t['line'] : '未知行号'; echo "#" . $i . " " . $file . " (" . $line . "): "; if ($class) { echo $class . $type . $function . "()\n"; } else { echo $function . "()\n"; } } } // 终止程序 exit(1); } /** * 美化打印变量但不终止程序 * 可以传入任意数量的参数 * * @return void */ public static function dump() { $args = func_get_args(); // 复用dd的逻辑,但不终止程序 ob_start(); call_user_func_array([self::class, 'dd'], $args); $output = ob_get_clean(); echo $output; // 不终止程序继续执行 return; } /** * 格式化变量输出(HTML) * * @param mixed $var 需要格式化的变量 * @param int $depth 当前递归深度 * @param int $maxDepth 最大递归深度 * @return string */ private static function formatVar($var, $depth = 0, $maxDepth = 10) { // 防止递归过深 if ($depth > $maxDepth) { return '** 最大递归深度 **'; } $output = ''; // 根据变量类型格式化输出 switch (gettype($var)) { case 'NULL': $output .= 'null'; break; case 'boolean': $output .= '' . ($var ? 'true' : 'false') . ''; break; case 'integer': case 'double': $output .= '' . $var . ''; break; case 'string': $output .= 'string(' . strlen($var) . ') "' . htmlspecialchars($var) . '"'; break; case 'array': $count = count($var); $output .= 'array(' . $count . ') {
'; $indent = str_repeat('    ', $depth + 1); foreach ($var as $key => $value) { $output .= $indent . '[' . htmlspecialchars($key) . '] => '; $output .= self::formatVar($value, $depth + 1, $maxDepth) . '
'; } if ($count > 0) { $output .= str_repeat('    ', $depth); } $output .= '}'; break; case 'object': $id = spl_object_id($var); $class = get_class($var); $output .= 'object(' . $class . '#' . $id . ') {
'; $indent = str_repeat('    ', $depth + 1); // 获取对象属性 $reflection = new \ReflectionObject($var); $properties = $reflection->getProperties(); foreach ($properties as $property) { $property->setAccessible(true); $propName = $property->getName(); $visibility = ''; if ($property->isPublic()) { $visibility = 'public'; } elseif ($property->isProtected()) { $visibility = 'protected'; $propName = '*' . $propName; } elseif ($property->isPrivate()) { $visibility = 'private'; $propName = '#' . $propName; } $output .= $indent . '[' . $visibility . ' ' . $propName . '] => '; if ($property->isInitialized($var)) { $propValue = $property->getValue($var); $output .= self::formatVar($propValue, $depth + 1, $maxDepth) . '
'; } else { $output .= 'uninitialized
'; } } if (count($properties) > 0) { $output .= str_repeat('    ', $depth); } $output .= '}'; break; case 'resource': $output .= 'resource(' . get_resource_type($var) . ')'; break; default: $output .= '' . gettype($var) . ': ' . htmlspecialchars((string)$var); break; } return $output; } /** * 格式化变量输出(CLI) * * @param mixed $var 需要格式化的变量 * @param int $depth 当前递归深度 * @param int $maxDepth 最大递归深度 * @return string */ private static function formatVarCli($var, $depth = 0, $maxDepth = 10) { // 防止递归过深 if ($depth > $maxDepth) { return "\033[1;30m** 最大递归深度 **\033[0m"; } $output = ''; $indent = str_repeat(' ', $depth); // 根据变量类型格式化输出 switch (gettype($var)) { case 'NULL': $output .= "\033[1;30mnull\033[0m"; break; case 'boolean': $output .= "\033[0;36m" . ($var ? 'true' : 'false') . "\033[0m"; break; case 'integer': case 'double': $output .= "\033[0;36m" . $var . "\033[0m"; break; case 'string': $output .= "\033[0;32mstring(" . strlen($var) . ")\033[0m \"" . $var . "\""; break; case 'array': $count = count($var); $output .= "\033[0;32marray(" . $count . ")\033[0m {"; if ($count > 0) { $output .= "\n"; foreach ($var as $key => $value) { $output .= $indent . ' [' . "\033[0;35m" . $key . "\033[0m" . '] => '; $output .= self::formatVarCli($value, $depth + 1, $maxDepth) . "\n"; } $output .= $indent; } $output .= '}'; break; case 'object': $id = spl_object_id($var); $class = get_class($var); $output .= "\033[0;32mobject(" . $class . "#" . $id . ")\033[0m {"; // 获取对象属性 $reflection = new \ReflectionObject($var); $properties = $reflection->getProperties(); if (count($properties) > 0) { $output .= "\n"; foreach ($properties as $property) { $property->setAccessible(true); $propName = $property->getName(); $visibility = ''; if ($property->isPublic()) { $visibility = 'public'; } elseif ($property->isProtected()) { $visibility = 'protected'; $propName = '*' . $propName; } elseif ($property->isPrivate()) { $visibility = 'private'; $propName = '#' . $propName; } $output .= $indent . ' [' . "\033[0;35m" . $visibility . ' ' . $propName . "\033[0m" . '] => '; if ($property->isInitialized($var)) { $propValue = $property->getValue($var); $output .= self::formatVarCli($propValue, $depth + 1, $maxDepth) . "\n"; } else { $output .= "\033[1;30muninitialized\033[0m\n"; } } $output .= $indent; } $output .= '}'; break; case 'resource': $output .= "\033[0;32mresource(" . get_resource_type($var) . ")\033[0m"; break; default: $output .= "\033[0;32m" . gettype($var) . "\033[0m: " . (string)$var; break; } return $output; } }