123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- <?php
- /**
- * Lithium: the most rad php framework
- *
- * @copyright Copyright 2009, Union of RAD (http://union-of-rad.org)
- * @license http://opensource.org/licenses/bsd-license.php The BSD License
- */
- namespace lithium\core;
- use Exception;
- use ErrorException;
- use lithium\util\collection\Filters;
- /**
- * The `ErrorHandler` class allows PHP errors and exceptions to be handled in a uniform way. Using
- * the `ErrorHandler`'s configuration, it is possible to have very broad but very tight control
- * over error handling in your application.
- *
- * {{{ embed:lithium\tests\cases\core\ErrorHandlerTest::testExceptionCatching(2-7) }}}
- *
- * Using a series of cascading rules and handlers, it is possible to capture and handle very
- * specific errors and exceptions.
- */
- class ErrorHandler extends \lithium\core\StaticObject {
- /**
- * Configuration parameters.
- *
- * @var array Config params
- */
- protected static $_config = array();
- /**
- * Types of checks available for sorting & parsing errors/exceptions.
- * Default checks are for `code`, `stack` and `message`.
- *
- * @var array Array of checks represented as closures, indexed by name.
- */
- protected static $_checks = array();
- /**
- * Currently registered exception handler.
- *
- * @var closure Closure representing exception handler.
- */
- protected static $_exceptionHandler = null;
- /**
- * State of error/exception handling.
- *
- * @var boolean True if custom error/exception handlers have been registered, false
- * otherwise.
- */
- protected static $_isRunning = false;
- protected static $_runOptions = array();
- /**
- * Setup basic error handling checks/types, as well as register the error and exception
- * handlers.
- *
- * Called on static class initialization (i.e. when first loaded).
- *
- * @return void
- */
- public static function __init() {
- static::$_checks = array(
- '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']);
- }
- );
- $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;
- }
- }
- ?>
|