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: private static $aDefaultConfig = array(
87: 'display_errors' => true,
88: 'error_log_path' => '',
89: 'error_reporting_level' => -1,
90: 'auth_error_suppr_op' => false,
91: 'default_error_code' => 1
92: );
93:
94: 95: 96: 97: 98:
99: private $aConfig;
100:
101: 102: 103: 104: 105:
106: public function __construct (array $aConfig = array())
107: {
108: $this->aConfig = Helpers::arrayMergeRecursiveDistinct(self::$aDefaultConfig, $aConfig);
109: $this->aExcludedPaths = array();
110: $this->bIsRunningFromCLI = defined('STDIN');
111: $this->callbackGenericDisplay = array($this, 'displayDefaultApologies');
112: $this->callbackAdditionalShutdownFct = '';
113:
114: error_reporting($this->aConfig['error_reporting_level']);
115: if ($this->aConfig['display_errors'] && $this->bIsRunningFromCLI) {
116: ini_set('display_errors', 'stderr');
117: } else {
118: ini_set('display_errors', $this->aConfig['display_errors']);
119: }
120: ini_set('log_errors', true);
121: ini_set('html_errors', false);
122: ini_set('display_startup_errors', true);
123: if (! empty($this->aConfig['error_log_path'])) {
124: ini_set('error_log', $this->aConfig['error_log_path']);
125: }
126: ini_set('ignore_repeated_errors', true);
127:
128:
129:
130:
131: if (ini_get('date.timezone') == '') {
132: date_default_timezone_set('Europe/Paris');
133: }
134:
135: set_error_handler(array($this, 'internalErrorHandler'));
136: set_exception_handler(array($this, 'internalExceptionHandler'));
137: register_shutdown_function(array($this, 'internalShutdownFunction'));
138: }
139:
140: 141: 142: 143: 144: 145: 146:
147: public function addExcludedPath ($sPath)
148: {
149: if (substr($sPath, -1) !== '/') {
150: $sPath .= '/';
151: }
152: $sPath = realpath($sPath);
153: if (! in_array($sPath, $this->aExcludedPaths)) {
154: $this->aExcludedPaths[] = $sPath;
155: }
156: }
157:
158: 159: 160: 161: 162: 163:
164: public function setCallbackGenericDisplay ($cbGenericDisplay)
165: {
166: $this->callbackGenericDisplay = $cbGenericDisplay;
167: }
168:
169: 170: 171: 172: 173:
174: public function setCallbackAdditionalShutdownFct ($cbAddShutdownFct)
175: {
176: $this->callbackAdditionalShutdownFct = $cbAddShutdownFct;
177: }
178:
179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189:
190: public function internalErrorHandler ($iErrNo, $sErrStr, $sErrFile, $iErrLine)
191: {
192:
193: foreach ($this->aExcludedPaths as $sExcludedPath) {
194: if (stripos($sErrFile, $sExcludedPath) === 0) {
195: return true;
196: }
197: }
198:
199:
200: if ($this->aConfig['error_reporting_level'] !== 0
201: && error_reporting() === 0 && $this->aConfig['auth_error_suppr_op']
202: ) {
203: $iErrorReporting = 0;
204: } else {
205: $iErrorReporting = $this->aConfig['error_reporting_level'];
206: }
207:
208:
209: if (($iErrorReporting & $iErrNo) !== 0) {
210: $msg = "[from error handler] " . self::$aErrorTypes[$iErrNo]
211: . " -- $sErrStr, in file: '$sErrFile', line $iErrLine";
212: throw new \ErrorException($msg, $this->aConfig['default_error_code'], $iErrNo, $sErrFile, $iErrLine);
213: }
214: return true;
215: }
216:
217: 218: 219: 220: 221: 222:
223: public function internalExceptionHandler (\Exception $oException)
224: {
225: if (! $this->aConfig['display_errors'] && ini_get('error_log') !== '' && ! $this->bIsRunningFromCLI) {
226: call_user_func($this->callbackGenericDisplay, $oException);
227: }
228: $this->log($oException);
229: if ($oException->getCode() != 0) {
230: $iErrorCode = $oException->getCode();
231: } else {
232: $iErrorCode = $this->aConfig['default_error_code'];
233: }
234: exit($iErrorCode);
235: }
236:
237: 238: 239:
240: public function displayDefaultApologies ()
241: {
242: echo '<div class="exception-handler-message">We are sorry, an internal error occurred.<br />'
243: . 'We apologize for any inconvenience this may cause</div>';
244: }
245:
246: 247: 248:
249: public function internalShutdownFunction ()
250: {
251: $aError = error_get_last();
252: if (! $this->aConfig['display_errors'] && is_array($aError) && $aError['type'] === E_ERROR) {
253: $oException = new \ErrorException(
254: $aError['message'],
255: $this->aConfig['default_error_code'],
256: $aError['type'],
257: $aError['file'],
258: $aError['line']
259: );
260: call_user_func($this->callbackGenericDisplay, $oException);
261: }
262: if (! empty($this->callbackAdditionalShutdownFct)) {
263: call_user_func($this->callbackAdditionalShutdownFct);
264:
265: }
266: }
267:
268:
269: 270: 271: 272: 273:
274: public function log ($mError)
275: {
276: if (is_array($mError) || (is_object($mError) && ! ($mError instanceof \Exception))) {
277: $mError = print_r($mError, true);
278: }
279:
280: if ($this->aConfig['display_errors']) {
281: if ($this->bIsRunningFromCLI) {
282: file_put_contents('php://stderr', $mError . "\n", E_USER_ERROR);
283: } else {
284: echo $mError;
285: }
286: }
287:
288: if (! empty($this->aConfig['error_log_path'])) {
289: error_log($mError);
290: }
291: }
292: }
293: