Completed
Push — remove-apc-wo-u-cache ( 905c5a...8ea0d1 )
by Alexander
16:50 queued 13:18
created

ErrorHandler::handleError()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 0
cts 13
cp 0
rs 8.5125
c 0
b 0
f 0
cc 5
eloc 13
nc 7
nop 4
crap 30
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\base;
9
10
use Yii;
11
use yii\helpers\VarDumper;
12
use yii\web\HttpException;
13
14
/**
15
 * ErrorHandler handles uncaught PHP errors and exceptions.
16
 *
17
 * ErrorHandler is configured as an application component in [[\yii\base\Application]] by default.
18
 * You can access that instance via `Yii::$app->errorHandler`.
19
 *
20
 * For more details and usage information on ErrorHandler, see the [guide article on handling errors](guide:runtime-handling-errors).
21
 *
22
 * @author Qiang Xue <[email protected]>
23
 * @author Alexander Makarov <[email protected]>
24
 * @author Carsten Brandt <[email protected]>
25
 * @since 2.0
26
 */
27
abstract class ErrorHandler extends Component
28
{
29
    /**
30
     * @var bool whether to discard any existing page output before error display. Defaults to true.
31
     */
32
    public $discardExistingOutput = true;
33
    /**
34
     * @var int the size of the reserved memory. A portion of memory is pre-allocated so that
35
     * when an out-of-memory issue occurs, the error handler is able to handle the error with
36
     * the help of this reserved memory. If you set this value to be 0, no memory will be reserved.
37
     * Defaults to 256KB.
38
     */
39
    public $memoryReserveSize = 262144;
40
    /**
41
     * @var \Exception|null the exception that is being handled currently.
42
     */
43
    public $exception;
44
45
    /**
46
     * @var string Used to reserve memory for fatal error handler.
47
     */
48
    private $_memoryReserve;
49
50
51
    /**
52
     * Register this error handler
53
     */
54
    public function register()
55
    {
56
        ini_set('display_errors', false);
57
        set_exception_handler([$this, 'handleException']);
58
        set_error_handler([$this, 'handleError']);
59
60
        if ($this->memoryReserveSize > 0) {
61
            $this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);
62
        }
63
        register_shutdown_function([$this, 'handleFatalError']);
64
    }
65
66
    /**
67
     * Unregisters this error handler by restoring the PHP error and exception handlers.
68
     */
69
    public function unregister()
70
    {
71
        restore_error_handler();
72
        restore_exception_handler();
73
    }
74
75
    /**
76
     * Handles uncaught PHP exceptions.
77
     *
78
     * This method is implemented as a PHP exception handler.
79
     *
80
     * @param \Exception $exception the exception that is not caught
81
     */
82
    public function handleException($exception)
83
    {
84
        if ($exception instanceof ExitException) {
85
            return;
86
        }
87
88
        $this->exception = $exception;
89
90
        // disable error capturing to avoid recursive errors while handling exceptions
91
        $this->unregister();
92
93
        // set preventive HTTP status code to 500 in case error handling somehow fails and headers are sent
94
        // HTTP exceptions will override this value in renderException()
95
        if (PHP_SAPI !== 'cli') {
96
            http_response_code(500);
97
        }
98
99
        try {
100
            $this->logException($exception);
101
            if ($this->discardExistingOutput) {
102
                $this->clearOutput();
103
            }
104
            $this->renderException($exception);
105
            if (!YII_ENV_TEST) {
106
                \Yii::getLogger()->flush(true);
107
                exit(1);
108
            }
109
        } catch (\Throwable $e) {
110
            // another exception could be thrown while displaying the exception
111
            $this->handleFallbackExceptionMessage($e, $exception);
112
        }
113
114
        $this->exception = null;
115
    }
116
117
    /**
118
     * Handles exception thrown during exception processing in [[handleException()]].
119
     * @param \Throwable $exception Exception that was thrown during main exception processing.
120
     * @param \Exception $previousException Main exception processed in [[handleException()]].
121
     * @since 2.0.11
122
     */
123
    protected function handleFallbackExceptionMessage($exception, $previousException)
124
    {
125
        $msg = "An Error occurred while handling another error:\n";
126
        $msg .= (string) $exception;
127
        $msg .= "\nPrevious exception:\n";
128
        $msg .= (string) $previousException;
129
        if (YII_DEBUG) {
130
            if (PHP_SAPI === 'cli') {
131
                echo $msg . "\n";
132
            } else {
133
                echo '<pre>' . htmlspecialchars($msg, ENT_QUOTES, Yii::$app->charset) . '</pre>';
134
            }
135
        } else {
136
            echo 'An internal server error occurred.';
137
        }
138
        $msg .= "\n\$_SERVER = " . VarDumper::export($_SERVER);
139
        error_log($msg);
140
        exit(1);
141
    }
