Passed
Push — 8.0 ( 458751...19874d )
by liu
02:08
created

Handle::getDeployMsg()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 16
ccs 0
cts 10
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 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
        return $this->app->isDebug() ? $this->getDedugMsg($exception) : $this->getDeployMsg($exception);
0 ignored issues
show
Bug introduced by
The method getDedugMsg() does not exist on think\exception\Handle. Did you maybe mean getDebugMsg()? ( Ignorable by Annotation )

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

150
        return $this->app->isDebug() ? $this->/** @scrutinizer ignore-call */ getDedugMsg($exception) : $this->getDeployMsg($exception);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
151
    }
152
153
    /**
154
     * 获取部署模式异常数据
155
     * @access protected
156
     * @param Throwable $exception
157
     * @return array
158
     */
159
    protected function getDeployMsg(Throwable $exception): array
160
    {
161
        $data = [
162
            'code'    => $this->getCode($exception),
163
            'message' => $this->getMessage($exception),
164
        ];
165
166
        $reflectionClass = new ReflectionClass($exception);
167
        $alwaysMsg       = $reflectionClass->getAttributes(AlwaysErrorMsg::class);
168
169
        if (empty($alwaysMsg) && !$this->app->config->get('app.show_error_msg')) {
170
            // 不显示详细错误信息
171
            $data['message'] = $this->app->config->get('app.error_message');
172
        }
173
174
        return $data;
175
    }
176
177
    /**
178
     * 收集调试模式异常数据
179
     * @access protected
180
     * @param Throwable $exception
181
     * @return array
182
     */
183
    protected function getDebugMsg(Throwable $exception): array
184
    {
185
        // 调试模式,获取详细的错误信息
186
        $traces        = [];
187
        $nextException = $exception;
188
189
        do {
190
            $traces[] = [
191
                'name'    => $nextException::class,
192
                'file'    => $nextException->getFile(),
193
                'line'    => $nextException->getLine(),
194
                'code'    => $this->getCode($nextException),
195
                'message' => $this->getMessage($nextException),
196
                'trace'   => $nextException->getTrace(),
197
                'source'  => $this->getSourceCode($nextException),
198
            ];
199
        } while ($nextException = $nextException->getPrevious());
200
201
        return [
202
            'code'    => $this->getCode($exception),
203
            'message' => $this->getMessage($exception),
204
            'traces'  => $traces,
205
            'datas'   => $this->getExtendData($exception),
206
            'tables'  => [
207
                'GET Data'            => $this->app->request->get(),
208
                'POST Data'           => $this->app->request->post(),
209
                'Files'               => $this->app->request->file(),
210
                'Cookies'             => $this->app->request->cookie(),
211
                '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

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