Completed
Push — master ( b09865...c05ece )
by Alexander
03:58
created

src/ErrorHandler/ErrorHandler.php (1 issue)

1
<?php
2
3
namespace Yiisoft\Yii\Web\ErrorHandler;
4
5
use Psr\Log\LoggerInterface;
6
7
class ErrorHandler
8
{
9
    /**
10
     * @var int the size of the reserved memory. A portion of memory is pre-allocated so that
11
     * when an out-of-memory issue occurs, the error handler is able to handle the error with
12
     * the help of this reserved memory. If you set this value to be 0, no memory will be reserved.
13
     * Defaults to 256KB.
14
     */
15
    private $memoryReserveSize = 262144;
16
17
    private $memoryReserve;
18
19
    private $logger;
20
21
    private $defaultRenderer;
22
23
    public function __construct(LoggerInterface $logger, ThrowableRendererInterface $defaultRenderer)
24
    {
25
        $this->logger = $logger;
26
        $this->defaultRenderer = $defaultRenderer;
27
    }
28
29
    /**
30
     * Handles PHP execution errors such as warnings and notices.
31
     *
32
     * This method is used as a PHP error handler. It will raise an [[\ErrorException]].
33
     *
34
     * @param int $severity the level of the error raised.
35
     * @param string $message the error message.
36
     * @param string $file the filename that the error was raised in.
37
     * @param int $line the line number the error was raised at.
38
     *
39
     * @throws \ErrorException
40
     */
41
    public function handleError(int $severity, string $message, string $file, int $line): void
42
    {
43
        if (!(error_reporting() & $severity)) {
44
            // This error code is not included in error_reporting
45
            return;
46
        }
47
48
        // in case error appeared in __toString method we can't throw any exception
49
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
50
        array_shift($trace);
51
        foreach ($trace as $frame) {
52
            if ($frame['function'] === '__toString') {
53
                trigger_error($message, $severity);
54
                return;
55
            }
56
        }
57
58
        throw new \ErrorException($message, 0, $severity, $file, $line);
59
    }
60
61
    /**
62
     * Handle throwable and return output
63
     *
64
     * @param \Throwable $t
65
     * @param ThrowableRendererInterface|null $renderer
66
     * @return string
67
     */
68
    public function handleCaughtThrowable(\Throwable $t, ThrowableRendererInterface $renderer = null): string
69
    {
70
        if ($renderer === null) {
71
            $renderer = $this->defaultRenderer;
72
        }
73
74
        try {
75
            $this->log($t);
76
            return $renderer->render($t);
77
        } catch (\Throwable $t) {
78
            return nl2br($t);
79
        }
80
    }
81
82
    /**
83
     * Handle throwable, echo output and exit
84
     *
85
     * @param \Throwable $t
86
     */
87
    public function handleThrowable(\Throwable $t): void
88
    {
89
        // disable error capturing to avoid recursive errors while handling exceptions
90
        $this->unregister();
91
92
        // set preventive HTTP status code to 500 in case error handling somehow fails and headers are sent
93
        http_response_code(500);
94
95
        echo $this->handleCaughtThrowable($t);
96
        exit(1);
97
    }
98
99
    /**
100
     * Register this error handler.
101
     */
102
    public function register(): void
103
    {
104
        ini_set('display_errors', false);
105
        set_exception_handler([$this, 'handleThrowable']);
106
        set_error_handler([$this, 'handleError']);
107
108
        if ($this->memoryReserveSize > 0) {
109
            $this->memoryReserve = str_repeat('x', $this->memoryReserveSize);
110
        }
111
        register_shutdown_function([$this, 'handleFatalError']);
112
    }
113
114
    /**
115
     * Unregisters this error handler by restoring the PHP error and exception handlers.
116
     */
117
    public function unregister(): void
118
    {
119
        restore_error_handler();
120
        restore_exception_handler();
121
    }
122
123
    public function handleFatalError(): void
124
    {
125
        unset($this->_memoryReserve);
0 ignored issues
show
The property _memoryReserve does not exist on Yiisoft\Yii\Web\ErrorHandler\ErrorHandler. Did you mean memoryReserve?
Loading history...
126
        $error = error_get_last();
127
        if ($this->isFatalError($error)) {
128
            $exception = new \ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']);
129
            $this->handleThrowable($exception);
130
            exit(1);
131
        }
132
    }
133
134
    private function isFatalError(array $error): bool
135
    {
136
        return isset($error['type']) && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING], true);
137
    }
138
139
    private function log(\Throwable $t/*, ServerRequestInterface $request*/): void
140
    {
141
        $renderer = new PlainTextRenderer();
142
        $this->logger->error($renderer->render($t), [
143
            'throwable' => $t,
144
            //'request' => $request,
145
        ]);
146
    }
147
}
148