Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 621 | Details | Vergleich mit vorheriger | 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
 
991 lars 738
                $bytesSize = (int) ceil($size / 3) * 3;
148 lars 739
 
621 lars 740
                $bytes = random_bytes($bytesSize);
741
 
148 lars 742
                $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size);
743
            }
744
 
745
            return $string;
746
        })($length);
747
    }
748
 
749
    /**
750
     * Set the callable that will be used to generate random strings.
751
     *
752
     * @param  callable|null  $factory
753
     * @return void
754
     */
755
    public static function createRandomStringsUsing(callable $factory = null)
756
    {
757
        static::$randomStringFactory = $factory;
758
    }
759
 
760
    /**
761
     * Set the sequence that will be used to generate random strings.
762
     *
763
     * @param  array  $sequence
764
     * @param  callable|null  $whenMissing
765
     * @return void
766
     */
767
    public static function createRandomStringsUsingSequence(array $sequence, $whenMissing = null)
768
    {
769
        $next = 0;
770
 
771
        $whenMissing ??= function ($length) use (&$next) {
772
            $factoryCache = static::$randomStringFactory;
773
 
774
            static::$randomStringFactory = null;
775
 
776
            $randomString = static::random($length);
777
 
778
            static::$randomStringFactory = $factoryCache;
779
 
780
            $next++;
781
 
782
            return $randomString;
783
        };
784
 
785
        static::createRandomStringsUsing(function ($length) use (&$next, $sequence, $whenMissing) {
786
            if (array_key_exists($next, $sequence)) {
787
                return $sequence[$next++];
788
            }
789
 
790
            return $whenMissing($length);
791
        });
792
    }
793
 
794
    /**
795
     * Indicate that random strings should be created normally and not using a custom factory.
796
     *
797
     * @return void
798
     */
799
    public static function createRandomStringsNormally()
800
    {
801
        static::$randomStringFactory = null;
802
    }
803
 
804
    /**
805
     * Repeat the given string.
806
     *
807
     * @param  string  $string
808
     * @param  int  $times
809
     * @return string
810
     */
811
    public static function repeat(string $string, int $times)
812
    {
813
        return str_repeat($string, $times);
814
    }
815
 
816
    /**
817
     * Replace a given value in the string sequentially with an array.
818
     *
819
     * @param  string  $search
820
     * @param  iterable<string>  $replace
821
     * @param  string  $subject
822
     * @return string
823
     */
824
    public static function replaceArray($search, $replace, $subject)
825
    {
826
        if ($replace instanceof Traversable) {
827
            $replace = collect($replace)->all();
828
        }
829
 
830
        $segments = explode($search, $subject);
831
 
832
        $result = array_shift($segments);
833
 
834
        foreach ($segments as $segment) {
835
            $result .= (array_shift($replace) ?? $search).$segment;
836
        }
837
 
838
        return $result;
839
    }
840
 
841
    /**
842
     * Replace the given value in the given string.
843
     *
844
     * @param  string|iterable<string>  $search
845
     * @param  string|iterable<string>  $replace
846
     * @param  string|iterable<string>  $subject
847
     * @return string
848
     */
849
    public static function replace($search, $replace, $subject)
850
    {
851
        if ($search instanceof Traversable) {
852
            $search = collect($search)->all();
853
        }
854
 
855
        if ($replace instanceof Traversable) {
856
            $replace = collect($replace)->all();
857
        }
858
 
859
        if ($subject instanceof Traversable) {
860
            $subject = collect($subject)->all();
861
        }
862
 
863
        return str_replace($search, $replace, $subject);
864
    }
865
 
866
    /**
867
     * Replace the first occurrence of a given value in the string.
868
     *
869
     * @param  string  $search
870
     * @param  string  $replace
871
     * @param  string  $subject
872
     * @return string
873
     */
874
    public static function replaceFirst($search, $replace, $subject)
875
    {
876
        $search = (string) $search;
877
 
878
        if ($search === '') {
879
            return $subject;
880
        }
881
 
882
        $position = strpos($subject, $search);
883
 
884
        if ($position !== false) {
885
            return substr_replace($subject, $replace, $position, strlen($search));
886
        }
887
 
888
        return $subject;
889
    }
890
 
891
    /**
892
     * Replace the last occurrence of a given value in the string.
893
     *
894
     * @param  string  $search
895
     * @param  string  $replace
896
     * @param  string  $subject
897
     * @return string
898
     */
899
    public static function replaceLast($search, $replace, $subject)
900
    {
901
        if ($search === '') {
902
            return $subject;
903
        }
904
 
905
        $position = strrpos($subject, $search);
906
 
907
        if ($position !== false) {
908
            return substr_replace($subject, $replace, $position, strlen($search));
909
        }
910
 
911
        return $subject;
912
    }
913
 
914
    /**
915
     * Remove any occurrence of the given string in the subject.
916
     *
917
     * @param  string|iterable<string>  $search
918
     * @param  string  $subject
919
     * @param  bool  $caseSensitive
920
     * @return string
921
     */
922
    public static function remove($search, $subject, $caseSensitive = true)
923
    {
924
        if ($search instanceof Traversable) {
925
            $search = collect($search)->all();
926
        }
927
 
928
        $subject = $caseSensitive
929
                    ? str_replace($search, '', $subject)
930
                    : str_ireplace($search, '', $subject);
931
 
932
        return $subject;
933
    }
934
 
935
    /**
936
     * Reverse the given string.
937
     *
938
     * @param  string  $value
939
     * @return string
940
     */
941
    public static function reverse(string $value)
942
    {
943
        return implode(array_reverse(mb_str_split($value)));
944
    }
945
 
946
    /**
947
     * Begin a string with a single instance of a given value.
948
     *
949
     * @param  string  $value
950
     * @param  string  $prefix
951
     * @return string
952
     */
953
    public static function start($value, $prefix)
954
    {
955
        $quoted = preg_quote($prefix, '/');
956
 
957
        return $prefix.preg_replace('/^(?:'.$quoted.')+/u', '', $value);
958
    }
959
 
960
    /**
961
     * Convert the given string to upper-case.
962
     *
963
     * @param  string  $value
964
     * @return string
965
     */
966
    public static function upper($value)
967
    {
968
        return mb_strtoupper($value, 'UTF-8');
969
    }
970
 
971
    /**
972
     * Convert the given string to title case.
973
     *
974
     * @param  string  $value
975
     * @return string
976
     */
977
    public static function title($value)
978
    {
979
        return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
980
    }
981
 
982
    /**
983
     * Convert the given string to title case for each word.
984
     *
985
     * @param  string  $value
986
     * @return string
987
     */
988
    public static function headline($value)
989
    {
990
        $parts = explode(' ', $value);
991
 
992
        $parts = count($parts) > 1
993
            ? array_map([static::class, 'title'], $parts)
994
            : array_map([static::class, 'title'], static::ucsplit(implode('_', $parts)));
995
 
996
        $collapsed = static::replace(['-', '_', ' '], '_', implode('_', $parts));
997
 
998
        return implode(' ', array_filter(explode('_', $collapsed)));
999
    }
1000
 
1001
    /**
1002
     * Get the singular form of an English word.
1003
     *
1004
     * @param  string  $value
1005
     * @return string
1006
     */
1007
    public static function singular($value)
1008
    {
1009
        return Pluralizer::singular($value);
1010
    }
1011
 
1012
    /**
1013
     * Generate a URL friendly "slug" from a given string.
1014
     *
1015
     * @param  string  $title
1016
     * @param  string  $separator
1017
     * @param  string|null  $language
1018
     * @param  array<string, string>  $dictionary
1019
     * @return string
1020
     */
1021
    public static function slug($title, $separator = '-', $language = 'en', $dictionary = ['@' => 'at'])
1022
    {
1023
        $title = $language ? static::ascii($title, $language) : $title;
1024
 
1025
        // Convert all dashes/underscores into separator
1026
        $flip = $separator === '-' ? '_' : '-';
1027
 
1028
        $title = preg_replace('!['.preg_quote($flip).']+!u', $separator, $title);
1029
 
1030
        // Replace dictionary words
1031
        foreach ($dictionary as $key => $value) {
1032
            $dictionary[$key] = $separator.$value.$separator;
1033
        }
1034
 
1035
        $title = str_replace(array_keys($dictionary), array_values($dictionary), $title);
1036
 
1037
        // Remove all characters that are not the separator, letters, numbers, or whitespace
1038
        $title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', static::lower($title));
1039
 
1040
        // Replace all separator characters and whitespace by a single separator
1041
        $title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);
1042
 
1043
        return trim($title, $separator);
1044
    }
1045
 
1046
    /**
1047
     * Convert a string to snake case.
1048
     *
1049
     * @param  string  $value
1050
     * @param  string  $delimiter
1051
     * @return string
1052
     */
1053
    public static function snake($value, $delimiter = '_')
1054
    {
1055
        $key = $value;
1056
 
1057
        if (isset(static::$snakeCache[$key][$delimiter])) {
1058
            return static::$snakeCache[$key][$delimiter];
1059
        }
1060
 
1061
        if (! ctype_lower($value)) {
1062
            $value = preg_replace('/\s+/u', '', ucwords($value));
1063
 
1064
            $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value));
