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']); } ); $self = get_called_class(); static::$_exceptionHandler = function($exception, $return = false) use ($self) { if (ob_get_length()) { ob_end_clean(); } $info = compact('exception') + array( 'type' => get_class($exception), 'stack' => $self::trace($exception->getTrace()) ); foreach (array('message', 'file', 'line', 'trace') as $key) { $method = 'get' . ucfirst($key); $info[$key] = $exception->{$method}(); } return $return ? $info : $self::handle($info); }; } /** * Configure the `ErrorHandler`. * * @param array $config Configuration directives. * @return Current configuration set. */ public static function config($config = array()) { return (static::$_config = array_merge($config, static::$_config)); } /** * Register error and exception handlers. * * This method (`ErrorHandler::run()`) needs to be called as early as possible in the bootstrap * cycle; immediately after `require`-ing `bootstrap/libraries.php` is your best bet. * * @param array $config The configuration with which to start the error handler. Available * options include: * - `'trapErrors'` _boolean_: Defaults to `false`. If set to `true`, PHP errors * will be caught by `ErrorHandler` and handled in-place. Execution will resume * in the same context in which the error occurred. * - `'convertErrors'` _boolean_: Defaults to `true`, and specifies that all PHP * errors should be converted to `ErrorException`s and thrown from the point * where the error occurred. The exception will be caught at the first point in * the stack trace inside a matching `try`/`catch` block, or that has a matching * error handler applied using the `apply()` method. * @return void */ public static function run(array $config = array()) { $defaults = array('trapErrors' => false, 'convertErrors' => true); if (static::$_isRunning) { return; } static::$_isRunning = true; static::$_runOptions = $config + $defaults; $self = get_called_class(); $trap = function($code, $message, $file, $line = 0, $context = null) use ($self) { $trace = debug_backtrace(); $trace = array_slice($trace, 1, count($trace)); $self::handle(compact('type', 'code', 'message', 'file', 'line', 'trace', 'context')); }; $convert = function($code, $message, $file, $line = 0, $context = null) use ($self) { 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. * * @return void */ 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()`. * * @return void */ public static function stop() { restore_error_handler(); restore_exception_handler(); static::$_isRunning = false; } /** * Wipes out all configuration and resets the error handler to its initial state when loaded. * Mainly used for testing. * * @return void */ public static function reset() { static::$_config = array(); static::$_checks = array(); static::$_exceptionHandler = null; static::__init(); } /** * 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 = array()) { $checks = static::$_checks; $rules = $scope ?: static::$_config; $handler = static::$_exceptionHandler; $info = is_object($info) ? $handler($info, true) : $info; $defaults = array( 'type' => null, 'code' => 0, 'message' => null, 'file' => null, 'line' => 0, 'trace' => array(), '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 ?: array('type' => 'Exception'); list($class, $method) = is_string($object) ? explode('::', $object) : $object; $wrap = static::$_exceptionHandler; $_self = get_called_class(); $filter = function($self, $params, $chain) use ($_self, $conditions, $handler, $wrap) { try { return $chain->next($self, $params, $chain); } catch (Exception $e) { if (!$_self::matches($e, $conditions)) { throw $e; } return $handler($wrap($e, true), $params); } }; if (is_string($class)) { Filters::apply($class, $method, $filter); } else { $class->applyFilter($method, $filter); } } 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 = array(); foreach ($stack as $frame) { if (isset($frame['function'])) { if (isset($frame['class'])) { $result[] = trim($frame['class'], '\\') . '::' . $frame['function']; } else { $result[] = $frame['function']; } } } return $result; } } ?>