Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.44% covered (success)
94.44%
34 / 36
87.50% covered (warning)
87.50%
7 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Throws
94.44% covered (success)
94.44%
34 / 36
87.50% covered (warning)
87.50%
7 / 8
22.08
0.00% covered (danger)
0.00%
0 / 1
 getErrorCode
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 reThrow
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 wrap
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
9
 throwCore
50.00% covered (danger)
50.00%
2 / 4
0.00% covered (danger)
0.00%
0 / 1
2.50
 throwIf
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 throwIfNull
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 throwIfNullOrEmpty
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 throwIfNullOrWhiteSpace
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace PeServer\Core\Throws;
6
7use Throwable;
8use TypeError;
9use PeServer\Core\Collection\Arr;
10use PeServer\Core\ReflectionUtility;
11use PeServer\Core\Text;
12use PeServer\Core\Type;
13
14/**
15 * 例外処理系。
16 */
17abstract class Throws
18{
19    #region function
20
21    /**
22     * `Throwable::getCode` のラッパー。
23     *
24     * なんかもうつらい。
25     *
26     * @param Throwable $throwable
27     * @return int 取得したエラーコード。取得できなかった場合は `PHP_INT_MIN` を返す。
28     */
29    public static function getErrorCode(Throwable $throwable): int
30    {
31        $rawCode = $throwable->getCode();
32        $code = PHP_INT_MIN;
33        if (is_int($rawCode)) {
34            $code = $rawCode;
35        }
36
37        return $code;
38    }
39
40    /**
41     * 例外の再スロー。
42     *
43     * @param class-string<Throwable> $className 例外名。
44     * @param Throwable $previous ラップする元の例外。
45     */
46    public static function reThrow(string $className, Throwable $previous, string $message = null): never
47    {
48        $message = $message ?? $previous->getMessage();
49        $code = self::getErrorCode($previous);
50
51        /** @var Throwable */
52        $exception = ReflectionUtility::create($className, Throwable::class, $message, $code, $previous);
53        throw $exception;
54    }
55
56    /**
57     * 対象の例外を受け取った場合に指定した例外として再送出する。
58     *
59     * @template TResult
60     * @param $catchExceptions 対象の例外名(複数ある場合は配列で指定)。
61     * @phpstan-param class-string<Throwable>|non-empty-array<class-string<Throwable>> $catchExceptions 対象の例外名(複数ある場合は配列で指定)。
62     * @param class-string<Throwable> $throwException 再送出する例外名。
63     * @param callable $callback 例外を発生させる可能性のある処理。
64     * @phpstan-param callable():TResult $callback
65     * @return mixed `$callback` が戻り値を持つ場合にその値。戻り値を持たない場合は `null`。
66     * @phpstan-return TResult
67     * @throws TypeError 入力パラメータが不正。
68     */
69    public static function wrap(string|array $catchExceptions, string $throwException, callable $callback): mixed
70    {
71        if (is_string($catchExceptions)) {
72            $catchExceptions = [$catchExceptions];
73        } elseif (Arr::isNullOrEmpty($catchExceptions)) { //@phpstan-ignore-line [DOCTYPE] non-empty-array
74            throw new TypeError('array: $catchException');
75        }
76
77        foreach ($catchExceptions as $key => $catchException) {
78            if (!is_subclass_of($catchException, Throwable::class)) { //@phpstan-ignore-line [DOCTYPE] class-string<Throwable>
79                throw new TypeError('$catchException[' . $key . ']: ' . $catchException);
80            }
81        }
82
83        if (!is_subclass_of($throwException, Throwable::class)) { //@phpstan-ignore-line [DOCTYPE] class-string<Throwable>
84            throw new TypeError('$throwException');
85        }
86
87        try {
88            return $callback();
89        } catch (Throwable $throwable) {
90            foreach ($catchExceptions as $key => $catchException) {
91                if (is_a($throwable, $catchException)) {
92                    self::reThrow($throwException, $throwable);
93                }
94            }
95            throw $throwable;
96        }
97    }
98
99    /**
100     * 例外ぶん投げ。
101     *
102     * @param string $argument
103     * @param class-string<Throwable> $exceptionClass
104     */
105    private static function throwCore(string $argument, string $exceptionClass): never
106    {
107        try {
108            $exception = ReflectionUtility::create($exceptionClass, Throwable::class, $argument);
109        } catch (TypeError $ex) {
110            throw new InvalidClassNameError($exceptionClass);
111        }
112
113        throw $exception;
114    }
115
116    /**
117     * 偽の場合に例外。
118     *
119     * @param boolean $value
120     * @param string $argument
121     * @param class-string<Throwable> $exceptionClass
122     * @return void
123     */
124    public static function throwIf(bool $value, string $argument = '', string $exceptionClass = InvalidException::class): void
125    {
126        if (!$value) {
127            self::throwCore($argument, $exceptionClass);
128        }
129    }
130
131    /**
132     * nullの場合に例外。
133     *
134     * @param mixed $value
135     * @param string $argument
136     * @param class-string<Throwable> $exceptionClass
137     * @return void
138     */
139    public static function throwIfNull(mixed $value, string $argument = '', string $exceptionClass = InvalidException::class): void
140    {
141        if ($value === null) {
142            self::throwCore($argument, $exceptionClass);
143        }
144    }
145
146    /**
147     * 文字列がnullか空の場合に例外。
148     *
149     * @param string|null $value
150     * @param string $argument
151     * @param class-string<Throwable> $exceptionClass
152     * @return void
153     */
154    public static function throwIfNullOrEmpty(?string $value, string $argument = '', string $exceptionClass = InvalidException::class): void
155    {
156        if (Text::isNullOrEmpty($value)) {
157            self::throwCore($argument, $exceptionClass);
158        }
159    }
160
161    /**
162     * 文字列がnullかホワイトスペースのみの場合に例外。
163     *
164     * @param string|null $value
165     * @param string $argument
166     * @param class-string<Throwable> $exceptionClass
167     * @return void
168     */
169    public static function throwIfNullOrWhiteSpace(?string $value, string $argument = '', string $exceptionClass = InvalidException::class): void
170    {
171        if (Text::isNullOrWhiteSpace($value)) {
172            self::throwCore($argument, $exceptionClass);
173        }
174    }
175
176    #endregion
177}