1: <?php
2:
3: namespace GAubry\ErrorHandler;
4:
5: use GAubry\Helpers\Helpers;
6:
7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
24: class ErrorHandler
25: {
26:
27: 28: 29: 30: 31:
32: public static $aErrorTypes = array(
33: E_ERROR => 'ERROR',
34: E_WARNING => 'WARNING',
35: E_PARSE => 'PARSING ERROR',
36: E_NOTICE => 'NOTICE',
37: E_CORE_ERROR => 'CORE ERROR',
38: E_CORE_WARNING => 'CORE WARNING',
39: E_COMPILE_ERROR => 'COMPILE ERROR',
40: E_COMPILE_WARNING => 'COMPILE WARNING',
41: E_USER_ERROR => 'USER ERROR',
42: E_USER_WARNING => 'USER WARNING',
43: E_USER_NOTICE => 'USER NOTICE',
44: E_STRICT => 'STRICT NOTICE',
45: E_RECOVERABLE_ERROR => 'RECOVERABLE ERROR'
46: );
47:
48: 49: 50: 51:
52: private $bIsRunningFromCLI;
53:
54: 55: 56: 57: 58: 59: 60:
61: private $aExcludedPaths;
62:
63: 64: 65: 66:
67: private $callbackGenericDisplay;
68:
69: 70: 71: 72:
73: private $callbackAdditionalShutdownFct;
74:
75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87:
88: private static $aDefaultConfig = array(
89: 'display_errors' => true,
90: 'error_log_path' => '',
91: 'error_reporting_level' => -1,
92: 'auth_error_suppr_op' => false,
93: 'default_error_code' => 1,
94: 'error_div_class' => 'error'
95: );
96:
97: 98: 99: 100: 101:
102: private $aConfig;
103:
104: 105: 106: 107: 108:
109: public function __construct (array $aConfig = array())
110: {
111: $this->aConfig = Helpers::arrayMergeRecursiveDistinct(self::$aDefaultConfig, $aConfig);
112: $this->aExcludedPaths = array();
113: $this->bIsRunningFromCLI = defined('STDIN');
114: $this->callbackGenericDisplay = array($this, 'displayDefaultApologies');
115: $this->callbackAdditionalShutdownFct = '';
116:
117: error_reporting($this->aConfig['error_reporting_level']);
118: if ($this->aConfig['display_errors'] && $this->bIsRunningFromCLI) {
119: ini_set('display_errors', 'stderr');
120: } else {
121: ini_set('display_errors', $this->aConfig['display_errors']);
122: }
123: ini_set('log_errors', true);
124: ini_set('html_errors', false);
125: ini_set('display_startup_errors', true);
126: if (! empty($this->aConfig['error_log_path'])) {
127: ini_set('error_log', $this->aConfig['error_log_path']);
128: }
129: ini_set('ignore_repeated_errors', true);
130:
131:
132:
133:
134: if (ini_get('date.timezone') == '') {
135: date_default_timezone_set('Europe/Paris');
136: }
137:
138: set_error_handler(array($this, 'internalErrorHandler'));
139: set_exception_handler(array($this, 'internalExceptionHandler'));
140: register_shutdown_function(array($this, 'internalShutdownFunction'));
141: }
142:
143: 144: 145: 146: 147: 148: 149:
150: public function addExcludedPath ($sPath)
151: {
152: if (substr($sPath, -1) !== '/') {
153: $sPath .= '/';
154: }
155: $sPath = realpath($sPath);
156: if (! in_array($sPath, $this->aExcludedPaths)) {
157: $this->aExcludedPaths[] = $sPath;
158: }
159: }
160:
161: 162: 163: 164: 165: 166:
167: public function setCallbackGenericDisplay ($cbGenericDisplay)
168: {
169: $this->callbackGenericDisplay = $cbGenericDisplay;
170: }
171:
172: 173: 174: 175: 176:
177: public function setCallbackAdditionalShutdownFct ($cbAddShutdownFct)
178: {
179: $this->callbackAdditionalShutdownFct = $cbAddShutdownFct;
180: }
181:
182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193:
194: public function internalErrorHandler ($iErrNo, $sErrStr, $sErrFile, $iErrLine)
195: {
196:
197: foreach ($this->aExcludedPaths as $sExcludedPath) {
198: if (stripos($sErrFile, $sExcludedPath) === 0) {
199: return true;
200: }
201: }
202:
203:
204: if ($this->aConfig['error_reporting_level'] !== 0
205: && error_reporting() === 0 && $this->aConfig['auth_error_suppr_op']
206: ) {
207: $iErrorReporting = 0;
208: } else {
209: $iErrorReporting = $this->aConfig['error_reporting_level'];
210: }
211:
212:
213: if (($iErrorReporting & $iErrNo) !== 0) {
214: $msg = "[from error handler] " . self::$aErrorTypes[$iErrNo]
215: . " -- $sErrStr, in file: '$sErrFile', line $iErrLine";
216: throw new \ErrorException($msg, $this->aConfig['default_error_code'], $iErrNo, $sErrFile, $iErrLine);
217: }
218: return true;
219: }
220:
221: 222: 223: 224: 225: 226:
227: public function internalExceptionHandler (\Exception $oException)
228: {
229: if (! $this->aConfig['display_errors'] && ini_get('error_log') !== '' && ! $this->bIsRunningFromCLI) {
230: call_user_func($this->callbackGenericDisplay, $oException);
231: }
232: $this->log($oException);
233: if ($oException->getCode() != 0) {
234: $iErrorCode = $oException->getCode();
235: } else {
236: $iErrorCode = $this->aConfig['default_error_code'];
237: }
238: exit($iErrorCode);
239: }
240:
241: 242: 243:
244: public function displayDefaultApologies ()
245: {
246: echo '<div class="exception-handler-message">We are sorry, an internal error occurred.<br />'
247: . 'We apologize for any inconvenience this may cause</div>';
248: }
249:
250: 251: 252:
253: public function internalShutdownFunction ()
254: {
255: $aError = error_get_last();
256: if (! $this->aConfig['display_errors'] && is_array($aError) && $aError['type'] === E_ERROR) {
257: $oException = new \ErrorException(
258: $aError['message'],
259: $this->aConfig['default_error_code'],
260: $aError['type'],
261: $aError['file'],
262: $aError['line']
263: );
264: call_user_func($this->callbackGenericDisplay, $oException);
265: }
266: if (! empty($this->callbackAdditionalShutdownFct)) {
267: call_user_func($this->callbackAdditionalShutdownFct);
268:
269: }
270: }
271:
272:
273: 274: 275: 276: 277:
278: public function log ($mError)
279: {
280: if (is_array($mError) || (is_object($mError) && ! ($mError instanceof \Exception))) {
281: $mError = print_r($mError, true);
282: }
283:
284: if ($this->aConfig['display_errors']) {
285: if ($this->bIsRunningFromCLI) {
286: file_put_contents('php://stderr', $mError . "\n", E_USER_ERROR);
287: } else {
288: echo '<div class="' . $this->aConfig['error_div_class'] . '">' . $mError . '</div>';
289: }
290: }
291:
292: if (! empty($this->aConfig['error_log_path'])) {
293: error_log($mError);
294: }
295: }
296: }
297: