Skip to content

Commit 427eeeb

Browse files
committed
Add render_backtrace() function
1 parent cc96fe3 commit 427eeeb

File tree

4 files changed

+64
-56
lines changed

4 files changed

+64
-56
lines changed

Diff for: system/Common.php

+51
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,57 @@ function remove_invisible_characters(string $str, bool $urlEncoded = true): stri
913913
}
914914
}
915915

916+
if (! function_exists('render_backtrace')) {
917+
/**
918+
* Renders a backtrace in a nice string format.
919+
*
920+
* @param list<array{
921+
* file?: string,
922+
* line?: int,
923+
* class?: string,
924+
* type?: string,
925+
* function: string,
926+
* args?: list<mixed>
927+
* }> $backtrace
928+
*/
929+
function render_backtrace(array $backtrace): string
930+
{
931+
$backtraces = [];
932+
933+
foreach ($backtrace as $index => $trace) {
934+
$frame = $trace + ['file' => '[internal function]', 'line' => 0, 'class' => '', 'type' => '', 'args' => []];
935+
936+
if ($frame['file'] !== '[internal function]') {
937+
$frame['file'] = sprintf('%s(%s)', $frame['file'], $frame['line']);
938+
}
939+
940+
unset($frame['line']);
941+
$idx = $index;
942+
$idx = str_pad((string) ++$idx, 2, ' ', STR_PAD_LEFT);
943+
944+
$args = implode(', ', array_map(static fn ($value): string => match (true) {
945+
is_object($value) => sprintf('Object(%s)', $value::class),
946+
is_array($value) => $value !== [] ? '[...]' : '[]',
947+
$value === null => 'null',
948+
is_resource($value) => sprintf('resource (%s)', get_resource_type($value)),
949+
default => var_export($value, true),
950+
}, $frame['args']));
951+
952+
$backtraces[] = sprintf(
953+
'%s %s: %s%s%s(%s)',
954+
$idx,
955+
clean_path($frame['file']),
956+
$frame['class'],
957+
$frame['type'],
958+
$frame['function'],
959+
$args
960+
);
961+
}
962+
963+
return implode("\n", $backtraces);
964+
}
965+
}
966+
916967
if (! function_exists('request')) {
917968
/**
918969
* Returns the shared Request.

Diff for: system/Debug/Exceptions.php

+3-40
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,7 +527,7 @@ 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),
530+
'trace' => render_backtrace($trace),
531531
]
532532
);
533533

@@ -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
}

Diff for: tests/system/CommonFunctionsTest.php

+10
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
}

Diff for: tests/system/Debug/ExceptionsTest.php

-16
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 = $this->getPrivateMethodInvoker($this->exception, 'maskSensitiveData');

0 commit comments

Comments
 (0)