1065
        }
1066
 
1067
        return static::$snakeCache[$key][$delimiter] = $value;
1068
    }
1069
 
1070
    /**
1071
     * Remove all "extra" blank space from the given string.
1072
     *
1073
     * @param  string  $value
1074
     * @return string
1075
     */
1076
    public static function squish($value)
1077
    {
399 lars 1078
        return preg_replace('~(\s|\x{3164})+~u', ' ', preg_replace('~^[\s\x{FEFF}]+|[\s\x{FEFF}]+$~u', '', $value));
148 lars 1079
    }
1080
 
1081
    /**
1082
     * Determine if a given string starts with a given substring.
1083
     *
1084
     * @param  string  $haystack
1085
     * @param  string|iterable<string>  $needles
1086
     * @return bool
1087
     */
1088
    public static function startsWith($haystack, $needles)
1089
    {
1090
        if (! is_iterable($needles)) {
1091
            $needles = [$needles];
1092
        }
1093
 
1094
        foreach ($needles as $needle) {
1095
            if ((string) $needle !== '' && str_starts_with($haystack, $needle)) {
1096
                return true;
1097
            }
1098
        }
1099
 
1100
        return false;
1101
    }
1102
 
1103
    /**
1104
     * Convert a value to studly caps case.
1105
     *
1106
     * @param  string  $value
1107
     * @return string
1108
     */
1109
    public static function studly($value)
1110
    {
1111
        $key = $value;
1112
 
1113
        if (isset(static::$studlyCache[$key])) {
1114
            return static::$studlyCache[$key];
1115
        }
1116
 
1117
        $words = explode(' ', static::replace(['-', '_'], ' ', $value));
1118
 
1119
        $studlyWords = array_map(fn ($word) => static::ucfirst($word), $words);
1120
 
1121
        return static::$studlyCache[$key] = implode($studlyWords);
1122
    }
1123
 
1124
    /**
1125
     * Returns the portion of the string specified by the start and length parameters.
1126
     *
1127
     * @param  string  $string
1128
     * @param  int  $start
1129
     * @param  int|null  $length
1130
     * @param  string  $encoding
1131
     * @return string
1132
     */
1133
    public static function substr($string, $start, $length = null, $encoding = 'UTF-8')
1134
    {
1135
        return mb_substr($string, $start, $length, $encoding);
1136
    }
1137
 
1138
    /**
1139
     * Returns the number of substring occurrences.
1140
     *
1141
     * @param  string  $haystack
1142
     * @param  string  $needle
1143
     * @param  int  $offset
1144
     * @param  int|null  $length
1145
     * @return int
1146
     */
1147
    public static function substrCount($haystack, $needle, $offset = 0, $length = null)
1148
    {
1149
        if (! is_null($length)) {
1150
            return substr_count($haystack, $needle, $offset, $length);
1151
        }
1152
 
1153
        return substr_count($haystack, $needle, $offset);
1154
    }
1155
 
1156
    /**
1157
     * Replace text within a portion of a string.
1158
     *
1159
     * @param  string|string[]  $string
1160
     * @param  string|string[]  $replace
1161
     * @param  int|int[]  $offset
1162
     * @param  int|int[]|null  $length
1163
     * @return string|string[]
1164
     */
1165
    public static function substrReplace($string, $replace, $offset = 0, $length = null)
1166
    {
1167
        if ($length === null) {
1168
            $length = strlen($string);
1169
        }
1170
 
1171
        return substr_replace($string, $replace, $offset, $length);
1172
    }
1173
 
1174
    /**
1175
     * Swap multiple keywords in a string with other keywords.
1176
     *
1177
     * @param  array  $map
1178
     * @param  string  $subject
1179
     * @return string
1180
     */
1181
    public static function swap(array $map, $subject)
1182
    {
1183
        return strtr($subject, $map);
1184
    }
1185
 
1186
    /**
1187
     * Make a string's first character lowercase.
1188
     *
1189
     * @param  string  $string
1190
     * @return string
1191
     */
1192
    public static function lcfirst($string)
1193
    {
1194
        return static::lower(static::substr($string, 0, 1)).static::substr($string, 1);
1195
    }
1196
 
1197
    /**
1198
     * Make a string's first character uppercase.
1199
     *
1200
     * @param  string  $string
1201
     * @return string
1202
     */
1203
    public static function ucfirst($string)
1204
    {
1205
        return static::upper(static::substr($string, 0, 1)).static::substr($string, 1);
1206
    }
1207
 
1208
    /**
1209
     * Split a string into pieces by uppercase characters.
1210
     *
1211
     * @param  string  $string
1212
     * @return string[]
1213
     */
