1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Core;
6:
7: use Atk4\Core\ExceptionRenderer\RendererAbstract;
8: use Atk4\Core\Translator\ITranslatorAdapter;
9: use PHPUnit\Framework\SelfDescribing;
10:
11: if (!interface_exists(SelfDescribing::class)) {
12: eval('namespace PHPUnit\Framework; interface SelfDescribing { public function toString(): string; }');
13: }
14:
15: /**
16: * Base exception of all Agile Toolkit exceptions.
17: */
18: class Exception extends \Exception implements SelfDescribing
19: {
20: use WarnDynamicPropertyTrait;
21:
22: /** @var string */
23: protected $customExceptionTitle = 'Critical Error';
24:
25: /** @var array<string, mixed> */
26: private $params = [];
27:
28: /** @var array<int, string> */
29: private $solutions = [];
30:
31: /** @var ITranslatorAdapter */
32: private $translator;
33:
34: public function __construct(string $message = '', int $code = 0, \Throwable $previous = null)
35: {
36: parent::__construct($message, $code, $previous);
37:
38: // save trace but skip constructors of this exception
39: $trace = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT);
40: for ($i = 0; $i < count($trace); ++$i) {
41: $frame = $trace[$i];
42: if (isset($frame['object']) && $frame['object'] === $this && $frame['function'] === '__construct') {
43: array_shift($trace);
44: }
45: }
46: $traceReflectionProperty = new \ReflectionProperty(parent::class, 'trace');
47: $traceReflectionProperty->setAccessible(true);
48: $traceReflectionProperty->setValue($this, $trace);
49: }
50:
51: /**
52: * Change message (subject) of a current exception. Primary use is
53: * for localization purposes.
54: *
55: * @return $this
56: */
57: public function setMessage(string $message): self
58: {
59: $this->message = $message;
60:
61: return $this;
62: }
63:
64: #[\Override]
65: public function toString(): string
66: {
67: $res = static::class . ': ' . $this->getMessage() . "\n";
68: foreach ($this->getParams() as $param => $value) {
69: $valueStr = RendererAbstract::toSafeString($value, true);
70: $res .= ' ' . $param . ': ' . str_replace("\n", "\n" . ' ', $valueStr) . "\n";
71: }
72:
73: return $res;
74: }
75:
76: /**
77: * Return exception message using color sequences.
78: *
79: * <exception name>: <string>
80: * <info>
81: *
82: * trace
83: *
84: * --
85: * <triggered by>
86: */
87: public function getColorfulText(): string
88: {
89: return (string) new ExceptionRenderer\Console($this, $this->translator);
90: }
91:
92: /**
93: * Return exception message using HTML block and Fomantic-UI formatting. It's your job
94: * to put it inside boilerplate HTML and output, e.g:.
95: *
96: * $app = new \Atk4\Ui\App();
97: * $app->initLayout([\Atk4\Ui\Layout\Centered::class]);
98: * $app->layout->template->dangerouslySetHtml('Content', $e->getHtml());
99: * $app->run();
100: * $app->callBeforeExit();
101: */
102: public function getHtml(): string
103: {
104: return (string) new ExceptionRenderer\Html($this, $this->translator);
105: }
106:
107: /**
108: * Return exception in JSON Format.
109: */
110: public function getJson(): string
111: {
112: return (string) new ExceptionRenderer\Json($this, $this->translator);
113: }
114:
115: /**
116: * Follow the getter-style of PHP Exception.
117: *
118: * @return array<string, mixed>
119: */
120: public function getParams(): array
121: {
122: return $this->params;
123: }
124:
125: /**
126: * @param mixed $value
127: *
128: * @return $this
129: */
130: public function addMoreInfo(string $param, $value): self
131: {
132: $this->params[$param] = $value;
133:
134: return $this;
135: }
136:
137: /**
138: * @return $this
139: */
140: public function addSolution(string $solution): self
141: {
142: $this->solutions[] = $solution;
143:
144: return $this;
145: }
146:
147: /**
148: * @return array<int, string>
149: */
150: public function getSolutions(): array
151: {
152: return $this->solutions;
153: }
154:
155: public function getCustomExceptionTitle(): string
156: {
157: return $this->customExceptionTitle;
158: }
159:
160: /**
161: * Set custom Translator adapter.
162: *
163: * @return $this
164: */
165: public function setTranslatorAdapter(ITranslatorAdapter $translator = null): self
166: {
167: $this->translator = $translator;
168:
169: return $this;
170: }
171: }
172: