Passed
Push — 8.0 ( 4594fa...458751 )
by liu
11:09
created

Handle::convertExceptionToArray()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 48
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 6
eloc 34
c 3
b 0
f 0
nc 4
nop 1
dl 0
loc 48
ccs 0
cts 37
cp 0
crap 42
rs 8.7537
1
<?php
2
// +----------------------------------------------------------------------
3
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
6
// +----------------------------------------------------------------------
7
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
// +----------------------------------------------------------------------
9
// | Author: yunwuxin <[email protected]>
10
// +----------------------------------------------------------------------
11
declare (strict_types = 1);
12
13
namespace think\exception;
14
15
use Exception;
16
use ReflectionClass;
17
use think\App;
18
use think\console\Output;
19
use think\db\exception\DataNotFoundException;
20
use think\db\exception\ModelNotFoundException;
21
use think\Request;
22
use think\Response;
23
use Throwable;
24
25
/**
26
 * 系统异常处理类
27
 */
28
class Handle
29
{
30
    protected $ignoreReport = [
31
        HttpException::class,
32
        HttpResponseException::class,
33
        ModelNotFoundException::class,
34
        DataNotFoundException::class,
35
        ValidateException::class,
36
    ];
37
38
    protected $isJson = false;
39
40
    public function __construct(protected App $app)
41
    {
42
    }
43
44
    /**
45
     * Report or log an exception.
46
     *
47
     * @access public
48
     * @param Throwable $exception
49
     * @return void
50
     */
51
    public function report(Throwable $exception): void
52
    {
53
        if (!$this->isIgnoreReport($exception)) {
54
            // 收集异常数据
55
            if ($this->app->isDebug()) {
56
                $data = [
57
                    'file'    => $exception->getFile(),
58
                    'line'    => $exception->getLine(),
59
                    'message' => $this->getMessage($exception),
60
                    'code'    => $this->getCode($exception),
61
                ];
62
                $log = "[{$data['code']}]{$data['message']}[{$data['file']}:{$data['line']}]";
63
            } else {
64
                $data = [
65
                    'code'    => $this->getCode($exception),
66
                    'message' => $this->getMessage($exception),
67
                ];
68
                $log = "[{$data['code']}]{$data['message']}";
69
            }
70
71
            if ($this->app->config->get('log.record_trace')) {
72
                $log .= PHP_EOL . $exception->getTraceAsString();
73
            }
74
75
            try {
76
                $this->app->log->record($log, 'error');
77
            } catch (Exception $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
78
        }
79
    }
80
81
    protected function isIgnoreReport(Throwable $exception): bool
82
    {
83
        foreach ($this->ignoreReport as $class) {
84
            if ($exception instanceof $class) {
85
                return true;
86
            }
87
        }
88
89
        return false;
90
    }
91
92
    /**
93
     * Render an exception into an HTTP response.
94
     *
95
     * @access public
96
     * @param Request   $request
97
     * @param Throwable $e
98
     * @return Response
99
     */
100
    public function render(Request $request, Throwable $e): Response
101
    {
102
        $this->isJson = $request->isJson();
103
        if ($e instanceof HttpResponseException) {
104
            return $e->getResponse();
105
        } elseif ($e instanceof HttpException) {
106
            return $this->renderHttpException($e);
107
        } else {
108
            return $this->convertExceptionToResponse($e);
109
        }
110
    }
111
112
    /**
113
     * @access public
114
     * @param Output    $output
115
     * @param Throwable $e
116
     */
117
    public function renderForConsole(Output $output, Throwable $e): void
118
    {
119
        if ($this->app->isDebug()) {
120
            $output->setVerbosity(Output::VERBOSITY_DEBUG);
121
        }
122
123
        $output->renderException($e);
124
    }
125
126
    /**
127
     * @access protected
128
     * @param HttpException $e
129
     * @return Response
130
     */
131
    protected function renderHttpException(HttpException $e): Response
132
    {
133
        $status   = $e->getStatusCode();
134
        $template = $this->app->config->get('app.http_exception_template');
135
136
        if (!$this->app->isDebug() && !empty($template[$status])) {
137
            return Response::create($template[$status], 'view', $status)->assign(['e' => $e]);
0 ignored issues
show
Bug introduced by
The method assign() does not exist on think\Response. It seems like you code against a sub-type of think\Response such as think\response\View. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

137
            return Response::create($template[$status], 'view', $status)->/** @scrutinizer ignore-call */ assign(['e' => $e]);
Loading history...
138
        } else {
139
            return $this->convertExceptionToResponse($e);
140
        }
141
    }
142
143
    /**
144
     * 收集异常数据
145
     * @param Throwable $exception
146
     * @return array
147
     */
148
    protected function convertExceptionToArray(Throwable $exception): array
149
    {
150
        if ($this->app->isDebug()) {
151
            // 调试模式,获取详细的错误信息
152
            $traces        = [];
153
            $nextException = $exception;
154
            do {
155
                $traces[] = [
156
                    'name'    => $nextException::class,
157
                    'file'    => $nextException->getFile(),
158
                    'line'    => $nextException->getLine(),
159
                    'code'    => $this->getCode($nextException),
160
                    'message' => $this->getMessage($nextException),
161
                    'trace'   => $nextException->getTrace(),
162
                    'source'  => $this->getSourceCode($nextException),
163
                ];
164
            } while ($nextException = $nextException->getPrevious());
165
            $data = [
166
                'code'    => $this->getCode($exception),
167
                'message' => $this->getMessage($exception),
168
                'traces'  => $traces,
169
                'datas'   => $this->getExtendData($exception),
170
                'tables'  => [
171
                    'GET Data'            => $this->app->request->get(),
172
                    'POST Data'           => $this->app->request->post(),
173
                    'Files'               => $this->app->request->file(),
174
                    'Cookies'             => $this->app->request->cookie(),
175
                    'Session'             => $this->app->exists('session') ? $this->app->session->all() : [],
0 ignored issues
show
Bug introduced by
The method all() does not exist on think\Session. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

175
                    'Session'             => $this->app->exists('session') ? $this->app->session->/** @scrutinizer ignore-call */ all() : [],
Loading history...
176
                    'Server/Request Data' => $this->app->request->server(),
177
                ],
178
            ];
179
        } else {
180
            // 部署模式仅显示 Code 和 Message
181
            $data = [
182
                'code'    => $this->getCode($exception),
183
                'message' => $this->getMessage($exception),
184
            ];
185
186
            $reflectionClass = new ReflectionClass($exception);
187
            $alwaysMsg       = $reflectionClass->getAttributes(AlwaysErrorMsg::class);
188
189
            if (empty($alwaysMsg) && !$this->app->config->get('app.show_error_msg')) {
190
                // 不显示详细错误信息
191
                $data['message'] = $this->app->config->get('app.error_message');
192
            }
193
        }
194
195
        return $data;
196
    }
197
198
    /**
199
     * @access protected
200
     * @param Throwable $exception
201
     * @return Response
202
     */
203
    protected function convertExceptionToResponse(Throwable $exception): Response
204
    {
205
        if (!$this->isJson) {
206
            $response = Response::create($this->renderExceptionContent($exception));
207
        } else {
208
            $response = Response::create($this->convertExceptionToArray($exception), 'json');
209
        }
210
211
        if ($exception instanceof HttpException) {
212
            $statusCode = $exception->getStatusCode();
213
            $response->header($exception->getHeaders());
214
        }
215
216
        return $response->code($statusCode ?? 500);
217
    }
218
219
    protected function renderExceptionContent(Throwable $exception): string
220
    {
221
        ob_start();
222
        $data = $this->convertExceptionToArray($exception);
223
        extract($data);
224
        include $this->app->config->get('app.exception_tmpl') ?: __DIR__ . '/../../tpl/think_exception.tpl';
225
226
        return ob_get_clean();
227
    }
228
229
    /**
230
     * 获取错误编码
231
     * ErrorException则使用错误级别作为错误编码
232
     * @access protected
233
     * @param Throwable $exception
234
     * @return integer                错误编码
235
     */
236
    protected function getCode(Throwable $exception)
237
    {
238
        $code = $exception->getCode();
239
240
        if (!$code && $exception instanceof ErrorException) {
241
            $code = $exception->getSeverity();
242
        }
243
244
        return $code;
245
    }
246
247
    /**
248
     * 获取错误信息
249
     * ErrorException则使用错误级别作为错误编码
250
     * @access protected
251
     * @param Throwable $exception
252
     * @return string                错误信息
253
     */
254
    protected function getMessage(Throwable $exception): string
255
    {
256
        $message = $exception->getMessage();
257
258
        if ($this->app->runningInConsole()) {
259
            return $message;
260
        }
261
262
        $lang = $this->app->lang;
263
264
        if (str_contains($message, ':')) {
265
            $name    = strstr($message, ':', true);
266
            $message = $lang->has($name) ? $lang->get($name) . strstr($message, ':') : $message;
267
        } elseif (str_contains($message, ',')) {
268
            $name    = strstr($message, ',', true);
269
            $message = $lang->has($name) ? $lang->get($name) . ':' . substr(strstr($message, ','), 1) : $message;
270
        } elseif ($lang->has($message)) {
271
            $message = $lang->get($message);
272
        }
273
274
        return $message;
275
    }
276
277
    /**
278
     * 获取出错文件内容
279
     * 获取错误的前9行和后9行
280
     * @access protected
281
     * @param Throwable $exception
282
     * @return array                 错误文件内容
283
     */
284
    protected function getSourceCode(Throwable $exception): array
285
    {
286
        // 读取前9行和后9行
287
        $line  = $exception->getLine();
288
        $first = ($line - 9 > 0) ? $line - 9 : 1;
289
290
        try {
291
            $contents = file($exception->getFile()) ?: [];
292
            $source   = [
293
                'first'  => $first,
294
                'source' => array_slice($contents, $first - 1, 19),
295
            ];
296
        } catch (Exception $e) {
297
            $source = [];
298
        }
299
300
        return $source;
301
    }
302
303
    /**
304
     * 获取异常扩展信息
305
     * 用于非调试模式html返回类型显示
306
     * @access protected
307
     * @param Throwable $exception
308
     * @return array                 异常类定义的扩展数据
309
     */
310
    protected function getExtendData(Throwable $exception): array
311
    {
312
        $data = [];
313
314
        if ($exception instanceof \think\Exception) {
315
            $data = $exception->getData();
316
        }
317
318
        return $data;
319
    }
320
321
    /**
322
     * 获取常量列表
323
     * @access protected
324
     * @return array 常量列表
325
     */
326
    protected function getConst(): array
327
    {
328
        $const = get_defined_constants(true);
329
330
        return $const['user'] ?? [];
331
    }
332
}
333