1214
    public static function ucsplit($string)
1215
    {
1216
        return preg_split('/(?=\p{Lu})/u', $string, -1, PREG_SPLIT_NO_EMPTY);
1217
    }
1218
 
1219
    /**
1220
     * Get the number of words a string contains.
1221
     *
1222
     * @param  string  $string
1223
     * @param  string|null  $characters
1224
     * @return int
1225
     */
1226
    public static function wordCount($string, $characters = null)
1227
    {
1228
        return str_word_count($string, 0, $characters);
1229
    }
1230
 
1231
    /**
1232
     * Generate a UUID (version 4).
1233
     *
1234
     * @return \Ramsey\Uuid\UuidInterface
1235
     */
1236
    public static function uuid()
1237
    {
1238
        return static::$uuidFactory
1239
                    ? call_user_func(static::$uuidFactory)
1240
                    : Uuid::uuid4();
1241
    }
1242
 
1243
    /**
1244
     * Generate a time-ordered UUID (version 4).
1245
     *
1246
     * @return \Ramsey\Uuid\UuidInterface
1247
     */
1248
    public static function orderedUuid()
1249
    {
1250
        if (static::$uuidFactory) {
1251
            return call_user_func(static::$uuidFactory);
1252
        }
1253
 
1254
        $factory = new UuidFactory;
1255
 
1256
        $factory->setRandomGenerator(new CombGenerator(
1257
            $factory->getRandomGenerator(),
1258
            $factory->getNumberConverter()
1259
        ));
1260
 
1261
        $factory->setCodec(new TimestampFirstCombCodec(
1262
            $factory->getUuidBuilder()
1263
        ));
1264
 
1265
        return $factory->uuid4();
1266
    }
1267
 
1268
    /**
1269
     * Set the callable that will be used to generate UUIDs.
1270
     *
1271
     * @param  callable|null  $factory
1272
     * @return void
1273
     */
1274
    public static function createUuidsUsing(callable $factory = null)
1275
    {
1276
        static::$uuidFactory = $factory;
1277
    }
1278
 
1279
    /**
1280
     * Set the sequence that will be used to generate UUIDs.
1281
     *
1282
     * @param  array  $sequence
1283
     * @param  callable|null  $whenMissing
1284
     * @return void
1285
     */
1286
    public static function createUuidsUsingSequence(array $sequence, $whenMissing = null)
1287
    {
1288
        $next = 0;
1289
 
1290
        $whenMissing ??= function () use (&$next) {
1291
            $factoryCache = static::$uuidFactory;
1292
 
1293
            static::$uuidFactory = null;
1294
 
1295
            $uuid = static::uuid();
1296
 
1297
            static::$uuidFactory = $factoryCache;
1298
 
1299
            $next++;
1300
 
1301
            return $uuid;
1302
        };
1303
 
1304
        static::createUuidsUsing(function () use (&$next, $sequence, $whenMissing) {
1305
            if (array_key_exists($next, $sequence)) {
1306
                return $sequence[$next++];
1307
            }
1308
 
1309
            return $whenMissing();
1310
        });
1311
    }
1312
 
1313
    /**
1314
     * Always return the same UUID when generating new UUIDs.
1315
     *
1316
     * @param  \Closure|null  $callback
1317
     * @return \Ramsey\Uuid\UuidInterface
1318
     */
1319
    public static function freezeUuids(Closure $callback = null)
1320
    {
1321
        $uuid = Str::uuid();
1322
 
1323
        Str::createUuidsUsing(fn () => $uuid);
1324
 
1325
        if ($callback !== null) {
1326
            try {
1327
                $callback($uuid);
1328
            } finally {
1329
                Str::createUuidsNormally();
1330
            }
1331
        }
1332
 
1333
        return $uuid;
1334
    }
1335
 
1336
    /**
1337
     * Indicate that UUIDs should be created normally and not using a custom factory.
1338
     *
1339
     * @return void
1340
     */
1341
    public static function createUuidsNormally()
1342
    {
1343
        static::$uuidFactory = null;
1344
    }
1345
 
1346
    /**
1347
     * Generate a ULID.
1348
     *
1349
     * @return \Symfony\Component\Uid\Ulid
1350
     */
1351
    public static function ulid()
1352
    {
1353
        return new Ulid();
1354
    }
1355
 
1356
    /**
1357
     * Remove all strings from the casing caches.
1358
     *
1359
     * @return void
1360
     */
1361
    public static function flushCache()
1362
    {
1363
        static::$snakeCache = [];
1364
        static::$camelCache = [];
1365
        static::$studlyCache = [];
1366
    }
1367
}