Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 399 | Zur aktuellen Revision | Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Support;
4
 
5
use Closure;
6
use Illuminate\Support\Traits\Macroable;
7
use JsonException;
8
use League\CommonMark\Environment\Environment;
9
use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
10
use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
11
use League\CommonMark\GithubFlavoredMarkdownConverter;
12
use League\CommonMark\MarkdownConverter;
13
use Ramsey\Uuid\Codec\TimestampFirstCombCodec;
14
use Ramsey\Uuid\Generator\CombGenerator;
15
use Ramsey\Uuid\Uuid;
16
use Ramsey\Uuid\UuidFactory;
17
use Symfony\Component\Uid\Ulid;
18
use Traversable;
19
use voku\helper\ASCII;
20
 
21
class Str
22
{
23
    use Macroable;
24
 
25
    /**
26
     * The cache of snake-cased words.
27
     *
28
     * @var array
29
     */
30
    protected static $snakeCache = [];
31
 
32
    /**
33
     * The cache of camel-cased words.
34
     *
35
     * @var array
36
     */
37
    protected static $camelCache = [];
38
 
39
    /**
40
     * The cache of studly-cased words.
41
     *
42
     * @var array
43
     */
44
    protected static $studlyCache = [];
45
 
46
    /**
47
     * The callback that should be used to generate UUIDs.
48
     *
49
     * @var callable|null
50
     */
51
    protected static $uuidFactory;
52
 
53
    /**
54
     * The callback that should be used to generate random strings.
55
     *
56
     * @var callable|null
57
     */
58
    protected static $randomStringFactory;
59
 
60
    /**
61
     * Get a new stringable object from the given string.
62
     *
63
     * @param  string  $string
64
     * @return \Illuminate\Support\Stringable
65
     */
66
    public static function of($string)
67
    {
68
        return new Stringable($string);
69
    }
70
 
71
    /**
72
     * Return the remainder of a string after the first occurrence of a given value.
73
     *
74
     * @param  string  $subject
75
     * @param  string  $search
76
     * @return string
77
     */
78
    public static function after($subject, $search)
79
    {
80
        return $search === '' ? $subject : array_reverse(explode($search, $subject, 2))[0];
81
    }
82
 
83
    /**
84
     * Return the remainder of a string after the last occurrence of a given value.
85
     *
86
     * @param  string  $subject
87
     * @param  string  $search
88
     * @return string
89
     */
90
    public static function afterLast($subject, $search)
91
    {
92
        if ($search === '') {
93
            return $subject;
94
        }
95
 
96
        $position = strrpos($subject, (string) $search);
97
 
98
        if ($position === false) {
99
            return $subject;
100
        }
101
 
102
        return substr($subject, $position + strlen($search));
103
    }
104
 
105
    /**
106
     * Transliterate a UTF-8 value to ASCII.
107
     *
108
     * @param  string  $value
109
     * @param  string  $language
110
     * @return string
111
     */
112
    public static function ascii($value, $language = 'en')
113
    {
114
        return ASCII::to_ascii((string) $value, $language);
115
    }
116
 
117
    /**
118
     * Transliterate a string to its closest ASCII representation.
119
     *
120
     * @param  string  $string
121
     * @param  string|null  $unknown
122
     * @param  bool|null  $strict
123
     * @return string
124
     */
125
    public static function transliterate($string, $unknown = '?', $strict = false)
126
    {
127
        return ASCII::to_transliterate($string, $unknown, $strict);
128
    }
129
 
130
    /**
131
     * Get the portion of a string before the first occurrence of a given value.
132
     *
133
     * @param  string  $subject
134
     * @param  string  $search
135
     * @return string
136
     */
137
    public static function before($subject, $search)
138
    {
139
        if ($search === '') {
140
            return $subject;
141
        }
142
 
143
        $result = strstr($subject, (string) $search, true);
144
 
145
        return $result === false ? $subject : $result;
146
    }
147
 
148
    /**
149
     * Get the portion of a string before the last occurrence of a given value.
150
     *
151
     * @param  string  $subject
152
     * @param  string  $search
153
     * @return string
154
     */
155
    public static function beforeLast($subject, $search)
156
    {
157
        if ($search === '') {
158
            return $subject;
159
        }
160
 
161
        $pos = mb_strrpos($subject, $search);
162
 
163
        if ($pos === false) {
164
            return $subject;
165
        }
166
 
167
        return static::substr($subject, 0, $pos);
168
    }
169
 
170
    /**
171
     * Get the portion of a string between two given values.
172
     *
173
     * @param  string  $subject
174
     * @param  string  $from
175
     * @param  string  $to
176
     * @return string
177
     */
178
    public static function between($subject, $from, $to)
179
    {
180
        if ($from === '' || $to === '') {
181
            return $subject;
182
        }
183
 
184
        return static::beforeLast(static::after($subject, $from), $to);
185
    }
186
 
187
    /**
188
     * Get the smallest possible portion of a string between two given values.
189
     *
190
     * @param  string  $subject
191
     * @param  string  $from
192
     * @param  string  $to
193
     * @return string
194
     */
195
    public static function betweenFirst($subject, $from, $to)
196
    {
197
        if ($from === '' || $to === '') {
198
            return $subject;
199
        }
200
 
201
        return static::before(static::after($subject, $from), $to);
202
    }
203
 
204
    /**
205
     * Convert a value to camel case.
206
     *
207
     * @param  string  $value
208
     * @return string
209
     */
210
    public static function camel($value)
211
    {
212
        if (isset(static::$camelCache[$value])) {
213
            return static::$camelCache[$value];
214
        }
215
 
216
        return static::$camelCache[$value] = lcfirst(static::studly($value));
217
    }
218
 
219
    /**
220
     * Determine if a given string contains a given substring.
221
     *
222
     * @param  string  $haystack
223
     * @param  string|iterable<string>  $needles
224
     * @param  bool  $ignoreCase
225
     * @return bool
226
     */
227
    public static function contains($haystack, $needles, $ignoreCase = false)
228
    {
229
        if ($ignoreCase) {
230
            $haystack = mb_strtolower($haystack);
231
        }
232
 
233
        if (! is_iterable($needles)) {
234
            $needles = (array) $needles;
235
        }
236
 
237
        foreach ($needles as $needle) {
238
            if ($ignoreCase) {
239
                $needle = mb_strtolower($needle);
240
            }
241
 
242
            if ($needle !== '' && str_contains($haystack, $needle)) {
243
                return true;
244
            }
245
        }
246
 
247
        return false;
248
    }
249
 
250
    /**
251
     * Determine if a given string contains all array values.
252
     *
253
     * @param  string  $haystack
254
     * @param  iterable<string>  $needles
255
     * @param  bool  $ignoreCase
256
     * @return bool
257
     */
258
    public static function containsAll($haystack, $needles, $ignoreCase = false)
259
    {
260
        foreach ($needles as $needle) {
261
            if (! static::contains($haystack, $needle, $ignoreCase)) {
262
                return false;
263
            }
264
        }
265
 
266
        return true;
267
    }
268
 
269
    /**
270
     * Determine if a given string ends with a given substring.
271
     *
272
     * @param  string  $haystack
273
     * @param  string|iterable<string>  $needles
274
     * @return bool
275
     */
276
    public static function endsWith($haystack, $needles)
277
    {
278
        if (! is_iterable($needles)) {
279
            $needles = (array) $needles;
280
        }
281
 
282
        foreach ($needles as $needle) {
283
            if ((string) $needle !== '' && str_ends_with($haystack, $needle)) {
284
                return true;
285
            }
286
        }
287
 
288
        return false;
289
    }
290
 
291
    /**
292
     * Extracts an excerpt from text that matches the first instance of a phrase.
293
     *
294
     * @param  string  $text
295
     * @param  string  $phrase
296
     * @param  array  $options
297
     * @return string|null
298
     */
299
    public static function excerpt($text, $phrase = '', $options = [])
300
    {
301
        $radius = $options['radius'] ?? 100;
302
        $omission = $options['omission'] ?? '...';
303
 
304
        preg_match('/^(.*?)('.preg_quote((string) $phrase).')(.*)$/iu', (string) $text, $matches);
305
 
306
        if (empty($matches)) {
307
            return null;
308
        }
309
 
310
        $start = ltrim($matches[1]);
311
 
312
        $start = str(mb_substr($start, max(mb_strlen($start, 'UTF-8') - $radius, 0), $radius, 'UTF-8'))->ltrim()->unless(
313
            fn ($startWithRadius) => $startWithRadius->exactly($start),
314
            fn ($startWithRadius) => $startWithRadius->prepend($omission),
315
        );
316
 
317
        $end = rtrim($matches[3]);
318
 
319
        $end = str(mb_substr($end, 0, $radius, 'UTF-8'))->rtrim()->unless(
320
            fn ($endWithRadius) => $endWithRadius->exactly($end),
321
            fn ($endWithRadius) => $endWithRadius->append($omission),
322
        );
323
 
324
        return $start->append($matches[2], $end)->toString();
325
    }
326
 
327
    /**
328
     * Cap a string with a single instance of a given value.
329
     *
330
     * @param  string  $value
331
     * @param  string  $cap
332
     * @return string
333
     */
334
    public static function finish($value, $cap)
335
    {
336
        $quoted = preg_quote($cap, '/');
337
 
338
        return preg_replace('/(?:'.$quoted.')+$/u', '', $value).$cap;
339
    }
340
 
341
    /**
342
     * Wrap the string with the given strings.
343
     *
344
     * @param  string  $value
345
     * @param  string  $before
346
     * @param  string|null  $after
347
     * @return string
348
     */
349
    public static function wrap($value, $before, $after = null)
350
    {
351
        return $before.$value.($after ??= $before);
352
    }
353
 
354
    /**
355
     * Determine if a given string matches a given pattern.
356
     *
357
     * @param  string|iterable<string>  $pattern
358
     * @param  string  $value
359
     * @return bool
360
     */
361
    public static function is($pattern, $value)
362
    {
363
        $value = (string) $value;
364
 
365
        if (! is_iterable($pattern)) {
366
            $pattern = [$pattern];
367
        }
368
 
369
        foreach ($pattern as $pattern) {
370
            $pattern = (string) $pattern;
371
 
372
            // If the given value is an exact match we can of course return true right
373
            // from the beginning. Otherwise, we will translate asterisks and do an
374
            // actual pattern match against the two strings to see if they match.
375
            if ($pattern === $value) {
376
                return true;
377
            }
378
 
379
            $pattern = preg_quote($pattern, '#');
380
 
381
            // Asterisks are translated into zero-or-more regular expression wildcards
382
            // to make it convenient to check if the strings starts with the given
383
            // pattern such as "library/*", making any string check convenient.
384
            $pattern = str_replace('\*', '.*', $pattern);
385
 
386
            if (preg_match('#^'.$pattern.'\z#u', $value) === 1) {
387
                return true;
388
            }
389
        }
390
 
391
        return false;
392
    }
393
 
394
    /**
395
     * Determine if a given string is 7 bit ASCII.
396
     *
397
     * @param  string  $value
398
     * @return bool
399
     */
400
    public static function isAscii($value)
401
    {
402
        return ASCII::is_ascii((string) $value);
403
    }
404
 
405
    /**
406
     * Determine if a given string is valid JSON.
407
     *
408
     * @param  string  $value
409
     * @return bool
410
     */
411
    public static function isJson($value)
412
    {
413
        if (! is_string($value)) {
414
            return false;
415
        }
416
 
417
        try {
418
            json_decode($value, true, 512, JSON_THROW_ON_ERROR);
419
        } catch (JsonException) {
420
            return false;
421
        }
422
 
423
        return true;
424
    }
425
 
426
    /**
427
     * Determine if a given string is a valid UUID.
428
     *
429
     * @param  string  $value
430
     * @return bool
431
     */
432
    public static function isUuid($value)
433
    {
434
        if (! is_string($value)) {
435
            return false;
436
        }
437
 
438
        return preg_match('/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iD', $value) > 0;
439
    }
440
 
441
    /**
442
     * Determine if a given string is a valid ULID.
443
     *
444
     * @param  string  $value
445
     * @return bool
446
     */
447
    public static function isUlid($value)
448
    {
449
        if (! is_string($value)) {
450
            return false;
451
        }
452
 
453
        return Ulid::isValid($value);
454
    }
455
 
456
    /**
457
     * Convert a string to kebab case.
458
     *
459
     * @param  string  $value
460
     * @return string
461
     */
462
    public static function kebab($value)
463
    {
464
        return static::snake($value, '-');
465
    }
466
 
467
    /**
468
     * Return the length of the given string.
469
     *
470
     * @param  string  $value
471
     * @param  string|null  $encoding
472
     * @return int
473
     */
474
    public static function length($value, $encoding = null)
475
    {
476
        if ($encoding) {
477
            return mb_strlen($value, $encoding);
478
        }
479
 
480
        return mb_strlen($value);
481
    }
482
 
483
    /**
484
     * Limit the number of characters in a string.
485
     *
486
     * @param  string  $value
487
     * @param  int  $limit
488
     * @param  string  $end
489
     * @return string
490
     */
491
    public static function limit($value, $limit = 100, $end = '...')
492
    {
493
        if (mb_strwidth($value, 'UTF-8') <= $limit) {
494
            return $value;
495
        }
496
 
497
        return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')).$end;
498
    }
499
 
500
    /**
501
     * Convert the given string to lower-case.
502
     *
503
     * @param  string  $value
504
     * @return string
505
     */
506
    public static function lower($value)
507
    {
508
        return mb_strtolower($value, 'UTF-8');
509
    }
510
 
511
    /**
512
     * Limit the number of words in a string.
513
     *
514
     * @param  string  $value
515
     * @param  int  $words
516
     * @param  string  $end
517
     * @return string
518
     */
519
    public static function words($value, $words = 100, $end = '...')
520
    {
521
        preg_match('/^\s*+(?:\S++\s*+){1,'.$words.'}/u', $value, $matches);
522
 
523
        if (! isset($matches[0]) || static::length($value) === static::length($matches[0])) {
524
            return $value;
525
        }
526
 
527
        return rtrim($matches[0]).$end;
528
    }
529
 
530
    /**
531
     * Converts GitHub flavored Markdown into HTML.
532
     *
533
     * @param  string  $string
534
     * @param  array  $options
535
     * @return string
536
     */
537
    public static function markdown($string, array $options = [])
538
    {
539
        $converter = new GithubFlavoredMarkdownConverter($options);
540
 
541
        return (string) $converter->convert($string);
542
    }
543
 
544
    /**
545
     * Converts inline Markdown into HTML.
546
     *
547
     * @param  string  $string
548
     * @param  array  $options
549
     * @return string
550
     */
551
    public static function inlineMarkdown($string, array $options = [])
552
    {
553
        $environment = new Environment($options);
554
 
555
        $environment->addExtension(new GithubFlavoredMarkdownExtension());
556
        $environment->addExtension(new InlinesOnlyExtension());
557
 
558
        $converter = new MarkdownConverter($environment);
559
 
560
        return (string) $converter->convert($string);
561
    }
562
 
563
    /**
564
     * Masks a portion of a string with a repeated character.
565
     *
566
     * @param  string  $string
567
     * @param  string  $character
568
     * @param  int  $index
569
     * @param  int|null  $length
570
     * @param  string  $encoding
571
     * @return string
572
     */
573
    public static function mask($string, $character, $index, $length = null, $encoding = 'UTF-8')
574
    {
575
        if ($character === '') {
576
            return $string;
577
        }
578
 
579
        $segment = mb_substr($string, $index, $length, $encoding);
580
 
581
        if ($segment === '') {
582
            return $string;
583
        }
584
 
585
        $strlen = mb_strlen($string, $encoding);
586
        $startIndex = $index;
587
 
588
        if ($index < 0) {
589
            $startIndex = $index < -$strlen ? 0 : $strlen + $index;
590
        }
591
 
592
        $start = mb_substr($string, 0, $startIndex, $encoding);
593
        $segmentLen = mb_strlen($segment, $encoding);
594
        $end = mb_substr($string, $startIndex + $segmentLen);
595
 
596
        return $start.str_repeat(mb_substr($character, 0, 1, $encoding), $segmentLen).$end;
597
    }
598
 
599
    /**
600
     * Get the string matching the given pattern.
601
     *
602
     * @param  string  $pattern
603
     * @param  string  $subject
604
     * @return string
605
     */
606
    public static function match($pattern, $subject)
607
    {
608
        preg_match($pattern, $subject, $matches);
609
 
610
        if (! $matches) {
611
            return '';
612
        }
613
 
614
        return $matches[1] ?? $matches[0];
615
    }
616
 
617
    /**
618
     * Get the string matching the given pattern.
619
     *
620
     * @param  string  $pattern
621
     * @param  string  $subject
622
     * @return \Illuminate\Support\Collection
623
     */
624
    public static function matchAll($pattern, $subject)
625
    {
626
        preg_match_all($pattern, $subject, $matches);
627
 
628
        if (empty($matches[0])) {
629
            return collect();
630
        }
631
 
632
        return collect($matches[1] ?? $matches[0]);
633
    }
634
 
635
    /**
636
     * Pad both sides of a string with another.
637
     *
638
     * @param  string  $value
639
     * @param  int  $length
640
     * @param  string  $pad
641
     * @return string
642
     */
643
    public static function padBoth($value, $length, $pad = ' ')
644
    {
645
        $short = max(0, $length - mb_strlen($value));
646
        $shortLeft = floor($short / 2);
647
        $shortRight = ceil($short / 2);
648
 
649
        return mb_substr(str_repeat($pad, $shortLeft), 0, $shortLeft).
650
               $value.
651
               mb_substr(str_repeat($pad, $shortRight), 0, $shortRight);
652
    }
653
 
654
    /**
655
     * Pad the left side of a string with another.
656
     *
657
     * @param  string  $value
658
     * @param  int  $length
659
     * @param  string  $pad
660
     * @return string
661
     */
662
    public static function padLeft($value, $length, $pad = ' ')
663
    {
664
        $short = max(0, $length - mb_strlen($value));
665
 
666
        return mb_substr(str_repeat($pad, $short), 0, $short).$value;
667
    }
668
 
669
    /**
670
     * Pad the right side of a string with another.
671
     *
672
     * @param  string  $value
673
     * @param  int  $length
674
     * @param  string  $pad
675
     * @return string
676
     */
677
    public static function padRight($value, $length, $pad = ' ')
678
    {
679
        $short = max(0, $length - mb_strlen($value));
680
 
681
        return $value.mb_substr(str_repeat($pad, $short), 0, $short);
682
    }
683
 
684
    /**
685
     * Parse a Class[@]method style callback into class and method.
686
     *
687
     * @param  string  $callback
688
     * @param  string|null  $default
689
     * @return array<int, string|null>
690
     */
691
    public static function parseCallback($callback, $default = null)
692
    {
693
        return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
694
    }
695
 
696
    /**
697
     * Get the plural form of an English word.
698
     *
699
     * @param  string  $value
700
     * @param  int|array|\Countable  $count
701
     * @return string
702
     */
703
    public static function plural($value, $count = 2)
704
    {
705
        return Pluralizer::plural($value, $count);
706
    }
707
 
708
    /**
709
     * Pluralize the last word of an English, studly caps case string.
710
     *
711
     * @param  string  $value
712
     * @param  int|array|\Countable  $count
713
     * @return string
714
     */
715
    public static function pluralStudly($value, $count = 2)
716
    {
717
        $parts = preg_split('/(.)(?=[A-Z])/u', $value, -1, PREG_SPLIT_DELIM_CAPTURE);
718
 
719
        $lastWord = array_pop($parts);
720
 
721
        return implode('', $parts).self::plural($lastWord, $count);
722
    }
723
 
724
    /**
725
     * Generate a more truly "random" alpha-numeric string.
726
     *
727
     * @param  int  $length
728
     * @return string
729
     */
730
    public static function random($length = 16)
731
    {
732
        return (static::$randomStringFactory ?? function ($length) {
733
            $string = '';
734
 
735
            while (($len = strlen($string)) < $length) {
736
                $size = $length - $len;
737
 
738
                $bytes = random_bytes($size);
739
 
740
                $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size);
741
            }
742
 
743
            return $string;
744
        })($length);
745
    }
746
 
747
    /**
748
     * Set the callable that will be used to generate random strings.
749
     *
750
     * @param  callable|null  $factory
751
     * @return void
752
     */
753
    public static function createRandomStringsUsing(callable $factory = null)
754
    {
755
        static::$randomStringFactory = $factory;
756
    }
757
 
758
    /**
759
     * Set the sequence that will be used to generate random strings.
760
     *
761
     * @param  array  $sequence
762
     * @param  callable|null  $whenMissing
763
     * @return void
764
     */
765
    public static function createRandomStringsUsingSequence(array $sequence, $whenMissing = null)
766
    {
767
        $next = 0;
768
 
769
        $whenMissing ??= function ($length) use (&$next) {
770
            $factoryCache = static::$randomStringFactory;
771
 
772
            static::$randomStringFactory = null;
773
 
774
            $randomString = static::random($length);
775
 
776
            static::$randomStringFactory = $factoryCache;
777
 
778
            $next++;
779
 
780
            return $randomString;
781
        };
782
 
783
        static::createRandomStringsUsing(function ($length) use (&$next, $sequence, $whenMissing) {
784
            if (array_key_exists($next, $sequence)) {
785
                return $sequence[$next++];
786
            }
787
 
788
            return $whenMissing($length);
789
        });
790
    }
791
 
792
    /**
793
     * Indicate that random strings should be created normally and not using a custom factory.
794
     *
795
     * @return void
796
     */
797
    public static function createRandomStringsNormally()
798
    {
799
        static::$randomStringFactory = null;
800
    }
801
 
802
    /**
803
     * Repeat the given string.
804
     *
805
     * @param  string  $string
806
     * @param  int  $times
807
     * @return string
808
     */
809
    public static function repeat(string $string, int $times)
810
    {
811
        return str_repeat($string, $times);
812
    }
813
 
814
    /**
815
     * Replace a given value in the string sequentially with an array.
816
     *
817
     * @param  string  $search
818
     * @param  iterable<string>  $replace
819
     * @param  string  $subject
820
     * @return string
821
     */
822
    public static function replaceArray($search, $replace, $subject)
823
    {
824
        if ($replace instanceof Traversable) {
825
            $replace = collect($replace)->all();
826
        }
827
 
828
        $segments = explode($search, $subject);
829
 
830
        $result = array_shift($segments);
831
 
832
        foreach ($segments as $segment) {
833
            $result .= (array_shift($replace) ?? $search).$segment;
834
        }
835
 
836
        return $result;
837
    }
838
 
839
    /**
840
     * Replace the given value in the given string.
841
     *
842
     * @param  string|iterable<string>  $search
843
     * @param  string|iterable<string>  $replace
844
     * @param  string|iterable<string>  $subject
845
     * @return string
846
     */
847
    public static function replace($search, $replace, $subject)
848
    {
849
        if ($search instanceof Traversable) {
850
            $search = collect($search)->all();
851
        }
852
 
853
        if ($replace instanceof Traversable) {
854
            $replace = collect($replace)->all();
855
        }
856
 
857
        if ($subject instanceof Traversable) {
858
            $subject = collect($subject)->all();
859
        }
860
 
861
        return str_replace($search, $replace, $subject);
862
    }
863
 
864
    /**
865
     * Replace the first occurrence of a given value in the string.
866
     *
867
     * @param  string  $search
868
     * @param  string  $replace
869
     * @param  string  $subject
870
     * @return string
871
     */
872
    public static function replaceFirst($search, $replace, $subject)
873
    {
874
        $search = (string) $search;
875
 
876
        if ($search === '') {
877
            return $subject;
878
        }
879
 
880
        $position = strpos($subject, $search);
881
 
882
        if ($position !== false) {
883
            return substr_replace($subject, $replace, $position, strlen($search));
884
        }
885
 
886
        return $subject;
887
    }
888
 
889
    /**
890
     * Replace the last occurrence of a given value in the string.
891
     *
892
     * @param  string  $search
893
     * @param  string  $replace
894
     * @param  string  $subject
895
     * @return string
896
     */
897
    public static function replaceLast($search, $replace, $subject)
898
    {
899
        if ($search === '') {
900
            return $subject;
901
        }
902
 
903
        $position = strrpos($subject, $search);
904
 
905
        if ($position !== false) {
906
            return substr_replace($subject, $replace, $position, strlen($search));
907
        }
908
 
909
        return $subject;
910
    }
911
 
912
    /**
913
     * Remove any occurrence of the given string in the subject.
914
     *
915
     * @param  string|iterable<string>  $search
916
     * @param  string  $subject
917
     * @param  bool  $caseSensitive
918
     * @return string
919
     */
920
    public static function remove($search, $subject, $caseSensitive = true)
921
    {
922
        if ($search instanceof Traversable) {
923
            $search = collect($search)->all();
924
        }
925
 
926
        $subject = $caseSensitive
927
                    ? str_replace($search, '', $subject)
928
                    : str_ireplace($search, '', $subject);
929
 
930
        return $subject;
931
    }
932
 
933
    /**
934
     * Reverse the given string.
935
     *
936
     * @param  string  $value
937
     * @return string
938
     */
939
    public static function reverse(string $value)
940
    {
941
        return implode(array_reverse(mb_str_split($value)));
942
    }
943
 
944
    /**
945
     * Begin a string with a single instance of a given value.
946
     *
947
     * @param  string  $value
948
     * @param  string  $prefix
949
     * @return string
950
     */
951
    public static function start($value, $prefix)
952
    {
953
        $quoted = preg_quote($prefix, '/');
954
 
955
        return $prefix.preg_replace('/^(?:'.$quoted.')+/u', '', $value);
956
    }
957
 
958
    /**
959
     * Convert the given string to upper-case.
960
     *
961
     * @param  string  $value
962
     * @return string
963
     */
964
    public static function upper($value)
965
    {
966
        return mb_strtoupper($value, 'UTF-8');
967
    }
968
 
969
    /**
970
     * Convert the given string to title case.
971
     *
972
     * @param  string  $value
973
     * @return string
974
     */
975
    public static function title($value)
976
    {
977
        return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
978
    }
979
 
980
    /**
981
     * Convert the given string to title case for each word.
982
     *
983
     * @param  string  $value
984
     * @return string
985
     */
986
    public static function headline($value)
987
    {
988
        $parts = explode(' ', $value);
989
 
990
        $parts = count($parts) > 1
991
            ? array_map([static::class, 'title'], $parts)
992
            : array_map([static::class, 'title'], static::ucsplit(implode('_', $parts)));
993
 
994
        $collapsed = static::replace(['-', '_', ' '], '_', implode('_', $parts));
995
 
996
        return implode(' ', array_filter(explode('_', $collapsed)));
997
    }
998
 
999
    /**
1000
     * Get the singular form of an English word.
1001
     *
1002
     * @param  string  $value
1003
     * @return string
1004
     */
1005
    public static function singular($value)
1006
    {
1007
        return Pluralizer::singular($value);
1008
    }
1009
 
1010
    /**
1011
     * Generate a URL friendly "slug" from a given string.
1012
     *
1013
     * @param  string  $title
1014
     * @param  string  $separator
1015
     * @param  string|null  $language
1016
     * @param  array<string, string>  $dictionary
1017
     * @return string
1018
     */
1019
    public static function slug($title, $separator = '-', $language = 'en', $dictionary = ['@' => 'at'])
1020
    {
1021
        $title = $language ? static::ascii($title, $language) : $title;
1022
 
1023
        // Convert all dashes/underscores into separator
1024
        $flip = $separator === '-' ? '_' : '-';
1025
 
1026
        $title = preg_replace('!['.preg_quote($flip).']+!u', $separator, $title);
1027
 
1028
        // Replace dictionary words
1029
        foreach ($dictionary as $key => $value) {
1030
            $dictionary[$key] = $separator.$value.$separator;
1031
        }
1032
 
1033
        $title = str_replace(array_keys($dictionary), array_values($dictionary), $title);
1034
 
1035
        // Remove all characters that are not the separator, letters, numbers, or whitespace
1036
        $title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', static::lower($title));
1037
 
1038
        // Replace all separator characters and whitespace by a single separator
1039
        $title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);
1040
 
1041
        return trim($title, $separator);
1042
    }
1043
 
1044
    /**
1045
     * Convert a string to snake case.
1046
     *
1047
     * @param  string  $value
1048
     * @param  string  $delimiter
1049
     * @return string
1050
     */
1051
    public static function snake($value, $delimiter = '_')
1052
    {
1053
        $key = $value;
1054
 
1055
        if (isset(static::$snakeCache[$key][$delimiter])) {
1056
            return static::$snakeCache[$key][$delimiter];
1057
        }
1058
 
1059
        if (! ctype_lower($value)) {
1060
            $value = preg_replace('/\s+/u', '', ucwords($value));
1061
 
1062
            $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value));
1063
        }
1064
 
1065
        return static::$snakeCache[$key][$delimiter] = $value;
1066
    }
1067
 
1068
    /**
1069
     * Remove all "extra" blank space from the given string.
1070
     *
1071
     * @param  string  $value
1072
     * @return string
1073
     */
1074
    public static function squish($value)
1075
    {
1076
        return preg_replace('~(\s|\x{3164})+~u', ' ', preg_replace('~^[\s]+|[\s]+$~u', '', $value));
1077
    }
1078
 
1079
    /**
1080
     * Determine if a given string starts with a given substring.
1081
     *
1082
     * @param  string  $haystack
1083
     * @param  string|iterable<string>  $needles
1084
     * @return bool
1085
     */
1086
    public static function startsWith($haystack, $needles)
1087
    {
1088
        if (! is_iterable($needles)) {
1089
            $needles = [$needles];
1090
        }
1091
 
1092
        foreach ($needles as $needle) {
1093
            if ((string) $needle !== '' && str_starts_with($haystack, $needle)) {
1094
                return true;
1095
            }
1096
        }
1097
 
1098
        return false;
1099
    }
1100
 
1101
    /**
1102
     * Convert a value to studly caps case.
1103
     *
1104
     * @param  string  $value
1105
     * @return string
1106
     */
1107
    public static function studly($value)
1108
    {
1109
        $key = $value;
1110
 
1111
        if (isset(static::$studlyCache[$key])) {
1112
            return static::$studlyCache[$key];
1113
        }
1114
 
1115
        $words = explode(' ', static::replace(['-', '_'], ' ', $value));
1116
 
1117
        $studlyWords = array_map(fn ($word) => static::ucfirst($word), $words);
1118
 
1119
        return static::$studlyCache[$key] = implode($studlyWords);
1120
    }
1121
 
1122
    /**
1123
     * Returns the portion of the string specified by the start and length parameters.
1124
     *
1125
     * @param  string  $string
1126
     * @param  int  $start
1127
     * @param  int|null  $length
1128
     * @param  string  $encoding
1129
     * @return string
1130
     */
1131
    public static function substr($string, $start, $length = null, $encoding = 'UTF-8')
1132
    {
1133
        return mb_substr($string, $start, $length, $encoding);
1134
    }
1135
 
1136
    /**
1137
     * Returns the number of substring occurrences.
1138
     *
1139
     * @param  string  $haystack
1140
     * @param  string  $needle
1141
     * @param  int  $offset
1142
     * @param  int|null  $length
1143
     * @return int
1144
     */
1145
    public static function substrCount($haystack, $needle, $offset = 0, $length = null)
1146
    {
1147
        if (! is_null($length)) {
1148
            return substr_count($haystack, $needle, $offset, $length);
1149
        }
1150
 
1151
        return substr_count($haystack, $needle, $offset);
1152
    }
1153
 
1154
    /**
1155
     * Replace text within a portion of a string.
1156
     *
1157
     * @param  string|string[]  $string
1158
     * @param  string|string[]  $replace
1159
     * @param  int|int[]  $offset
1160
     * @param  int|int[]|null  $length
1161
     * @return string|string[]
1162
     */
1163
    public static function substrReplace($string, $replace, $offset = 0, $length = null)
1164
    {
1165
        if ($length === null) {
1166
            $length = strlen($string);
1167
        }
1168
 
1169
        return substr_replace($string, $replace, $offset, $length);
1170
    }
1171
 
1172
    /**
1173
     * Swap multiple keywords in a string with other keywords.
1174
     *
1175
     * @param  array  $map
1176
     * @param  string  $subject
1177
     * @return string
1178
     */
1179
    public static function swap(array $map, $subject)
1180
    {
1181
        return strtr($subject, $map);
1182
    }
1183
 
1184
    /**
1185
     * Make a string's first character lowercase.
1186
     *
1187
     * @param  string  $string
1188
     * @return string
1189
     */
1190
    public static function lcfirst($string)
1191
    {
1192
        return static::lower(static::substr($string, 0, 1)).static::substr($string, 1);
1193
    }
1194
 
1195
    /**
1196
     * Make a string's first character uppercase.
1197
     *
1198
     * @param  string  $string
1199
     * @return string
1200
     */
1201
    public static function ucfirst($string)
