Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.14% covered (success)
97.14%
34 / 35
93.75% covered (success)
93.75%
15 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
Binary
97.14% covered (success)
97.14%
34 / 35
93.75% covered (success)
93.75%
15 / 16
23
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRange
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isEquals
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toHex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toBase64
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fromBase64
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 hasNull
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 toString
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 toArray
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 offsetExists
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 offsetGet
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 offsetSet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetUnset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getIterator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 count
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __toString
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace PeServer\Core;
6
7use ArrayAccess;
8use ArrayIterator;
9use Countable;
10use Iterator;
11use IteratorAggregate;
12use Stringable;
13use TypeError;
14use PeServer\Core\Collection\ArrayAccessHelper;
15use PeServer\Core\Throws\ArgumentException;
16use PeServer\Core\Throws\IndexOutOfRangeException;
17use PeServer\Core\Throws\NotSupportedException;
18use PeServer\Core\Throws\NullByteStringException;
19use PeServer\Core\Throws\BinaryException;
20use PeServer\Core\Throws\Throws;
21use ValueError;
22
23/**
24 * PHP文字列がバイトデータなのか普通の文字列なのかよくわからん。
25 *
26 * ソース上の型を明示するだけの目的で、効率とかは特になにもない。
27 * あとUTF8で動くこと前提。
28 *
29 * @phpstan-type Byte int<0,255>
30 * @implements ArrayAccess<non-negative-int,Byte>
31 * @implements IteratorAggregate<non-negative-int,Byte>
32 */
33readonly final class Binary implements ArrayAccess, IteratorAggregate, Countable, Stringable
34{
35    /**
36     * 生成。
37     *
38     * @param string $raw バイトデータとして扱う文字列。
39     */
40    public function __construct(
41        public readonly string $raw
42    ) {
43        //NOP
44    }
45
46    #region function
47
48    /**
49     * `substr` ラッパー。
50     *
51     * @param int $index
52     * @param int|null $length
53     * @return self
54     */
55    public function getRange(int $index, ?int $length = null): self
56    {
57        $raw = substr($this->raw, $index, $length);
58        return new self($raw);
59    }
60
61    /**
62     * 指定したバイナリと等しいか。
63     *
64     * @param self $target 対象バイナリ。
65     * @return bool 等しいか。
66     */
67    public function isEquals(self $target): bool
68    {
69        return $this->raw === $target->raw;
70    }
71
72    /**
73     * 16進数文字列に変換。
74     *
75     * `bin2hex` ラッパー。
76     *
77     * @return string `[0-9a-f]{2}*` で構成された文字列。
78     * @see https://www.php.net/manual/function.bin2hex.php
79     */
80    public function toHex(): string
81    {
82        return bin2hex($this->raw);
83    }
84
85    // public function convert(int $from, int $to): string
86    // {
87    //     return base_convert($this->raw, $from, $to);
88    // }
89
90    /**
91     * base64 文字列に変換。
92     *
93     * `base64_encode` ラッパー。
94     *
95     * @return string
96     * @see https://www.php.net/manual/function.base64-encode.php
97     */
98    public function toBase64(): string
99    {
100        return base64_encode($this->raw);
101    }
102
103    /**
104     * base64 文字列から Binary を取得。
105     *
106     * `base64_decode` ラッパー。
107     *
108     * @param string $base64
109     * @return Binary
110     * @throws ArgumentException 変換失敗。
111     * @see https://www.php.net/manual/function.base64-decode.php
112     */
113    public static function fromBase64(string $base64): Binary
114    {
115        $value = base64_decode($base64, true);
116        if ($value === false) {
117            throw new ArgumentException('$base64');
118        }
119
120        return new Binary($value);
121    }
122
123    /**
124     * NULLバイトを持つか。
125     *
126     * @return boolean NULLバイトを持つ。
127     */
128    public function hasNull(): bool
129    {
130        $nullIndex = mb_strpos($this->raw, "\0");
131        return $nullIndex !== false;
132    }
133
134    /**
135     * バイトデータを文字列に変換。
136     *
137     * @return string
138     * @throws NullByteStringException NULLバイトが存在する。
139     */
140    public function toString(): string
141    {
142        if ($this->hasNull()) {
143            throw new NullByteStringException();
144        }
145
146        return $this->raw;
147    }
148
149    /**
150     * 配列化。
151     *
152     * `unpack` ラッパー。
153     *
154     * @param string $format
155     * @param int $offset
156     * @phpstan-param non-negative-int $offset
157     * @return array<mixed>
158     * @see https://www.php.net/manual/function.unpack.php
159     */
160    public function toArray(string $format, int $offset = 0): array
161    {
162        $result = Throws::wrap(ValueError::class, BinaryException::class, fn () => unpack($format, $this->raw, $offset));
163        if ($result === false) {
164            throw new BinaryException();
165        }
166
167        /** @var array<mixed> */
168        return $result;
169    }
170
171
172    #endregion
173
174    #region ArrayAccess
175
176    /**
177     * @param int $offset
178     * @phpstan-param non-negative-int $offset
179     * @return bool
180     * @see ArrayAccess::offsetExists
181     */
182    public function offsetExists(mixed $offset): bool
183    {
184        if (!ArrayAccessHelper::offsetExistsUInt($offset)) { //@phpstan-ignore-line [DOCTYPE] non-negative-int
185            return false;
186        }
187
188        if (strlen($this->raw) <= $offset) {
189            return false;
190        }
191
192        return true;
193    }
194
195    /**
196     * @param int $offset
197     * @phpstan-param non-negative-int $offset
198     * @return int
199     * @phpstan-return Byte
200     * @throws TypeError
201     * @throws IndexOutOfRangeException
202     * @see ArrayAccess::offsetGet
203     */
204    public function offsetGet(mixed $offset): mixed
205    {
206        ArrayAccessHelper::offsetGetUInt($offset); //@phpstan-ignore-line [DOCTYPE] non-negative-int
207
208        if (strlen($this->raw) <= $offset) {
209            throw new IndexOutOfRangeException((string)$offset);
210        }
211
212        /** @phpstan-var Byte */
213        return ord($this->raw[$offset]);
214    }
215
216    /** @throws NotSupportedException */
217    public function offsetSet(mixed $offset, mixed $value): void
218    {
219        throw new NotSupportedException();
220    }
221
222    /** @throws NotSupportedException */
223    public function offsetUnset(mixed $offset): void
224    {
225        throw new NotSupportedException();
226    }
227
228    #endregion
229
230    #region IteratorAggregate
231
232    public function getIterator(): Iterator
233    {
234        return new ArrayIterator(str_split($this->raw));
235    }
236
237    #endregion
238
239    #region Countable
240
241    /**
242     * バイト長を取得。
243     *
244     * `strlen` ラッパー。
245     *
246     * @return int
247     * @phpstan-return non-negative-int
248     * @see https://www.php.net/manual/function.strlen.php
249     */
250    public function count(): int
251    {
252        return strlen($this->raw);
253    }
254
255    #endregion
256
257    #region Stringable
258
259    public function __toString(): string
260    {
261        if ($this->hasNull()) {
262            return $this->toHex();
263        }
264
265        return $this->raw;
266    }
267
268    #endregion
269}