Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
93.96% |
140 / 149 |
|
74.19% |
23 / 31 |
CRAP | |
0.00% |
0 / 1 |
Text | |
93.96% |
140 / 149 |
|
74.19% |
23 / 31 |
80.38 | |
0.00% |
0 / 1 |
isNullOrEmpty | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
isNullOrWhiteSpace | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
requireNotNullOrEmpty | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
requireNotNullOrWhiteSpace | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getLength | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getByteCount | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
fromCodePointCore | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
fromCodePoint | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
4 | |||
replaceMap | |
88.89% |
16 / 18 |
|
0.00% |
0 / 1 |
3.01 | |||
format | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
formatNumber | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPosition | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
getLastPosition | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
startsWith | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
6.04 | |||
endsWith | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
6.04 | |||
contains | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
6.05 | |||
substring | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
toLower | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
toUpper | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
split | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
splitLines | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
join | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
createTrimPattern | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
trim | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
trimStart | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
trimEnd | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
2.02 | |||
dump | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
replace | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
5.20 | |||
repeat | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
toCharacters | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
toString | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
3.07 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace PeServer\Core; |
6 | |
7 | use Throwable; |
8 | use PeServer\Core\Throws\ArgumentException; |
9 | use PeServer\Core\Throws\Throws; |
10 | use PeServer\Core\Throws\RegexException; |
11 | use PeServer\Core\Throws\StringException; |
12 | |
13 | /** |
14 | * 文字列操作。 |
15 | */ |
16 | abstract class Text |
17 | { |
18 | #region define |
19 | |
20 | /** トリム対象文字一覧。 */ |
21 | public const TRIM_CHARACTERS = " \n\r\t\v\0 "; |
22 | /** PHP の使ってる対象文字一覧 */ |
23 | public const TRIM_PHP_CHARACTERS = " \t\n\r\0\x0B"; |
24 | private const TRIM_TARGET_START = -1; |
25 | private const TRIM_TARGET_END = 1; |
26 | private const TRIM_TARGET_BOTH = 0; |
27 | |
28 | /** 空文字列。 */ |
29 | public const EMPTY = ''; |
30 | |
31 | #endregion |
32 | |
33 | #region function |
34 | |
35 | /** |
36 | * 文字列が `null` か空か |
37 | * |
38 | * @param string|null $s 対象文字列。 |
39 | * @return bool 真: `null`か空。 |
40 | * @phpstan-assert-if-false non-empty-string $s |
41 | */ |
42 | public static function isNullOrEmpty(?string $s): bool |
43 | { |
44 | if ($s === null) { |
45 | return true; |
46 | } |
47 | |
48 | if ($s === '0') { |
49 | return false; |
50 | } |
51 | |
52 | return empty($s); |
53 | } |
54 | |
55 | /** |
56 | * 文字列がnullかホワイトスペースのみで構築されているか |
57 | * |
58 | * `TRIM_CHARACTERS` がホワイトスペースとして扱われる。 |
59 | * |
60 | * @param string|null $s 対象文字列。 |
61 | * @return bool 真: nullかホワイトスペースのみ。 |
62 | * @phpstan-assert-if-false non-empty-string $s |
63 | */ |
64 | public static function isNullOrWhiteSpace(?string $s): bool |
65 | { |
66 | if (self::isNullOrEmpty($s)) { |
67 | return true; |
68 | } |
69 | |
70 | return strlen(self::trim($s)) === 0; |
71 | } |
72 | |
73 | /** |
74 | * 文字列が `null` か空の場合に代替文字列を返す。 |
75 | * |
76 | * @param string|null $s |
77 | * @param string $fallback |
78 | * @return string |
79 | */ |
80 | public static function requireNotNullOrEmpty(?string $s, string $fallback): string |
81 | { |
82 | if (self::isNullOrEmpty($s)) { |
83 | return $fallback; |
84 | } |
85 | |
86 | return $s; |
87 | } |
88 | |
89 | /** |
90 | * 文字列がnullかホワイトスペースのみで構築されている場合に代替文字列を返す。 |
91 | * |
92 | * @param string|null $s |
93 | * @param string $fallback |
94 | * @return string |
95 | */ |
96 | public static function requireNotNullOrWhiteSpace(?string $s, string $fallback): string |
97 | { |
98 | if (self::isNullOrWhiteSpace($s)) { |
99 | return $fallback; |
100 | } |
101 | |
102 | return $s; |
103 | } |
104 | |
105 | /** |
106 | * 文字列長を取得。 |
107 | * |
108 | * `mb_strlen` ラッパー。 |
109 | * |
110 | * @param string $value 対象文字列。 |
111 | * @return int 文字数。 |
112 | * @phpstan-return non-negative-int |
113 | * @see https://www.php.net/manual/function.mb-strlen.php |
114 | */ |
115 | public static function getLength(string $value): int |
116 | { |
117 | return mb_strlen($value); |
118 | } |
119 | |
120 | /* |
121 | public static function getCharacterLength(string $value): int |
122 | { |
123 | $length = self::getLength($value); |
124 | if($length < 2) { |
125 | return $length; |
126 | } |
127 | return \grapheme_strlen($value); |
128 | } |
129 | */ |
130 | |
131 | /** |
132 | * 文字列バイト数を取得。 |
133 | * |
134 | * `strlen` ラッパー。 |
135 | * |
136 | * @param string $value 対象文字列。 |
137 | * @return int バイト数。 |
138 | * @phpstan-return non-negative-int |
139 | * @see https://www.php.net/manual/function.strlen.php |
140 | */ |
141 | public static function getByteCount(string $value): int |
142 | { |
143 | return strlen($value); |
144 | } |
145 | |
146 | private static function fromCodePointCore(int $value): string |
147 | { |
148 | $single = mb_chr($value); |
149 | if ($single === false) { //@phpstan-ignore-line [PHP_VERSION] |
150 | throw new ArgumentException(); |
151 | } |
152 | |
153 | return $single; |
154 | } |
155 | |
156 | /** |
157 | * Unicode のコードポイントに対応する文字を返す。 |
158 | * |
159 | * `mb_chr` ラッパー。 |
160 | * |
161 | * @param int|int[] $value |
162 | * @phpstan-param non-negative-int|non-negative-int[] $value |
163 | * @return string |
164 | * @see https://www.php.net/manual/function.mb-chr.php |
165 | * @throws ArgumentException |
166 | */ |
167 | public static function fromCodePoint(int|array $value): string |
168 | { |
169 | if (is_int($value)) { |
170 | return self::fromCodePointCore($value); |
171 | } |
172 | |
173 | $result = ''; |
174 | foreach ($value as $cp) { |
175 | if (!is_int($cp)) { //@phpstan-ignore-line [DOCTYPE] |
176 | throw new ArgumentException(); |
177 | } |
178 | |
179 | $result .= self::fromCodePointCore($cp); |
180 | } |
181 | |
182 | return $result; |
183 | } |
184 | |
185 | /** |
186 | * プレースホルダー文字列置き換え処理 |
187 | * |
188 | * @param string $source 元文字列 |
189 | * @phpstan-param literal-string $source |
190 | * @param array<string,string> $map 置き換え対象辞書 |
191 | * @param non-empty-string $head プレースホルダー先頭 |
192 | * @param non-empty-string $tail プレースホルダー終端 |
193 | * @return string 置き換え後文字列 |
194 | * @throws StringException なんかもうあかんかった |
195 | */ |
196 | public static function replaceMap(string $source, array $map, string $head = '{', string $tail = '}'): string |
197 | { |
198 | Throws::throwIfNullOrEmpty($head, Text::EMPTY, StringException::class); |
199 | Throws::throwIfNullOrEmpty($tail, Text::EMPTY, StringException::class); |
200 | |
201 | $regex = new Regex(); |
202 | $escHead = $regex->escape($head); |
203 | $escTail = $regex->escape($tail); |
204 | $pattern = "/$escHead(.+?)$escTail/"; |
205 | |
206 | try { |
207 | $result = $regex->replaceCallback( |
208 | $source, |
209 | $pattern, |
210 | function ($matches) use ($map) { |
211 | if (isset($map[$matches[1]])) { |
212 | return $map[$matches[1]]; |
213 | } |
214 | return Text::EMPTY; |
215 | } |
216 | ); |
217 | |
218 | return $result; |
219 | } catch (Throwable $ex) { |
220 | Throws::reThrow(StringException::class, $ex); |
221 | } |
222 | } |
223 | |
224 | /** |
225 | * `sprintf` ラッパー。 |
226 | * |
227 | * 単純な文字列置き換えであれば `Text::replaceMap` を使用する。 |
228 | * |
229 | * @param string $format |
230 | * @param mixed ...$values |
231 | * @phpstan-param FormatAlias ...$values |
232 | * @return string |
233 | * @see https://www.php.net/manual/function.sprintf.php |
234 | * @see Text::replaceMap() |
235 | */ |
236 | public static function format(string $format, string|int|float ...$values): string |
237 | { |
238 | return sprintf($format, ...$values); |
239 | } |
240 | |
241 | /** |
242 | * 数字を千の位毎にグループ化してフォーマット |
243 | * |
244 | * @param int|float $number フォーマットする数値 |
245 | * @param int $decimals 小数点以下の桁数。 0 を指定すると、 戻り値の $decimalSeparator は省略されます |
246 | * @param string|null $decimalSeparator 小数点を表す区切り文字 |
247 | * @param string|null $thousandsSeparator 千の位毎の区切り文字 |
248 | * @return string 置き換え後文字列 |
249 | * @see https://www.php.net/manual/function.number-format.php |
250 | */ |
251 | public static function formatNumber(int|float $number, int $decimals = 0, ?string $decimalSeparator = '.', ?string $thousandsSeparator = ','): string |
252 | { |
253 | return number_format($number, $decimals, $decimalSeparator, $thousandsSeparator); |
254 | } |
255 | |
256 | /** |
257 | * 文字列位置を取得。 |
258 | * |
259 | * @param string $haystack 対象文字列。 |
260 | * @param string $needle 検索文字列。 |
261 | * @param int $offset 開始文字数目。 |
262 | * @phpstan-param non-negative-int $offset |
263 | * @return int 見つかった文字位置。見つかんない場合は `-1` |
264 | * @phpstan-return non-negative-int|-1 |
265 | * @throws ArgumentException |
266 | */ |
267 | public static function getPosition(string $haystack, string $needle, int $offset = 0): int |
268 | { |
269 | if ($offset < 0) { //@phpstan-ignore-line non-negative-int |
270 | throw new ArgumentException('$offset'); |
271 | } |
272 | |
273 | $result = mb_strpos($haystack, $needle, $offset); |
274 | if ($result === false) { |
275 | return -1; |
276 | } |
277 | |
278 | return $result; |
279 | } |
280 | |
281 | /** |
282 | * 文字列位置を取得。 |
283 | * |
284 | * @param string $haystack 対象文字列。 |
285 | * @param string $needle 検索文字列。 |
286 | * @param int $offset 終端文字数目。 |
287 | * @phpstan-param non-negative-int $offset |
288 | * @return int 見つかった文字位置。見つかんない場合は `-1` |
289 | * @phpstan-return non-negative-int|-1 |
290 | * @throws ArgumentException |
291 | */ |
292 | public static function getLastPosition(string $haystack, string $needle, int $offset = 0): int |
293 | { |
294 | if ($offset < 0) { //@phpstan-ignore-line [DOCTYPE] |
295 | throw new ArgumentException('$offset'); |
296 | } |
297 | |
298 | $result = mb_strrpos($haystack, $needle, $offset); |
299 | if ($result === false) { |
300 | return -1; |
301 | } |
302 | |
303 | return $result; |
304 | } |
305 | |
306 | /** |
307 | * 先頭文字列一致判定。 |
308 | * |
309 | * @param string $haystack 対象文字列。 |
310 | * @param string $needle 検索文字列。 |
311 | * @param boolean $ignoreCase 大文字小文字を無視するか。 |
312 | * @return boolean |
313 | */ |
314 | public static function startsWith(string $haystack, string $needle, bool $ignoreCase): bool |
315 | { |
316 | if (!$ignoreCase && function_exists('str_starts_with')) { |
317 | return str_starts_with($haystack, $needle); |
318 | } |
319 | |
320 | if (self::isNullOrEmpty($needle)) { |
321 | return true; |
322 | } |
323 | if (strlen($haystack) < strlen($needle)) { |
324 | return false; |
325 | } |
326 | |
327 | $word = mb_substr($haystack, 0, mb_strlen($needle)); |
328 | |
329 | if ($ignoreCase) { |
330 | return !strcasecmp($needle, $word); |
331 | } |
332 | return $needle === $word; |
333 | } |
334 | |
335 | /** |
336 | * 終端文字列一致判定。 |
337 | * |
338 | * @param string $haystack 対象文字列。 |
339 | * @param string $needle 検索文字列。 |
340 | * @param boolean $ignoreCase 大文字小文字を無視するか。 |
341 | * @return boolean |
342 | */ |
343 | public static function endsWith(string $haystack, string $needle, bool $ignoreCase): bool |
344 | { |
345 | if (!$ignoreCase && function_exists('str_ends_with')) { |
346 | return str_ends_with($haystack, $needle); |
347 | } |
348 | |
349 | if (self::isNullOrEmpty($needle)) { |
350 | return true; |
351 | } |
352 | if (strlen($haystack) < strlen($needle)) { |
353 | return false; |
354 | } |
355 | |
356 | $word = mb_substr($haystack, -mb_strlen($needle)); |
357 | |
358 | if ($ignoreCase) { |
359 | return !strcasecmp($needle, $word); |
360 | } |
361 | return $needle === $word; |
362 | } |
363 | |
364 | /** |
365 | * 文字列を含んでいるか判定。 |
366 | * |
367 | * @param string $haystack 対象文字列。 |
368 | * @param string $needle 検索文字列。 |
369 | * @param boolean $ignoreCase 大文字小文字を無視するか。 |
370 | * @return boolean |
371 | */ |
372 | public static function contains(string $haystack, string $needle, bool $ignoreCase): bool |
373 | { |
374 | if (!$ignoreCase && function_exists('str_contains')) { |
375 | return str_contains($haystack, $needle); |
376 | } |
377 | |
378 | if (self::isNullOrEmpty($needle)) { |
379 | return true; |
380 | } |
381 | if (strlen($haystack) < strlen($needle)) { |
382 | return false; |
383 | } |
384 | |
385 | if ($ignoreCase) { |
386 | return stripos($haystack, $needle) !== false; |
387 | } |
388 | |
389 | return strpos($haystack, $needle) !== false; |
390 | } |
391 | |
392 | /** |
393 | * 文字列部分切り出し。 |
394 | * |
395 | * @param string $value 対象文字列。 |
396 | * @param int $offset 開始文字数目。負数の場合は後ろから。 |
397 | * @param int $length 抜き出す長さ。負数の場合は最後まで($offset) |
398 | * @return string 切り抜き後文字列。 |
399 | */ |
400 | public static function substring(string $value, int $offset, int $length = -1): string |
401 | { |
402 | return mb_substr($value, $offset, 0 <= $length ? $length : null); |
403 | } |
404 | |
405 | /** |
406 | * 大文字を小文字に変換。 |
407 | * |
408 | * @param string $value |
409 | * @return string |
410 | */ |
411 | public static function toLower(string $value): string |
412 | { |
413 | return mb_strtolower($value); |
414 | } |
415 | |
416 | /** |
417 | * 小文字を大文字に変換。 |
418 | * |
419 | * @param string $value |
420 | * @return string |
421 | */ |
422 | public static function toUpper(string $value): string |
423 | { |
424 | return mb_strtoupper($value); |
425 | } |
426 | |
427 | /** |
428 | * 文字列分割。 |
429 | * |
430 | * `explode` ラッパー。 |
431 | * |
432 | * @param string $value 対象文字列。 |
433 | * @param non-empty-string $separator 分割対象文字列。 |
434 | * @param int $limit 分割数。 |
435 | * @return string[] 分割された文字列。 |
436 | * @throws ArgumentException 分割失敗(PHP8未満) |
437 | * @throws \ValueError 分割失敗(PHP8以上) |
438 | * @see https://www.php.net/manual/function.explode.php |
439 | */ |
440 | public static function split(string $value, string $separator, int $limit = PHP_INT_MAX): array |
441 | { |