1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Core\ExceptionRenderer;
6:
7: use Atk4\Core\Exception;
8:
9: class Json extends RendererAbstract
10: {
11: /** @var array<string, mixed> */
12: protected array $json = [
13: 'success' => false,
14: 'code' => 0,
15: 'message' => '',
16: 'title' => '',
17: 'class' => '',
18: 'params' => [],
19: 'solution' => [],
20: 'trace' => [],
21: 'previous' => [],
22: ];
23:
24: #[\Override]
25: protected function processHeader(): void
26: {
27: $title = $this->getExceptionTitle();
28: $class = get_class($this->exception);
29:
30: $this->json['code'] = $this->exception->getCode();
31: $this->json['message'] = $this->getExceptionMessage();
32: $this->json['title'] = $title;
33: $this->json['class'] = $class;
34: }
35:
36: #[\Override]
37: protected function processParams(): void
38: {
39: if (!$this->exception instanceof Exception) {
40: return;
41: }
42:
43: if (count($this->exception->getParams()) === 0) {
44: return;
45: }
46:
47: foreach ($this->exception->getParams() as $key => $val) {
48: $this->json['params'][$key] = static::toSafeString($val);
49: }
50: }
51:
52: #[\Override]
53: protected function processSolutions(): void
54: {
55: if (!$this->exception instanceof Exception) {
56: return;
57: }
58:
59: /** @var Exception $exception */
60: $exception = $this->exception;
61:
62: if (count($exception->getSolutions()) === 0) {
63: return;
64: }
65:
66: foreach ($exception->getSolutions() as $key => $val) {
67: $this->json['solution'][$key] = $val;
68: }
69: }
70:
71: #[\Override]
72: protected function processStackTrace(): void
73: {
74: $this->output .= '<span style="color: sandybrown;">Stack Trace:</span>' . "\n";
75:
76: $this->processStackTraceInternal();
77: }
78:
79: #[\Override]
80: protected function processStackTraceInternal(): void
81: {
82: $inAtk = true;
83: $trace = $this->getStackTrace(false);
84: foreach ($trace as $index => $call) {
85: $call = $this->parseStackTraceFrame($call);
86:
87: $escapeFrame = false;
88: if ($inAtk && !preg_match('~atk4[/\\\\][^/\\\\]+[/\\\\]src[/\\\\]~', $call['file'])) {
89: $escapeFrame = true;
90: $inAtk = false;
91: }
92:
93: if ($escapeFrame) {
94: $call['args'] = array_map(static function ($arg) {
95: return static::toSafeString($arg);
96: }, $call['args']);
97: }
98:
99: $this->json['stack'][] = $call;
100: }
101: }
102:
103: #[\Override]
104: protected function processPreviousException(): void
105: {
106: if (!$this->exception->getPrevious()) {
107: return;
108: }
109:
110: $previous = new static($this->exception->getPrevious(), $this->adapter);
111: $text = (string) $previous; // need to trigger processAll;
112:
113: $this->json['previous'] = $previous->json;
114: }
115:
116: #[\Override]
117: protected function parseStackTraceFrame(array $frame): array
118: {
119: return [
120: 'line' => $frame['line'] ?? '',
121: 'file' => $frame['file'] ?? '',
122: 'class' => $frame['class'] ?? null,
123: 'object' => ($frame['object'] ?? null) !== null ? static::toSafeString($frame['object']) : null,
124: 'function' => $frame['function'] ?? null,
125: 'args' => $frame['args'] ?? [],
126: ];
127: }
128:
129: #[\Override]
130: public function __toString(): string
131: {
132: try {
133: $this->processAll();
134: } catch (\Throwable $e) {
135: // fallback if error occur
136: $this->json = [
137: 'success' => false,
138: 'code' => $this->exception->getCode(),
139: 'message' => 'Error during json renderer: ' . $this->exception->getMessage(),
140: // avoid translation
141: // 'message' => $this->_($this->exception->getMessage()),
142: 'title' => get_class($this->exception),
143: 'class' => get_class($this->exception),
144: 'params' => [],
145: 'solution' => [],
146: 'trace' => [],
147: 'previous' => [
148: 'title' => get_class($e),
149: 'class' => get_class($e),
150: 'code' => $e->getCode(),
151: 'message' => $e->getMessage(),
152: ],
153: ];
154: }
155:
156: return (string) json_encode($this->json, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE);
157: }
158: }
159: