Passed
Pull Request — master (#70)
by Sergei
04:12 queued 02:00
created

ErrorHandler::initializeHandlers()   B

Complexity

Conditions 11
Paths 5

Size

Total Lines 48
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 22.8932

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 22
c 1
b 1
f 0
dl 0
loc 48
ccs 14
cts 26
cp 0.5385
rs 7.3166
cc 11
nc 5
nop 0
crap 22.8932

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\ErrorHandler;
6
7
use Psr\EventDispatcher\EventDispatcherInterface;
8
use Psr\Http\Message\ServerRequestInterface;
9
use Psr\Log\LoggerInterface;
10
use Throwable;
11
use Yiisoft\ErrorHandler\Event\ApplicationError;
12
use Yiisoft\ErrorHandler\Exception\ErrorException;
13
use Yiisoft\ErrorHandler\Renderer\PlainTextRenderer;
14
use Yiisoft\Http\Status;
15
16
use function error_get_last;
17
use function error_reporting;
18
use function function_exists;
19
use function ini_set;
20
use function http_response_code;
21
use function register_shutdown_function;
22
use function set_error_handler;
23
use function set_exception_handler;
24
use function str_repeat;
25
26
/**
27
 * `ErrorHandler` handles out of memory errors, fatals, warnings, notices and exceptions.
28
 */
29
final class ErrorHandler
30
{
31
    /**
32
     * @var int The size of the reserved memory. A portion of memory is pre-allocated so that
33
     * when an out-of-memory issue occurs, the error handler is able to handle the error with
34
     * the help of this reserved memory. If you set this value to be 0, no memory will be reserved.
35
     * Defaults to 256KB.
36
     */
37
    private int $memoryReserveSize = 262_144;
0 ignored issues
show
Bug introduced by
The constant Yiisoft\ErrorHandler\262_144 was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
38
    private string $memoryReserve = '';
39
    private bool $debug = false;
40
    private ?string $workingDirectory = null;
41
    private bool $enabled = false;
42
    private bool $isHandlersInitialized = false;
43
44
    private LoggerInterface $logger;
45
    private ThrowableRendererInterface $defaultRenderer;
46
    private ?EventDispatcherInterface $eventDispatcher;
47
48 17
    public function __construct(
49
        LoggerInterface $logger,
50
        ThrowableRendererInterface $defaultRenderer,
51
        EventDispatcherInterface $eventDispatcher = null
52
    ) {
53 17
        $this->logger = $logger;
54 17
        $this->defaultRenderer = $defaultRenderer;
55 17
        $this->eventDispatcher = $eventDispatcher;
56
    }
57
58
    /**
59
     * Handles throwable and returns error data.
60
     *
61
     * @param Throwable $t
62
     * @param ThrowableRendererInterface|null $renderer
63
     * @param ServerRequestInterface|null $request
64
     *
65
     * @return ErrorData
66
     */
67 12
    public function handle(
68
        Throwable $t,
69
        ThrowableRendererInterface $renderer = null,
70
        ServerRequestInterface $request = null
71
    ): ErrorData {
72 12
        if ($renderer === null) {
73 5
            $renderer = $this->defaultRenderer;
74
        }
75
76
        try {
77 12
            $this->logger->error((string) (new PlainTextRenderer())->renderVerbose($t, $request), ['throwable' => $t]);
78 12
            return $this->debug ? $renderer->renderVerbose($t, $request) : $renderer->render($t, $request);
79 4
        } catch (Throwable $t) {
80 4
            return new ErrorData((string) $t);
81
        }
82
    }
83
84
    /**
85
     * Enables and disables debug mode.
86
     *
87
     * Ensure that is is disabled in production environment since debug mode exposes sensitive details.
88
     *
89
     * @param bool $enable Enable/disable debugging mode.
90
     */
91 2
    public function debug(bool $enable = true): void
92
    {
93 2
        $this->debug = $enable;
94
    }
95
96
    /**
97
     * Sets the size of the reserved memory.
98
     *
99
     * @param int $size The size of the reserved memory.
100
     *
101
     * @see $memoryReserveSize
102
     */
103 6
    public function memoryReserveSize(int $size): void
104
    {
105 6
        $this->memoryReserveSize = $size;
106
    }
107
108
    /**
109
     * Register PHP exception and error handlers once and enable this error handler.
110
     */
111 2
    public function register(): void
112
    {
113 2
        if ($this->memoryReserveSize > 0) {
114
            $this->memoryReserve = str_repeat('x', $this->memoryReserveSize);
115
        }
116
117 2
        $this->initializeHandlers();
118
119 2
        $this->enabled = true;
120
    }
121
122
    /**
123
     * Disable this error handler.
124
     */
125 1
    public function unregister(): void
126
    {
127 1
        $this->memoryReserve = '';
128
129 1
        $this->enabled = false;
130
    }
131
132 2
    private function initializeHandlers(): void
133
    {
134 2
        if ($this->isHandlersInitialized) {
135
            return;
136
        }
137
138
        // Disables the display of error.
139 2
        if (function_exists('ini_set')) {
140 2
            ini_set('display_errors', '0');
141
        }
142
143
        // Handles throwable, echo output and exit.
144 2
        set_exception_handler(function (Throwable $t): void {
145
            if ($this->enabled) {
146
                $this->renderThrowableAndTerminate($t);
147
            }
148 2
        });
149
150
        // Handles PHP execution errors such as warnings and notices.
151 2
        set_error_handler(function (int $severity, string $message, string $file, int $line): bool {
152 2
            if (!$this->enabled || !(error_reporting() & $severity)) {
153
                // This error code is not included in error_reporting.
154
                return true;
155
            }
156
157 2
            throw new ErrorException($message, $severity, $severity, $file, $line);
158 2
        });
159
160
        // Handles fatal error.
161 2
        register_shutdown_function(function (): void {
162
            if (!$this->enabled) {
163
                return;
164
            }
165
166
            $this->memoryReserve = '';
167
            $e = error_get_last();
168
169
            if ($e !== null && ErrorException::isFatalError($e)) {
170
                $error = new ErrorException($e['message'], $e['type'], $e['type'], $e['file'], $e['line']);
171
                $this->renderThrowableAndTerminate($error);
172
            }
173 2
        });
174
175 2
        if (!(PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg')) {
176
            $this->workingDirectory = getcwd();
177
        }
178
179 2
        $this->isHandlersInitialized = true;
180
    }
181
182
    /**
183
     * Renders the throwable and terminates the script.
184
     *
185
     * @param Throwable $t
186
     */
187
    private function renderThrowableAndTerminate(Throwable $t): void
188
    {
189
        if (!empty($this->workingDirectory)) {
190
            chdir($this->workingDirectory);
191
        }
192
        // disable error capturing to avoid recursive errors while handling exceptions
193
        $this->unregister();
194
        // set preventive HTTP status code to 500 in case error handling somehow fails and headers are sent
195
        http_response_code(Status::INTERNAL_SERVER_ERROR);
196
197
        echo $this->handle($t);
198
        if ($this->eventDispatcher !== null) {
199
            $this->eventDispatcher->dispatch(new ApplicationError($t));
200
        }
201
202
        register_shutdown_function(static function (): void {
203
            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...
204
        });
205
    }
206
}
207