false, 'convertErrors' => true]; if (static::$_isRunning) { return; } static::$_isRunning = true; static::$_runOptions = $config + $defaults; $trap = function($code, $message, $file, $line = 0, $context = null) { $trace = debug_backtrace(); $trace = array_slice($trace, 1, count($trace)); static::handle(compact('code', 'message', 'file', 'line', 'trace', 'context')); }; $convert = function($code, $message, $file, $line = 0, $context = null) { throw new ErrorException($message, 500, $code, $file, $line); }; if (static::$_runOptions['trapErrors']) { set_error_handler($trap); } elseif (static::$_runOptions['convertErrors']) { set_error_handler($convert); } set_exception_handler(static::$_exceptionHandler); } /** * Returns the state of the `ErrorHandler`, indicating whether or not custom error/exception * handers have been regsitered. */ public static function isRunning() { return static::$_isRunning; } /** * Unooks `ErrorHandler`'s exception and error handlers, and restores PHP's defaults. May have * unexpected results if it is not matched with a prior call to `run()`, or if other error * handlers are set after a call to `run()`. */ public static function stop() { restore_error_handler(); restore_exception_handler(); static::$_isRunning = false; } /** * Setup basic error handling checks/types, as well as register the error and exception * handlers and wipes out all configuration and resets the error handler to its initial state * when loaded. Mainly used for testing. */ public static function reset() { static::$_config = []; static::$_checks = []; static::$_exceptionHandler = null; static::$_checks = [ 'type' => function($config, $info) { return (boolean) array_filter((array) $config['type'], function($type) use ($info) { return $type === $info['type'] || is_subclass_of($info['type'], $type); }); }, 'code' => function($config, $info) { return ($config['code'] & $info['code']); }, 'stack' => function($config, $info) { return (boolean) array_intersect((array) $config['stack'], $info['stack']); }, 'message' => function($config, $info) { return preg_match($config['message'], $info['message']); } ]; static::$_exceptionHandler = function($exception, $return = false) { if (ob_get_length()) { ob_end_clean(); } $info = compact('exception') + [ 'type' => get_class($exception), 'stack' => static::trace($exception->getTrace()) ]; foreach (['message', 'file', 'line', 'trace'] as $key) { $method = 'get' . ucfirst($key); $info[$key] = $exception->{$method}(); } return $return ? $info : static::handle($info); }; } /** * Receives the handled errors and exceptions that have been caught, and processes them * in a normalized manner. * * @param object|array $info * @param array $scope * @return boolean True if successfully handled, false otherwise. */ public static function handle($info, $scope = []) { $checks = static::$_checks; $rules = $scope ?: static::$_config; $handler = static::$_exceptionHandler; $info = is_object($info) ? $handler($info, true) : $info; $defaults = [ 'type' => null, 'code' => 0, 'message' => null, 'file' => null, 'line' => 0, 'trace' => [], 'context' => null, 'exception' => null ]; $info = (array) $info + $defaults; $info['stack'] = static::trace($info['trace']); $info['origin'] = static::_origin($info['trace']); foreach ($rules as $config) { foreach (array_keys($config) as $key) { if ($key === 'conditions' || $key === 'scope' || $key === 'handler') { continue; } if (!isset($info[$key]) || !isset($checks[$key])) { continue 2; } if (($check = $checks[$key]) && !$check($config, $info)) { continue 2; } } if (!isset($config['handler'])) { return false; } if ((isset($config['conditions']) && $call = $config['conditions']) && !$call($info)) { return false; } if ((isset($config['scope'])) && static::handle($info, $config['scope']) !== false) { return true; } $handler = $config['handler']; return $handler($info) !== false; } return false; } /** * Determine frame from the stack trace where the error/exception was first generated. * * @param array $stack Stack trace from error/exception that was produced. * @return string Class where error/exception was generated. */ protected static function _origin(array $stack) { foreach ($stack as $frame) { if (isset($frame['class'])) { return trim($frame['class'], '\\'); } } } public static function apply($object, array $conditions, $handler) { $conditions = $conditions ?: ['type' => 'Exception']; list($class, $method) = is_string($object) ? explode('::', $object) : $object; Filters::apply($class, $method, function($params, $next) use ($conditions, $handler) { $wrap = static::$_exceptionHandler; try { return $next($params); } catch (Exception $e) { if (!static::matches($e, $conditions)) { throw $e; } return $handler($wrap($e, true), $params); } }); } public static function matches($info, $conditions) { $checks = static::$_checks; $handler = static::$_exceptionHandler; $info = is_object($info) ? $handler($info, true) : $info; foreach (array_keys($conditions) as $key) { if ($key === 'conditions' || $key === 'scope' || $key === 'handler') { continue; } if (!isset($info[$key]) || !isset($checks[$key])) { return false; } if (($check = $checks[$key]) && !$check($conditions, $info)) { return false; } } if ((isset($config['conditions']) && $call = $config['conditions']) && !$call($info)) { return false; } return true; } /** * Trim down a typical stack trace to class & method calls. * * @param array $stack A `debug_backtrace()`-compatible stack trace output. * @return array Returns a flat stack array containing class and method references. */ public static function trace(array $stack) { $result = []; foreach ($stack as $frame) { if (isset($frame['function'])) { if (isset($frame['class'])) { $result[] = trim($frame['class'], '\\') . '::' . $frame['function']; } else { $result[] = $frame['function']; } } } return $result; } /** * Exit immediately. Primarily used for overrides during testing. * * @param integer|string $status integer range 0 to 254, string printed on exit * @return void */ protected static function _stop($status = 0) { exit($status); } } ErrorHandler::reset(); ?>