Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix console exception renderer #421

Merged
merged 31 commits into from
Feb 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ac6f1fa
create test to show the problem
DarkSide666 Feb 7, 2025
43e7123
fix heredoc bugs and add \e[0m after stacktrace
DarkSide666 Feb 7, 2025
57374ce
add color constants and use unified styling method
DarkSide666 Feb 7, 2025
99a71e9
cs fix
DarkSide666 Feb 7, 2025
1a76586
cs fix 2
DarkSide666 Feb 7, 2025
943740d
proper typehint
DarkSide666 Feb 7, 2025
87820b1
address comments
DarkSide666 Feb 7, 2025
e6b08a5
slightly rename constants
DarkSide666 Feb 7, 2025
d0e094c
format should always be set
DarkSide666 Feb 7, 2025
6692774
do not overcomplicate
DarkSide666 Feb 8, 2025
7219914
remove useless asserts
DarkSide666 Feb 8, 2025
cd953a1
do not use static method
DarkSide666 Feb 8, 2025
b610b87
improved tests
DarkSide666 Feb 8, 2025
dffa0f8
fix cs
mvorisek Feb 8, 2025
907b383
non shortened name
mvorisek Feb 8, 2025
6eb3f69
this NL was not in original
mvorisek Feb 8, 2025
7bd1791
fix cs
mvorisek Feb 8, 2025
3a05e41
no space format
mvorisek Feb 8, 2025
618a075
reset all at start
mvorisek Feb 8, 2025
4631062
unformat empty
mvorisek Feb 8, 2025
7f91293
this NL was not present too
mvorisek Feb 8, 2025
e8725db
fix typo
mvorisek Feb 8, 2025
9ea30c5
restore original arguments coloring
mvorisek Feb 8, 2025
47dc176
no unformat, it does not cover all usecases
mvorisek Feb 8, 2025
2ef2ade
assert exact strings
mvorisek Feb 8, 2025
f852ef5
fix cs
mvorisek Feb 8, 2025
81ebb09
more assertions
mvorisek Feb 8, 2025
d20bc63
fix stan
mvorisek Feb 8, 2025
2dbf79a
even json_encode can fail
mvorisek Feb 8, 2025
361e392
refactor "m" to "ex"
mvorisek Feb 8, 2025
4073c41
move some tests
mvorisek Feb 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 60 additions & 37 deletions src/ExceptionRenderer/Console.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,35 @@

class Console extends RendererAbstract
{
private const RESET = "\e[0m";
private const FORMAT_BOLD = "\e[1m";
private const COLOR_BLACK = "\e[30m";
private const COLOR_RED = "\e[31m";
private const COLOR_GREEN = "\e[32m";
private const COLOR_YELLOW = "\e[33m";
private const BACKGROUND_COLOR_RED = "\e[41m";
private const BACKGROUND_COLOR_MAGENTA = "\e[45m";
private const COLOR_BRIGHT_RED = "\e[91m";
private const COLOR_BRIGHT_GREEN = "\e[92m";

/**
* @param non-empty-list<self::FORMAT_*|self::COLOR_*|self::BACKGROUND_COLOR_*> $formats
*/
private function text(string $text, array $formats): string
{
assert(!str_contains($text, "\e["));

return implode('', $formats) . $text . self::RESET;
}

#[\Override]
protected function processAll(): void
{
$this->output .= self::RESET;

parent::processAll();
}

#[\Override]
protected function processHeader(): void
{
Expand All @@ -21,10 +50,13 @@ protected function processHeader(): void
'{CODE}' => $this->exception->getCode() ? ' [code: ' . $this->exception->getCode() . ']' : '',
];

$this->output .= $this->replaceTokens(<<<'EOF'
\e[1;41m--[ {TITLE} ]\e[0m
{CLASS}: \e[1;30m{MESSAGE}\e[0;31m {CODE}
EOF, $tokens);
$this->output .= $this->replaceTokens(
$this->text('--[ {TITLE} ]', [self::FORMAT_BOLD, self::BACKGROUND_COLOR_RED]) . "\n"
. '{CLASS}: '
. $this->text('{MESSAGE}', [self::FORMAT_BOLD, self::COLOR_BLACK]) . ' '
. $this->text('{CODE}', [self::COLOR_RED]),
$tokens
);
}

#[\Override]
Expand All @@ -43,7 +75,7 @@ protected function processParams(): void

foreach ($exception->getParams() as $key => $val) {
$key = str_pad($key, 19, ' ', \STR_PAD_LEFT);
$this->output .= \PHP_EOL . "\e[91m" . $key . ': ' . static::toSafeString($val) . "\e[0m";
$this->output .= "\n" . $this->text($key . ': ' . static::toSafeString($val), [self::COLOR_BRIGHT_RED]);
}
}

Expand All @@ -59,29 +91,23 @@ protected function processSolutions(): void
}

foreach ($this->exception->getSolutions() as $key => $val) {
$this->output .= \PHP_EOL . "\e[92mSolution: " . $val . "\e[0m";
$this->output .= "\n" . $this->text('Solution: ' . $val, [self::COLOR_BRIGHT_GREEN]);
}
}

#[\Override]
protected function processStackTrace(): void
{
$this->output .= <<<'EOF'

\e[1;41m--[ Stack Trace ]\e[0m

EOF;

$this->output .= "\n" . $this->text('--[ Stack Trace ]', [self::FORMAT_BOLD, self::BACKGROUND_COLOR_RED]) . "\n";
$this->processStackTraceInternal();
}

#[\Override]
protected function processStackTraceInternal(): void
{
$text = <<<'EOF'
\e[0m{FILE}\e[0m:\e[0;31m{LINE}\e[0m {OBJECT} {CLASS}{FUNCTION_COLOR}{FUNCTION}{FUNCTION_ARGS}

EOF;
$text = '{FILE}:'
. $this->text('{LINE}', [self::COLOR_RED]) . ' '
. '{OBJECT} {CLASS}{FUNCTION}{FUNCTION_ARGS}';

$inAtk = true;
$shortTrace = $this->getStackTrace(true);
Expand All @@ -95,35 +121,35 @@ protected function processStackTraceInternal(): void
$inAtk = false;
}

$tokens = [];
$tokens['{FILE}'] = str_pad(mb_substr($call['file_rel'], -40), 40, ' ', \STR_PAD_LEFT);
$tokens['{LINE}'] = str_pad($call['line'], 4, ' ', \STR_PAD_LEFT);
$tokens['{OBJECT}'] = $call['object'] !== null ? " - \e[0;32m" . $call['object_formatted'] . "\e[0m" : '';
$tokens['{CLASS}'] = $call['class'] !== null ? "\e[0;32m" . $call['class_formatted'] . "::\e[0m" : '';
$functionColor = $escapeFrame ? self::COLOR_RED : self::COLOR_YELLOW;

$tokens['{FUNCTION_COLOR}'] = $escapeFrame ? "\e[0;31m" : "\e[0;33m";
$tokens['{FUNCTION}'] = $call['function'];
$tokens = [
'{FILE}' => str_pad(mb_substr($call['file_rel'], -40), 40, ' ', \STR_PAD_LEFT),
'{LINE}' => str_pad($call['line'], 4, ' ', \STR_PAD_LEFT),
'{OBJECT}' => $call['object'] !== null ? ' - ' . $this->text($call['object_formatted'], [self::COLOR_GREEN]) : '',
'{CLASS}' => $call['class'] !== null ? $this->text($call['class_formatted'] . '::', [self::COLOR_GREEN]) : '',
'{FUNCTION}' => $call['function'] !== null ? $this->text($call['function'], [$functionColor]) : '',
];

if ($index === 'self') {
$tokens['{FUNCTION_ARGS}'] = '';
} elseif (count($call['args']) === 0) {
$tokens['{FUNCTION_ARGS}'] = '()';
$tokens['{FUNCTION_ARGS}'] = $this->text('()', [$functionColor]);
} else {
if ($escapeFrame) {
$tokens['{FUNCTION_ARGS}'] = "\e[0;31m(" . \PHP_EOL . str_repeat(' ', 40) . implode(',' . \PHP_EOL . str_repeat(' ', 40), array_map(static function ($arg) {
$tokens['{FUNCTION_ARGS}'] = $this->text('(' . "\n" . str_repeat(' ', 40) . implode(',' . "\n" . str_repeat(' ', 40), array_map(static function ($arg) {
return static::toSafeString($arg);
}, $call['args'])) . ')';
}, $call['args'])) . ')', [self::COLOR_RED]);
} else {
$tokens['{FUNCTION_ARGS}'] = '(...)';
$tokens['{FUNCTION_ARGS}'] = $this->text('(...)', [$functionColor]);
}
}

$this->output .= $this->replaceTokens($text, $tokens);
$this->output .= rtrim($this->replaceTokens($text, $tokens), ' ') . "\n";
}

if ($isShortened) {
$this->output .= '...
';
$this->output .= '...' . "\n";
}
}

Expand All @@ -134,12 +160,9 @@ protected function processPreviousException(): void
return;
}

$this->output .= \PHP_EOL . "\e[1;45mCaused by Previous Exception:\e[0m" . \PHP_EOL;

$this->output .= (string) (new static($this->exception->getPrevious(), $this->adapter, $this->exception));
$this->output .= <<<'EOF'
\e[1;31m--
\e[0m
EOF;
$this->output .= "\n"
. $this->text('Caused by Previous Exception:', [self::FORMAT_BOLD, self::BACKGROUND_COLOR_MAGENTA]) . "\n"
. ((string) (new static($this->exception->getPrevious(), $this->adapter, $this->exception)))
. $this->text('--', [self::FORMAT_BOLD, self::COLOR_RED]);
}
}
59 changes: 39 additions & 20 deletions src/ExceptionRenderer/Html.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ protected function processHeader(): void
'{CODE}' => $this->exception->getCode() ? ' [code: ' . $this->exception->getCode() . ']' : '',
];

$this->output .= $this->replaceTokens('
$this->output .= $this->replaceTokens(<<<'EOF'
<div class="ui negative icon message">
<i class="warning sign icon"></i>
<div class="content">
Expand All @@ -30,7 +30,8 @@ protected function processHeader(): void
{MESSAGE}
</div>
</div>
', $tokens);

EOF, $tokens);
}

protected function encodeHtml(string $value): string
Expand All @@ -49,19 +50,23 @@ protected function processParams(): void
return;
}

$text = '
$text = <<<'EOF'

<table class="ui very compact small selectable table top aligned">
<thead><tr><th colspan="2" class="ui inverted red table">Exception Parameters</th></tr></thead>
<tbody>{PARAMS}
</tbody>
</table>
';

EOF;

$tokens = [
'{PARAMS}' => '',
];
$textInner = '
<tr><td><b>{KEY}</b></td><td style="width: 100%;">{VAL}</td></tr>';
$textInner = <<<'EOF'

<tr><td><b>{KEY}</b></td><td style="width: 100%;">{VAL}</td></tr>
EOF;
foreach ($this->exception->getParams() as $key => $val) {
$key = $this->encodeHtml($key);
$val = '<span style="white-space: pre-wrap;">' . preg_replace('~(?<=\n)( +)~', '$1$1', $this->encodeHtml(static::toSafeString($val, true))) . '</span>';
Expand Down Expand Up @@ -89,19 +94,23 @@ protected function processSolutions(): void
return;
}

$text = '
$text = <<<'EOF'

<table class="ui very compact small selectable table top aligned">
<thead><tr><th colspan="2" class="ui inverted green table">Suggested solutions</th></tr></thead>
<tbody>{SOLUTIONS}
</tbody>
</table>
';

EOF;

$tokens = [
'{SOLUTIONS}' => '',
];
$textInner = '
<tr><td>{VAL}</td></tr>';
$textInner = <<<'EOF'

<tr><td>{VAL}</td></tr>
EOF;
foreach ($exception->getSolutions() as $key => $val) {
$tokens['{SOLUTIONS}'] .= $this->replaceTokens($textInner, ['{VAL}' => $this->encodeHtml($val)]);
}
Expand All @@ -112,32 +121,38 @@ protected function processSolutions(): void
#[\Override]
protected function processStackTrace(): void
{
$this->output .= '
$this->output .= <<<'EOF'

<table class="ui very compact small selectable table top aligned">
<thead><tr><th colspan="4">Stack Trace</th></tr></thead>
<thead><tr><th style="text-align: right">#</th><th>File</th><th>Object</th><th>Method</th></tr></thead>
<tbody>
';

EOF;

$this->processStackTraceInternal();

$this->output .= '
$this->output .= <<<'EOF'

</tbody>
</table>
';

EOF;
}

#[\Override]
protected function processStackTraceInternal(): void
{
$text = '
$text = <<<'EOF'

<tr class="{CSS_CLASS}">
<td style="text-align: right">{INDEX}</td>
<td>{FILE_LINE}</td>
<td>{OBJECT}</td>
<td>{FUNCTION}{FUNCTION_ARGS}</td>
</tr>
';

EOF;

$inAtk = true;
$shortTrace = $this->getStackTrace(true);
Expand Down Expand Up @@ -178,14 +193,16 @@ protected function processStackTraceInternal(): void
}

if ($isShortened) {
$this->output .= '
$this->output .= <<<'EOF'

<tr>
<td style="text-align: right">...</td>
<td></td>
<td></td>
<td></td>
</tr>
';

EOF;
}
}

Expand All @@ -196,11 +213,13 @@ protected function processPreviousException(): void
return;
}

$this->output .= '
$this->output .= <<<'EOF'

<div class="ui top attached segment">
<div class="ui top attached label">Caused by Previous Exception:</div>
</div>
';

EOF;

$this->output .= (string) (new static($this->exception->getPrevious(), $this->adapter, $this->exception));
}
Expand Down
6 changes: 5 additions & 1 deletion src/ExceptionRenderer/Json.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,12 @@ protected function parseStackTraceFrame(array $frame): array
#[\Override]
public function __toString(): string
{
$toStringFx = fn () => json_encode($this->json, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_PRETTY_PRINT | \JSON_THROW_ON_ERROR);

try {
$this->processAll();

return $toStringFx();
} catch (\Throwable $e) {
// fallback if error occur
$this->json = [
Expand All @@ -153,6 +157,6 @@ public function __toString(): string
];
}

return (string) json_encode($this->json, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE);
return $toStringFx();
}
}
Loading