1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Core\ExceptionRenderer;
6:
7: use Atk4\Core\Exception;
8:
9: class Console extends RendererAbstract
10: {
11: #[\Override]
12: protected function processHeader(): void
13: {
14: $title = $this->getExceptionTitle();
15: $class = get_class($this->exception);
16:
17: $tokens = [
18: '{TITLE}' => $title,
19: '{CLASS}' => $class,
20: '{MESSAGE}' => $this->getExceptionMessage(),
21: '{CODE}' => $this->exception->getCode() ? ' [code: ' . $this->exception->getCode() . ']' : '',
22: ];
23:
24: $this->output .= $this->replaceTokens(<<<'EOF'
25: \e[1;41m--[ {TITLE} ]\e[0m
26: {CLASS}: \e[1;30m{MESSAGE}\e[0;31m {CODE}
27: EOF, $tokens);
28: }
29:
30: #[\Override]
31: protected function processParams(): void
32: {
33: if (!$this->exception instanceof Exception) {
34: return;
35: }
36:
37: /** @var Exception $exception */
38: $exception = $this->exception;
39:
40: if (count($exception->getParams()) === 0) {
41: return;
42: }
43:
44: foreach ($exception->getParams() as $key => $val) {
45: $key = str_pad($key, 19, ' ', \STR_PAD_LEFT);
46: $this->output .= \PHP_EOL . "\e[91m" . $key . ': ' . static::toSafeString($val) . "\e[0m";
47: }
48: }
49:
50: #[\Override]
51: protected function processSolutions(): void
52: {
53: if (!$this->exception instanceof Exception) {
54: return;
55: }
56:
57: if (count($this->exception->getSolutions()) === 0) {
58: return;
59: }
60:
61: foreach ($this->exception->getSolutions() as $key => $val) {
62: $this->output .= \PHP_EOL . "\e[92mSolution: " . $val . "\e[0m";
63: }
64: }
65:
66: #[\Override]
67: protected function processStackTrace(): void
68: {
69: $this->output .= <<<'EOF'
70:
71: \e[1;41m--[ Stack Trace ]\e[0m
72: EOF . "\n";
73:
74: $this->processStackTraceInternal();
75: }
76:
77: #[\Override]
78: protected function processStackTraceInternal(): void
79: {
80: $text = <<<'EOF'
81: \e[0m{FILE}\e[0m:\e[0;31m{LINE}\e[0m {OBJECT} {CLASS}{FUNCTION_COLOR}{FUNCTION}{FUNCTION_ARGS}
82: EOF . "\n";
83:
84: $inAtk = true;
85: $shortTrace = $this->getStackTrace(true);
86: $isShortened = end($shortTrace) && key($shortTrace) !== 0 && key($shortTrace) !== 'self';
87: foreach ($shortTrace as $index => $call) {
88: $call = $this->parseStackTraceFrame($call);
89:
90: $escapeFrame = false;
91: if ($inAtk && !preg_match('~atk4[/\\\\][^/\\\\]+[/\\\\]src[/\\\\]~', $call['file'])) {
92: $escapeFrame = true;
93: $inAtk = false;
94: }
95:
96: $tokens = [];
97: $tokens['{FILE}'] = str_pad(mb_substr($call['file_rel'], -40), 40, ' ', \STR_PAD_LEFT);
98: $tokens['{LINE}'] = str_pad($call['line'], 4, ' ', \STR_PAD_LEFT);
99: $tokens['{OBJECT}'] = $call['object'] !== null ? " - \e[0;32m" . $call['object_formatted'] . "\e[0m" : '';
100: $tokens['{CLASS}'] = $call['class'] !== null ? "\e[0;32m" . $call['class_formatted'] . "::\e[0m" : '';
101:
102: $tokens['{FUNCTION_COLOR}'] = $escapeFrame ? "\e[0;31m" : "\e[0;33m";
103: $tokens['{FUNCTION}'] = $call['function'];
104:
105: if ($index === 'self') {
106: $tokens['{FUNCTION_ARGS}'] = '';
107: } elseif (count($call['args']) === 0) {
108: $tokens['{FUNCTION_ARGS}'] = '()';
109: } else {
110: if ($escapeFrame) {
111: $tokens['{FUNCTION_ARGS}'] = "\e[0;31m(" . \PHP_EOL . str_repeat(' ', 40) . implode(',' . \PHP_EOL . str_repeat(' ', 40), array_map(static function ($arg) {
112: return static::toSafeString($arg);
113: }, $call['args'])) . ')';
114: } else {
115: $tokens['{FUNCTION_ARGS}'] = '(...)';
116: }
117: }
118:
119: $this->output .= $this->replaceTokens($text, $tokens);
120: }
121:
122: if ($isShortened) {
123: $this->output .= '...
124: ';
125: }
126: }
127:
128: #[\Override]
129: protected function processPreviousException(): void
130: {
131: if (!$this->exception->getPrevious()) {
132: return;
133: }
134:
135: $this->output .= \PHP_EOL . "\e[1;45mCaused by Previous Exception:\e[0m" . \PHP_EOL;
136:
137: $this->output .= (string) (new static($this->exception->getPrevious(), $this->adapter, $this->exception));
138: $this->output .= <<<'EOF'
139: \e[1;31m--
140: \e[0m
141: EOF;
142: }
143: }
144: