Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
20.69% covered (danger)
20.69%
12 / 58
27.27% covered (danger)
27.27%
3 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 2
ErrorHandler
11.76% covered (danger)
11.76%
6 / 51
12.50% covered (danger)
12.50%
1 / 8
94.12
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 register
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 receiveShutdown
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
 receiveException
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 receiveError
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 trap
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 catchErrorCore
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 catchError
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
LocalPhpErrorReceiver
85.71% covered (warning)
85.71%
6 / 7
66.67% covered (warning)
66.67%
2 / 3
4.05
0.00% covered (danger)
0.00%
0 / 1
 __construct
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 disposeImpl
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 receiveError
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace PeServer\Core\Errors;
6
7use PeServer\Core\Code;
8use PeServer\Core\Collection\Arr;
9use PeServer\Core\DI\Inject;
10use PeServer\Core\DisposerBase;
11use PeServer\Core\Http\HttpHeader;
12use PeServer\Core\Http\HttpMethod;
13use PeServer\Core\Http\HttpRequest;
14use PeServer\Core\Http\HttpResponse;
15use PeServer\Core\Http\HttpStatus;
16use PeServer\Core\Http\IResponsePrinterFactory;
17use PeServer\Core\Http\ResponsePrinter;
18use PeServer\Core\IO\Directory;
19use PeServer\Core\IO\File;
20use PeServer\Core\IO\Path;
21use PeServer\Core\Log\ILogger;
22use PeServer\Core\Log\Logging;
23use PeServer\Core\Mvc\Template\ITemplateFactory;
24use PeServer\Core\Mvc\Template\SmartyTemplate;
25use PeServer\Core\Mvc\Template\TemplateFactory;
26use PeServer\Core\Mvc\Template\TemplateOptions;
27use PeServer\Core\Mvc\Template\TemplateParameter;
28use PeServer\Core\ResultData;
29use PeServer\Core\Store\SpecialStore;
30use PeServer\Core\Store\Stores;
31use PeServer\Core\Text;
32use PeServer\Core\Throws\HttpStatusException;
33use PeServer\Core\Throws\InvalidErrorLevelError;
34use PeServer\Core\Throws\InvalidOperationException;
35use PeServer\Core\Throws\Throws;
36use PeServer\Core\Web\UrlHelper;
37use PeServer\Core\Web\WebSecurity;
38use Throwable;
39
40/**
41 * エラーハンドリング処理。
42 */
43class ErrorHandler
44{
45    #region variable
46
47    /** 登録済みか。 */
48    private bool $isRegistered = false;
49
50    #endregion
51
52    public function __construct(
53        protected readonly ILogger $logger
54    ) {
55    }
56
57    #region function
58
59    /**
60     * エラーハンドラの登録処理。
61     *
62     * 明示的に呼び出しが必要。
63     *
64     * @return void
65     */
66    final public function register(): void
67    {
68        if ($this->isRegistered) {
69            throw new InvalidOperationException();
70        }
71
72        register_shutdown_function([$this, 'receiveShutdown']);
73        set_exception_handler([$this, 'receiveException']);
74        set_error_handler([$this, 'receiveError']);
75        $this->isRegistered = true;
76    }
77
78    /**
79     * シャットダウン処理でエラーがあれば処理する。
80     */
81    final public function receiveShutdown(): void
82    {
83        $lastError = error_get_last();
84        if ($lastError === null) {
85            return;
86        }
87
88        $type = $lastError['type'];
89        $message = $lastError['message'];
90        $file = $lastError['file'];
91        $line = $lastError['line'];
92
93        $this->catchErrorCore(
94            $type,
95            $message,
96            $file,
97            $line,
98            null
99        );
100    }
101
102    /**
103     * 未ハンドル例外を処理する。
104     *
105     * @param Throwable $throwable
106     */
107    final public function receiveException(Throwable $throwable): never
108    {
109        $this->catchErrorCore(
110            Throws::getErrorCode($throwable),
111            $throwable->getMessage(),
112            $throwable->getFile(),
113            $throwable->getLine(),
114            $throwable
115        );
116    }
117
118    /**
119     * エラーを処理する。
120     *
121     * @param int $errorNumber
122     * @param string $errorMessage
123     * @param string $errorFile
124     * @param int $errorLineNumber
125     */
126    final public function receiveError(int $errorNumber, string $errorMessage, string $errorFile, int $errorLineNumber/* , array $_ */): never
127    {
128        $this->catchErrorCore(
129            $errorNumber,
130            $errorMessage,
131            $errorFile,
132            $errorLineNumber,
133            null
134        );
135    }
136
137    /**
138     * E_ERROR 的なやつらを一時的に補足する。
139     *
140     * @template TValue
141     * @param callable $action 補足したい処理。
142     * @phpstan-param callable():TValue $action 補足したい処理。
143     * @param int $errorLevel 補足対象のエラーレベル。 https://www.php.net/manual/errorfunc.constants.php
144     * @return ResultData 結果。補足できたかどうかの真偽値が成功状態に設定されるので処理の結果自体は呼び出し側で確認すること。
145     * @phpstan-return ResultData<TValue>
146     */
147    public static function trap(callable $action, int $errorLevel = E_ALL): ResultData
148    {
149        return Code::using(new LocalPhpErrorReceiver($errorLevel), function (LocalPhpErrorReceiver $disposable) use ($action) {
150            $result = $action();
151            if ($disposable->isError) {
152                /** @phpstan-var ResultData<TValue> */
153                return ResultData::createFailure();
154            }
155
156            return ResultData::createSuccess($result);
157        });
158    }
159
160    /**
161     * エラー取得処理(呼び出し側)。
162     *
163     * こいつが呼ばれた時点でもはや何もできない。
164     *
165     * @param int $errorNumber
166     * @param string $message
167     * @param string $file
168     * @param int $lineNumber
169     * @param Throwable|null $throwable
170     * @SuppressWarnings(PHPMD.ExitExpression)
171     */
172    private function catchErrorCore(int $errorNumber, string $message, string $file, int $lineNumber, ?Throwable $throwable): never
173    {
174        $this->catchError($errorNumber, $message, $file, $lineNumber, $throwable);
175        exit($errorNumber);
176    }
177
178    /**
179     * エラー取得処理(本体)。
180     *
181     * @param int $errorNumber
182     * @param string $message
183     * @param string $file
184     * @param int $lineNumber
185     * @param Throwable|null $throwable
186     * @return void
187     */
188    protected function catchError(int $errorNumber, string $message, string $file, int $lineNumber, ?Throwable $throwable): void
189    {
190        $values = [
191            'error_number' => $errorNumber,
192            'message' => $message,
193            'file' => $file,
194            'line_number' => $lineNumber,
195            'throwable' => $throwable,
196        ];
197
198        var_dump($values);
199    }
200
201    #endregion
202}
203
204//phpcs:ignore PSR1.Classes.ClassDeclaration.MultipleClasses
205final class LocalPhpErrorReceiver extends DisposerBase
206{
207    public bool $isError = false;
208
209    public function __construct(int $errorLevel)
210    {
211        if (!$errorLevel) {
212            throw new InvalidErrorLevelError();
213        }
214
215        set_error_handler([$this, 'receiveError'], $errorLevel);
216    }
217
218    protected function disposeImpl(): void
219    {
220        restore_error_handler();
221
222        parent::disposeImpl();
223    }
224
225    /**
226     * エラーを処理する。
227     *
228     * @param int $errorNumber
229     * @param string $errorMessage
230     * @param string $errorFile
231     * @param int $errorLineNumber
232     * @return bool
233     */
234    final public function receiveError(int $errorNumber, string $errorMessage, string $errorFile, int $errorLineNumber): bool
235    {
236        $this->isError = true;
237        return $this->isError;
238    }
239}