-
Notifications
You must be signed in to change notification settings - Fork 283
/
Handler.php
247 lines (221 loc) · 7.18 KB
/
Handler.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
<?php
namespace PhpConsole;
/**
* Overrides PHP errors and exceptions handlers, so all errors, exceptions and debug messages will be sent to PHP Console client
* By default all handled errors and exceptions will be passed to previously defined handlers
*
* You will need to install Google Chrome extension "PHP Console"
* https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef
*
* @package PhpConsole
* @version 3.1
* @link http://consle.com
* @author Sergey Barbushin http://linkedin.com/in/barbushin
* @copyright © Sergey Barbushin, 2011-2013. All rights reserved.
* @license http://www.opensource.org/licenses/BSD-3-Clause "The BSD 3-Clause License"
*/
class Handler {
const ERRORS_RECURSION_LIMIT = 3;
/** @var Handler */
protected static $instance;
/** @var Connector */
protected $connector;
protected $isStarted = false;
protected $isHandling = false;
protected $errorsHandlerLevel;
protected $handleErrors = true;
protected $handleExceptions = true;
protected $callOldHandlers = true;
protected $oldErrorsHandler;
protected $oldExceptionsHandler;
protected $recursiveHandlingLevel = 0;
/**
* @return static
*/
public static function getInstance() {
if(!self::$instance) {
self::$instance = new static();
}
return self::$instance;
}
protected function __construct() {
$this->connector = Connector::getInstance();
}
/**
* @codeCoverageIgnore
*/
private final function __clone() {
}
/**
* Start errors & exceptions handlers
* @throws \Exception
*/
public function start() {
if($this->isStarted) {
throw new \Exception(get_called_class() . ' is already started, use ' . get_called_class() . '::getInstance()->isStarted() to check it.');
}
$this->isStarted = true;
if($this->handleErrors) {
$this->initErrorsHandler();
}
if($this->handleExceptions) {
$this->initExceptionsHandler();
}
}
/**
* Validate that method is called before start. Required for handlers configuration methods.
* @throws \Exception
*/
protected function checkIsCalledBeforeStart() {
if($this->isStarted) {
throw new \Exception('This method can be called only before ' . get_class($this) . '::start()');
}
}
/**
* Enable or disable errors handler
* @param bool $isEnabled
*/
public function setHandleErrors($isEnabled) {
$this->checkIsCalledBeforeStart();
$this->handleErrors = $isEnabled; // @codeCoverageIgnore
}
/**
* Enable or disable exceptions handler
* @param bool $isEnabled
*/
public function setHandleExceptions($isEnabled) {
$this->checkIsCalledBeforeStart();
$this->handleExceptions = $isEnabled; // @codeCoverageIgnore
}
/**
* Enable or disable calling overridden errors & exceptions
* @param bool $isEnabled
*/
public function setCallOldHandlers($isEnabled) {
$this->callOldHandlers = $isEnabled;
}
/**
* @return Connector
*/
public function getConnector() {
return $this->connector;
}
/**
* Check if PHP Console handler is started
* @return bool
*/
public function isStarted() {
return $this->isStarted;
}
/**
* Override PHP exceptions handler to PHP Console handler
*/
protected function initExceptionsHandler() {
$this->oldExceptionsHandler = set_exception_handler(array($this, 'handleException'));
}
/**
* Set custom errors handler level like E_ALL ^ E_STRICT
* But, anyway, it's strongly recommended to configure ignore some errors type in PHP Console extension options
* IMPORTANT: previously old error handler will be called only with new errors level
* @param int $level see http://us1.php.net/manual/ru/function.error-reporting.php
*/
public function setErrorsHandlerLevel($level) {
$this->checkIsCalledBeforeStart();
$this->errorsHandlerLevel = $level;
}
/**
* Override PHP errors handler to PHP Console handler
*/
protected function initErrorsHandler() {
ini_set('display_errors', false);
error_reporting($this->errorsHandlerLevel ? : E_ALL | E_STRICT);
$this->oldErrorsHandler = set_error_handler(array($this, 'handleError'));
register_shutdown_function(array($this, 'checkFatalErrorOnShutDown'));
$this->connector->registerFlushOnShutDown();
}
/**
* Method is called by register_shutdown_function(), it's required to handle fatal PHP errors. Never call it manually.
* @codeCoverageIgnore
*/
public function checkFatalErrorOnShutDown() {
$error = error_get_last();
if($error) {
ini_set('memory_limit', memory_get_usage(true) + 1000000); // if memory limit exceeded
$this->callOldHandlers = false;
$this->handleError($error['type'], $error['message'], $error['file'], $error['line'], null, 1);
}
}
/**
* Handle error data
* @param int|null $code
* @param string|null $text
* @param string|null $file
* @param int|null $line
* @param null $context
* @param int|array $ignoreTraceCalls Ignore tracing classes by name prefix `array('PhpConsole')` or fixed number of calls to ignore
*/
public function handleError($code = null, $text = null, $file = null, $line = null, $context = null, $ignoreTraceCalls = 0) {
if(!$this->isStarted || error_reporting() === 0 || $this->isHandlingDisabled() || ($this->errorsHandlerLevel && !($code & $this->errorsHandlerLevel))) {
return;
}
$this->onHandlingStart();
$this->connector->getErrorsDispatcher()->dispatchError($code, $text, $file, $line, is_numeric($ignoreTraceCalls) ? $ignoreTraceCalls + 1 : $ignoreTraceCalls);
if($this->oldErrorsHandler && $this->callOldHandlers) {
call_user_func_array($this->oldErrorsHandler, array($code, $text, $file, $line, $context));
}
$this->onHandlingComplete();
}
/**
* Method is called before handling any error or exception
*/
protected function onHandlingStart() {
$this->recursiveHandlingLevel++;
}
/**
* Method is called after handling any error or exception
*/
protected function onHandlingComplete() {
$this->recursiveHandlingLevel--;
}
/**
* Check if errors/exception handling is disabled
* @return bool
*/
protected function isHandlingDisabled() {
return $this->recursiveHandlingLevel >= static::ERRORS_RECURSION_LIMIT;
}
/**
* Handle exception object
* @param \Exception|\Throwable $exception
*/
public function handleException($exception) {
if(!$this->isStarted || $this->isHandlingDisabled()) {
return;
}
try {
$this->onHandlingStart();
$this->connector->getErrorsDispatcher()->dispatchException($exception);
if($this->oldExceptionsHandler && $this->callOldHandlers) {
call_user_func($this->oldExceptionsHandler, $exception);
}
}
catch(\Throwable $internalException) {
$this->handleException($internalException);
}
catch(\Exception $internalException) {
$this->handleException($internalException);
}
$this->onHandlingComplete();
}
/**
* Handle debug data
* @param mixed $data
* @param string|null $tags Tags separated by dot, e.g. "low.db.billing"
* @param int|array $ignoreTraceCalls Ignore tracing classes by name prefix `array('PhpConsole')` or fixed number of calls to ignore
*/
public function debug($data, $tags = null, $ignoreTraceCalls = 0) {
if($this->connector->isActiveClient()) {
$this->connector->getDebugDispatcher()->dispatchDebug($data, $tags, is_numeric($ignoreTraceCalls) ? $ignoreTraceCalls + 1 : $ignoreTraceCalls);
}
}
}