Skip to content

Commit 54cb511

Browse files
committed
Add render_backtrace() function
1 parent 5d8baed commit 54cb511

File tree

4 files changed

+65
-57
lines changed

4 files changed

+65
-57
lines changed

system/Common.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,57 @@ function remove_invisible_characters(string $str, bool $urlEncoded = true): stri
941941
}
942942
}
943943

944+
if (! function_exists('render_backtrace')) {
945+
/**
946+
* Renders a backtrace in a nice string format.
947+
*
948+
* @param list<array{
949+
* file?: string,
950+
* line?: int,
951+
* class?: string,
952+
* type?: string,
953+
* function: string,
954+
* args?: list<mixed>
955+
* }> $backtrace
956+
*/
957+
function render_backtrace(array $backtrace): string
958+
{
959+
$backtraces = [];
960+
961+
foreach ($backtrace as $index => $trace) {
962+
$frame = $trace + ['file' => '[internal function]', 'line' => 0, 'class' => '', 'type' => '', 'args' => []];
963+
964+
if ($frame['file'] !== '[internal function]') {
965+
$frame['file'] = sprintf('%s(%s)', $frame['file'], $frame['line']);
966+
}
967+
968+
unset($frame['line']);
969+
$idx = $index;
970+
$idx = str_pad((string) ++$idx, 2, ' ', STR_PAD_LEFT);
971+
972+
$args = implode(', ', array_map(static fn ($value): string => match (true) {
973+
is_object($value) => sprintf('Object(%s)', $value::class),
974+
is_array($value) => $value !== [] ? '[...]' : '[]',
975+
$value === null => 'null',
976+
is_resource($value) => sprintf('resource (%s)', get_resource_type($value)),
977+
default => var_export($value, true),
978+
}, $frame['args']));
979+
980+
$backtraces[] = sprintf(
981+
'%s %s: %s%s%s(%s)',
982+
$idx,
983+
clean_path($frame['file']),
984+
$frame['class'],
985+
$frame['type'],
986+
$frame['function'],
987+
$args
988+
);
989+
}
990+
991+
return implode("\n", $backtraces);
992+
}
993+
}
994+
944995
if (! function_exists('request')) {
945996
/**
946997
* Returns the shared Request.

system/Debug/Exceptions.php

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public function exceptionHandler(Throwable $exception)
136136
'routeInfo' => $routeInfo,
137137
'exFile' => clean_path($exception->getFile()), // {file} refers to THIS file
138138
'exLine' => $exception->getLine(), // {line} refers to THIS line
139-
'trace' => self::renderBacktrace($exception->getTrace()),
139+
'trace' => render_backtrace($exception->getTrace()),
140140
]);
141141

142142
// Get the first exception.
@@ -149,7 +149,7 @@ public function exceptionHandler(Throwable $exception)
149149
'message' => $prevException->getMessage(),
150150
'exFile' => clean_path($prevException->getFile()), // {file} refers to THIS file
151151
'exLine' => $prevException->getLine(), // {line} refers to THIS line
152-
'trace' => self::renderBacktrace($prevException->getTrace()),
152+
'trace' => render_backtrace($prevException->getTrace()),
153153
]);
154154
}
155155
}
@@ -527,8 +527,8 @@ private function handleDeprecationError(string $message, ?string $file = null, ?
527527
'message' => $message,
528528
'errFile' => clean_path($file ?? ''),
529529
'errLine' => $line ?? 0,
530-
'trace' => self::renderBacktrace($trace),
531-
],
530+
'trace' => render_backtrace($trace),
531+
]
532532
);
533533

534534
return true;
@@ -646,41 +646,4 @@ public static function highlightFile(string $file, int $lineNumber, int $lines =
646646

647647
return '<pre><code>' . $out . '</code></pre>';
648648
}
649-
650-
private static function renderBacktrace(array $backtrace): string
651-
{
652-
$backtraces = [];
653-
654-
foreach ($backtrace as $index => $trace) {
655-
$frame = $trace + ['file' => '[internal function]', 'line' => '', 'class' => '', 'type' => '', 'args' => []];
656-
657-
if ($frame['file'] !== '[internal function]') {
658-
$frame['file'] = sprintf('%s(%s)', $frame['file'], $frame['line']);
659-
}
660-
661-
unset($frame['line']);
662-
$idx = $index;
663-
$idx = str_pad((string) ++$idx, 2, ' ', STR_PAD_LEFT);
664-
665-
$args = implode(', ', array_map(static fn ($value): string => match (true) {
666-
is_object($value) => sprintf('Object(%s)', $value::class),
667-
is_array($value) => $value !== [] ? '[...]' : '[]',
668-
$value === null => 'null',
669-
is_resource($value) => sprintf('resource (%s)', get_resource_type($value)),
670-
default => var_export($value, true),
671-
}, $frame['args']));
672-
673-
$backtraces[] = sprintf(
674-
'%s %s: %s%s%s(%s)',
675-
$idx,
676-
clean_path($frame['file']),
677-
$frame['class'],
678-
$frame['type'],
679-
$frame['function'],
680-
$args,
681-
);
682-
}
683-
684-
return implode("\n", $backtraces);
685-
}
686649
}

tests/system/CommonFunctionsTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,4 +805,14 @@ public function testIsWindowsUsingMock(): void
805805
$this->assertSame(str_contains(php_uname(), 'Windows'), is_windows());
806806
$this->assertSame(defined('PHP_WINDOWS_VERSION_MAJOR'), is_windows());
807807
}
808+
809+
public function testRenderBacktrace(): void
810+
{
811+
$trace = (new RuntimeException('Test exception'))->getTrace();
812+
$renders = explode("\n", render_backtrace($trace));
813+
814+
foreach ($renders as $render) {
815+
$this->assertMatchesRegularExpression('/^\s*\d* .+(?:\(\d+\))?: \S+(?:(?:\->|::)\S+)?\(.*\)$/', $render);
816+
}
817+
}
808818
}

tests/system/Debug/ExceptionsTest.php

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -128,22 +128,6 @@ public function testDetermineCodes(): void
128128
$this->assertSame([500, EXIT_DATABASE], $determineCodes(new DatabaseException('This.')));
129129
}
130130

131-
public function testRenderBacktrace(): void
132-
{
133-
$renderer = self::getPrivateMethodInvoker(Exceptions::class, 'renderBacktrace');
134-
$exception = new RuntimeException('This.');
135-
136-
$renderedBacktrace = $renderer($exception->getTrace());
137-
$renderedBacktrace = explode("\n", $renderedBacktrace);
138-
139-
foreach ($renderedBacktrace as $trace) {
140-
$this->assertMatchesRegularExpression(
141-
'/^\s*\d* .+(?:\(\d+\))?: \S+(?:(?:\->|::)\S+)?\(.*\)$/',
142-
$trace,
143-
);
144-
}
145-
}
146-
147131
public function testMaskSensitiveData(): void
148132
{
149133
$maskSensitiveData = self::getPrivateMethodInvoker($this->exception, 'maskSensitiveData');

0 commit comments

Comments
 (0)