在base.php文件中,用一句代碼\think\Error::register();實(shí)現(xiàn)錯(cuò)誤和異常處理機(jī)制的注冊(cè)健提。
// 注冊(cè)錯(cuò)誤和異常處理機(jī)制
\think\Error::register();
thinkphp\library\think\Error.php
namespace think;
use think\console\Output as ConsoleOutput;
use think\exception\ErrorException;
use think\exception\Handle;
use think\exception\ThrowableError;
class Error
{
/**
* 注冊(cè)異常處理
* @access public
* @return void
*/
public static function register()
{
error_reporting(E_ALL);
/*
指定appError來(lái)處理系統(tǒng)錯(cuò)誤扎狱,捕獲錯(cuò)誤后把錯(cuò)誤以異常的形式拋出侧到。
當(dāng)程序出現(xiàn)錯(cuò)誤的時(shí)候自動(dòng)調(diào)用appError函數(shù),
該函數(shù)實(shí)例化了一個(gè)PHP自帶的錯(cuò)誤異常類ErrorException淤击,
如果符合異常處理的匠抗,就將錯(cuò)誤信息以異常的形式拋出來(lái),否則將錯(cuò)誤信息寫入日志中
*/
set_error_handler([__CLASS__, 'appError']);
/*
指定appException來(lái)處理用戶拋出的異常污抬,
如果不是異常汞贸,就實(shí)例化ThrowableError類,將異常包裝起來(lái)并拋出
*/
set_exception_handler([__CLASS__, 'appException']);
/*
指定appShutdown處理超時(shí)異常。
注冊(cè)一個(gè)會(huì)在php中止時(shí)執(zhí)行的函數(shù) 注冊(cè)一個(gè)callback矢腻,它會(huì)在腳本執(zhí)行完成或者exit()后被調(diào)用门驾。
*/
register_shutdown_function([__CLASS__, 'appShutdown']);
}
/**
* 異常處理
* @access public
* @param \Exception|\Throwable $e 異常
* @return void
*/
public static function appException($e)
{
if (!$e instanceof \Exception) {
$e = new ThrowableError($e);
}
//獲取handle類默認(rèn)handle類,你也可以在配置件文件的exception_handle項(xiàng)設(shè)置自己的handle類
$handler = self::getExceptionHandler();
$handler->report($e);
//將錯(cuò)誤記錄到日志
if (IS_CLI) {
$handler->renderForConsole(new ConsoleOutput, $e);//輸出錯(cuò)誤日志到cli窗口
} else {
$handler->render($e)->send();//輸出錯(cuò)誤日志到頁(yè)面
}
}
/**
* 錯(cuò)誤處理
* @access public
* @param integer $errno 錯(cuò)誤編號(hào)
* @param integer $errstr 詳細(xì)錯(cuò)誤信息
* @param string $errfile 出錯(cuò)的文件
* @param integer $errline 出錯(cuò)行號(hào)
* @return void
* @throws ErrorException
*/
public static function appError($errno, $errstr, $errfile = '', $errline = 0)
{
$exception = new ErrorException($errno, $errstr, $errfile, $errline);
// 符合異常處理的則將錯(cuò)誤信息托管至 think\exception\ErrorException
if (error_reporting() & $errno) {
throw $exception;
}
self::getExceptionHandler()->report($exception);
}
/**
* 異常中止處理
* @access public
* @return void
*/
public static function appShutdown()
{
// 將錯(cuò)誤信息托管至 think\ErrorException
if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) {
self::appException(new ErrorException(
$error['type'], $error['message'], $error['file'], $error['line']
));
}
// 寫入日志
Log::save();
}
/**
* 確定錯(cuò)誤類型是否致命
* @access protected
* @param int $type 錯(cuò)誤類型
* @return bool
*/
protected static function isFatal($type)
{
return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]);
}
/**
* 獲取異常處理的實(shí)例
* @access public
* @return Handle
*/
public static function getExceptionHandler()
{
static $handle;
if (!$handle) {
// 異常處理 handle
$class = Config::get('exception_handle');
if ($class && is_string($class) && class_exists($class) &&
is_subclass_of($class, "\\think\\exception\\Handle")
) {
$handle = new $class;
} else {
$handle = new Handle;
if ($class instanceof \Closure) {
$handle->setRender($class);
}
}
}
return $handle;
}
}
thinkphp\library\think\exception\Handle.php
namespace think\exception;
use Exception;
use think\App;
use think\Config;
use think\console\Output;
use think\Lang;
use think\Log;
use think\Response;
class Handle
{
protected $render;
protected $ignoreReport = [
'\\think\\exception\\HttpException',
];
public function setRender($render)
{
$this->render = $render;
}
/**
* Report or log an exception.
*
* @param \Exception $exception
* @return void
*/
public function report(Exception $exception)
{
if (!$this->isIgnoreReport($exception)) {
// 收集異常數(shù)據(jù)
if (App::$debug) {
$data = [
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'message' => $this->getMessage($exception),
'code' => $this->getCode($exception),
];
$log = "[{$data['code']}]{$data['message']}[{$data['file']}:{$data['line']}]";
} else {
$data = [
'code' => $this->getCode($exception),
'message' => $this->getMessage($exception),
];
$log = "[{$data['code']}]{$data['message']}";
}
if (Config::get('record_trace')) {
$log .= "\r\n" . $exception->getTraceAsString();
}
Log::record($log, 'error');
}
}
protected function isIgnoreReport(Exception $exception)
{
foreach ($this->ignoreReport as $class) {
if ($exception instanceof $class) {
return true;
}
}
return false;
}
/**
* Render an exception into an HTTP response.
*
* @param \Exception $e
* @return Response
*/
public function render(Exception $e)
{
if ($this->render && $this->render instanceof \Closure) {
$result = call_user_func_array($this->render, [$e]);
if ($result) {
return $result;
}
}
if ($e instanceof HttpException) {
return $this->renderHttpException($e);
} else {
return $this->convertExceptionToResponse($e);
}
}
/**
* @param Output $output
* @param Exception $e
*/
public function renderForConsole(Output $output, Exception $e)
{
if (App::$debug) {
$output->setVerbosity(Output::VERBOSITY_DEBUG);
}
$output->renderException($e);
}
/**
* @param HttpException $e
* @return Response
*/
protected function renderHttpException(HttpException $e)
{
$status = $e->getStatusCode();
$template = Config::get('http_exception_template');
if (!App::$debug && !empty($template[$status])) {
return Response::create($template[$status], 'view', $status)->assign(['e' => $e]);
} else {
return $this->convertExceptionToResponse($e);
}
}
/**
* @param Exception $exception
* @return Response
*/
protected function convertExceptionToResponse(Exception $exception)
{
// 收集異常數(shù)據(jù)
if (App::$debug) {
// 調(diào)試模式踏堡,獲取詳細(xì)的錯(cuò)誤信息
$data = [
'name' => get_class($exception),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'message' => $this->getMessage($exception),
'trace' => $exception->getTrace(),
'code' => $this->getCode($exception),
'source' => $this->getSourceCode($exception),
'datas' => $this->getExtendData($exception),
'tables' => [
'GET Data' => $_GET,
'POST Data' => $_POST,
'Files' => $_FILES,
'Cookies' => $_COOKIE,
'Session' => isset($_SESSION) ? $_SESSION : [],
'Server/Request Data' => $_SERVER,
'Environment Variables' => $_ENV,
'ThinkPHP Constants' => $this->getConst(),
],
];
} else {
// 部署模式僅顯示 Code 和 Message
$data = [
'code' => $this->getCode($exception),
'message' => $this->getMessage($exception),
];
if (!Config::get('show_error_msg')) {
// 不顯示詳細(xì)錯(cuò)誤信息
$data['message'] = Config::get('error_message');
}
}
//保留一層
while (ob_get_level() > 1) {
ob_end_clean();
}
$data['echo'] = ob_get_clean();
ob_start();
extract($data);
include Config::get('exception_tmpl');
// 獲取并清空緩存
$content = ob_get_clean();
$response = new Response($content, 'html');
if ($exception instanceof HttpException) {
$statusCode = $exception->getStatusCode();
$response->header($exception->getHeaders());
}
if (!isset($statusCode)) {
$statusCode = 500;
}
$response->code($statusCode);
return $response;
}
/**
* 獲取錯(cuò)誤編碼
* ErrorException則使用錯(cuò)誤級(jí)別作為錯(cuò)誤編碼
* @param \Exception $exception
* @return integer 錯(cuò)誤編碼
*/
protected function getCode(Exception $exception)
{
$code = $exception->getCode();
if (!$code && $exception instanceof ErrorException) {
$code = $exception->getSeverity();
}
return $code;
}
/**
* 獲取錯(cuò)誤信息
* ErrorException則使用錯(cuò)誤級(jí)別作為錯(cuò)誤編碼
* @param \Exception $exception
* @return string 錯(cuò)誤信息
*/
protected function getMessage(Exception $exception)
{
$message = $exception->getMessage();
if (IS_CLI) {
return $message;
}
if (strpos($message, ':')) {
$name = strstr($message, ':', true);
$message = Lang::has($name) ? Lang::get($name) . strstr($message, ':') : $message;
} elseif (strpos($message, ',')) {
$name = strstr($message, ',', true);
$message = Lang::has($name) ? Lang::get($name) . ':' . substr(strstr($message, ','), 1) : $message;
} elseif (Lang::has($message)) {
$message = Lang::get($message);
}
return $message;
}
/**
* 獲取出錯(cuò)文件內(nèi)容
* 獲取錯(cuò)誤的前9行和后9行
* @param \Exception $exception
* @return array 錯(cuò)誤文件內(nèi)容
*/
protected function getSourceCode(Exception $exception)
{
// 讀取前9行和后9行
$line = $exception->getLine();
$first = ($line - 9 > 0) ? $line - 9 : 1;
try {
$contents = file($exception->getFile());
$source = [
'first' => $first,
'source' => array_slice($contents, $first - 1, 19),
];
} catch (Exception $e) {
$source = [];
}
return $source;
}
/**
* 獲取異常擴(kuò)展信息
* 用于非調(diào)試模式html返回類型顯示
* @param \Exception $exception
* @return array 異常類定義的擴(kuò)展數(shù)據(jù)
*/
protected function getExtendData(Exception $exception)
{
$data = [];
if ($exception instanceof \think\Exception) {
$data = $exception->getData();
}
return $data;
}
/**
* 獲取常量列表
* @return array 常量列表
*/
private static function getConst()
{
return get_defined_constants(true)['user'];
}
}
重寫Handle類
由上可知錯(cuò)誤將會(huì)由appShutdown猎唁、appError與appException接管,
并由Handle或者自定義Handle類report選擇組裝并輸出錯(cuò)誤
name我們?cè)囍远x錯(cuò)誤處理類重寫render
首先修改exception_handle
'exception_handle'=>'app\lib\exception\ExceptionHandle'
然后新建application/lib/exception/ExceptionHandler.php
負(fù)責(zé)渲染錯(cuò)誤信息
namespace app\lib\exception;
use think\Exception;
use think\exception\Handle;
use think\Request;
/**
* 注意:
* tp默認(rèn)調(diào)用的異常處理類是think\exception\Handle;
* 調(diào)用異常處理類可以在config.php配置默認(rèn)為空 'exception_handle'=>'',調(diào)用的是 think\exception\Handle
* 默認(rèn)調(diào)用application/lib/exception/下的這個(gè)類則需要修改配置為:'exception_handle'=>'app\lib\exception\ExceptionHandle'
*
*/
class ExceptionHandler extends Handle{
private $code;//Http Code
private $msg;
private $errorCode;
public function render(Exception $e){
if ($e instanceof BaseException) {//G牦 =胗纭!帐偎!如果BaseException 與這個(gè)Exceptionhandler不是同一個(gè)命名空間一定要引入空間啊
$this->code=$e->code;
$this->msg=$e->message;
$this->errorCode=$e->errorCode;
}else{
$this->code=500;
$this->msg="服務(wù)器內(nèi)部錯(cuò)誤";
$this->errorCode=999;//自定義的哦
//這里手動(dòng)記錄日志
}
$request=Request::instance();
$result=array(
'msg'=>$this->msg,
'error_code'=>$this->errorCode,
'request_url'=>$request->url()
);
return json($result);
}
}
為應(yīng)用定義一個(gè)公共異常類
然后新建application/lib/exception/BaseException.php
baseException定義code msg errorCode這三個(gè)屬性的默認(rèn)值
namespace app\lib\exception;
use think\Exception;
class BaseException extends Exception{
//將錯(cuò)誤代碼設(shè)為http狀態(tài)碼逐纬,業(yè)務(wù)錯(cuò)誤代碼設(shè)為errorCode
public $code=400;
//錯(cuò)誤的具體信息
public $message="參數(shù)錯(cuò)誤";
//自定義的業(yè)務(wù)錯(cuò)誤碼
public $errorCode="10000";
}
//基類屬性參考
protected string $message ;//異常消息內(nèi)容
protected int $code ;//異常代碼
protected string $file ;//拋出異常的文件名
protected int $line ;//拋出異常在該文件中的行號(hào)
每個(gè)模塊每個(gè)錯(cuò)誤類型定義一個(gè)異常類繼承BaseException
application/lib/exception/BannerMissException.php
namespace app\lib\exception;
//use app\lib\exception\Handle;
class BannerMissException extends BaseException{
//http狀態(tài)碼
public $code=404;
//錯(cuò)誤的具體信息
public $message="請(qǐng)求的Banner不存在";
//自定義的錯(cuò)誤碼
public $errorCode=40000;
}
用法:
在model中查詢數(shù)據(jù)并在banner控制器調(diào)用 當(dāng)model中沒有數(shù)據(jù)時(shí)調(diào)用BannermissException 這時(shí)exceptionHandler的render將捕獲這個(gè)錯(cuò)誤
模塊
namespace app\api\Model;
use think\Model;
// use think\Excption;
class Banner extends Model{
public static function getBannerById($id){
//TODO:根據(jù)bannerid號(hào)獲取banner信息
return null;
//return "this is banner info";
}
}
控制器
namespace app\api\controller\v1;
use think\Controller;
use app\api\Model\Banner as BannerModel;//這里的Banner和Model的Banner重名
use app\lib\exception\BannerMissException;
class Banner extends controller{
public function index(){
//http://localhost/thinkphp5/public/index.php/api/v1.Banner/index
}
public function getBanner($id){
$banner=BannerModel::getBannerById($id);
if (!$banner) {
throw new BannerMissException();
}
}
}