Passed
Push — 8.0 ( 625916...1629cd )
by yun
02:25
created

Handle::isShowErrorMsg()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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

138
            return Response::create($template[$status], 'view', $status)->/** @scrutinizer ignore-call */ assign(['e' => $e]);
Loading history...
139
        } else {
140
            return $this->convertExceptionToResponse($request, $e);
141
        }
142
    }
143
144
    /**
145
     * 收集异常数据
146
     * @param Throwable $exception
147
     * @return array
148
     */
149
    protected function convertExceptionToArray(Throwable $exception): array
150
    {
151
        return $this->app->isDebug() ? $this->getDebugMsg($exception) : $this->getDeployMsg($exception);
152
    }
153
154
    /**
155
     * 是否显示错误信息
156
     * @param \Throwable $exception
157
     * @return bool
158
     */
159
    protected function isShowErrorMsg(Throwable $exception)
160
    {
161
        foreach ($this->showErrorMsg as $class) {
162
            if ($exception instanceof $class) {
163
                return true;
164
            }
165
        }
166
167
        return false;
168
    }
169
170
    /**
171
     * 获取部署模式异常数据
172
     * @access protected
173
     * @param Throwable $exception
174
     * @return array
175
     */
176
    protected function getDeployMsg(Throwable $exception): array
177
    {
178
        $showErrorMsg = $this->isShowErrorMsg($exception);
179
        if ($showErrorMsg || $this->app->config->get('app.show_error_msg', false)) {
180
            $message = $this->getMessage($exception);
181
        } else {
182
            // 不显示详细错误信息
183
            $message = $this->app->config->get('app.error_message');
184
        }
185
186
        return [
187
            'code'    => $this->getCode($exception),
188
            'message' => $message,
189
        ];
190
    }
191
192
    /**
193
     * 收集调试模式异常数据
194
     * @access protected
195
     * @param Throwable $exception
196
     * @return array
197
     */
198
    protected function getDebugMsg(Throwable $exception): array
199
    {
200
        // 调试模式,获取详细的错误信息
201
        $traces        = [];
202
        $nextException = $exception;
203
204
        do {
205
            $traces[] = [
206
                'name'    => $nextException::class,
207
                'file'    => $nextException->getFile(),
208
                'line'    => $nextException->getLine(),
209
                'code'    => $this->getCode($nextException),
210
                'message' => $this->getMessage($nextException),
211
                'trace'   => $nextException->getTrace(),
212
                'source'  => $this->getSourceCode($nextException),
213
            ];
214
        } while ($nextException = $nextException->getPrevious());
215
216
        return [
217
            'code'    => $this->getCode($exception),
218
            'message' => $this->getMessage($exception),
219
            'traces'  => $traces,
220
            'datas'   => $this->getExtendData($exception),
221
            'tables'  => [
222
                'GET Data'            => $this->app->request->get(),
223
                'POST Data'           => $this->app->request->post(),
224
                'Files'               => $this->app->request->file(),
225
                'Cookies'             => $this->app->request->cookie(),
226
                '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

226
                'Session'             => $this->app->exists('session') ? $this->app->session->/** @scrutinizer ignore-call */ all() : [],
Loading history...
227
                'Server/Request Data' => $this->app->request->server(),
228
            ],
229
        ];
230
    }
231
232
    protected function isJson(Request $request, Throwable $exception)
0 ignored issues
show
Unused Code introduced by
The parameter $exception is not used and could be removed. ( Ignorable by Annotation )

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

232
    protected function isJson(Request $request, /** @scrutinizer ignore-unused */ Throwable $exception)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
233
    {
234
        return $request->isJson();
235
    }
236
237
    /**
238
     * @access protected
239
     * @param Throwable $exception
240
     * @return Response
241
     */
242
    protected function convertExceptionToResponse(Request $request, Throwable $exception): Response
243
    {
244
        if ($this->isJson($request, $exception)) {
245
            $response = Response::create($this->convertExceptionToArray($exception), 'json');
246
        } else {
247
            $response = Response::create($this->renderExceptionContent($exception));
248
        }
249
250
        if ($exception instanceof HttpException) {
251
            $statusCode = $exception->getStatusCode();
252
            $response->header($exception->getHeaders());
253
        }
254
255
        return $response->code($statusCode ?? 500);
256
    }
257
258
    protected function renderExceptionContent(Throwable $exception): string
259
    {
260
        ob_start();
261
        $data = $this->convertExceptionToArray($exception);
262
        extract($data);
263
        include $this->app->config->get('app.exception_tmpl') ?: __DIR__ . '/../../tpl/think_exception.tpl';
264
265
        return ob_get_clean();
266
    }
267
268
    /**
269
     * 获取错误编码
270
     * ErrorException则使用错误级别作为错误编码
271
     * @access protected
272
     * @param Throwable $exception
273
     * @return integer                错误编码
274
     */
275
    protected function getCode(Throwable $exception)
276
    {
277
        $code = $exception->getCode();
278
279
        if (!$code && $exception instanceof ErrorException) {
280
            $code = $exception->getSeverity();
281
        }
282
283
        return $code;
284
    }
285
286
    /**
287
     * 获取错误信息
288
     * ErrorException则使用错误级别作为错误编码
289
     * @access protected
290
     * @param Throwable $exception
291
     * @return string                错误信息
292
     */
293
    protected function getMessage(Throwable $exception): string
294
    {
295
        $message = $exception->getMessage();
296
297
        if ($this->app->runningInConsole()) {
298
            return $message;
299
        }
300
301
        $lang = $this->app->lang;
302
303
        if (str_contains($message, ':')) {
304
            $name    = strstr($message, ':', true);
305
            $message = $lang->has($name) ? $lang->get($name) . strstr($message, ':') : $message;
306
        } elseif (str_contains($message, ',')) {
307
            $name    = strstr($message, ',', true);
308
            $message = $lang->has($name) ? $lang->get($name) . ':' . substr(strstr($message, ','), 1) : $message;
309
        } elseif ($lang->has($message)) {
310
            $message = $lang->get($message);
311
        }
312
313
        return $message;
314
    }
315
316
    /**
317
     * 获取出错文件内容
318
     * 获取错误的前9行和后9行
319
     * @access protected
320
     * @param Throwable $exception
321
     * @return array                 错误文件内容
322
     */
323
    protected function getSourceCode(Throwable $exception): array
324
    {
325
        // 读取前9行和后9行
326
        $line  = $exception->getLine();
327
        $first = ($line - 9 > 0) ? $line - 9 : 1;
328
329
        try {
330
            $contents = file($exception->getFile()) ?: [];
331
            $source   = [
332
                'first'  => $first,
333
                'source' => array_slice($contents, $first - 1, 19),
334
            ];
335
        } catch (Exception $e) {
336
            $source = [];
337
        }
338
339
        return $source;
340
    }
341
342
    /**
343
     * 获取异常扩展信息
344
     * 用于非调试模式html返回类型显示
345
     * @access protected
346
     * @param Throwable $exception
347
     * @return array                 异常类定义的扩展数据
348
     */
349
    protected function getExtendData(Throwable $exception): array
350
    {
351
        $data = [];
352
353
        if ($exception instanceof \think\Exception) {
354
            $data = $exception->getData();
355
        }
356
357
        return $data;
358
    }
359
360
    /**
361
     * 获取常量列表
362
     * @access protected
363
     * @return array 常量列表
364
     */
365
    protected function getConst(): array
366
    {
367
        $const = get_defined_constants(true);
368
369
        return $const['user'] ?? [];
370
    }
371
}
372