Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
HttpErrorHandler
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 4
156
0.00% covered (danger)
0.00%
0 / 1
 getFileContents
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
 getSuppressionStatusList
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getHttpStatus
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 catchError
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3declare(strict_types=1);
4
5namespace PeServer\Core\Errors;
6
7use PeServer\Core\DI\Inject;
8use PeServer\Core\Http\HttpRequest;
9use PeServer\Core\Http\HttpResponse;
10use PeServer\Core\Http\HttpStatus;
11use PeServer\Core\Http\IResponsePrinterFactory;
12use PeServer\Core\IO\Directory;
13use PeServer\Core\IO\File;
14use PeServer\Core\IO\Path;
15use PeServer\Core\Mvc\Template\ITemplateFactory;
16use PeServer\Core\Mvc\Template\TemplateFactory;
17use PeServer\Core\Mvc\Template\TemplateOptions;
18use PeServer\Core\Mvc\Template\TemplateParameter;
19use PeServer\Core\ProgramContext;
20use PeServer\Core\Throws\HttpStatusException;
21use PeServer\Core\Web\UrlHelper;
22use PeServer\Core\Web\WebSecurity;
23use Throwable;
24
25class HttpErrorHandler extends ErrorHandler
26{
27    #region variable
28
29    #[Inject(TemplateFactory::class)] //@phpstan-ignore-next-line [INJECT]
30    private ITemplateFactory $templateFactory;
31
32    #[Inject] //@phpstan-ignore-next-line [INJECT]
33    private IResponsePrinterFactory $responsePrinterFactory;
34
35    #[Inject] //@phpstan-ignore-next-line [INJECT]
36    private WebSecurity $webSecurity;
37
38    #[Inject] //@phpstan-ignore-next-line [INJECT]
39    private ProgramContext $programContext;
40
41    #endregion
42
43    #region function
44
45    /**
46     * 検出できるソースファイル内容をすべて取得。
47     *
48     * @param string $file
49     * @param Throwable|null $throwable
50     * @return array<string,string>
51     */
52    private function getFileContents(string $file, ?Throwable $throwable): array
53    {
54        $files = [
55            "$file" => File::readContent($file)->raw,
56        ];
57
58        if ($throwable !== null) {
59            foreach ($throwable->getTrace() as $item) {
60                if (isset($item['file'])) {
61                    $f = $item['file'];
62                    if (!isset($files[$f])) {
63                        $files[$f] = File::readContent($f)->raw;
64                    }
65                }
66            }
67        }
68
69        return $files;
70    }
71
72    /**
73     * 抑制エラーコード指定。
74     *
75     * @return HttpStatus[]
76     */
77    protected function getSuppressionStatusList(): array
78    {
79        return [
80            HttpStatus::NotFound,
81        ];
82    }
83
84
85    /**
86     * 例外からHTTP応答ステータスコードを取得する。
87     *
88     * @param Throwable|null $throwable
89     * @return HttpStatus 設定されたHTTPステータスコード。
90     */
91    final protected function getHttpStatus(?Throwable $throwable): HttpStatus
92    {
93        $status = $throwable instanceof HttpStatusException
94            ? $throwable->status
95            : HttpStatus::ServiceUnavailable;
96
97        return $status;
98    }
99
100    protected function catchError(int $errorNumber, string $message, string $file, int $lineNumber, ?Throwable $throwable): void
101    {
102        $response = new HttpResponse();
103        $response->status = $this->getHttpStatus($throwable);
104
105        $values = [
106            'error_number' => $errorNumber,
107            'message' => $message,
108            'file' => $file,
109            'line_number' => $lineNumber,
110            'throwable' => $throwable,
111            'cache' => $this->getFileContents($file, $throwable)
112        ];
113
114        $isSuppressionStatus = false;
115        foreach ($this->getSuppressionStatusList() as $suppressionStatus) {
116            if ($response->status === $suppressionStatus) {
117                $isSuppressionStatus = true;
118                $this->logger->info('HTTP {0}: {1}', $suppressionStatus->value, $suppressionStatus->name);
119                break;
120            }
121        }
122        if (!$isSuppressionStatus) {
123            $this->logger->error($values);
124        }
125
126        $options = new TemplateOptions(
127            Path::combine(__DIR__, '..'),
128            'template',
129            $this->programContext,
130            UrlHelper::none(),
131            $this->webSecurity,
132            Path::combine(Directory::getTemporaryDirectory(), 'PeServer-Core')
133        );
134        $template = $this->templateFactory->createTemplate($options);
135
136        $response->content = $template->build('error-display.tpl', new TemplateParameter($response->status, $values, []));
137
138        $printer = $this->responsePrinterFactory->createResponsePrinter(HttpRequest::none(), $response);
139
140        $printer->execute();
141    }
142
143    #endregion
144}