1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace CentErr; |
6
|
|
|
|
7
|
|
|
use CentErr\Emitter\EmitterInterface; |
8
|
|
|
use ErrorException; |
9
|
|
|
use Throwable; |
10
|
|
|
|
11
|
|
|
class ErrorHandler implements ErrorHandlerInterface |
12
|
|
|
{ |
13
|
|
|
/** |
14
|
|
|
* @var EmitterInterface |
15
|
|
|
*/ |
16
|
|
|
protected $emitter; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* @var callable[] |
20
|
|
|
*/ |
21
|
|
|
protected $processors; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @var array |
25
|
|
|
*/ |
26
|
|
|
protected $options = [ |
27
|
|
|
'blockingErrors' => true, |
28
|
|
|
]; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var bool |
32
|
|
|
*/ |
33
|
|
|
protected $treatErrorsAsExceptions = false; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var bool |
37
|
|
|
*/ |
38
|
|
|
private $registered = false; |
39
|
|
|
|
40
|
11 |
|
public function __construct(EmitterInterface $emitter, array $processors = [], array $options = []) |
41
|
|
|
{ |
42
|
11 |
|
$this->emitter = $emitter; |
43
|
11 |
|
$this->processors = $processors; |
44
|
11 |
|
$this->options = array_merge($this->options, $options); |
45
|
|
|
|
46
|
11 |
|
$this->treatErrorsAsExceptions = $this->options['blockingErrors']; |
47
|
11 |
|
} |
48
|
|
|
|
49
|
6 |
|
final public function register() : void |
50
|
|
|
{ |
51
|
6 |
|
if ($this->registered) { |
52
|
|
|
return; |
53
|
|
|
} |
54
|
|
|
|
55
|
6 |
|
set_error_handler([$this, 'handleError']); |
56
|
6 |
|
set_exception_handler([$this, 'handleException']); |
57
|
6 |
|
register_shutdown_function([$this, 'onShutdown']); |
58
|
|
|
|
59
|
6 |
|
$this->registered = true; |
60
|
6 |
|
} |
61
|
|
|
|
62
|
6 |
|
final public function unregister() : void |
63
|
|
|
{ |
64
|
6 |
|
if (!$this->registered) { |
65
|
|
|
return; |
66
|
|
|
} |
67
|
|
|
|
68
|
6 |
|
restore_error_handler(); |
69
|
6 |
|
restore_exception_handler(); |
70
|
|
|
|
71
|
6 |
|
$this->registered = false; |
72
|
6 |
|
} |
73
|
|
|
|
74
|
2 |
|
final public function isRegistered() : bool |
75
|
|
|
{ |
76
|
2 |
|
return $this->registered; |
77
|
|
|
} |
78
|
|
|
|
79
|
6 |
|
public function handleError(int $level, string $message, string $file = null, int $line = null) : bool |
80
|
|
|
{ |
81
|
6 |
|
if (! ($level & error_reporting())) { |
82
|
3 |
|
return false; |
83
|
|
|
} |
84
|
|
|
|
85
|
3 |
|
$exception = new ErrorException($message, 0, $level, $file, $line); |
86
|
|
|
|
87
|
3 |
|
if ($this->treatErrorsAsExceptions) { |
88
|
2 |
|
throw $exception; |
89
|
|
|
} |
90
|
|
|
|
91
|
1 |
|
$this->handleException($exception); |
92
|
|
|
|
93
|
1 |
|
if ($this->isFatalError($level)) { |
94
|
|
|
$this->terminate(); |
95
|
|
|
} |
96
|
|
|
|
97
|
1 |
|
return true; |
98
|
|
|
} |
99
|
|
|
|
100
|
4 |
|
public function handleException(Throwable $exception) : void |
101
|
|
|
{ |
102
|
4 |
|
$exception = $this->process($exception); |
103
|
|
|
|
104
|
4 |
|
$this->emit($exception); |
105
|
4 |
|
} |
106
|
|
|
|
107
|
|
|
public function onShutdown() : void |
108
|
|
|
{ |
109
|
|
|
$this->treatErrorsAsExceptions = false; |
110
|
|
|
|
111
|
|
|
$error = error_get_last(); |
112
|
|
|
|
113
|
|
|
if ($error && $this->isFatalError($error['type'])) { |
|
|
|
|
114
|
|
|
$this->handleError( |
115
|
|
|
$error['type'], |
116
|
|
|
$error['message'], |
117
|
|
|
$error['file'], |
118
|
|
|
$error['line'] |
119
|
|
|
); |
120
|
|
|
} |
121
|
|
|
} |
122
|
|
|
|
123
|
4 |
|
protected function process(Throwable $exception) : Throwable |
124
|
|
|
{ |
125
|
4 |
|
foreach ($this->processors as $processor) { |
126
|
1 |
|
$exception = $processor($exception); |
127
|
|
|
} |
128
|
|
|
|
129
|
4 |
|
return $exception; |
130
|
|
|
} |
131
|
|
|
|
132
|
4 |
|
protected function emit(Throwable $exception) : void |
133
|
|
|
{ |
134
|
4 |
|
$this->emitter->emit($exception); |
135
|
4 |
|
} |
136
|
|
|
|
137
|
1 |
|
final protected function isFatalError(int $errorLevel) : bool |
138
|
|
|
{ |
139
|
1 |
|
$fatalErrorLevels = E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR; |
140
|
|
|
|
141
|
1 |
|
return ($errorLevel & $fatalErrorLevels) > 0; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
final protected function terminate() : void |
145
|
|
|
{ |
146
|
|
|
exit(1); |
147
|
|
|
} |
148
|
|
|
} |
149
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.