1202
    {
1203
        return static::upper(static::substr($string, 0, 1)).static::substr($string, 1);
1204
    }
1205
 
1206
    /**
1207
     * Split a string into pieces by uppercase characters.
1208
     *
1209
     * @param  string  $string
1210
     * @return string[]
1211
     */
1212
    public static function ucsplit($string)
1213
    {
1214
        return preg_split('/(?=\p{Lu})/u', $string, -1, PREG_SPLIT_NO_EMPTY);
1215
    }
1216
 
1217
    /**
1218
     * Get the number of words a string contains.
1219
     *
1220
     * @param  string  $string
1221
     * @param  string|null  $characters
1222
     * @return int
1223
     */
1224
    public static function wordCount($string, $characters = null)
1225
    {
1226
        return str_word_count($string, 0, $characters);
1227
    }
1228
 
1229
    /**
1230
     * Generate a UUID (version 4).
1231
     *
1232
     * @return \Ramsey\Uuid\UuidInterface
1233
     */
1234
    public static function uuid()
1235
    {
1236
        return static::$uuidFactory
1237
                    ? call_user_func(static::$uuidFactory)
1238
                    : Uuid::uuid4();
1239
    }
1240
 
1241
    /**
1242
     * Generate a time-ordered UUID (version 4).
1243
     *
1244
     * @return \Ramsey\Uuid\UuidInterface
1245
     */
1246
    public static function orderedUuid()
1247
    {
1248
        if (static::$uuidFactory) {
1249
            return call_user_func(static::$uuidFactory);
1250
        }
1251
 
1252
        $factory = new UuidFactory;
1253
 
1254
        $factory->setRandomGenerator(new CombGenerator(
1255
            $factory->getRandomGenerator(),
1256
            $factory->getNumberConverter()
1257
        ));
1258
 
1259
        $factory->setCodec(new TimestampFirstCombCodec(
1260
            $factory->getUuidBuilder()
1261
        ));
1262
 
1263
        return $factory->uuid4();
1264
    }
1265
 
1266
    /**
1267
     * Set the callable that will be used to generate UUIDs.
1268
     *
1269
     * @param  callable|null  $factory
1270
     * @return void
1271
     */
1272
    public static function createUuidsUsing(callable $factory = null)
1273
    {
1274
        static::$uuidFactory = $factory;
1275
    }
1276
 
1277
    /**
1278
     * Set the sequence that will be used to generate UUIDs.
1279
     *
1280
     * @param  array  $sequence
1281
     * @param  callable|null  $whenMissing
1282
     * @return void
1283
     */
1284
    public static function createUuidsUsingSequence(array $sequence, $whenMissing = null)
1285
    {
1286
        $next = 0;
1287
 
1288
        $whenMissing ??= function () use (&$next) {
1289
            $factoryCache = static::$uuidFactory;
1290
 
1291
            static::$uuidFactory = null;
1292
 
1293
            $uuid = static::uuid();
1294
 
1295
            static::$uuidFactory = $factoryCache;
1296
 
1297
            $next++;
1298
 
1299
            return $uuid;
1300
        };
1301
 
1302
        static::createUuidsUsing(function () use (&$next, $sequence, $whenMissing) {
1303
            if (array_key_exists($next, $sequence)) {
1304
                return $sequence[$next++];
1305
            }
1306
 
1307
            return $whenMissing();
1308
        });
1309
    }
1310
 
1311
    /**
1312
     * Always return the same UUID when generating new UUIDs.
1313
     *
1314
     * @param  \Closure|null  $callback
1315
     * @return \Ramsey\Uuid\UuidInterface
1316
     */
1317
    public static function freezeUuids(Closure $callback = null)
1318
    {
1319
        $uuid = Str::uuid();
1320
 
1321
        Str::createUuidsUsing(fn () => $uuid);
1322
 
1323
        if ($callback !== null) {
1324
            try {
1325
                $callback($uuid);
1326
            } finally {
1327
                Str::createUuidsNormally();
1328
            }
1329
        }
1330
 
1331
        return $uuid;
1332
    }
1333
 
1334
    /**
1335
     * Indicate that UUIDs should be created normally and not using a custom factory.
1336
     *
1337
     * @return void
1338
     */
1339
    public static function createUuidsNormally()
1340
    {
1341
        static::$uuidFactory = null;
1342
    }
1343
 
1344
    /**
1345
     * Generate a ULID.
1346
     *
1347
     * @return \Symfony\Component\Uid\Ulid
1348
     */
1349
    public static function ulid()
1350
    {
1351
        return new Ulid();
1352
    }
1353
 
1354
    /**
1355
     * Remove all strings from the casing caches.
1356
     *
1357
     * @return void
1358
     */
1359
    public static function flushCache()
1360
    {
1361
        static::$snakeCache = [];
1362
        static::$camelCache = [];
1363
        static::$studlyCache = [];
1364
    }
1365
}