Passed
Push — master ( c2175a...688bf6 )
by Evgeniy
02:12
created

ErrorHandlerTrait::generateResponse()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 13
ccs 7
cts 7
cp 1
rs 10
cc 3
nc 3
nop 2
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace HttpSoft\ErrorHandler;
6
7
use ErrorException;
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\ServerRequestInterface;
10
use Psr\Http\Server\RequestHandlerInterface;
11
use Throwable;
12
13
use function error_reporting;
14
use function get_class;
15
use function restore_error_handler;
16
use function set_error_handler;
17
18
trait ErrorHandlerTrait
19
{
20
    /**
21
     * @var ErrorListenerInterface[]
22
     */
23
    private array $listeners = [];
24
25
    /**
26
     * @var ErrorResponseGeneratorInterface
27
     */
28
    private ErrorResponseGeneratorInterface $responseGenerator;
29
30
    /**
31
     * Adds an error listener to the queue.
32
     *
33
     * @param ErrorListenerInterface $listener
34
     */
35 6
    public function addListener(ErrorListenerInterface $listener): void
36
    {
37 6
        $this->listeners[get_class($listener)] = $listener;
38 6
    }
39
40
    /**
41
     * Handles errors and exceptions in the layers it wraps.
42
     *
43
     * When an exception is intercepted, a response with error information is generated and returned;
44
     * otherwise, the response returned by `Psr\Http\Server\RequestHandlerInterface` instance is used.
45
     *
46
     * @param ServerRequestInterface $request
47
     * @param RequestHandlerInterface $handler
48
     * @return ResponseInterface
49
     * @throws ErrorException
50
     */
51 26
    private function handleError(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
52
    {
53 26
        set_error_handler(static function (int $severity, string $message, string $file, int $line): bool {
54
            // https://www.php.net/manual/en/function.error-reporting.php#8866
55
            // Usages the defined levels of `error_reporting()`.
56 6
            if (!(error_reporting() & $severity)) {
57
                // This error code is not included in `error_reporting()`.
58 2
                return true;
59
            }
60
61 4
            throw new ErrorException($message, 0, $severity, $file, $line);
62 26
        });
63
64
        try {
65 26
            $response = $handler->handle($request);
66 16
        } catch (Throwable $error) {
67 16
            $this->triggerListeners($error, $request);
68 16
            $response = $this->generateResponse($error, $request);
69
        }
70
71 26
        restore_error_handler();
72 26
        return $response;
73
    }
74
75
    /**
76
     * Trigger all error listeners.
77
     *
78
     * @param Throwable $error
79
     * @param ServerRequestInterface $request
80
     */
81 16
    private function triggerListeners(Throwable $error, ServerRequestInterface $request): void
82
    {
83 16
        foreach ($this->listeners as $listener) {
84 4
            $listener->trigger($error, $request);
85
        }
86 16
    }
87
88
    /**
89
     * Returns a response with a valid status code.
90
     *
91
     * If the generated response has a valid status code, it will be returned unchanged.
92
     *
93
     * If the status code of the generated response is invalid, but the error
94
     * code is valid, the response code will be changed to an error code.
95
     *
96
     * If the status code of the generated response and error code are
97
     * not valid, a response with the status code 500 is returned.
98
     *
99
     * @see isValidResponseCode()
100
     * @param Throwable $error
101
     * @param ServerRequestInterface $request
102
     * @return ResponseInterface
103
     * @psalm-suppress RedundantCastGivenDocblockType
104
     */
105 16
    private function generateResponse(Throwable $error, ServerRequestInterface $request): ResponseInterface
106
    {
107 16
        $response = $this->responseGenerator->generate($error, $request);
108
109 16
        if ($this->isValidResponseCode((int) $response->getStatusCode())) {
110 12
            return $response;
111
        }
112
113 4
        if ($this->isValidResponseCode((int) $error->getCode())) {
114 2
            return $response->withStatus((int) $error->getCode());
115
        }
116
117 2
        return $response->withStatus(500);
118
    }
119
120
    /**
121
     * Checks whether the response status code is valid or not.
122
     *
123
     * The valid response status code must be 4xx (client errors) or 5xx (server errors).
124
     *
125
     * @param int $responseCode
126
     * @return bool
127
     */
128 16
    private function isValidResponseCode(int $responseCode): bool
129
    {
130 16
        return ($responseCode >= 400 && $responseCode < 600);
131
    }
132
}
133