Passed
Pull Request — master (#25)
by Evgeniy
02:24
created

ErrorException   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 112
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 18
eloc 45
dl 0
loc 112
ccs 0
cts 35
cp 0
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getName() 0 3 1
A isFatalError() 0 3 2
A isXdebugStackAvailable() 0 20 4
B addXDebugTraceToFatalIfAvailable() 0 30 9
A getSolution() 0 3 1
A __construct() 0 4 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\ErrorHandler\Exception;
6
7
use Exception;
8
use ReflectionProperty;
9
use Yiisoft\FriendlyException\FriendlyExceptionInterface;
10
11
use function array_slice;
12
use function in_array;
13
use function function_exists;
14
15
/**
16
 * ErrorException represents a PHP error.
17
 */
18
class ErrorException extends \ErrorException implements FriendlyExceptionInterface
19
{
20
    private const ERROR_NAMES = [
21
        E_ERROR => 'PHP Fatal Error',
22
        E_WARNING => 'PHP Warning',
23
        E_PARSE => 'PHP Parse Error',
24
        E_NOTICE => 'PHP Notice',
25
        E_CORE_ERROR => 'PHP Core Error',
26
        E_CORE_WARNING => 'PHP Core Warning',
27
        E_COMPILE_ERROR => 'PHP Compile Error',
28
        E_COMPILE_WARNING => 'PHP Compile Warning',
29
        E_USER_ERROR => 'PHP User Error',
30
        E_USER_WARNING => 'PHP User Warning',
31
        E_USER_NOTICE => 'PHP User Notice',
32
        E_STRICT => 'PHP Strict Warning',
33
        E_RECOVERABLE_ERROR => 'PHP Recoverable Error',
34
        E_DEPRECATED => 'PHP Deprecated Warning',
35
        E_USER_DEPRECATED => 'PHP User Deprecated Warning',
36
    ];
37
38
    public function __construct($message = '', $code = 0, $severity = 1, $filename = __FILE__, $lineno = __LINE__, Exception $previous = null)
39
    {
40
        parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
41
        $this->addXDebugTraceToFatalIfAvailable();
42
    }
43
44
    /**
45
     * Returns if error is one of fatal type.
46
     *
47
     * @param array $error error got from error_get_last()
48
     *
49
     * @return bool if error is one of fatal type
50
     */
51
    public static function isFatalError(array $error): bool
52
    {
53
        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);
54
    }
55
56
    /**
57
     * @return string the user-friendly name of this exception
58
     */
59
    public function getName(): string
60
    {
61
        return self::ERROR_NAMES[$this->getCode()] ?? 'Error';
62
    }
63
64
    /**
65
     * Fatal errors normally do not provide any trace making it harder to debug. In case XDebug is installed, we
66
     * can get a trace using xdebug_get_function_stack().
67
     */
68
    private function addXDebugTraceToFatalIfAvailable(): void
69
    {
70
        if ($this->isXdebugStackAvailable()) {
71
            // XDebug trace can't be modified and used directly with PHP 7
72
            // @see https://github.com/yiisoft/yii2/pull/11723
73
            $xDebugTrace = array_slice(array_reverse(xdebug_get_function_stack()), 1, -1);
74
            $trace = [];
75
76
            foreach ($xDebugTrace as $frame) {
77
                if (!isset($frame['function'])) {
78
                    $frame['function'] = 'unknown';
79
                }
80
81
                // XDebug < 2.1.1: http://bugs.xdebug.org/view.php?id=695
82
                if (!isset($frame['type']) || $frame['type'] === 'static') {
83
                    $frame['type'] = '::';
84
                } elseif ($frame['type'] === 'dynamic') {
85
                    $frame['type'] = '->';
86
                }
87
88
                // XDebug has a different key name
89
                if (isset($frame['params']) && !isset($frame['args'])) {
90
                    $frame['args'] = $frame['params'];
91
                }
92
                $trace[] = $frame;
93
            }
94
95
            $ref = new ReflectionProperty('Exception', 'trace');
96
            $ref->setAccessible(true);
97
            $ref->setValue($this, $trace);
98
        }
99
    }
100
101
    /**
102
     * Ensures that Xdebug stack trace is available based on Xdebug version.
103
     * Idea taken from developer bishopb at https://github.com/rollbar/rollbar-php
104
     */
105
    private function isXdebugStackAvailable(): bool
106
    {
107
        if (!function_exists('\xdebug_get_function_stack')) {
108
            return false;
109
        }
110
111
        // check for Xdebug being installed to ensure origin of xdebug_get_function_stack()
112
        $version = phpversion('xdebug');
113
114
        if ($version === false) {
115
            return false;
116
        }
117
118
        // Xdebug 2 and prior
119
        if (version_compare($version, '3.0.0', '<')) {
120
            return true;
121
        }
122
123
        // Xdebug 3 and later, proper mode is required
124
        return false !== strpos(ini_get('xdebug.mode'), 'develop');
125
    }
126
127
    public function getSolution(): ?string
128
    {
129
        return null;
130
    }
131
}
132