Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
ErrorHandler
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 5
306
0.00% covered (danger)
0.00%
0 / 1
 logException
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
42
 getAnonIp
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getHTTPSaveExceptionCode
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 __invoke
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
30
 fatal
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/** @noinspection PhpUnhandledExceptionInspection */
3declare(strict_types=1);
4
5// TODO unit test
6
7use Slim\Exception\HttpException;
8use Slim\Http\ServerRequest as Request;
9use Slim\Http\Response;
10
11class ErrorHandler {
12  static function logException(Throwable $throwable, bool $logTrace = false): string {
13    $errorUniqueId = uniqid('error-', true);
14    $code = ErrorHandler::getHTTPSaveExceptionCode($throwable);
15
16    $logHeadline = [ErrorHandler::getAnonIp(), $errorUniqueId];
17
18    if (method_exists($throwable, 'getTitle') and $throwable->getTitle()) {
19      $logHeadline[] = "({$throwable->getTitle()})";
20    } else {
21      $logHeadline[] = "($code)";
22    }
23
24    $logHeadline[] = "`{$throwable->getMessage()}`";
25
26    if (is_a($throwable, "Slim\Exception\HttpException")) {
27      $request = $throwable->getRequest();
28      $serverParams = $request->getServerParams();
29      $logHeadline[] = "on `[{$serverParams['REQUEST_METHOD']}{$serverParams['REQUEST_URI']}`";
30    } else {
31      $logHeadline[] = "on `[{$_SERVER['REQUEST_METHOD']}]` {$_SERVER['REQUEST_URI']}`";
32    }
33
34    if ($testMode = TestEnvironment::$testMode) {
35      $logHeadline[] = "(testMode = $testMode)";
36    }
37
38    if ($logTrace) {
39      $logHeadline[] = "at {$throwable->getFile()}:{$throwable->getLine()}";
40    }
41
42    error_log(implode(' ', $logHeadline));
43
44    return $errorUniqueId;
45  }
46
47  private static function getAnonIp(): string {
48    if (isset($_SERVER['REMOTE_ADDR'])) {
49      $ip = explode(".", $_SERVER['REMOTE_ADDR']);
50      return "user-" . md5($ip[0] . ($ip[1] ?? '') . ($ip[2] ?? '') . ($_SERVER['HTTP_USER_AGENT'] ?? ''));
51    }
52
53    return 'pid-' . getmypid();
54  }
55
56  static function getHTTPSaveExceptionCode(Throwable $throwable): int {
57    if (is_a($throwable, "Slim\Exception\HttpException") or is_a($throwable, 'HttpError')) {
58      return $throwable->getCode();
59    }
60
61    return 500;
62  }
63
64  public function __invoke(Request $request, Throwable $throwable): Response {
65    global $app;
66
67    $code = ErrorHandler::getHTTPSaveExceptionCode($throwable);
68    $errorUniqueId = ErrorHandler::logException($throwable, $code >= 500);
69
70    if (!is_a($throwable, "Slim\Exception\HttpException")) {
71      $newThrowable = new HttpException($request, $throwable->getMessage(), $code, $throwable);
72      if (method_exists($throwable, 'getTitle')) {
73        $newThrowable->setTitle($throwable->getTitle());
74      }
75      $throwable = $newThrowable;
76    }
77
78    $response = $app
79      ->getResponseFactory()
80      ->createResponse()
81      ->withStatus($throwable->getCode(), $throwable->getTitle())
82      ->withHeader('Content-Type', 'text/html')
83      ->withHeader('Error-ID', $errorUniqueId)
84      ->write(htmlspecialchars($throwable->getMessage() ?: $throwable->getDescription()));
85
86    if (TestEnvironment::$testMode) {
87      return $response
88        ->withHeader('Test-Mode', TestEnvironment::$testMode);
89    }
90
91    return $response
92      ->withHeader('Test-Mode', 'NO');
93  }
94
95  public static function fatal(): void {
96
97  }
98}