You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
312 lines
9.5 KiB
312 lines
9.5 KiB
<?php
|
|
/**
|
|
* li₃: the most RAD framework for PHP (http://li3.me)
|
|
*
|
|
* Copyright 2010, Union of RAD. All rights reserved. This source
|
|
* code is distributed under the terms of the BSD 3-Clause License.
|
|
* The full license text can be found in the LICENSE.txt file.
|
|
*/
|
|
|
|
namespace lithium\core;
|
|
|
|
use Exception;
|
|
use ErrorException;
|
|
use lithium\aop\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\StaticObjectDeprecated {
|
|
|
|
/**
|
|
* Configuration parameters.
|
|
*
|
|
* @var array Config params
|
|
*/
|
|
protected static $_config = [];
|
|
|
|
/**
|
|
* 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 = [];
|
|
|
|
/**
|
|
* 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 = [];
|
|
|
|
/**
|
|
* Configure the `ErrorHandler`.
|
|
*
|
|
* @param array $config Configuration directives.
|
|
* @return Current configuration set.
|
|
*/
|
|
public static function config($config = []) {
|
|
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.
|
|
*/
|
|
public static function run(array $config = []) {
|
|
$defaults = ['trapErrors' => 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();
|
|
|
|
?>
|