Passed
Push — master ( ba0809...47dfc5 )
by Alexander
02:31
created

ErrorHandler::setRenderer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Web\ErrorHandler;
6
7
use Psr\Log\LoggerAwareInterface;
8
use Psr\Log\LoggerAwareTrait;
9
use Psr\Log\LoggerInterface;
10
use Yiisoft\Http\Status;
11
12
final class ErrorHandler implements LoggerAwareInterface
13
{
14
    use LoggerAwareTrait;
15
16
    /**
17
     * @var int the size of the reserved memory. A portion of memory is pre-allocated so that
18
     * when an out-of-memory issue occurs, the error handler is able to handle the error with
19
     * the help of this reserved memory. If you set this value to be 0, no memory will be reserved.
20
     * Defaults to 256KB.
21
     */
22
    private int $memoryReserveSize = 262_144;
0 ignored issues
show
Bug introduced by
The constant Yiisoft\Yii\Web\ErrorHandler\262_144 was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
23
    private string $memoryReserve = '';
24
    private bool $exposeDetails = true;
25
26
    private ThrowableRendererInterface $defaultRenderer;
27
28 18
    public function __construct(LoggerInterface $logger, ThrowableRendererInterface $defaultRenderer)
29
    {
30 18
        $this->logger = $logger;
31 18
        $this->defaultRenderer = $defaultRenderer;
32 18
    }
33
34
    /**
35
     * Handles PHP execution errors such as warnings and notices.
36
     *
37
     * This method is used as a PHP error handler. It will raise an [[\ErrorException]].
38
     *
39
     * @param int $severity the level of the error raised.
40
     * @param string $message the error message.
41
     * @param string $file the filename that the error was raised in.
42
     * @param int $line the line number the error was raised at.
43
     *
44
     * @throws ErrorException
45
     */
46
    public function handleError(int $severity, string $message, string $file, int $line): void
47
    {
48
        if (!(error_reporting() & $severity)) {
49
            // This error code is not included in error_reporting
50
            return;
51
        }
52
53
        throw new ErrorException($message, $severity, $severity, $file, $line);
54
    }
55
56
    /**
57
     * Handle throwable and return output
58
     *
59
     * @param \Throwable $t
60
     * @param ThrowableRendererInterface|null $renderer
61
     * @return string
62
     */
63 10
    public function handleCaughtThrowable(\Throwable $t, ThrowableRendererInterface $renderer = null): string
64
    {
65 10
        if ($renderer === null) {
66 5
            $renderer = $this->defaultRenderer;
67
        }
68
69
        try {
70 10
            $this->log($t);
71 10
            return $this->exposeDetails ? $renderer->renderVerbose($t) : $renderer->render($t);
72
        } catch (\Throwable $t) {
73
            return (string)$t;
74
        }
75
    }
76
77
    /**
78
     * Handle throwable, echo output and exit
79
     *
80
     * @param \Throwable $t
81
     */
82
    public function handleThrowable(\Throwable $t): void
83
    {
84
        // disable error capturing to avoid recursive errors while handling exceptions
85
        $this->unregister();
86
87
        // set preventive HTTP status code to 500 in case error handling somehow fails and headers are sent
88
        http_response_code(Status::INTERNAL_SERVER_ERROR);
89
90
        echo $this->handleCaughtThrowable($t);
91
        exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
92
    }
93
94
    /**
95
     * Register this error handler.
96
     */
97 1
    public function register(): void
98
    {
99 1
        $this->disableDisplayErrors();
100 1
        set_exception_handler([$this, 'handleThrowable']);
101
        /** @psalm-suppress InvalidArgument */
102 1
        set_error_handler([$this, 'handleError']);
103
104 1
        if ($this->memoryReserveSize > 0) {
105 1
            $this->memoryReserve = str_repeat('x', $this->memoryReserveSize);
106
        }
107 1
        register_shutdown_function([$this, 'handleFatalError']);
108 1
    }
109
110 1
    private function disableDisplayErrors(): void
111
    {
112 1
        if (function_exists('ini_set')) {
113 1
            ini_set('display_errors', '0');
114
        }
115 1
    }
116
117
    /**
118
     * Unregisters this error handler by restoring the PHP error and exception handlers.
119
     */
120
    public function unregister(): void
121
    {
122
        restore_error_handler();
123
        restore_exception_handler();
124
    }
125
126
    public function handleFatalError(): void
127
    {
128
        unset($this->memoryReserve);
129
        $error = error_get_last();
130
        if ($error !== null && ErrorException::isFatalError($error)) {
131
            $exception = new ErrorException(
132
                $error['message'],
133
                $error['type'],
134
                $error['type'],
135
                $error['file'],
136
                $error['line']
137
            );
138
            $this->handleThrowable($exception);
139
            exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
140
        }
141
    }
142
143 10
    private function log(\Throwable $t/*, ServerRequestInterface $request*/): void
144
    {
145 10
        $renderer = new PlainTextRenderer();
146 10
        $this->logger->error(
147 10
            $renderer->renderVerbose($t),
148
            [
149 10
                'throwable' => $t,
150
                //'request' => $request,
151
            ]
152
        );
153 10
    }
154
155 1
    public function withExposedDetails(): self
156
    {
157 1
        $new = clone $this;
158 1
        $new->exposeDetails = true;
159 1
        return $new;
160
    }
161
162 1
    public function withoutExposedDetails(): self
163
    {
164 1
        $new = clone $this;
165 1
        $new->exposeDetails = false;
166 1
        return $new;
167
    }
168
169
    public function setRenderer(ThrowableRendererInterface $defaultRenderer): void
170
    {
171
        $this->defaultRenderer = $defaultRenderer;
172
    }
173
}
174