1: <?php
2:
3: namespace Charcoal\App\Handler;
4:
5: use Throwable;
6:
7:
8: use Psr\Http\Message\ServerRequestInterface;
9: use Psr\Http\Message\ResponseInterface;
10:
11:
12: use Slim\Http\Body;
13:
14:
15: use Pimple\Container;
16:
17:
18: use Charcoal\App\Handler\AbstractHandler;
19:
20: 21: 22: 23: 24: 25: 26: 27:
28: class PhpError extends AbstractHandler
29: {
30: 31: 32: 33: 34:
35: protected $displayErrorDetails;
36:
37: 38: 39: 40: 41:
42: protected $error;
43:
44: 45: 46: 47: 48: 49: 50: 51: 52: 53:
54: public function setDependencies(Container $container)
55: {
56: parent::setDependencies($container);
57:
58: $displayDetails = $container['settings']['displayErrorDetails'];
59: $this->setDisplayErrorDetails($displayDetails);
60:
61: return $this;
62: }
63:
64: 65: 66: 67: 68: 69: 70: 71:
72: public function __invoke(ServerRequestInterface $request, ResponseInterface $response, Throwable $error)
73: {
74: $this->setError($error);
75: $this->logger->error($error->getMessage());
76: $this->logger->error($error->getFile().':'.$error->getLine());
77:
78: $contentType = $this->determineContentType($request);
79: switch ($contentType) {
80: case 'application/json':
81: $output = $this->renderJsonOutput();
82: break;
83:
84: case 'text/xml':
85: case 'application/xml':
86: $output = $this->renderXmlOutput();
87: break;
88:
89: case 'text/html':
90: default:
91: $output = $this->renderHtmlOutput();
92: break;
93: }
94:
95: $this->writeToErrorLog();
96:
97: $body = new Body(fopen('php://temp', 'r+'));
98: $body->write($output);
99:
100: return $response
101: ->withStatus(500)
102: ->withHeader('Content-type', $contentType)
103: ->withBody($body);
104: }
105:
106: 107: 108: 109: 110: 111:
112: protected function setDisplayErrorDetails($state)
113: {
114: $this->displayErrorDetails = (boolean)$state;
115:
116: return $this;
117: }
118:
119: 120: 121: 122: 123:
124: public function displayErrorDetails()
125: {
126: return $this->displayErrorDetails;
127: }
128:
129: 130: 131: 132: 133: 134:
135: protected function setError(Throwable $error)
136: {
137: $this->error = $error;
138:
139: return $this;
140: }
141:
142: 143: 144: 145: 146:
147: public function error()
148: {
149: return $this->error;
150: }
151:
152: 153: 154: 155: 156:
157: protected function writeToErrorLog()
158: {
159: if ($this->displayErrorDetails()) {
160: return;
161: }
162:
163: $error = $this->error();
164:
165: $message = $this->translator()->translate('Application Error').':'.PHP_EOL;
166: $message .= $this->renderTextError($error);
167: while ($error = $error->getPrevious()) {
168: $message .= PHP_EOL.'Previous error:'.PHP_EOL;
169: $message .= $this->renderTextError($error);
170: }
171:
172: $message .= PHP_EOL.'View in rendered output by enabling the "displayErrorDetails" setting.'.PHP_EOL;
173:
174: error_log($message);
175: }
176:
177: 178: 179: 180: 181: 182:
183: protected function renderTextError(Throwable $error)
184: {
185: $code = $error->getCode();
186: $message = $error->getMessage();
187: $file = $error->getFile();
188: $line = $error->getLine();
189: $trace = $error->getTraceAsString();
190:
191: $text = sprintf('Type: %s'.PHP_EOL, get_class($error));
192:
193: if ($code) {
194: $text .= sprintf('Code: %s'.PHP_EOL, $code);
195: }
196:
197: if ($message) {
198: $text .= sprintf('Message: %s'.PHP_EOL, htmlentities($message));
199: }
200:
201: if ($file) {
202: $text .= sprintf('File: %s'.PHP_EOL, $file);
203: }
204:
205: if ($line) {
206: $text .= sprintf('Line: %s'.PHP_EOL, $line);
207: }
208:
209: if ($trace) {
210: $text .= sprintf('Trace: %s', $trace);
211: }
212:
213: return $text;
214: }
215:
216: 217: 218: 219: 220:
221: protected function renderJsonOutput()
222: {
223: $error = $this->error();
224: $json = [
225: 'message' => $this->translator()->translate('Application Error'),
226: ];
227:
228: if ($this->displayErrorDetails()) {
229: $json['error'] = [];
230:
231: do {
232: $json['error'][] = [
233: 'type' => get_class($error),
234: 'code' => $error->getCode(),
235: 'message' => $error->getMessage(),
236: 'file' => $error->getFile(),
237: 'line' => $error->getLine(),
238: 'trace' => explode("\n", $error->getTraceAsString()),
239: ];
240: } while ($error = $error->getPrevious());
241: }
242:
243: return json_encode($json, JSON_PRETTY_PRINT);
244: }
245:
246: 247: 248: 249: 250:
251: protected function renderXmlOutput()
252: {
253: $error = $this->error();
254: $title = $this->messageTitle();
255:
256: $xml = "<error>\n <message>".$title."</message>\n";
257: if ($this->displayErrorDetails()) {
258: do {
259: $xml .= " <error>\n";
260: $xml .= ' <type>'.get_class($error)."</type>\n";
261: $xml .= ' <code>'.$error->getCode()."</code>\n";
262: $xml .= ' <message>'.$this->createCdataSection($error->getMessage())."</message>\n";
263: $xml .= ' <file>'.$error->getFile()."</file>\n";
264: $xml .= ' <line>'.$error->getLine()."</line>\n";
265: $xml .= ' <trace>'.$this->createCdataSection($error->getTraceAsString())."</trace>\n";
266: $xml .= " </error>\n";
267: } while ($error = $error->getPrevious());
268: }
269: $xml .= '</error>';
270:
271: return $xml;
272: }
273:
274: 275: 276: 277: 278: 279:
280: private function createCdataSection($content)
281: {
282: return sprintf('<![CDATA[%s]]>', str_replace(']]>', ']]]]><![CDATA[>', $content));
283: }
284:
285: 286: 287: 288: 289: 290:
291: protected function renderHtmlError(Throwable $error)
292: {
293: $code = $error->getCode();
294: $message = $error->getMessage();
295: $file = $error->getFile();
296: $line = $error->getLine();
297: $trace = $error->getTraceAsString();
298:
299: $html = sprintf('<div><strong>Type:</strong> %s</div>', get_class($error));
300:
301: if ($code) {
302: $html .= sprintf('<div><strong>Code:</strong> %s</div>', $code);
303: }
304:
305: if ($message) {
306: $html .= sprintf('<div><strong>Message:</strong> %s</div>', htmlentities($message));
307: }
308:
309: if ($file) {
310: $html .= sprintf('<div><strong>File:</strong> %s</div>', $file);
311: }
312:
313: if ($line) {
314: $html .= sprintf('<div><strong>Line:</strong> %s</div>', $line);
315: }
316:
317: if ($trace) {
318: $html .= '<h2>Trace</h2>';
319: $html .= sprintf('<pre>%s</pre>', htmlentities($trace));
320: }
321:
322: return $html;
323: }
324:
325: 326: 327: 328: 329:
330: public function renderHtmlMessage()
331: {
332: $error = $this->error();
333:
334: if ($this->displayErrorDetails()) {
335: $html = '<p>The application could not run because of the following error:</p>';
336: $html .= '<h2>Details</h2>';
337: $html .= $this->renderHtmlError($error);
338:
339: while ($error = $error->getPrevious()) {
340: $html .= '<h2>Previous Error</h2>';
341: $html .= $this->renderHtmlError($error);
342: }
343: } else {
344: $html = '<p>'.$this->translator()->translate('A website error has occurred. Sorry for the temporary inconvenience.').'</p>';
345: }
346:
347: $title = $this->messageTitle();
348: $message = '<h1>'.$title."</h1>\n".$html."\n";
349:
350: return $message;
351: }
352: }
353: