Overview

Namespaces

  • GAubry
    • ErrorHandler
    • Helpers
    • Logger
    • Shell
  • Himedia
    • Padocc
      • DB
      • Minifier
      • Numbering
      • Properties
      • Task
        • Base
        • Extended
  • None
  • Psr
    • Log

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:      *   – 'error_div_class'       => (string) CSS class for <DIV> tags surrounding errors displayed
 85:      *                                in HTML context (non-CLI).
 86:      * @var array
 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:      * Configuration.
 99:      * @var array
100:      * @see self::$aDefaultConfig
101:      */
102:     private $aConfig;
103: 
104:     /**
105:      * Constructor.
106:      *
107:      * @param array $aConfig see self::$aDefaultConfig
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');    // or (PHP_SAPI === 'cli')
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:         // Make sure we have a timezone for date functions. It is not safe to rely on the system's timezone settings.
132:         // Please use the date.timezone setting, the TZ environment variable
133:         // or the date_default_timezone_set() function.
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:      * Allows to ignore errors on some paths, useful with old libraries and deprecated code…
145:      * Trailing slash is optional.
146:      *
147:      * @param string $sPath
148:      * @see internalErrorHandler()
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:      * Set callback to display an apology when errors are hidden.
163:      * Current \Exception will be provided in parameter.
164:      *
165:      * @param callback $cbGenericDisplay
166:      */
167:     public function setCallbackGenericDisplay ($cbGenericDisplay)
168:     {
169:         $this->callbackGenericDisplay = $cbGenericDisplay;
170:     }
171: 
172:     /**
173:      * Set callback to be executed at the end of the internal shutdown function.
174:      *
175:      * @param callback $cbAddShutdownFct
176:      */
177:     public function setCallbackAdditionalShutdownFct ($cbAddShutdownFct)
178:     {
179:         $this->callbackAdditionalShutdownFct = $cbAddShutdownFct;
180:     }
181: 
182:     /**
183:      * Customized error handler function: throws an Exception with the message error if @ operator not used
184:      * and error source is not in excluded paths.
185:      *
186:      * @param int $iErrNo level of the error raised.
187:      * @param string $sErrStr the error message.
188:      * @param string $sErrFile the filename that the error was raised in.
189:      * @param int $iErrLine the line number the error was raised at.
190:      * @throws \ErrorException if $iErrNo is present in $iErrorReporting
191:      * @return boolean true, then the normal error handler does not continue.
192:      * @see addExcludedPath()
193:      */
194:     public function internalErrorHandler ($iErrNo, $sErrStr, $sErrFile, $iErrLine)
195:     {
196:         // Si l'erreur provient d'un répertoire exclu de ce handler, alors l'ignorer.
197:         foreach ($this->aExcludedPaths as $sExcludedPath) {
198:             if (stripos($sErrFile, $sExcludedPath) === 0) {
199:                 return true;
200:             }
201:         }
202: 
203:         // Gestion de l'éventuel @ (error suppression operator) :
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:         // Le seuil de transformation en exception est-il atteint ?
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:      * Exception handler.
223:      * @SuppressWarnings(ExitExpression)
224:      *
225:      * @param \Exception $oException
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:      * Default callback to display an apology when errors are hidden.
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:      * Registered shutdown function.
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:             // @codeCoverageIgnoreStart
269:         }
270:     }
271:     // @codeCoverageIgnoreEnd
272: 
273:     /**
274:      * According to context, logs specified error into STDERR, STDOUT or via error_log().
275:      *
276:      * @param mixed $mError Error to log. Can be string, array or object.
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: 
Platform for Automatized Deployments with pOwerful Concise Configuration API documentation generated by ApiGen 2.8.0