之前发现自己很蠢。。。用的是thinkphp完整版审计的源码。。。然后里面有一些插件。。比如上一篇说的注册的路由,其实是验证码扩展里的,发现之后下了核心版的继续看

首先它注册了各类的异常处理

    public static function register()
    {

        error_reporting(E_ALL);
        //自定义错误处理
        set_error_handler([__CLASS__, 'appError']);
        //自定义异常处理
        set_exception_handler([__CLASS__, 'appException']);
        //注册一个会在PHP中止时执行的函数
        register_shutdown_function([__CLASS__, 'appShutdown']);
    }

我手动触发一个error mark 这里我echo了一个不存在的元素,会引发E_NOTICE等级的错误

    public static function appError($errno, $errstr, $errfile = '', $errline = 0)
    {
        $exception = new ErrorException($errno, $errstr, $errfile, $errline);

        // 符合异常处理的则将错误信息托管至 think\exception\ErrorException
        if (error_reporting() & $errno) {
            throw $exception;
        }

        self::getExceptionHandler()->report($exception);
    }

首先它new了一个ErrorException对象

    public function __construct($severity, $message, $file, $line, array $context = [])
    {
        $this->severity = $severity;
        $this->message  = $message;
        $this->file     = $file;
        $this->line     = $line;
        $this->code     = 0;

        empty($context) || $this->setData('Error Context', $context);
    }
    final protected function setData($label, array $data)
    {
        $this->data[$label] = $data;
    }

总之它就把错误信息传入这个对象

之后

if (error_reporting() & $errno) {
            throw $exception;
}

只要errno不为0就会吧之前的对象作为异常抛出,但没有catch去处理这个异常,所以就进到了我们的异常处理函数

public static function appException($e)
    {
        if (!$e instanceof \Exception) {
            $e = new ThrowableError($e);
        }

        $handler = self::getExceptionHandler();
        $handler->report($e);

        if (IS_CLI) {
            $handler->renderForConsole(new ConsoleOutput, $e);
        } else {
            $handler->render($e)->send();
        }
    }

$handler = self::getExceptionHandler();然后这里会获得一个操作异常的句柄

进report函数看一下

    public function report(Exception $exception)
    {
        if (!$this->isIgnoreReport($exception)) {
            // 收集异常数据
            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');
        }
    }

这里就是收集一些异常数据,并把它记录到Log类中

$handler->render($e)->send();这句就是把错误转化为http页面响应回去

    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);
        }
    }

这里有个第一部分好像是如果设置了this->render的一个回调函数就调用它来处理报错,我瞎猜的。。。

然后第二部分就是把异常或者报错转出Response 然后我们这里是跳到第二个函数

    protected function convertExceptionToResponse(Exception $exception)
    {
        // 收集异常数据
        if (App::$debug) {
            // 调试模式,获取详细的错误信息
            $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')) {
                // 不显示详细错误信息
                $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;
    }

首先判断你debug开没开着,如果开着的话就把上面的所有信息都给你整进去,不然的话就给你整这么点$data = [ 'code' => $this->getCode($exception), 'message' => $this->getMessage($exception), ];

然后缓冲输出 include Config::get('exception_tmpl');载入模板,然后$response = new Response($content, 'html')获得响应信息,这一段在以后看响应的时候再分析,然后设置状态为500

接着就是最后一个函数send

public function send()
    {
        // 监听response_send
        Hook::listen('response_send', $this);

        // 处理输出数据
        $data = $this->getContent();

        // Trace调试注入
        if (Env::get('app_trace', Config::get('app_trace'))) {
            Debug::inject($this, $data);
        }

        if (200 == $this->code) {
            $cache = Request::instance()->getCache();
            if ($cache) {
                $this->header['Cache-Control'] = 'max-age=' . $cache[1] . ',must-revalidate';
                $this->header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT';
                $this->header['Expires']       = gmdate('D, d M Y H:i:s', $_SERVER['REQUEST_TIME'] + $cache[1]) . ' GMT';
                Cache::tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]);
            }
        }

        if (!headers_sent() && !empty($this->header)) {
            // 发送状态码
            http_response_code($this->code);
            // 发送头部信息
            foreach ($this->header as $name => $val) {
                if (is_null($val)) {
                    header($name);
                } else {
                    header($name . ':' . $val);
                }
            }
        }

        echo $data;

        if (function_exists('fastcgi_finish_request')) {
            // 提高页面响应
            fastcgi_finish_request();
        }

        // 监听response_end
        Hook::listen('response_end', $this);

        // 清空当次请求有效的数据
        if (!($this instanceof RedirectResponse)) {
            Session::flush();
        }
    }

Hook我们先不管,反正一顿瞎鸡儿处理之后 echo $data;响应被打印出来