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