Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
72.22% |
39 / 54 |
|
46.67% |
7 / 15 |
CRAP | |
0.00% |
0 / 1 |
Encoding | |
72.22% |
39 / 54 |
|
46.67% |
7 / 15 |
42.63 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
throwIfInvalidEncodingName | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getEncodingNames | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDefaultEncoding | |
33.33% |
2 / 6 |
|
0.00% |
0 / 1 |
5.67 | |||
setDefaultEncoding | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
getAscii | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getUtf8 | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getUtf16 | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUtf32 | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getShiftJis | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getBinary | |
66.67% |
6 / 9 |
|
0.00% |
0 / 1 |
4.59 | |||
toString | |
66.67% |
6 / 9 |
|
0.00% |
0 / 1 |
4.59 | |||
getAliasNames | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
isValid | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getByteOrderMark | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace PeServer\Core; |
6 | |
7 | use ValueError; |
8 | use PeServer\Core\Binary; |
9 | use PeServer\Core\Collection\Arr; |
10 | use PeServer\Core\Throws\ArgumentException; |
11 | use PeServer\Core\Throws\EncodingException; |
12 | use PeServer\Core\Throws\InvalidOperationException; |
13 | use PeServer\Core\Throws\Throws; |
14 | |
15 | /** |
16 | * エンコーディング処理。 |
17 | * |
18 | * コンストラクタで指定されたエンコード名に対して通常文字列へあれこれする。 |
19 | */ |
20 | class Encoding |
21 | { |
22 | #region define |
23 | |
24 | /** アスキー */ |
25 | public const ENCODE_ASCII = 'ASCII'; |
26 | |
27 | /** UTF-7 */ |
28 | public const ENCODE_UTF7 = 'UTF-7'; |
29 | /** UTF-8 */ |
30 | public const ENCODE_UTF8 = 'UTF-8'; |
31 | /** UTF-16 */ |
32 | public const ENCODE_UTF16_PLAIN = 'UTF-16'; |
33 | public const ENCODE_UTF16_BE = 'UTF-16BE'; |
34 | public const ENCODE_UTF16_LE = 'UTF-16LE'; |
35 | public const ENCODE_UTF16_DEFAULT = self::ENCODE_UTF16_LE; |
36 | /** UTF-32 */ |
37 | public const ENCODE_UTF32_PLAIN = 'UTF-32'; |
38 | public const ENCODE_UTF32_BE = 'UTF-32BE'; |
39 | public const ENCODE_UTF32_LE = 'UTF-32LE'; |
40 | public const ENCODE_UTF32_DEFAULT = self::ENCODE_UTF32_LE; |
41 | |
42 | /** SJIS(SHIFT-JIS) */ |
43 | public const ENCODE_SJIS_PLAIN = 'SJIS'; |
44 | /** SJIS(CP932) */ |
45 | public const ENCODE_SJIS_WIN31J = 'CP932'; |
46 | /** SJIS(Windows) */ |
47 | public const ENCODE_SJIS_WIN = 'SJIS-win'; |
48 | /** SJIS(Shift_JIS-2004) */ |
49 | public const ENCODE_SJIS_2004 = 'SJIS-2004'; |
50 | /** SJIS(何も考えず使う用) */ |
51 | public const ENCODE_SJIS_DEFAULT = self::ENCODE_SJIS_WIN31J; |
52 | |
53 | public const ENCODE_JIS_PLAIN = 'JIS'; |
54 | public const ENCODE_JIS_DEFAULT = self::ENCODE_JIS_PLAIN; |
55 | |
56 | public const ENCODE_EUC_JP_PLAIN = 'EUC-JP'; |
57 | public const ENCODE_EUC_JP_WIN = 'eucJP-win'; |
58 | public const ENCODE_EUC_JP_DEFAULT = self::ENCODE_EUC_JP_WIN; |
59 | |
60 | #endregion |
61 | |
62 | #region variable |
63 | |
64 | /** |
65 | * キャッシュされたエンコーディング名一覧。 |
66 | * |
67 | * @var string[]|null |
68 | */ |
69 | protected static ?array $cacheNames = null; |
70 | |
71 | /** |
72 | * デフォルトエンコーディング。 |
73 | * |
74 | * `setDefaultEncoding` で設定され、 |
75 | * `getDefaultEncoding` で使用される。 |
76 | * |
77 | * ただし `getDefaultEncoding` で本プロパティ未設定の場合は上書きされる。 |
78 | * |
79 | * @var self|null |
80 | */ |
81 | private static ?self $defaultEncoding = null; |
82 | |
83 | /** |
84 | * エンコード名。 |
85 | */ |
86 | public readonly string $name; |
87 | |
88 | #endregion |
89 | |
90 | /** |
91 | * 生成 |
92 | * |
93 | * @param string $name エンコード名。 |
94 | * @phpstan-param non-empty-string|Encoding::ENCODE_* $name |
95 | */ |
96 | public function __construct(string $name) |
97 | { |
98 | self::throwIfInvalidEncodingName($name); |
99 | $this->name = $name; |
100 | } |
101 | |
102 | #region function |
103 | |
104 | /** |
105 | * エンコーディング名が正しいか。 |
106 | * |
107 | * @param string $name |
108 | * @throws ArgumentException 正しくない。 |
109 | */ |
110 | private static function throwIfInvalidEncodingName(string $name): void |
111 | { |
112 | $names = self::getEncodingNames(); |
113 | if (!Arr::containsValue($names, $name)) { |
114 | throw new ArgumentException('$name'); |
115 | } |
116 | } |
117 | |
118 | /** |
119 | * エンコーディング名一覧を取得。 |
120 | * |
121 | * キャッシュされる。 |
122 | * |
123 | * `mb_list_encodings` ラッパー。 |
124 | * |
125 | * @return string[] |
126 | * @see https://www.php.net/manual/function.mb-list-encodings.php |
127 | */ |
128 | public static function getEncodingNames(): array |
129 | { |
130 | return self::$cacheNames ??= mb_list_encodings(); |
131 | } |
132 | |
133 | /** |
134 | * `mb_internal_encoding` ラッパー |
135 | * |
136 | * @return Encoding |
137 | * @see https://www.php.net/manual/function.mb-internal-encoding.php |
138 | */ |
139 | public static function getDefaultEncoding(): Encoding |
140 | { |
141 | if (self::$defaultEncoding === null) { |
142 | $name = mb_internal_encoding(); |
143 | if (Text::isNullOrEmpty($name)) { |
144 | throw new InvalidOperationException(); |
145 | } |
146 | |
147 | return self::$defaultEncoding = new Encoding($name); |
148 | } |
149 | |
150 | return self::$defaultEncoding; |
151 | } |
152 | |
153 | /** |
154 | * `mb_internal_encoding` ラッパー |
155 | * |
156 | * @param Encoding $encoding |
157 | * @throws ArgumentException |
158 | * @see https://www.php.net/manual/function.mb-internal-encoding.php |
159 | */ |
160 | public static function setDefaultEncoding(Encoding $encoding): void |
161 | { |
162 | $result = mb_internal_encoding($encoding->name); |
163 | if (!$result) { |
164 | throw new ArgumentException('$encoding'); |
165 | } |
166 | |
167 | self::$defaultEncoding = $encoding; |
168 | } |
169 | |
170 | /** |
171 | * ASCIIエンコーディング。 |
172 | * |
173 | * @return Encoding |
174 | */ |
175 | public static function getAscii(): Encoding |
176 | { |
177 | return new Encoding(self::ENCODE_ASCII); |
178 | } |
179 | |
180 | /** |
181 | * UTF8エンコーディング。 |
182 | * |
183 | * @return Encoding |
184 | */ |
185 | public static function getUtf8(): Encoding |
186 | { |
187 | return new Encoding(self::ENCODE_UTF8); |
188 | } |
189 | |
190 | /** |
191 | * UTF16エンコーディング。 |
192 | * |
193 | * @return Encoding |
194 | */ |
195 | public static function getUtf16(): Encoding |
196 | { |
197 | return new Encoding(self::ENCODE_UTF16_DEFAULT); |
198 | } |
199 | |
200 | /** |
201 | * UTF32エンコーディング。 |
202 | * |
203 | * @return Encoding |
204 | */ |
205 | public static function getUtf32(): Encoding |
206 | { |
207 | return new Encoding(self::ENCODE_UTF32_DEFAULT); |
208 | } |
209 | |
210 | /** |
211 | * SJISエンコーディング。 |
212 | * |
213 | * @return Encoding |
214 | */ |
215 | public static function getShiftJis(): Encoding |
216 | { |
217 | return new Encoding(self::ENCODE_SJIS_DEFAULT); |
218 | } |
219 | |
220 | /** |
221 | * 文字列(デフォルトエンコーディング)を現在のエンコーディングへ変換。 |
222 | * |
223 | * @param string $input |
224 | * @return Binary |
225 | * @throws EncodingException |
226 | * @see https://www.php.net/manual/function.mb-convert-encoding.php |
227 | */ |
228 | public function getBinary(string $input): Binary |
229 | { |
230 | $default = self::getDefaultEncoding(); |
231 | if ($default->name === $this->name) { |
232 | return new Binary($input); |
233 | } |
234 | |
235 | try { |
236 | $output = mb_convert_encoding($input, $this->name, $default->name); |
237 | if ($output === false) { //@phpstan-ignore-line [PHP_VERSION] |
238 | throw new EncodingException(); |
239 | } |
240 | return new Binary($output); |
241 | } catch (ValueError $err) { |
242 | Throws::reThrow(EncodingException::class, $err); |
243 | } |
244 | } |
245 | |
246 | /** |
247 | * 現在のエンコーディングデータを文字列(デフォルトエンコーディング)へ変換。 |
248 | * |
249 | * @param Binary $input |
250 | * @return string |
251 | * @throws EncodingException |
252 | * @see https://www.php.net/manual/function.mb-convert-encoding.php |
253 | */ |
254 | public function toString(Binary $input): string |
255 | { |
256 | $default = self::getDefaultEncoding(); |
257 | if ($default->name === $this->name) { |
258 | return $input->raw; |
259 | } |
260 | |
261 | try { |
262 | $output = mb_convert_encoding($input->raw, $default->name, $this->name); |
263 | if ($output === false) { //@phpstan-ignore-line |
264 | throw new EncodingException(); |
265 | } |
266 | return $output; |
267 | } catch (ValueError $err) { |
268 | Throws::reThrow(EncodingException::class, $err); |
269 | } |
270 | } |
271 | |
272 | /** |
273 | * 現在のエンコーディング・タイプのエイリアスを取得。 |
274 | * |
275 | * @return string[] |
276 | * @see https://www.php.net/manual/function.mb-encoding-aliases.php |
277 | */ |
278 | public static function getAliasNames(string $encoding): array |
279 | { |
280 | $names = Throws::wrap(ValueError::class, EncodingException::class, fn () => mb_encoding_aliases($encoding)); |
281 | if ($names === false) { //@phpstan-ignore-line [PHP_VERSION] |
282 | throw new EncodingException($encoding); |
283 | } |
284 | |
285 | return $names; |
286 | } |
287 | |
288 | /** |
289 | * 現在のエンコーディングで有効か。 |
290 | * |
291 | * @param Binary $input |
292 | * @return bool 有効か。 |
293 | */ |
294 | public function isValid(Binary $input): bool |
295 | { |
296 | return mb_check_encoding($input->raw, $this->name); |
297 | } |
298 | |
299 | /** |
300 | * 現在のエンコーディングからBOMを取得する。 |
301 | * |
302 | * @return Binary エンコーディング対するBOM。対応しない場合空のバイナリ。 |
303 | */ |
304 | public function getByteOrderMark(): Binary |
305 | { |
306 | //wikipedia の bom 見たけどわからん。 |
307 | //TODO: 定義しているエンコーディングともあわんし、どうしたもんか |
308 | $bomMap = [ |
309 | 'UTF-8' => "\xEF\xBB\xBF", |
310 | 'UTF-16BE' => "\xFE\xFF", |
311 | 'UTF-16LE' => "\xFF\xFE", |
312 | 'UTF-32BE' => "\x00\x00\xFE\xFF", |
313 | 'UTF-32LE' => "\xFF\xFE\x00\x00", |
314 | ]; |
315 | |
316 | if (isset($bomMap[$this->name])) { |
317 | return new Binary($bomMap[$this->name]); |
318 | } |
319 | |
320 | return new Binary(''); |
321 | } |
322 | |
323 | #endregion |
324 | } |