142
143
    /**
144
     * Handles PHP execution errors such as warnings and notices.
145
     *
146
     * This method is used as a PHP error handler. It will simply raise an [[ErrorException]].
147
     *
148
     * @param int $code the level of the error raised.
149
     * @param string $message the error message.
150
     * @param string $file the filename that the error was raised in.
151
     * @param int $line the line number the error was raised at.
152
     * @return bool whether the normal error handler continues.
153
     *
154
     * @throws ErrorException
155
     */
156
    public function handleError($code, $message, $file, $line)
157
    {
158
        if (error_reporting() & $code) {
159
            // load ErrorException manually here because autoloading them will not work
160
            // when error occurs while autoloading a class
161
            if (!class_exists(ErrorException::class, false)) {
162
                require_once __DIR__ . '/ErrorException.php';
163
            }
164
            $exception = new ErrorException($message, $code, $code, $file, $line);
165
166
            // in case error appeared in __toString method we can't throw any exception
167
            $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
168
            array_shift($trace);
169
            foreach ($trace as $frame) {
170
                if ($frame['function'] === '__toString') {
171
                    $this->handleException($exception);
172
                    exit(1);
173
                }
174
            }
175
176
            throw $exception;
177
        }
178
        return false;
179
    }
180
181
    /**
182
     * Handles fatal PHP errors
183
     */
184
    public function handleFatalError()
185
    {
186
        unset($this->_memoryReserve);
187
188
        // load ErrorException manually here because autoloading them will not work
189
        // when error occurs while autoloading a class
190
        if (!class_exists(ErrorException::class, false)) {
191
            require_once __DIR__ . '/ErrorException.php';
192
        }
193
194
        $error = error_get_last();
195
196
        if (ErrorException::isFatalError($error)) {
197
            $exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']);
198
            $this->exception = $exception;
199
200
            $this->logException($exception);
201
202
            if ($this->discardExistingOutput) {
203
                $this->clearOutput();
204
            }
205
            $this->renderException($exception);
206
207
            // need to explicitly flush logs because exit() next will terminate the app immediately
208
            Yii::getLogger()->flush(true);
209
            exit(1);
210
        }
211
    }
212
213
    /**
214
     * Renders the exception.
215
     * @param \Exception $exception the exception to be rendered.
216
     */
217
    abstract protected function renderException($exception);
218
219
    /**
220
     * Logs the given exception
221
     * @param \Exception $exception the exception to be logged
222
     * @since 2.0.3 this method is now public.
223
     */
224
    public function logException($exception)
225
    {
226
        $category = get_class($exception);
227
        if ($exception instanceof HttpException) {
228
            $category = HttpException::class . ': ' . $exception->statusCode;
229
        } elseif ($exception instanceof \ErrorException) {
0 ignored issues
show
Bug introduced by
The class ErrorException does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
230
            $category .= ':' . $exception->getSeverity();
231
        }
232
        Yii::error($exception, $category);
233
    }
234
235
    /**
236
     * Removes all output echoed before calling this method.
237
     */
238
    public function clearOutput()
239
    {
240
        // the following manual level counting is to deal with zlib.output_compression set to On
241
        for ($level = ob_get_level(); $level > 0; --$level) {
242
            if (!@ob_end_clean()) {
243
                ob_clean();
244
            }
245
        }
246
    }
247
248
    /**
249
     * Converts an exception into a PHP error.
250
     *
251
     * This method can be used to convert exceptions inside of methods like `__toString()`
252
     * to PHP errors because exceptions cannot be thrown inside of them.
253
     * @param \Exception $exception the exception to convert to a PHP error.
254
     */
255
    public static function convertExceptionToError($exception)
256
    {
257
        trigger_error(static::convertExceptionToString($exception), E_USER_ERROR);
258
    }
259
260
    /**
261
     * Converts an exception into a simple string.
262
     * @param \Exception|\Error $exception the exception being converted
263
     * @return string the string representation of the exception.
264
     */
265
    public static function convertExceptionToString($exception)
266
    {
267
        if ($exception instanceof Exception && ($exception instanceof UserException || !YII_DEBUG)) {
268
            $message = "{$exception->getName()}: {$exception->getMessage()}";
269
        } elseif (YII_DEBUG) {
270
            if ($exception instanceof Exception) {
271
                $message = "Exception ({$exception->getName()})";
272
            } elseif ($exception instanceof ErrorException) {
273
                $message = "{$exception->getName()}";
0 ignored issues
show
Bug introduced by
Consider using $exception->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
274
            } else {
275
                $message = 'Exception';
276
            }
277
            $message .= " '" . get_class($exception) . "' with message '{$exception->getMessage()}' \n\nin "
278
                . $exception->getFile() . ':' . $exception->getLine() . "\n\n"
279
                . "Stack trace:\n" . $exception->getTraceAsString();
280
        } else {
281
            $message = 'Error: ' . $exception->getMessage();
282
        }
283
        return $message;
284
    }
285
}
286