• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tempestphp / tempest-framework / 11294471361

11 Oct 2024 02:47PM UTC coverage: 82.102% (-0.03%) from 82.134%
11294471361

Pull #556

github

web-flow
Merge 92f1b555e into c1233aa56
Pull Request #556: fix(phpstan): fix phpstan issues

5 of 13 new or added lines in 5 files covered. (38.46%)

1 existing line in 1 file now uncovered.

6757 of 8230 relevant lines covered (82.1%)

38.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.05
/src/Tempest/Support/src/StringHelper.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Tempest\Support;
6

7
use Countable;
8
use function ltrim;
9
use function preg_quote;
10
use function preg_replace;
11
use function rtrim;
12
use Stringable;
13
use function trim;
14

15
final readonly class StringHelper implements Stringable
16
{
17
    public function __construct(
159✔
18
        private string $string = '',
19
    ) {
20
    }
159✔
21

22
    public function toString(): string
48✔
23
    {
24
        return $this->string;
48✔
25
    }
26

27
    public function __toString(): string
135✔
28
    {
29
        return $this->string;
135✔
30
    }
31

32
    public function equals(string|Stringable $other): bool
20✔
33
    {
34
        return $this->string === (string) $other;
20✔
35
    }
36

37
    public function title(): self
1✔
38
    {
39
        return new self(mb_convert_case($this->string, MB_CASE_TITLE, 'UTF-8'));
1✔
40
    }
41

42
    public function lower(): self
×
43
    {
44
        return new self(mb_strtolower($this->string, 'UTF-8'));
×
45
    }
46

47
    public function upper(): self
×
48
    {
49
        return new self(mb_strtoupper($this->string, 'UTF-8'));
×
50
    }
51

52
    public function snake(string $delimiter = '_'): self
53✔
53
    {
54
        $string = $this->string;
53✔
55

56
        if (ctype_lower($string)) {
53✔
57
            return $this;
×
58
        }
59

60
        $string = preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $string);
53✔
61
        $string = preg_replace(
53✔
62
            '![^' . preg_quote($delimiter) . '\pL\pN\s]+!u',
53✔
63
            $delimiter,
53✔
64
            mb_strtolower($string, 'UTF-8')
53✔
65
        );
53✔
66
        $string = preg_replace('/\s+/u', $delimiter, $string);
53✔
67
        $string = trim($string, $delimiter);
53✔
68

69
        return (new self($string))->deduplicate($delimiter);
53✔
70
    }
71

72
    public function kebab(): self
1✔
73
    {
74
        return $this->snake('-');
1✔
75
    }
76

77
    public function pascal(): self
38✔
78
    {
79
        $words = explode(' ', str_replace(['-', '_'], ' ', $this->string));
38✔
80

81
        // TODO: use `mb_ucfirst` when it has landed in PHP 8.4
82
        $studlyWords = array_map(static fn (string $word) => ucfirst($word), $words);
38✔
83

84
        return new self(implode('', $studlyWords));
38✔
85
    }
86

87
    public function camel(): self
34✔
88
    {
89
        return new self(lcfirst((string)$this->pascal()));
34✔
90
    }
91

92
    public function deduplicate(string|array $characters = ' '): self
54✔
93
    {
94
        $string = $this->string;
54✔
95

96
        foreach (arr($characters) as $character) {
54✔
97
            $string = preg_replace('/' . preg_quote($character, '/') . '+/u', $character, $string);
54✔
98
        }
99

100
        return new self($string);
54✔
101
    }
102

103
    public function pluralize(int|array|Countable $count = 2): self
52✔
104
    {
105
        return new self(LanguageHelper::pluralize($this->string, $count));
52✔
106
    }
107

108
    public function pluralizeLast(int|array|Countable $count = 2): self
52✔
109
    {
110
        $parts = preg_split('/(.)(?=[A-Z])/u', $this->string, -1, PREG_SPLIT_DELIM_CAPTURE);
52✔
111

112
        $lastWord = array_pop($parts);
52✔
113

114
        $string = implode('', $parts) . (new self($lastWord))->pluralize($count);
52✔
115

116
        return new self($string);
52✔
117
    }
118

119
    public function random(int $length = 16): self
3✔
120
    {
121
        $string = '';
3✔
122

123
        while (($len = strlen($string)) < $length) {
3✔
124
            $size = $length - $len;
2✔
125
            $bytesSize = (int)ceil($size / 3) * 3;
2✔
126
            $bytes = random_bytes($bytesSize);
2✔
127
            $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), offset: 0, length: $size);
2✔
128
        }
129

130
        return new self($string);
3✔
131
    }
132

133
    public function finish(string $cap): self
1✔
134
    {
135
        return new self(
1✔
136
            preg_replace('/(?:' . preg_quote($cap, '/') . ')+$/u', replacement: '', subject: $this->string) . $cap
1✔
137
        );
1✔
138
    }
139

140
    public function after(Stringable|string|array $search): self
2✔
141
    {
142
        $search = $this->normalizeString($search);
2✔
143

144
        if ($search === '' || $search === []) {
2✔
145
            return $this;
1✔
146
        }
147

148
        $nearestPosition = mb_strlen($this->string); // Initialize with a large value
2✔
149
        $foundSearch = '';
2✔
150

151
        foreach (arr($search) as $term) {
2✔
152
            $position = mb_strpos($this->string, $term);
2✔
153

154
            if ($position !== false && $position < $nearestPosition) {
2✔
155
                $nearestPosition = $position;
2✔
156
                $foundSearch = $term;
2✔
157
            }
158
        }
159

160
        if ($nearestPosition === mb_strlen($this->string)) {
2✔
161
            return $this;
2✔
162
        }
163

164
        $string = mb_substr($this->string, $nearestPosition + mb_strlen($foundSearch));
2✔
165

166
        return new self($string);
2✔
167
    }
168

169
    public function afterLast(Stringable|string|array $search): self
3✔
170
    {
171
        $search = $this->normalizeString($search);
3✔
172

173
        if ($search === '' || $search === []) {
3✔
174
            return $this;
1✔
175
        }
176

177
        $farthestPosition = -1;
3✔
178
        $foundSearch = null;
3✔
179

180
        foreach (arr($search) as $term) {
3✔
181
            $position = mb_strrpos($this->string, $term);
3✔
182

183
            if ($position !== false && $position > $farthestPosition) {
3✔
184
                $farthestPosition = $position;
3✔
185
                $foundSearch = $term;
3✔
186
            }
187
        }
188

189
        if ($farthestPosition === -1 || $foundSearch === null) {
3✔
190
            return $this;
1✔
191
        }
192

193
        $string = mb_substr($this->string, $farthestPosition + strlen($foundSearch));
3✔
194

195
        return new self($string);
3✔
196
    }
197

198
    public function before(Stringable|string|array $search): self
1✔
199
    {
200
        $search = $this->normalizeString($search);
1✔
201

202
        if ($search === '' || $search === []) {
1✔
203
            return $this;
1✔
204
        }
205

206
        $nearestPosition = mb_strlen($this->string);
1✔
207

208
        foreach (arr($search) as $char) {
1✔
209
            $position = mb_strpos($this->string, $char);
1✔
210

211
            if ($position !== false && $position < $nearestPosition) {
1✔
212
                $nearestPosition = $position;
1✔
213
            }
214
        }
215

216
        if ($nearestPosition === mb_strlen($this->string)) {
1✔
217
            return $this;
1✔
218
        }
219

220
        $string = mb_substr($this->string, start: 0, length: $nearestPosition);
1✔
221

222
        return new self($string);
1✔
223
    }
224

225
    public function beforeLast(Stringable|string|array $search): self
2✔
226
    {
227
        $search = $this->normalizeString($search);
2✔
228

229
        if ($search === '' || $search === []) {
2✔
230
            return $this;
1✔
231
        }
232

233
        $farthestPosition = -1;
2✔
234

235
        foreach (arr($search) as $char) {
2✔
236
            $position = mb_strrpos($this->string, $char);
2✔
237

238
            if ($position !== false && $position > $farthestPosition) {
2✔
239
                $farthestPosition = $position;
2✔
240
            }
241
        }
242

243
        if ($farthestPosition === -1) {
2✔
244
            return $this;
2✔
245
        }
246

247
        $string = mb_substr($this->string, start: 0, length: $farthestPosition);
2✔
248

249
        return new self($string);
2✔
250
    }
251

252
    public function between(string|Stringable $from, string|Stringable $to): self
1✔
253
    {
254
        $from = $this->normalizeString($from);
1✔
255
        $to = $this->normalizeString($to);
1✔
256

257
        if ($from === '' || $to === '') {
1✔
258
            return $this;
1✔
259
        }
260

261
        return $this->after($from)->beforeLast($to);
1✔
262
    }
263

264
    public function trim(string $characters = " \n\r\t\v\0"): self
×
265
    {
266
        return new self(trim($this->string, $characters));
×
267
    }
268

269
    public function ltrim(string $characters = " \n\r\t\v\0"): self
×
270
    {
271
        return new self(ltrim($this->string, $characters));
×
272
    }
273

274
    public function rtrim(string $characters = " \n\r\t\v\0"): self
×
275
    {
276
        return new self(rtrim($this->string, $characters));
×
277
    }
278

279
    public function length(): int
3✔
280
    {
281
        return mb_strlen($this->string);
3✔
282
    }
283

284
    public function classBasename(): self
54✔
285
    {
286
        return new self(basename(str_replace('\\', '/', $this->string)));
54✔
287
    }
288

289
    public function startsWith(Stringable|string|array $needles): bool
15✔
290
    {
291
        if (! is_array($needles)) {
15✔
292
            $needles = [$needles];
1✔
293
        }
294

295
        foreach ($needles as $needle) {
15✔
296
            if (str_starts_with($this->string, (string) $needle)) {
15✔
297
                return true;
2✔
298
            }
299
        }
300

301
        return false;
14✔
302
    }
303

304
    public function endsWith(Stringable|string|array $needles): bool
2✔
305
    {
306
        if (! is_array($needles)) {
2✔
307
            $needles = [$needles];
2✔
308
        }
309

310
        foreach ($needles as $needle) {
2✔
311
            if (str_ends_with($this->string, (string) $needle)) {
2✔
312
                return true;
2✔
313
            }
314
        }
315

316
        return false;
2✔
317
    }
318

319
    public function replaceFirst(Stringable|string $search, Stringable|string $replace): self
1✔
320
    {
321
        $search = $this->normalizeString($search);
1✔
322

323
        if ($search === '') {
1✔
324
            return $this;
1✔
325
        }
326

327
        $position = strpos($this->string, (string) $search);
1✔
328

329
        if ($position === false) {
1✔
330
            return $this;
1✔
331
        }
332

333
        return new self(substr_replace($this->string, $replace, $position, strlen($search)));
1✔
334
    }
335

336
    public function replaceLast(Stringable|string $search, Stringable|string $replace): self
2✔
337
    {
338
        $search = $this->normalizeString($search);
2✔
339

340
        if ($search === '') {
2✔
341
            return $this;
1✔
342
        }
343

344
        $position = strrpos($this->string, (string) $search);
2✔
345

346
        if ($position === false) {
2✔
347
            return $this;
1✔
348
        }
349

350
        return new self(substr_replace($this->string, $replace, $position, strlen($search)));
2✔
351
    }
352

353
    public function replaceEnd(Stringable|string $search, Stringable|string $replace): self
1✔
354
    {
355
        $search = $this->normalizeString($search);
1✔
356

357
        if ($search === '') {
1✔
358
            return $this;
1✔
359
        }
360

361
        if (! $this->endsWith($search)) {
1✔
362
            return $this;
1✔
363
        }
364

365
        return $this->replaceLast($search, $replace);
1✔
366
    }
367

368
    public function replaceStart(Stringable|string $search, Stringable|string $replace): self
×
369
    {
370
        if ($search === '') {
×
371
            return $this;
×
372
        }
373

374
        if (! $this->startsWith($search)) {
×
375
            return $this;
×
376
        }
377

378
        return $this->replaceFirst($search, $replace);
×
379
    }
380

381
    public function append(string|Stringable ...$append): self
1✔
382
    {
383
        return new self($this->string . implode('', $append));
1✔
384
    }
385

386
    public function prepend(string|Stringable ...$prepend): self
48✔
387
    {
388
        return new self(implode('', $prepend) . $this->string);
48✔
389
    }
390

391
    public function replace(Stringable|string|array $search, Stringable|string|array $replace): self
49✔
392
    {
393
        $search = $this->normalizeString($search);
49✔
394
        $replace = $this->normalizeString($replace);
49✔
395

396
        return new self(str_replace($search, $replace, $this->string));
49✔
397
    }
398

399
    public function replaceRegex(string|array $regex, string|array|callable $replace): self
66✔
400
    {
401
        if (is_callable($replace)) {
66✔
402
            return new self(preg_replace_callback($regex, $replace, $this->string));
66✔
403
        }
404

405
        return new self(preg_replace($regex, $replace, $this->string));
48✔
406
    }
407

408
    public function match(string $regex): array
5✔
409
    {
410
        preg_match($regex, $this->string, $matches);
5✔
411

412
        return $matches;
5✔
413
    }
414

415
    public function matchAll(string $regex, int $flags = 0, int $offset = 0): array
8✔
416
    {
417
        $result = preg_match_all($regex, $this->string, $matches, $flags, $offset);
8✔
418

419
        if ($result === 0) {
8✔
420
            return [];
1✔
421
        }
422

423
        return $matches;
8✔
424
    }
425

426
    public function matches(string $regex): bool
1✔
427
    {
428
        return ($this->match($regex)[0] ?? null) !== null;
1✔
429
    }
430

431
    public function dd(mixed ...$dd): void
×
432
    {
NEW
433
        ld($this->string, ...$dd);
×
434
    }
435

436
    public function dump(mixed ...$dumps): self
×
437
    {
NEW
438
        lw($this->string, ...$dumps);
×
439

440
        return $this;
×
441
    }
442

443
    public function excerpt(int $from, int $to, bool $asArray = false): self|ArrayHelper
1✔
444
    {
445
        $lines = explode(PHP_EOL, $this->string);
1✔
446

447
        $from = max(0, $from - 1);
1✔
448

449
        $to = min($to - 1, count($lines));
1✔
450

451
        $lines = array_slice($lines, $from, $to - $from + 1, true);
1✔
452

453
        if ($asArray) {
1✔
454
            return arr($lines)
1✔
455
                ->mapWithKeys(fn (string $line, int $number) => yield $number + 1 => $line);
1✔
456
        }
457

458
        return new self(implode(PHP_EOL, $lines));
1✔
459
    }
460

461
    private function normalizeString(mixed $value): mixed
59✔
462
    {
463
        if ($value instanceof Stringable) {
59✔
464
            return (string) $value;
6✔
465
        }
466

467
        return $value;
59✔
468
    }
469

470
    /**
471
     * Explode the string into an ArrayHelper by a separator.
472
     *
473
     * @param string $separator The separator to explode the string by.
474
     */
475
    public function explode(string $separator = ' '): ArrayHelper
1✔
476
    {
477
        return ArrayHelper::explode($this->string, $separator);
1✔
478
    }
479

480
    /**
481
     * Implode the array into a string by a separator.
482
     *
483
     * @param array|ArrayHelper $array The array to implode.
484
     * @param string $separator The separator to implode the array by.
485
     *
486
     * @return self The imploded string.
487
     */
488
    public static function implode(array|ArrayHelper $array, string $separator = ' '): self
1✔
489
    {
490
        $array = ($array instanceof ArrayHelper)
1✔
491
            ? $array->toArray()
1✔
492
            : $array;
1✔
493

494
        return new self(implode($separator, $array));
1✔
495
    }
496
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc