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

tempestphp / tempest-framework / 11714560237

06 Nov 2024 06:46PM UTC coverage: 82.608% (+0.003%) from 82.605%
11714560237

push

github

web-flow
feat(container): support injecting properties using `#[Inject]` (#690)

6 of 6 new or added lines in 2 files covered. (100.0%)

95 existing lines in 9 files now uncovered.

7210 of 8728 relevant lines covered (82.61%)

49.34 hits per line

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

93.56
/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(
277✔
18
        private string $string = '',
19
    ) {
20
    }
277✔
21

22
    /**
23
     * Converts the instance to a string.
24
     */
25
    public function toString(): string
220✔
26
    {
27
        return $this->string;
220✔
28
    }
29

30
    /**
31
     * Converts the instance to a string.
32
     */
33
    public function __toString(): string
146✔
34
    {
35
        return $this->string;
146✔
36
    }
37

38
    /**
39
     * Asserts whether the instance is equal to the given instance or string.
40
     */
41
    public function equals(string|Stringable $other): bool
26✔
42
    {
43
        return $this->string === (string) $other;
26✔
44
    }
45

46
    /**
47
     * Converts the instance to title case.
48
     */
49
    public function title(): self
1✔
50
    {
51
        return new self(mb_convert_case($this->string, MB_CASE_TITLE, 'UTF-8'));
1✔
52
    }
53

54
    /**
55
     * Converts the instance to lower case.
56
     */
57
    public function lower(): self
×
58
    {
59
        return new self(mb_strtolower($this->string, 'UTF-8'));
×
60
    }
61

62
    /**
63
     * Converts the instance to upper case.
64
     */
65
    public function upper(): self
×
66
    {
67
        return new self(mb_strtoupper($this->string, 'UTF-8'));
×
68
    }
69

70
    /**
71
     * Converts the instance to snake case.
72
     */
73
    public function snake(string $delimiter = '_'): self
181✔
74
    {
75
        $string = $this->string;
181✔
76

77
        if (ctype_lower($string)) {
181✔
78
            return $this;
52✔
79
        }
80

81
        $string = preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $string);
151✔
82
        $string = preg_replace(
151✔
83
            '![^' . preg_quote($delimiter) . '\pL\pN\s]+!u',
151✔
84
            $delimiter,
151✔
85
            mb_strtolower($string, 'UTF-8')
151✔
86
        );
151✔
87
        $string = preg_replace('/\s+/u', $delimiter, $string);
151✔
88
        $string = trim($string, $delimiter);
151✔
89

90
        return (new self($string))->deduplicate($delimiter);
151✔
91
    }
92

93
    /**
94
     * Converts the instance to kebab case.
95
     */
96
    public function kebab(): self
174✔
97
    {
98
        return $this->snake('-');
174✔
99
    }
100

101
    /**
102
     * Converts the instance to pascal case.
103
     */
104
    public function pascal(): self
58✔
105
    {
106
        $words = explode(' ', str_replace(['-', '_'], ' ', $this->string));
58✔
107

108
        // TODO: use `mb_ucfirst` when it has landed in PHP 8.4
109
        $studlyWords = array_map(static fn (string $word) => ucfirst($word), $words);
58✔
110

111
        return new self(implode('', $studlyWords));
58✔
112
    }
113

114
    /**
115
     * Converts the instance to camel case.
116
     */
117
    public function camel(): self
54✔
118
    {
119
        return new self(lcfirst((string)$this->pascal()));
54✔
120
    }
121

122
    /**
123
     * Replaces consecutive instances of a given character with a single character.
124
     */
125
    public function deduplicate(string|array $characters = ' '): self
152✔
126
    {
127
        $string = $this->string;
152✔
128

129
        foreach (arr($characters) as $character) {
152✔
130
            $string = preg_replace('/' . preg_quote($character, '/') . '+/u', $character, $string);
152✔
131
        }
132

133
        return new self($string);
152✔
134
    }
135

136
    /**
137
     * Converts the instance to its English plural form.
138
     */
139
    public function pluralize(int|array|Countable $count = 2): self
54✔
140
    {
141
        return new self(LanguageHelper::pluralize($this->string, $count));
54✔
142
    }
143

144
    /**
145
     * Converts the last word to its English plural form.
146
     */
147
    public function pluralizeLast(int|array|Countable $count = 2): self
54✔
148
    {
149
        $parts = preg_split('/(.)(?=[A-Z])/u', $this->string, -1, PREG_SPLIT_DELIM_CAPTURE);
54✔
150

151
        $lastWord = array_pop($parts);
54✔
152

153
        $string = implode('', $parts) . (new self($lastWord))->pluralize($count);
54✔
154

155
        return new self($string);
54✔
156
    }
157

158
    /**
159
     * Creates a random alpha-numeric string of the given length.
160
     */
161
    public function random(int $length = 16): self
3✔
162
    {
163
        $string = '';
3✔
164

165
        while (($len = strlen($string)) < $length) {
3✔
166
            $size = $length - $len;
2✔
167
            $bytesSize = (int)ceil($size / 3) * 3;
2✔
168
            $bytes = random_bytes($bytesSize);
2✔
169
            $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), offset: 0, length: $size);
2✔
170
        }
171

172
        return new self($string);
3✔
173
    }
174

175
    /**
176
     * Caps the instance with the given string.
177
     */
178
    public function finish(string $cap): self
1✔
179
    {
180
        return new self(
1✔
181
            preg_replace('/(?:' . preg_quote($cap, '/') . ')+$/u', replacement: '', subject: $this->string) . $cap
1✔
182
        );
1✔
183
    }
184

185
    /**
186
     * Returns the remainder of the string after the first occurrence of the given value.
187
     */
188
    public function after(Stringable|string|array $search): self
3✔
189
    {
190
        $search = $this->normalizeString($search);
3✔
191

192
        if ($search === '' || $search === []) {
3✔
193
            return $this;
1✔
194
        }
195

196
        $nearestPosition = mb_strlen($this->string); // Initialize with a large value
3✔
197
        $foundSearch = '';
3✔
198

199
        foreach (arr($search) as $term) {
3✔
200
            $position = mb_strpos($this->string, $term);
3✔
201

202
            if ($position !== false && $position < $nearestPosition) {
3✔
203
                $nearestPosition = $position;
3✔
204
                $foundSearch = $term;
3✔
205
            }
206
        }
207

208
        if ($nearestPosition === mb_strlen($this->string)) {
3✔
209
            return $this;
3✔
210
        }
211

212
        $string = mb_substr($this->string, $nearestPosition + mb_strlen($foundSearch));
3✔
213

214
        return new self($string);
3✔
215
    }
216

217
    /**
218
     * Returns the remainder of the string after the last occurrence of the given value.
219
     */
220
    public function afterLast(Stringable|string|array $search): self
3✔
221
    {
222
        $search = $this->normalizeString($search);
3✔
223

224
        if ($search === '' || $search === []) {
3✔
225
            return $this;
1✔
226
        }
227

228
        $farthestPosition = -1;
3✔
229
        $foundSearch = null;
3✔
230

231
        foreach (arr($search) as $term) {
3✔
232
            $position = mb_strrpos($this->string, $term);
3✔
233

234
            if ($position !== false && $position > $farthestPosition) {
3✔
235
                $farthestPosition = $position;
3✔
236
                $foundSearch = $term;
3✔
237
            }
238
        }
239

240
        if ($farthestPosition === -1 || $foundSearch === null) {
3✔
241
            return $this;
1✔
242
        }
243

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

246
        return new self($string);
3✔
247
    }
248

249
    /**
250
     * Returns the portion of the string before the first occurrence of the given value.
251
     */
252
    public function before(Stringable|string|array $search): self
1✔
253
    {
254
        $search = $this->normalizeString($search);
1✔
255

256
        if ($search === '' || $search === []) {
1✔
257
            return $this;
1✔
258
        }
259

260
        $nearestPosition = mb_strlen($this->string);
1✔
261

262
        foreach (arr($search) as $char) {
1✔
263
            $position = mb_strpos($this->string, $char);
1✔
264

265
            if ($position !== false && $position < $nearestPosition) {
1✔
266
                $nearestPosition = $position;
1✔
267
            }
268
        }
269

270
        if ($nearestPosition === mb_strlen($this->string)) {
1✔
271
            return $this;
1✔
272
        }
273

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

276
        return new self($string);
1✔
277
    }
278

279
    /**
280
     * Returns the portion of the string before the last occurrence of the given value.
281
     */
282
    public function beforeLast(Stringable|string|array $search): self
3✔
283
    {
284
        $search = $this->normalizeString($search);
3✔
285

286
        if ($search === '' || $search === []) {
3✔
287
            return $this;
1✔
288
        }
289

290
        $farthestPosition = -1;
3✔
291

292
        foreach (arr($search) as $char) {
3✔
293
            $position = mb_strrpos($this->string, $char);
3✔
294

295
            if ($position !== false && $position > $farthestPosition) {
3✔
296
                $farthestPosition = $position;
3✔
297
            }
298
        }
299

300
        if ($farthestPosition === -1) {
3✔
301
            return $this;
3✔
302
        }
303

304
        $string = mb_substr($this->string, start: 0, length: $farthestPosition);
3✔
305

306
        return new self($string);
3✔
307
    }
308

309
    /**
310
     * Returns the portion of the string between the widest possible instances of the given strings.
311
     */
312
    public function between(string|Stringable $from, string|Stringable $to): self
1✔
313
    {
314
        $from = $this->normalizeString($from);
1✔
315
        $to = $this->normalizeString($to);
1✔
316

317
        if ($from === '' || $to === '') {
1✔
318
            return $this;
1✔
319
        }
320

321
        return $this->after($from)->beforeLast($to);
1✔
322
    }
323

324
    /**
325
     * Removes all whitespace (or specified characters) from both ends of the instance.
326
     */
327
    public function trim(string $characters = " \n\r\t\v\0"): self
3✔
328
    {
329
        return new self(trim($this->string, $characters));
3✔
330
    }
331

332
    /**
333
     * Removes all whitespace (or specified characters) from the start of the instance.
334
     */
335
    public function ltrim(string $characters = " \n\r\t\v\0"): self
×
336
    {
337
        return new self(ltrim($this->string, $characters));
×
338
    }
339

340
    /**
341
     * Removes all whitespace (or specified characters) from the end of the instance.
342
     */
343
    public function rtrim(string $characters = " \n\r\t\v\0"): self
×
344
    {
345
        return new self(rtrim($this->string, $characters));
×
346
    }
347

348
    /**
349
     * Returns the multi-bytes length of the instance.
350
     */
351
    public function length(): int
3✔
352
    {
353
        return mb_strlen($this->string);
3✔
354
    }
355

356
    /**
357
     * Returns the base name of the instance, assuming the instance is a class name.
358
     */
359
    public function classBasename(): self
56✔
360
    {
361
        return new self(basename(str_replace('\\', '/', $this->string)));
56✔
362
    }
363

364
    /**
365
     * Asserts whether the instance starts with one of the given needles.
366
     */
367
    public function startsWith(Stringable|string|array $needles): bool
186✔
368
    {
369
        if (! is_array($needles)) {
186✔
370
            $needles = [$needles];
175✔
371
        }
372

373
        foreach ($needles as $needle) {
186✔
374
            if (str_starts_with($this->string, (string) $needle)) {
186✔
375
                return true;
19✔
376
            }
377
        }
378

379
        return false;
180✔
380
    }
381

382
    /**
383
     * Asserts whether the instance ends with one of the given `$needles`.
384
     */
385
    public function endsWith(Stringable|string|array $needles): bool
7✔
386
    {
387
        if (! is_array($needles)) {
7✔
388
            $needles = [$needles];
5✔
389
        }
390

391
        foreach ($needles as $needle) {
7✔
392
            if (str_ends_with($this->string, (string) $needle)) {
7✔
393
                return true;
7✔
394
            }
395
        }
396

397
        return false;
3✔
398
    }
399

400
    /**
401
     * Replaces the first occurence of `$search` with `$replace`.
402
     */
403
    public function replaceFirst(Stringable|string $search, Stringable|string $replace): self
9✔
404
    {
405
        $search = $this->normalizeString($search);
9✔
406

407
        if ($search === '') {
9✔
408
            return $this;
1✔
409
        }
410

411
        $position = strpos($this->string, (string) $search);
9✔
412

413
        if ($position === false) {
9✔
414
            return $this;
1✔
415
        }
416

417
        return new self(substr_replace($this->string, $replace, $position, strlen($search)));
9✔
418
    }
419

420
    /**
421
     * Replaces the last occurence of `$search` with `$replace`.
422
     */
423
    public function replaceLast(Stringable|string $search, Stringable|string $replace): self
4✔
424
    {
425
        $search = $this->normalizeString($search);
4✔
426

427
        if ($search === '') {
4✔
428
            return $this;
1✔
429
        }
430

431
        $position = strrpos($this->string, (string) $search);
4✔
432

433
        if ($position === false) {
4✔
434
            return $this;
1✔
435
        }
436

437
        return new self(substr_replace($this->string, $replace, $position, strlen($search)));
4✔
438
    }
439

440
    /**
441
     * Replaces `$search` with `$replace` if `$search` is at the end of the instance.
442
     */
443
    public function replaceEnd(Stringable|string $search, Stringable|string $replace): self
3✔
444
    {
445
        $search = $this->normalizeString($search);
3✔
446

447
        if ($search === '') {
3✔
448
            return $this;
1✔
449
        }
450

451
        if (! $this->endsWith($search)) {
3✔
452
            return $this;
1✔
453
        }
454

455
        return $this->replaceLast($search, $replace);
3✔
456
    }
457

458
    /**
459
     * Replaces `$search` with `$replace` if `$search` is at the start of the instance.
460
     */
461
    public function replaceStart(Stringable|string $search, Stringable|string $replace): self
142✔
462
    {
463
        if ($search === '') {
142✔
464
            return $this;
×
465
        }
466

467
        if (! $this->startsWith($search)) {
142✔
468
            return $this;
142✔
469
        }
470

471
        return $this->replaceFirst($search, $replace);
8✔
472
    }
473

474
    /**
475
     * Appends the given strings to the instance.
476
     */
477
    public function append(string|Stringable ...$append): self
16✔
478
    {
479
        return new self($this->string . implode('', $append));
16✔
480
    }
481

482
    /**
483
     * Prepends the given strings to the instance.
484
     */
485
    public function prepend(string|Stringable ...$prepend): self
76✔
486
    {
487
        return new self(implode('', $prepend) . $this->string);
76✔
488
    }
489

490
    /**
491
     * Wraps the instance with the given string. If `$after` is specified, it will be appended instead of `$before`.
492
     *
493
     * ### Example
494
     * ```php
495
     * str('Scott')->wrap(before: 'Leon ', after: ' Kennedy'); // Leon Scott Kennedy
496
     * ```
497
     */
498
    public function wrap(string|Stringable $before, string|Stringable $after = null): self
1✔
499
    {
500
        return new self($before . $this->string . ($after ??= $before));
1✔
501
    }
502

503
    /**
504
     * Removes the specified `$before` and `$after` from the beginning and the end of the instance. If `$after` is null, `$before` is used instead.
505
     * Setting `$strict` to `false` will unwrap the instance even if both ends do not correspond to the specified `$before` and `$after`.
506
     *
507
     * ### Example
508
     * ```php
509
     *  str('Scott Kennedy')->unwrap(before: 'Leon ', after: ' Kennedy', strict: false); // Scott
510
     * ```
511
     */
512
    public function unwrap(string|Stringable $before, string|Stringable $after = null, bool $strict = true): self
1✔
513
    {
514
        $string = $this->string;
1✔
515

516
        if ($string === '') {
1✔
UNCOV
517
            return $this;
×
518
        }
519

520
        if ($after === null) {
1✔
521
            $after = $before;
1✔
522
        }
523

524
        if (! $strict) {
1✔
525
            return (new self($string))->after($before)->beforeLast($after);
1✔
526
        }
527

528
        if ($this->startsWith($before) && $this->endsWith($after)) {
1✔
529
            $string = (string) (new self($string))->after($before)->beforeLast($after);
1✔
530
        }
531

532
        return new self($string);
1✔
533
    }
534

535
    /**
536
     * Replaces all occurrences of the given `$search` with `$replace`.
537
     */
