Skip to content

Instantly share code, notes, and snippets.

@kmuenkel
Last active January 16, 2024 05:45
Show Gist options
  • Save kmuenkel/ae182ff80277011dd6457f64170d4217 to your computer and use it in GitHub Desktop.
Save kmuenkel/ae182ff80277011dd6457f64170d4217 to your computer and use it in GitHub Desktop.
Parse a stack trace from debug_backtrace or an Exception in a more readable format
<?php
function stack(\Throwable|array $context = null, string $prune = 'vendor', int $text = 64, int $arr = 4): void {
$currentTrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$current = array_shift($currentTrace);
$caller = implode(':', [$current['file'] ?? '', $current['line'] ?? '']);
$exception = $context instanceof \Throwable ? get_class($context) : '';
$message = $context instanceof \Throwable ? $context->getMessage() : '';
$trace = $context instanceof \Throwable ? $context->getTrace() : ($context ?: $currentTrace);
if ($context instanceof \Throwable) {
array_unshift($trace, ['line' => $context->getFile() . ':' . $context->getLine()]);
}
$trace = array_map(function (array $line) use ($text, $arr): array {
$args = array_map($self = function (mixed $arg) use (&$self, $text, $arr): string {
static $depth = 0;
$depth++;
$getItemTypes = function (array $items) use ($self, $depth, $text, $arr): string {
$count = (string)count($items);
if ($depth > 1) {
return $count;
}
$sample = array_splice($items, 0, $arr);
$types = array_map('gettype', $sample);
$types = (array_is_list($sample) && count(array_unique($types)) == 1)
? current($types) : array_map($self, $sample);
$type = fn (string $type, string|int $index): string => "$index: $type";
$types = is_array($types) ? array_map($type, $types, array_keys($types)) : [$types . "[$count]"];
return implode(', ', $items ? array_merge($types, ['...+' . count($items)]) : $types);
};
$response = match (gettype($arg)) {
'string' => '"' . (($l = strlen($arg)) > ($t = $text + 3)
? substr($arg, 0, $text) . '...+' . $l - $t : $arg) . '"',
'object' => $arg instanceof \Closure ? 'closure' : get_class($arg),
'resource' => 'resource',
'array' => (array_is_list($arg) ? 'array' : 'hash') . '(' . $getItemTypes($arg) . ')',
'NULL' => 'null',
'boolean' => $arg ? 'true' : 'false',
'integer', 'double' => (string)$arg,
default => $arg
};
$depth--;
return $response;
}, $line['args'] ?? []);
$function = implode('::', array_filter([$line['class'] ?? null, $line['function'] ?? null]));
$function .= '(' . implode(', ', $args) . ')';
$line = implode(':', array_filter([$line['file'] ?? null, $line['line'] ?? null]));
return array_filter(compact('function', 'line'));
}, $trace);
if ($prune) {
$truncate = function (array $line, int $key) use ($trace, $prune) : array|string {
$isEnd = !$key || $key == array_key_last($trace);
$isRegex = ($delim = substr($prune, 0, 1)) == substr($prune, -1, 1) && in_array($delim, ['\\', '~']);
$prune = $isRegex ? $prune : '~' . preg_quote($prune, '~') . '~i';
return !$isEnd && preg_match($prune, $line['line'] ?? '') ? '...' : $line;
};
$trace = array_map($truncate, $trace, array_keys($trace));
$truncate = fn (array|string $line, int $key): bool => $line != '...' || ($trace[$key - 1] ?? '') != '...';
$trace = array_filter($trace, $truncate, ARRAY_FILTER_USE_BOTH);
}
die(print_r(array_filter(compact('caller', 'exception', 'message', 'trace')), true));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment