Overview

Namespaces

  • GAubry
    • ErrorHandler
    • Helpers

Classes

  • ErrorHandler
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Todo
  1: <?php
  2: 
  3: namespace GAubry\ErrorHandler;
  4: 
  5: use GAubry\Helpers\Helpers;
  6: 
  7: /**
  8:  * Simple error and exception handler.
  9:  *   – wraps the error to an ErrorException instance according to error reporting level
 10:  *   – when running the PHP CLI, reports errors/exceptions to STDERR (even fatal error)
 11:  *     and uses exception code as exit status
 12:  *   – allows to deactivate '@' operator
 13:  *   – catches fatal error
 14:  *   – accepts callback to be executed at the end of the internal shutdown function
 15:  *   – accepts callback to display an apology when errors are hidden
 16:  *   – allows to ignore errors on some paths, useful with old libraries and deprecated code…
 17:  *
 18:  * Copyright (c) 2012 Geoffroy Aubry <geoffroy.aubry@free.fr>
 19:  * Licensed under the GNU Lesser General Public License v3 (LGPL version 3).
 20:  *
 21:  * @copyright 2012 Geoffroy Aubry <geoffroy.aubry@free.fr>
 22:  * @license http://www.gnu.org/licenses/lgpl.html
 23:  */
 24: class ErrorHandler
 25: {
 26: 
 27:     /**
 28:      * Error codes.
 29:      * @var array
 30:      * @see internalErrorHandler()
 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:      * CLI ?
 50:      * @var bool
 51:      */
 52:     private $bIsRunningFromCLI;
 53: 
 54:     /**
 55:      * Errors will be ignored on these paths.
 56:      * Useful with old libraries and deprecated code.
 57:      *
 58:      * @var array
 59:      * @see addExcludedPath()
 60:      */
 61:     private $aExcludedPaths;
 62: 
 63:     /**
 64:      * Callback to display an apology when errors are hidden.
 65:      * @var callback
 66:      */
 67:     private $callbackGenericDisplay;
 68: 
 69:     /**
 70:      * Callback to be executed at the end of the internal shutdown function
 71:      * @var callback
 72:      */
 73:     private $callbackAdditionalShutdownFct;
 74: 
 75:     /**
 76:      * Default config.
 77:      *   – 'display_errors'        => (bool) Determines whether errors should be printed to the screen
 78:      *                                as part of the output or if they should be hidden from the user.
 79:      *   – 'error_log_path'        => (string) Name of the file where script errors should be logged.
 80:      *   – 'error_reporting_level' => (int) Error reporting level.
 81:      *   – 'auth_error_suppr_op'   => (bool) Allows to deactivate '@' operator.
 82:      *   – 'default_error_code'    => (int) Default error code for errors converted into exceptions
 83:      *                                or for exceptions without code.
 84:      * @var array
 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:      * Configuration.
 96:      * @var array
 97:      * @see self::$aDefaultConfig
 98:      */
 99:     private $aConfig;
100: 
101:     /**
102:      * Constructor.
103:      *
104:      * @param array $aConfig see self::$aDefaultConfig
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');    // or (PHP_SAPI === 'cli')
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:         // Make sure we have a timezone for date functions. It is not safe to rely on the system's timezone settings.
129:         // Please use the date.timezone setting, the TZ environment variable
130:         // or the date_default_timezone_set() function.
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:      * Allows to ignore errors on some paths, useful with old libraries and deprecated code…
142:      * Trailing slash is optional.
143:      *
144:      * @param string $sPath
145:      * @see internalErrorHandler()
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:      * Set callback to display an apology when errors are hidden.
160:      * Current \Exception will be provided in parameter.
161:      *
162:      * @param callback $cbGenericDisplay
163:      */
164:     public function setCallbackGenericDisplay ($cbGenericDisplay)
165:     {
166:         $this->callbackGenericDisplay = $cbGenericDisplay;
167:     }
168: 
169:     /**
170:      * Set callback to be executed at the end of the internal shutdown function.
171:      *
172:      * @param callback $cbAddShutdownFct
173:      */
174:     public function setCallbackAdditionalShutdownFct ($cbAddShutdownFct)
175:     {
176:         $this->callbackAdditionalShutdownFct = $cbAddShutdownFct;
177:     }
178: 
179:     /**
180:      * Customized error handler function: throws an Exception with the message error if @ operator not used
181:      * and error source is not in excluded paths.
182:      *
183:      * @param int $iErrNo level of the error raised.
184:      * @param string $sErrStr the error message.
185:      * @param string $sErrFile the filename that the error was raised in.
186:      * @param int $iErrLine the line number the error was raised at.
187:      * @return boolean true, then the normal error handler does not continues.
188:      * @see addExcludedPath()
189:      */
190:     public function internalErrorHandler ($iErrNo, $sErrStr, $sErrFile, $iErrLine)
191:     {
192:         // Si l'erreur provient d'un répertoire exclu de ce handler, alors l'ignorer.
193:         foreach ($this->aExcludedPaths as $sExcludedPath) {
194:             if (stripos($sErrFile, $sExcludedPath) === 0) {
195:                 return true;
196:             }
197:         }
198: 
199:         // Gestion de l'éventuel @ (error suppression operator) :
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:         // Le seuil de transformation en exception est-il atteint ?
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:      * Exception handler.
219:      * @SuppressWarnings(ExitExpression)
220:      *
221:      * @param \Exception $oException
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:      * Default callback to display an apology when errors are hidden.
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:      * Registered shutdown function.
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:             // @codeCoverageIgnoreStart
265:         }
266:     }
267:     // @codeCoverageIgnoreEnd
268: 
269:     /**
270:      * According to context, logs specified error into STDERR, STDOUT or via error_log().
271:      *
272:      * @param mixed $mError Error to log. Can be string, array or object.
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: 
ErrorHandler API documentation generated by ApiGen 2.8.0