538
    public function replace(Stringable|string|array $search, Stringable|string|array $replace): self
69✔
539
    {
540
        $search = $this->normalizeString($search);
69✔
541
        $replace = $this->normalizeString($replace);
69✔
542

543
        return new self(str_replace($search, $replace, $this->string));
69✔
544
    }
545

546
    /**
547
     * Replaces the patterns matching the given regular expression.
548
     */
549
    public function replaceRegex(string|array $regex, string|array|callable $replace): self
70✔
550
    {
551
        if (is_callable($replace)) {
70✔
552
            return new self(preg_replace_callback($regex, $replace, $this->string));
65✔
553
        }
554

555
        return new self(preg_replace($regex, $replace, $this->string));
70✔
556
    }
557

558
    /**
559
     * Gets the first portion of the instance that matches the given regular expression.
560
     */
561
    public function match(string $regex): array
15✔
562
    {
563
        preg_match($regex, $this->string, $matches);
15✔
564

565
        return $matches;
15✔
566
    }
567

568
    /**
569
     * Gets all portions of the instance that match the given regular expression.
570
     */
571
    public function matchAll(string $regex, int $flags = 0, int $offset = 0): array
1✔
572
    {
573
        $result = preg_match_all($regex, $this->string, $matches, $flags, $offset);
1✔
574

575
        if ($result === 0) {
1✔
576
            return [];
1✔
577
        }
578

579
        return $matches;
1✔
580
    }
581

582
    /**
583
     * Asserts whether the instance matches the given regular expression.
584
     */
585
    public function matches(string $regex): bool
10✔
586
    {
587
        return ($this->match($regex)[0] ?? null) !== null;
10✔
588
    }
589

590
    /**
591
     * Dumps the instance and stops the execution of the script.
592
     */
UNCOV
593
    public function dd(mixed ...$dd): void
×
594
    {
UNCOV
595
        ld($this->string, ...$dd);
×
596
    }
597

598
    /**
599
     * Dumps the instance.
600
     */
UNCOV
601
    public function dump(mixed ...$dumps): self
×
602
    {
UNCOV
603
        lw($this->string, ...$dumps);
×
604

UNCOV
605
        return $this;
×
606
    }
607

608
    /**
609
     * Extracts an excerpt from the instance.
610
     */
611
    public function excerpt(int $from, int $to, bool $asArray = false): self|ArrayHelper
1✔
612
    {
613
        $lines = explode(PHP_EOL, $this->string);
1✔
614

615
        $from = max(0, $from - 1);
1✔
616

617
        $to = min($to - 1, count($lines));
1✔
618

619
        $lines = array_slice($lines, $from, $to - $from + 1, true);
1✔
620

621
        if ($asArray) {
1✔
622
            return arr($lines)
1✔
623
                ->mapWithKeys(fn (string $line, int $number) => yield $number + 1 => $line);
1✔
624
        }
625

626
        return new self(implode(PHP_EOL, $lines));
1✔
627
    }
628

629
    private function normalizeString(mixed $value): mixed
86✔
630
    {
631
        if ($value instanceof Stringable) {
86✔
632
            return (string) $value;
6✔
633
        }
634

635
        return $value;
86✔
636
    }
637

638
    /**
639
     * Explodes the string into an `ArrayHelper` instance by a separator.
640
     */
641
    public function explode(string $separator = ' '): ArrayHelper
3✔
642
    {
643
        return ArrayHelper::explode($this->string, $separator);
3✔
644
    }
645

646
    /**
647
     * Implodes the given array into a string by a separator.
648
     */
649
    public static function implode(array|ArrayHelper $parts, string $glue = ' '): self
1✔
650
    {
651
        return arr($parts)->implode($glue);
1✔
652
    }
653

654
    /**
655
     * Joins all values using the specified `$glue`. The last item of the string is separated by `$finalGlue`.
656
     */
657
    public static function join(array|ArrayHelper $parts, string $glue = ', ', ?string $finalGlue = ' and '): self
5✔
658
    {
659
        return arr($parts)->join($glue, $finalGlue);
5✔
660
    }
661
}
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

© 2026 Coveralls, Inc