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

voku / Stringy / 24291583386

11 Apr 2026 09:04PM UTC coverage: 97.998% (+0.01%) from 97.987%
24291583386

Pull #51

github

web-flow
Merge 2fbd52617 into e878921e9
Pull Request #51: Fix camelCase and upperCamelize for all-caps inputs

1028 of 1049 relevant lines covered (98.0%)

69.1 hits per line

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

97.87
/src/Stringy.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Stringy;
6

7
use Defuse\Crypto\Crypto;
8
use voku\helper\AntiXSS;
9
use voku\helper\ASCII;
10
use voku\helper\EmailCheck;
11
use voku\helper\URLify;
12
use voku\helper\UTF8;
13

14
/**
15
 * ## 🇷🇺 Русским гражданам
16
 * В Украине сейчас идет война. Силами РФ наносятся удары по гражданской инфраструктуре в [Харькове][1], [Киеве][2], [Чернигове][3], [Сумах][4], [Ирпене][5] и десятках других городов. Гибнут люди - и гражданское население, и военные, в том числе российские призывники, которых бросили воевать. Чтобы лишить собственный народ доступа к информации, правительство РФ запретило называть войну войной, закрыло независимые СМИ и принимает сейчас ряд диктаторских законов. Эти законы призваны заткнуть рот всем, кто против войны. За обычный призыв к миру сейчас можно получить несколько лет тюрьмы.
17
 * Не молчите! Молчание - знак вашего согласия с политикой российского правительства.
18
 *  **Вы можете сделать выбор НЕ МОЛЧАТЬ.**
19
 * ---
20
 * ## 🇺🇸 To people of Russia
21
 * There is a war in Ukraine right now. The forces of the Russian Federation are attacking civilian infrastructure in [Kharkiv][1], [Kyiv][2], [Chernihiv][3], [Sumy][4], [Irpin][5] and dozens of other cities. People are dying – both civilians and military servicemen, including Russian conscripts who were thrown into the fighting. In order to deprive its own people of access to information, the government of the Russian Federation has forbidden calling a war a war, shut down independent media and is passing a number of dictatorial laws. These laws are meant to silence all those who are against war. You can be jailed for multiple years for simply calling for peace.
22
 * Do not be silent! Silence is a sign that you accept the Russian government's policy.
23
 * **You can choose NOT TO BE SILENT.**
24
 * ---
25
 * - [1] https://cloudfront-us-east-2.images.arcpublishing.com/reuters/P7K2MSZDGFMIJPDD7CI2GIROJI.jpg "Kharkiv under attack"
26
 * - [2] https://gdb.voanews.com/01bd0000-0aff-0242-fad0-08d9fc92c5b3_cx0_cy5_cw0_w1023_r1_s.jpg "Kyiv under attack"
27
 * - [3] https://ichef.bbci.co.uk/news/976/cpsprodpb/163DD/production/_123510119_hi074310744.jpg "Chernihiv under attack"
28
 * - [4] https://www.youtube.com/watch?v=8K-bkqKKf2A "Sumy under attack"
29
 * - [5] https://cloudfront-us-east-2.images.arcpublishing.com/reuters/K4MTMLEHTRKGFK3GSKAT4GR3NE.jpg "Irpin under attack"
30
 *
31
 * @template-implements \IteratorAggregate<string>
32
 * @template-implements \ArrayAccess<array-key,string>
33
 */
34
class Stringy implements \ArrayAccess, \Countable, \IteratorAggregate, \JsonSerializable
35
{
36
    /**
37
     * An instance's string.
38
     *
39
     * @var string
40
     */
41
    protected $str;
42

43
    /**
44
     * The string's encoding, which should be one of the mbstring module's
45
     * supported encodings.
46
     *
47
     * @var string
48
     */
49
    protected $encoding;
50

51
    /**
52
     * @var UTF8
53
     */
54
    private $utf8;
55

56
    /**
57
     * @var ASCII
58
     */
59
    private $ascii;
60

61
    /**
62
     * Initializes a Stringy object and assigns both str and encoding properties
63
     * the supplied values. $str is cast to a string prior to assignment, and if
64
     * $encoding is not specified, it defaults to mb_internal_encoding(). Throws
65
     * an InvalidArgumentException if the first argument is an array or object
66
     * without a __toString method.
67
     *
68
     * @param object|scalar $str      [optional] <p>Value to modify, after being cast to string. Default: ''</p>
69
     * @param string        $encoding [optional] <p>The character encoding. Fallback: 'UTF-8'</p>
70
     *
71
     * @throws \InvalidArgumentException
72
     *                                   <p>if an array or object without a
73
     *                                   __toString method is passed as the first argument</p>
74
     *
75
     * @psalm-mutation-free
76
     */
77
    public function __construct($str = '', ?string $encoding = null)
78
    {
79
        /* @phpstan-ignore-next-line | always false in theory */
80
        if (\is_array($str)) {
3,695✔
81
            throw new \InvalidArgumentException(
3✔
82
                'Passed value cannot be an array'
3✔
83
            );
3✔
84
        }
85

86
        if (
87
            \is_object($str)
3,692✔
88
            &&
89
            !\method_exists($str, '__toString')
3,692✔
90
        ) {
91
            throw new \InvalidArgumentException(
4✔
92
                'Passed object must have a __toString method'
4✔
93
            );
4✔
94
        }
95

96
        $this->str = (string) $str;
3,689✔
97

98
        static $ASCII = null;
3,689✔
99
        if ($ASCII === null) {
3,689✔
100
            $ASCII = new ASCII();
×
101
        }
102
        $this->ascii = $ASCII;
3,689✔
103

104
        static $UTF8 = null;
3,689✔
105
        if ($UTF8 === null) {
3,689✔
106
            $UTF8 = new UTF8();
×
107
        }
108
        $this->utf8 = $UTF8;
3,689✔
109

110
        if ($encoding !== 'UTF-8') {
3,689✔
111
            $this->encoding = $this->utf8::normalize_encoding($encoding, 'UTF-8');
2,506✔
112
        } else {
113
            $this->encoding = $encoding;
2,749✔
114
        }
115
    }
116

117
    /**
118
     * Returns the value in $str.
119
     *
120
     * EXAMPLE: <code>
121
     * </code>
122
     *
123
     * @psalm-mutation-free
124
     *
125
     * @return string
126
     *                <p>The current value of the $str property.</p>
127
     */
128
    public function __toString()
129
    {
130
        return (string) $this->str;
1,159✔
131
    }
132

133
    /**
134
     * Return part of the string occurring after a specific string.
135
     *
136
     * EXAMPLE: <code>
137
     * s('宮本 茂')->after('本'); // ' 茂'
138
     * </code>
139
     *
140
     * @param string $string <p>The delimiting string.</p>
141
     *
142
     * @psalm-mutation-free
143
     *
144
     * @return static
145
     */
146
    public function after(string $string): self
147
    {
148
        $strArray = UTF8::str_split_pattern(
4✔
149
            $this->str,
4✔
150
            $string
4✔
151
        );
4✔
152

153
        unset($strArray[0]);
4✔
154

155
        return new static(
4✔
156
            \implode(' ', $strArray),
4✔
157
            $this->encoding
4✔
158
        );
4✔
159
    }
160

161
    /**
162
     * Gets the substring after the first occurrence of a separator.
163
     * If no match is found returns new empty Stringy object.
164
     *
165
     * EXAMPLE: <code>
166
     * s('</b></b>')->afterFirst('b'); // '></b>'
167
     * </code>
168
     *
169
     * @param string $separator
170
     *
171
     * @psalm-mutation-free
172
     *
173
     * @return static
174
     */
175
    public function afterFirst(string $separator): self
176
    {
177
        return static::create(
3✔
178
            $this->utf8::str_substr_after_first_separator(
3✔
179
                $this->str,
3✔
180
                $separator,
3✔
181
                $this->encoding
3✔
182
            )
3✔
183
        );
3✔
184
    }
185

186
    /**
187
     * Gets the substring after the first occurrence of a separator.
188
     * If no match is found returns new empty Stringy object.
189
     *
190
     * EXAMPLE: <code>
191
     * s('</B></B>')->afterFirstIgnoreCase('b'); // '></B>'
192
     * </code>
193
     *
194
     * @param string $separator
195
     *
196
     * @psalm-mutation-free
197
     *
198
     * @return static
199
     */
200
    public function afterFirstIgnoreCase(string $separator): self
201
    {
202
        return static::create(
2✔
203
            $this->utf8::str_isubstr_after_first_separator(
2✔
204
                $this->str,
2✔
205
                $separator,
2✔
206
                $this->encoding
2✔
207
            )
2✔
208
        );
2✔
209
    }
210

211
    /**
212
     * Gets the substring after the last occurrence of a separator.
213
     * If no match is found returns new empty Stringy object.
214
     *
215
     * EXAMPLE: <code>
216
     * s('</b></b>')->afterLast('b'); // '>'
217
     * </code>
218
     *
219
     * @param string $separator
220
     *
221
     * @psalm-mutation-free
222
     *
223
     * @return static
224
     */
225
    public function afterLast(string $separator): self
226
    {
227
        return static::create(
2✔
228
            $this->utf8::str_substr_after_last_separator(
2✔
229
                $this->str,
2✔
230
                $separator,
2✔
231
                $this->encoding
2✔
232
            )
2✔
233
        );
2✔
234
    }
235

236
    /**
237
     * Gets the substring after the last occurrence of a separator.
238
     * If no match is found returns new empty Stringy object.
239
     *
240
     * EXAMPLE: <code>
241
     * s('</B></B>')->afterLastIgnoreCase('b'); // '>'
242
     * </code>
243
     *
244
     * @param string $separator
245
     *
246
     * @psalm-mutation-free
247
     *
248
     * @return static
249
     */
250
    public function afterLastIgnoreCase(string $separator): self
251
    {
252
        return static::create(
2✔
253
            $this->utf8::str_isubstr_after_last_separator(
2✔
254
                $this->str,
2✔
255
                $separator,
2✔
256
                $this->encoding
2✔
257
            )
2✔
258
        );
2✔
259
    }
260

261
    /**
262
     * Returns a new string with $suffix appended.
263
     *
264
     * EXAMPLE: <code>
265
     * s('fòô')->append('bàř'); // 'fòôbàř'
266
     * </code>
267
     *
268
     * @param string ...$suffix <p>The string to append.</p>
269
     *
270
     * @psalm-mutation-free
271
     *
272
     * @return static
273
     *                <p>Object with appended $suffix.</p>
274
     */
275
    public function append(string ...$suffix): self
276
    {
277
        if (\count($suffix) <= 1) {
21✔
278
            $suffix = $suffix[0];
19✔
279
        } else {
280
            $suffix = \implode('', $suffix);
2✔
281
        }
282

283
        return static::create($this->str . $suffix, $this->encoding);
21✔
284
    }
285

286
    /**
287
     * Append an password (limited to chars that are good readable).
288
     *
289
     * EXAMPLE: <code>
290
     * s('')->appendPassword(8); // e.g.: '89bcdfgh'
291
     * </code>
292
     *
293
     * @param int $length <p>Length of the random string.</p>
294
     *
295
     * @return static
296
     *                <p>Object with appended password.</p>
297
     */
298
    public function appendPassword(int $length): self
299
    {
300
        return $this->appendRandomString(
2✔
301
            $length,
2✔
302
            '2346789bcdfghjkmnpqrtvwxyzBCDFGHJKLMNPQRTVWXYZ!?_#'
2✔
303
        );
2✔
304
    }
305

306
    /**
307
     * Append an random string.
308
     *
309
     * EXAMPLE: <code>
310
     * s('')->appendUniqueIdentifier(5, 'ABCDEFGHI'); // e.g.: 'CDEHI'
311
     * </code>
312
     *
313
     * @param int    $length        <p>Length of the random string.</p>
314
     * @param string $possibleChars [optional] <p>Characters string for the random selection.</p>
315
     *
316
     * @return static
317
     *                <p>Object with appended random string.</p>
318
     */
319
    public function appendRandomString(int $length, string $possibleChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'): self
320
    {
321
        if ($length <= 0 || $possibleChars === '') {
10✔
322
            return $this->append('');
6✔
323
        }
324

325
        $str = $this->utf8::get_random_string($length, $possibleChars);
6✔
326

327
        return $this->append($str);
6✔
328
    }
329

330
    /**
331
     * Returns a new string with $suffix appended.
332
     *
333
     * EXAMPLE: <code>
334
     * </code>
335
     *
336
     * @param CollectionStringy|static ...$suffix <p>The Stringy objects to append.</p>
337
     *
338
     * @phpstan-param CollectionStringy<int,static>|static ...$suffix
339
     *
340
     * @psalm-mutation-free
341
     *
342
     * @return static
343
     *                <p>Object with appended $suffix.</p>
344
     */
345
    public function appendStringy(...$suffix): self
346
    {
347
        $suffixStr = '';
7✔
348
        foreach ($suffix as $suffixTmp) {
7✔
349
            if ($suffixTmp instanceof CollectionStringy) {
7✔
350
                $suffixStr .= $suffixTmp->implode('');
1✔
351
            } else {
352
                $suffixStr .= $suffixTmp->toString();
7✔
353
            }
354
        }
355

356
        return static::create($this->str . $suffixStr, $this->encoding);
7✔
357
    }
358

359
    /**
360
     * Append an unique identifier.
361
     *
362
     * EXAMPLE: <code>
363
     * s('')->appendUniqueIdentifier(); // e.g.: '1f3870be274f6c49b3e31a0c6728957f'
364
     * </code>
365
     *
366
     * @param int|string $entropyExtra [optional] <p>Extra entropy via a string or int value.</p>
367
     * @param bool       $md5          [optional] <p>Return the unique identifier as md5-hash? Default: true</p>
368
     *
369
     * @return static
370
     *                <p>Object with appended unique identifier as md5-hash.</p>
371
     */
372
    public function appendUniqueIdentifier($entropyExtra = '', bool $md5 = true): self
373
    {
374
        return $this->append(
2✔
375
            $this->utf8::get_unique_string($entropyExtra, $md5)
2✔
376
        );
2✔
377
    }
378

379
    /**
380
     * Returns the character at $index, with indexes starting at 0.
381
     *
382
     * EXAMPLE: <code>
383
     * s('fòôbàř')->at(3); // 'b'
384
     * </code>
385
     *
386
     * @param int $index <p>Position of the character.</p>
387
     *
388
     * @psalm-mutation-free
389
     *
390
     * @return static
391
     *                <p>The character at $index.</p>
392
     */
393
    public function at(int $index): self
394
    {
395
        if ($this->encoding === 'UTF-8') {
32✔
396
            return static::create((string) \mb_substr($this->str, $index, 1), $this->encoding);
32✔
397
        }
398

399
        return static::create($this->utf8::substr($this->str, $index, 1, $this->encoding), $this->encoding);
×
400
    }
401

402
    /**
403
     * Decode the base64 encoded string.
404
     *
405
     * EXAMPLE: <code>
406
     * </code>
407
     *
408
     * @psalm-mutation-free
409
     *
410
     * @return self
411
     */
412
    public function base64Decode(): self
413
    {
414
        return static::create(
2✔
415
            \base64_decode($this->str, true),
2✔
416
            $this->encoding
2✔
417
        );
2✔
418
    }
419

420
    /**
421
     * Encode the string to base64.
422
     *
423
     * EXAMPLE: <code>
424
     * </code>
425
     *
426
     * @psalm-mutation-free
427
     *
428
     * @return self
429
     */
430
    public function base64Encode(): self
431
    {
432
        return static::create(
2✔
433
            \base64_encode($this->str),
2✔
434
            $this->encoding
2✔
435
        );
2✔
436
    }
437

438
    /**
439
     * Creates a hash from the string using the CRYPT_BLOWFISH algorithm.
440
     *
441
     * WARNING: Using this algorithm, will result in the ```$this->str```
442
     *          being truncated to a maximum length of 72 characters.
443
     *
444
     * EXAMPLE: <code>
445
     * </code>
446
     *
447
     * @param array<array-key, int|string> $options [optional] <p>An array of bcrypt hashing options.</p>
448
     *
449
     * @psalm-mutation-free
450
     *
451
     * @return static
452
     */
453
    public function bcrypt(array $options = []): self
454
    {
455
        return new static(
3✔
456
            \password_hash(
3✔
457
                $this->str,
3✔
458
                \PASSWORD_BCRYPT,
3✔
459
                $options
3✔
460
            ),
3✔
461
            $this->encoding
3✔
462
        );
3✔
463
    }
464

465
    /**
466
     * Return part of the string occurring before a specific string.
467
     *
468
     * EXAMPLE: <code>
469
     * </code>
470
     *
471
     * @param string $string <p>The delimiting string.</p>
472
     *
473
     * @psalm-mutation-free
474
     *
475
     * @return static
476
     */
477
    public function before(string $string): self
478
    {
479
        $strArray = UTF8::str_split_pattern(
4✔
480
            $this->str,
4✔
481
            $string,
4✔
482
            1
4✔
483
        );
4✔
484

485
        return new static(
4✔
486
            $strArray[0] ?? '',
4✔
487
            $this->encoding
4✔
488
        );
4✔
489
    }
490

491
    /**
492
     * Gets the substring before the first occurrence of a separator.
493
     * If no match is found returns new empty Stringy object.
494
     *
495
     * EXAMPLE: <code>
496
     * s('</b></b>')->beforeFirst('b'); // '</'
497
     * </code>
498
     *
499
     * @param string $separator
500
     *
501
     * @psalm-mutation-free
502
     *
503
     * @return static
504
     */
505
    public function beforeFirst(string $separator): self
506
    {
507
        return static::create(
2✔
508
            $this->utf8::str_substr_before_first_separator(
2✔
509
                $this->str,
2✔
510
                $separator,
2✔
511
                $this->encoding
2✔
512
            )
2✔
513
        );
2✔
514
    }
515

516
    /**
517
     * Gets the substring before the first occurrence of a separator.
518
     * If no match is found returns new empty Stringy object.
519
     *
520
     * EXAMPLE: <code>
521
     * s('</B></B>')->beforeFirstIgnoreCase('b'); // '</'
522
     * </code>
523
     *
524
     * @param string $separator
525
     *
526
     * @psalm-mutation-free
527
     *
528
     * @return static
529
     */
530
    public function beforeFirstIgnoreCase(string $separator): self
531
    {
532
        return static::create(
2✔
533
            $this->utf8::str_isubstr_before_first_separator(
2✔
534
                $this->str,
2✔
535
                $separator,
2✔
536
                $this->encoding
2✔
537
            )
2✔
538
        );
2✔
539
    }
540

541
    /**
542
     * Gets the substring before the last occurrence of a separator.
543
     * If no match is found returns new empty Stringy object.
544
     *
545
     * EXAMPLE: <code>
546
     * s('</b></b>')->beforeLast('b'); // '</b></'
547
     * </code>
548
     *
549
     * @param string $separator
550
     *
551
     * @psalm-mutation-free
552
     *
553
     * @return static
554
     */
555
    public function beforeLast(string $separator): self
556
    {
557
        return static::create(
2✔
558
            $this->utf8::str_substr_before_last_separator(
2✔
559
                $this->str,
2✔
560
                $separator,
2✔
561
                $this->encoding
2✔
562
            )
2✔
563
        );
2✔
564
    }
565

566
    /**
567
     * Gets the substring before the last occurrence of a separator.
568
     * If no match is found returns new empty Stringy object.
569
     *
570
     * EXAMPLE: <code>
571
     * s('</B></B>')->beforeLastIgnoreCase('b'); // '</B></'
572
     * </code>
573
     *
574
     * @param string $separator
575
     *
576
     * @psalm-mutation-free
577
     *
578
     * @return static
579
     */
580
    public function beforeLastIgnoreCase(string $separator): self
581
    {
582
        return static::create(
2✔
583
            $this->utf8::str_isubstr_before_last_separator(
2✔
584
                $this->str,
2✔
585
                $separator,
2✔
586
                $this->encoding
2✔
587
            )
2✔
588
        );
2✔
589
    }
590

591
    /**
592
     * Returns the substring between $start and $end, if found, or an empty
593
     * string. An optional offset may be supplied from which to begin the
594
     * search for the start string.
595
     *
596
     * EXAMPLE: <code>
597
     * s('{foo} and {bar}')->between('{', '}'); // 'foo'
598
     * </code>
599
     *
600
     * @param string $start  <p>Delimiter marking the start of the substring.</p>
601
     * @param string $end    <p>Delimiter marking the end of the substring.</p>
602
     * @param int    $offset [optional] <p>Index from which to begin the search. Default: 0</p>
603
     *
604
     * @psalm-mutation-free
605
     *
606
     * @return static
607
     *                <p>Object whose $str is a substring between $start and $end.</p>
608
     */
609
    public function between(string $start, string $end, ?int $offset = null): self
610
    {
611
        $str = $this->utf8::between(
48✔
612
            $this->str,
48✔
613
            $start,
48✔
614
            $end,
48✔
615
            (int) $offset,
48✔
616
            $this->encoding
48✔
617
        );
48✔
618

619
        return static::create($str, $this->encoding);
48✔
620
    }
621

622
    /**
623
     * Call a user function.
624
     *
625
     * EXAMPLE: <code>
626
     * S::create('foo bar lall')->callUserFunction(static function ($str) {
627
     *     return UTF8::str_limit($str, 8);
628
     * })->toString(); // "foo bar…"
629
     * </code>
630
     *
631
     * @param callable $function
632
     * @param mixed    ...$parameter
633
     *
634
     * @psalm-mutation-free
635
     *
636
     * @return static
637
     *                <p>Object having a $str changed via $function.</p>
638
     */
639
    public function callUserFunction(callable $function, ...$parameter): self
640
    {
641
        $str = $function($this->str, ...$parameter);
2✔
642

643
        return static::create(
2✔
644
            $str,
2✔
645
            $this->encoding
2✔
646
        );
2✔
647
    }
648

649
    /**
650
     * Returns a camelCase version of the string. Trims surrounding spaces,
651
     * capitalizes letters following digits, spaces, dashes and underscores,
652
     * and removes spaces, dashes, as well as underscores.
653
     *
654
     * EXAMPLE: <code>
655
     * s('Camel-Case')->camelize(); // 'camelCase'
656
     * </code>
657
     *
658
     * @psalm-mutation-free
659
     *
660
     * @return static
661
     *                <p>Object with $str in camelCase.</p>
662
     */
663
    public function camelize(): self
664
    {
665
        $str = $this->str;
65✔
666

667
        if (\preg_match('/[-_\s]/', $str)) {
65✔
668
            $str = $this->utf8::strtolower($str, $this->encoding);
56✔
669
        }
670

671
        return static::create(
65✔
672
            $this->utf8::str_camelize($str, $this->encoding),
65✔
673
            $this->encoding
65✔
674
        );
65✔
675
    }
676

677
    /**
678
     * Returns the string with the first letter of each word capitalized,
679
     * except for when the word is a name which shouldn't be capitalized.
680
     *
681
     * EXAMPLE: <code>
682
     * s('jaap de hoop scheffer')->capitalizePersonName(); // 'Jaap de Hoop Scheffer'
683
     * </code>
684
     *
685
     * @psalm-mutation-free
686
     *
687
     * @return static
688
     *                <p>Object with $str capitalized.</p>
689
     */
690
    public function capitalizePersonalName(): self
691
    {
692
        return static::create(
78✔
693
            $this->utf8::str_capitalize_name($this->str),
78✔
694
            $this->encoding
78✔
695
        );
78✔
696
    }
697

698
    /**
699
     * Returns an array consisting of the characters in the string.
700
     *
701
     * EXAMPLE: <code>
702
     * s('fòôbàř')->chars(); // ['f', 'ò', 'ô', 'b', 'à', 'ř']
703
     * </code>
704
     *
705
     * @psalm-mutation-free
706
     *
707
     * @return string[]
708
     *                  <p>An array of string chars.</p>
709
     */
710
    public function chars(): array
711
    {
712
        /** @var string[] */
713
        return $this->utf8::str_split($this->str);
14✔
714
    }
715

716
    /**
717
     * Splits the string into chunks of Stringy objects.
718
     *
719
     * EXAMPLE: <code>
720
     * s('foobar')->chunk(3); // ['foo', 'bar']
721
     * </code>
722
     *
723
     * @param int $length [optional] <p>Max character length of each array element.</p>
724
     *
725
     * @psalm-mutation-free
726
     *
727
     * @return static[]
728
     *                  <p>An array of Stringy objects.</p>
729
     *
730
     * @phpstan-return array<int,static>
731
     */
732
    public function chunk(int $length = 1): array
733
    {
734
        if ($length < 1) {
13✔
735
            throw new \InvalidArgumentException('The chunk length must be greater than zero.');
×
736
        }
737

738
        if ($this->str === '') {
13✔
739
            return [];
2✔
740
        }
741

742
        $chunks = $this->utf8::str_split($this->str, $length);
11✔
743

744
        foreach ($chunks as &$value) {
11✔
745
            $value = static::create($value, $this->encoding);
11✔
746
        }
747

748
        /** @noinspection PhpSillyAssignmentInspection */
749
        /** @var static[] $chunks */
750
        $chunks = $chunks;
11✔
751

752
        return $chunks;
11✔
753
    }
754

755
    /**
756
     * Splits the string into chunks of Stringy objects collection.
757
     *
758
     * EXAMPLE: <code>
759
     * </code>
760
     *
761
     * @param int $length [optional] <p>Max character length of each array element.</p>
762
     *
763
     * @psalm-mutation-free
764
     *
765
     * @return CollectionStringy|static[]
766
     *                                    <p>An collection of Stringy objects.</p>
767
     *
768
     * @phpstan-return CollectionStringy<int,static>
769
     */
770
    public function chunkCollection(int $length = 1): CollectionStringy
771
    {
772
        /**
773
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the collection class
774
         */
775
        return CollectionStringy::create(
7✔
776
            $this->chunk($length)
7✔
777
        );
7✔
778
    }
779

780
    /**
781
     * Trims the string and replaces consecutive whitespace characters with a
782
     * single space. This includes tabs and newline characters, as well as
783
     * multibyte whitespace such as the thin space and ideographic space.
784
     *
785
     * EXAMPLE: <code>
786
     * s('   Ο     συγγραφέας  ')->collapseWhitespace(); // 'Ο συγγραφέας'
787
     * </code>
788
     *
789
     * @psalm-mutation-free
790
     *
791
     * @return static
792
     *                <p>Object with a trimmed $str and condensed whitespace.</p>
793
     */
794
    public function collapseWhitespace(): self
795
    {
796
        return static::create(
39✔
797
            $this->utf8::collapse_whitespace($this->str),
39✔
798
            $this->encoding
39✔
799
        );
39✔
800
    }
801

802
    /**
803
     * Returns true if the string contains $needle, false otherwise. By default
804
     * the comparison is case-sensitive, but can be made insensitive by setting
805
     * $caseSensitive to false.
806
     *
807
     * EXAMPLE: <code>
808
     * s('Ο συγγραφέας είπε')->contains('συγγραφέας'); // true
809
     * </code>
810
     *
811
     * @param string $needle        <p>Substring to look for.</p>
812
     * @param bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
813
     *
814
     * @psalm-mutation-free
815
     *
816
     * @return bool
817
     *              <p>Whether or not $str contains $needle.</p>
818
     */
819
    public function contains(string $needle, bool $caseSensitive = true): bool
820
    {
821
        return $this->utf8::str_contains(
63✔
822
            $this->str,
63✔
823
            $needle,
63✔
824
            $caseSensitive
63✔
825
        );
63✔
826
    }
827

828
    /**
829
     * Returns true if the string contains all $needles, false otherwise. By
830
     * default the comparison is case-sensitive, but can be made insensitive by
831
     * setting $caseSensitive to false.
832
     *
833
     * EXAMPLE: <code>
834
     * s('foo & bar')->containsAll(['foo', 'bar']); // true
835
     * </code>
836
     *
837
     * @param string[] $needles       <p>SubStrings to look for.</p>
838
     * @param bool     $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
839
     *
840
     * @psalm-mutation-free
841
     *
842
     * @return bool
843
     *              <p>Whether or not $str contains $needle.</p>
844
     */
845
    public function containsAll(array $needles, bool $caseSensitive = true): bool
846
    {
847
        return $this->utf8::str_contains_all(
131✔
848
            $this->str,
131✔
849
            $needles,
131✔
850
            $caseSensitive
131✔
851
        );
131✔
852
    }
853

854
    /**
855
     * Returns true if the string contains any $needles, false otherwise. By
856
     * default the comparison is case-sensitive, but can be made insensitive by
857
     * setting $caseSensitive to false.
858
     *
859
     * EXAMPLE: <code>
860
     * s('str contains foo')->containsAny(['foo', 'bar']); // true
861
     * </code>
862
     *
863
     * @param string[] $needles       <p>SubStrings to look for.</p>
864
     * @param bool     $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
865
     *
866
     * @psalm-mutation-free
867
     *
868
     * @return bool
869
     *              <p>Whether or not $str contains $needle.</p>
870
     */
871
    public function containsAny(array $needles, bool $caseSensitive = true): bool
872
    {
873
        return $this->utf8::str_contains_any(
129✔
874
            $this->str,
129✔
875
            $needles,
129✔
876
            $caseSensitive
129✔
877
        );
129✔
878
    }
879

880
    /**
881
     * Checks if string starts with "BOM" (Byte Order Mark Character) character.
882
     *
883
     * EXAMPLE: <code>s("\xef\xbb\xbf foobar")->containsBom(); // true</code>
884
     *
885
     * @psalm-mutation-free
886
     *
887
     * @return bool
888
     *              <strong>true</strong> if the string has BOM at the start,<br>
889
     *              <strong>false</strong> otherwise
890
     */
891
    public function containsBom(): bool
892
    {
893
        return $this->utf8::string_has_bom($this->str);
×
894
    }
895

896
    /**
897
     * Returns the length of the string, implementing the countable interface.
898
     *
899
     * EXAMPLE: <code>
900
     * </code>
901
     *
902
     * @psalm-mutation-free
903
     *
904
     * @return int
905
     *             <p>The number of characters in the string, given the encoding.</p>
906
     */
907
    public function count(): int
908
    {
909
        return $this->length();
3✔
910
    }
911

912
    /**
913
     * Returns the number of occurrences of $substring in the given string.
914
     * By default, the comparison is case-sensitive, but can be made insensitive
915
     * by setting $caseSensitive to false.
916
     *
917
     * EXAMPLE: <code>
918
     * s('Ο συγγραφέας είπε')->countSubstr('α'); // 2
919
     * </code>
920
     *
921
     * @param string $substring     <p>The substring to search for.</p>
922
     * @param bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
923
     *
924
     * @psalm-mutation-free
925
     *
926
     * @return int
927
     */
928
    public function countSubstr(string $substring, bool $caseSensitive = true): int
929
    {
930
        return $this->utf8::substr_count_simple(
45✔
931
            $this->str,
45✔
932
            $substring,
45✔
933
            $caseSensitive,
45✔
934
            $this->encoding
45✔
935
        );
45✔
936
    }
937

938
    /**
939
     * Calculates the crc32 polynomial of a string.
940
     *
941
     * EXAMPLE: <code>
942
     * </code>
943
     *
944
     * @psalm-mutation-free
945
     *
946
     * @return int
947
     */
948
    public function crc32(): int
949
    {
950
        return \crc32($this->str);
2✔
951
    }
952

953
    /**
954
     * Creates a Stringy object and assigns both str and encoding properties
955
     * the supplied values. $str is cast to a string prior to assignment, and if
956
     * $encoding is not specified, it defaults to mb_internal_encoding(). It
957
     * then returns the initialized object. Throws an InvalidArgumentException
958
     * if the first argument is an array or object without a __toString method.
959
     *
960
     * @param mixed  $str      [optional] <p>Value to modify, after being cast to string. Default: ''</p>
961
     * @param string $encoding [optional] <p>The character encoding. Fallback: 'UTF-8'</p>
962
     *
963
     * @throws \InvalidArgumentException
964
     *                                   <p>if an array or object without a
965
     *                                   __toString method is passed as the first argument</p>
966
     *
967
     * @return static
968
     *                <p>A Stringy object.</p>
969
     */
970
    public static function create($str = '', ?string $encoding = null): self
971
    {
972
        return new static($str, $encoding);
3,620✔
973
    }
974

975
    /**
976
     * One-way string encryption (hashing).
977
     *
978
     * Hash the string using the standard Unix DES-based algorithm or an
979
     * alternative algorithm that may be available on the system.
980
     *
981
     * PS: if you need encrypt / decrypt, please use ```static::encrypt($password)```
982
     *     and ```static::decrypt($password)```
983
     *
984
     * EXAMPLE: <code>
985
     * </code>
986
     *
987
     * @param string $salt <p>A salt string to base the hashing on.</p>
988
     *
989
     * @psalm-mutation-free
990
     *
991
     * @return static
992
     */
993
    public function crypt(string $salt): self
994
    {
995
        return new static(
3✔
996
            \crypt(
3✔
997
                $this->str,
3✔
998
                $salt
3✔
999
            ),
3✔
1000
            $this->encoding
3✔
1001
        );
3✔
1002
    }
1003

1004
    /**
1005
     * Returns a lowercase and trimmed string separated by dashes. Dashes are
1006
     * inserted before uppercase characters (with the exception of the first
1007
     * character of the string), and in place of spaces as well as underscores.
1008
     *
1009
     * EXAMPLE: <code>
1010
     * s('fooBar')->dasherize(); // 'foo-bar'
1011
     * </code>
1012
     *
1013
     * @psalm-mutation-free
1014
     *
1015
     * @return static
1016
     *                <p>Object with a dasherized $str</p>
1017
     */
1018
    public function dasherize(): self
1019
    {
1020
        return static::create(
57✔
1021
            $this->utf8::str_dasherize($this->str),
57✔
1022
            $this->encoding
57✔
1023
        );
57✔
1024
    }
1025

1026
    /**
1027
     * Decrypt the string.
1028
     *
1029
     * EXAMPLE: <code>
1030
     * </code>
1031
     *
1032
     * @param string $password The key for decrypting
1033
     *
1034
     * @psalm-mutation-free
1035
     *
1036
     * @return static
1037
     */
1038
    public function decrypt(string $password): self
1039
    {
1040
        /**
1041
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to vendor stuff
1042
         */
1043
        return new static(
5✔
1044
            Crypto::decryptWithPassword($this->str, $password),
5✔
1045
            $this->encoding
5✔
1046
        );
5✔
1047
    }
1048

1049
    /**
1050
     * Returns a lowercase and trimmed string separated by the given delimiter.
1051
     * Delimiters are inserted before uppercase characters (with the exception
1052
     * of the first character of the string), and in place of spaces, dashes,
1053
     * and underscores. Alpha delimiters are not converted to lowercase.
1054
     *
1055
     * EXAMPLE: <code>
1056
     * s('fooBar')->delimit('::'); // 'foo::bar'
1057
     * </code>
1058
     *
1059
     * @param string $delimiter <p>Sequence used to separate parts of the string.</p>
1060
     *
1061
     * @psalm-mutation-free
1062
     *
1063
     * @return static
1064
     *                <p>Object with a delimited $str.</p>
1065
     */
1066
    public function delimit(string $delimiter): self
1067
    {
1068
        return static::create(
90✔
1069
            $this->utf8::str_delimit($this->str, $delimiter),
90✔
1070
            $this->encoding
90✔
1071
        );
90✔
1072
    }
1073

1074
    /**
1075
     * Encode the given string into the given $encoding + set the internal character encoding.
1076
     *
1077
     * EXAMPLE: <code>
1078
     * </code>
1079
     *
1080
     * @param string $new_encoding         <p>The desired character encoding.</p>
1081
     * @param bool   $auto_detect_encoding [optional] <p>Auto-detect the current string-encoding</p>
1082
     *
1083
     * @psalm-mutation-free
1084
     *
1085
     * @return static
1086
     */
1087
    public function encode(string $new_encoding, bool $auto_detect_encoding = false): self
1088
    {
1089
        if ($auto_detect_encoding) {
2✔
1090
            $str = $this->utf8::encode(
1✔
1091
                $new_encoding,
1✔
1092
                $this->str
1✔
1093
            );
1✔
1094
        } else {
1095
            $str = $this->utf8::encode(
1✔
1096
                $new_encoding,
1✔
1097
                $this->str,
1✔
1098
                false,
1✔
1099
                $this->encoding
1✔
1100
            );
1✔
1101
        }
1102

1103
        return new static($str, $new_encoding);
2✔
1104
    }
1105

1106
    /**
1107
     * Encrypt the string.
1108
     *
1109
     * EXAMPLE: <code>
1110
     * </code>
1111
     *
1112
     * @param string $password <p>The key for encrypting</p>
1113
     *
1114
     * @psalm-mutation-free
1115
     *
1116
     * @return static
1117
     */
1118
    public function encrypt(string $password): self
1119
    {
1120
        /**
1121
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to vendor stuff
1122
         */
1123
        return new static(
4✔
1124
            Crypto::encryptWithPassword($this->str, $password),
4✔
1125
            $this->encoding
4✔
1126
        );
4✔
1127
    }
1128

1129
    /**
1130
     * Returns true if the string ends with $substring, false otherwise. By
1131
     * default, the comparison is case-sensitive, but can be made insensitive
1132
     * by setting $caseSensitive to false.
1133
     *
1134
     * EXAMPLE: <code>
1135
     * s('fòôbàř')->endsWith('bàř', true); // true
1136
     * </code>
1137
     *
1138
     * @param string $substring     <p>The substring to look for.</p>
1139
     * @param bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
1140
     *
1141
     * @psalm-mutation-free
1142
     *
1143
     * @return bool
1144
     *              <p>Whether or not $str ends with $substring.</p>
1145
     */
1146
    public function endsWith(string $substring, bool $caseSensitive = true): bool
1147
    {
1148
        if ($caseSensitive) {
97✔
1149
            return $this->utf8::str_ends_with($this->str, $substring);
53✔
1150
        }
1151

1152
        return $this->utf8::str_iends_with($this->str, $substring);
44✔
1153
    }
1154

1155
    /**
1156
     * Returns true if the string ends with any of $substrings, false otherwise.
1157
     * By default, the comparison is case-sensitive, but can be made insensitive
1158
     * by setting $caseSensitive to false.
1159
     *
1160
     * EXAMPLE: <code>
1161
     * s('fòôbàř')->endsWithAny(['bàř', 'baz'], true); // true
1162
     * </code>
1163
     *
1164
     * @param string[] $substrings    <p>Substrings to look for.</p>
1165
     * @param bool     $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
1166
     *
1167
     * @psalm-mutation-free
1168
     *
1169
     * @return bool
1170
     *              <p>Whether or not $str ends with $substring.</p>
1171
     */
1172
    public function endsWithAny(array $substrings, bool $caseSensitive = true): bool
1173
    {
1174
        if ($caseSensitive) {
33✔
1175
            return $this->utf8::str_ends_with_any($this->str, $substrings);
21✔
1176
        }
1177

1178
        return $this->utf8::str_iends_with_any($this->str, $substrings);
12✔
1179
    }
1180

1181
    /**
1182
     * Ensures that the string begins with $substring. If it doesn't, it's
1183
     * prepended.
1184
     *
1185
     * EXAMPLE: <code>
1186
     * s('foobar')->ensureLeft('http://'); // 'http://foobar'
1187
     * </code>
1188
     *
1189
     * @param string $substring <p>The substring to add if not present.</p>
1190
     *
1191
     * @psalm-mutation-free
1192
     *
1193
     * @return static
1194
     *                <p>Object with its $str prefixed by the $substring.</p>
1195
     */
1196
    public function ensureLeft(string $substring): self
1197
    {
1198
        return static::create(
30✔
1199
            $this->utf8::str_ensure_left($this->str, $substring),
30✔
1200
            $this->encoding
30✔
1201
        );
30✔
1202
    }
1203

1204
    /**
1205
     * Ensures that the string ends with $substring. If it doesn't, it's appended.
1206
     *
1207
     * EXAMPLE: <code>
1208
     * s('foobar')->ensureRight('.com'); // 'foobar.com'
1209
     * </code>
1210
     *
1211
     * @param string $substring <p>The substring to add if not present.</p>
1212
     *
1213
     * @psalm-mutation-free
1214
     *
1215
     * @return static
1216
     *                <p>Object with its $str suffixed by the $substring.</p>
1217
     */
1218
    public function ensureRight(string $substring): self
1219
    {
1220
        return static::create(
30✔
1221
            $this->utf8::str_ensure_right($this->str, $substring),
30✔
1222
            $this->encoding
30✔
1223
        );
30✔
1224
    }
1225

1226
    /**
1227
     * Create a escape html version of the string via "htmlspecialchars()".
1228
     *
1229
     * EXAMPLE: <code>
1230
     * s('<∂∆ onerror="alert(xss)">')->escape(); // '&lt;∂∆ onerror=&quot;alert(xss)&quot;&gt;'
1231
     * </code>
1232
     *
1233
     * @psalm-mutation-free
1234
     *
1235
     * @return static
1236
     */
1237
    public function escape(): self
1238
    {
1239
        return static::create(
12✔
1240
            $this->utf8::htmlspecialchars(
12✔
1241
                $this->str,
12✔
1242
                \ENT_QUOTES | \ENT_SUBSTITUTE,
12✔
1243
                $this->encoding
12✔
1244
            ),
12✔
1245
            $this->encoding
12✔
1246
        );
12✔
1247
    }
1248

1249
    /**
1250
     * Split a string by a string.
1251
     *
1252
     * EXAMPLE: <code>
1253
     * </code>
1254
     *
1255
     * @param string $delimiter <p>The boundary string</p>
1256
     * @param int    $limit     [optional] <p>The maximum number of elements in the exploded
1257
     *                          collection.</p>
1258
     *
1259
     *   - If limit is set and positive, the returned collection will contain a maximum of limit elements with the last
1260
     *   element containing the rest of string.
1261
     *   - If the limit parameter is negative, all components except the last -limit are returned.
1262
     *   - If the limit parameter is zero, then this is treated as 1
1263
     *
1264
     * @psalm-mutation-free
1265
     *
1266
     * @return array<int,static>
1267
     */
1268
    public function explode(string $delimiter, int $limit = \PHP_INT_MAX): array
1269
    {
1270
        if ($this->str === '') {
3✔
1271
            return [];
×
1272
        }
1273

1274
        /** @phpstan-ignore-next-line - FP -> non-empty-string is already checked */
1275
        $strings = \explode($delimiter, $this->str, $limit);
3✔
1276
        /** @phpstan-ignore-next-line - if "$delimiter" is an empty string, then "explode()" will return "false" */
1277
        if ($strings === false) {
3✔
1278
            $strings = [];
×
1279
        }
1280

1281
        return \array_map(
3✔
1282
            function ($str) {
3✔
1283
                return new static($str, $this->encoding);
3✔
1284
            },
3✔
1285
            $strings
3✔
1286
        );
3✔
1287
    }
1288

1289
    /**
1290
     * Split a string by a string.
1291
     *
1292
     * EXAMPLE: <code>
1293
     * </code>
1294
     *
1295
     * @param string $delimiter <p>The boundary string</p>
1296
     * @param int    $limit     [optional] <p>The maximum number of elements in the exploded
1297
     *                          collection.</p>
1298
     *
1299
     *   - If limit is set and positive, the returned collection will contain a maximum of limit elements with the last
1300
     *   element containing the rest of string.
1301
     *   - If the limit parameter is negative, all components except the last -limit are returned.
1302
     *   - If the limit parameter is zero, then this is treated as 1
1303
     *
1304
     * @psalm-mutation-free
1305
     *
1306
     * @return CollectionStringy|static[]
1307
     *                                    <p>An collection of Stringy objects.</p>
1308
     *
1309
     * @phpstan-return CollectionStringy<int,static>
1310
     */
1311
    public function explodeCollection(string $delimiter, int $limit = \PHP_INT_MAX): CollectionStringy
1312
    {
1313
        /**
1314
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the collection class
1315
         */
1316
        return CollectionStringy::create(
1✔
1317
            $this->explode($delimiter, $limit)
1✔
1318
        );
1✔
1319
    }
1320

1321
    /**
1322
     * Create an extract from a sentence, so if the search-string was found, it try to centered in the output.
1323
     *
1324
     * EXAMPLE: <code>
1325
     * $sentence = 'This is only a Fork of Stringy, take a look at the new features.';
1326
     * s($sentence)->extractText('Stringy'); // '...Fork of Stringy...'
1327
     * </code>
1328
     *
1329
     * @param string   $search
1330
     * @param int|null $length                 [optional] <p>Default: null === text->length / 2</p>
1331
     * @param string   $replacerForSkippedText [optional] <p>Default: …</p>
1332
     *
1333
     * @psalm-mutation-free
1334
     *
1335
     * @return static
1336
     */
1337
    public function extractText(string $search = '', ?int $length = null, string $replacerForSkippedText = '…'): self
1338
    {
1339
        return static::create(
2✔
1340
            $this->utf8::extract_text(
2✔
1341
                $this->str,
2✔
1342
                $search,
2✔
1343
                $length,
2✔
1344
                $replacerForSkippedText,
2✔
1345
                $this->encoding
2✔
1346
            ),
2✔
1347
            $this->encoding
2✔
1348
        );
2✔
1349
    }
1350

1351
    /**
1352
     * Returns the first $n characters of the string.
1353
     *
1354
     * EXAMPLE: <code>
1355
     * s('fòôbàř')->first(3); // 'fòô'
1356
     * </code>
1357
     *
1358
     * @param int $n <p>Number of characters to retrieve from the start.</p>
1359
     *
1360
     * @psalm-mutation-free
1361
     *
1362
     * @return static
1363
     *                <p>Object with its $str being the first $n chars.</p>
1364
     */
1365
    public function first(int $n): self
1366
    {
1367
        if ($n <= 0) {
37✔
1368
            return static::create('', $this->encoding);
12✔
1369
        }
1370

1371
        return static::create(
25✔
1372
            $this->utf8::first_char($this->str, $n, $this->encoding),
25✔
1373
            $this->encoding
25✔
1374
        );
25✔
1375
    }
1376

1377
    /**
1378
     * Return a formatted string via sprintf + named parameters via array syntax.
1379
     *
1380
     * <p>
1381
     * <br>
1382
     * It will use "sprintf()" so you can use e.g.:
1383
     * <br>
1384
     * <br><pre>s('There are %d monkeys in the %s')->format(5, 'tree');</pre>
1385
     * <br>
1386
     * <br><pre>s('There are %2$d monkeys in the %1$s')->format('tree', 5);</pre>
1387
     * <br>
1388
     * <br>
1389
     * But you can also use named parameter via array syntax e.g.:
1390
     * <br>
1391
     * <br><pre>s('There are %:count monkeys in the %:location')->format(['count' => 5, 'location' => 'tree');</pre>
1392
     * </p>
1393
     *
1394
     * EXAMPLE: <code>
1395
     * $input = 'one: %2$d, %1$s: 2, %:text_three: %3$d';
1396
     * s($input)->format(['text_three' => '%4$s'], 'two', 1, 3, 'three'); // 'One: 1, two: 2, three: 3'
1397
     * </code>
1398
     *
1399
     * @param mixed ...$args [optional]
1400
     *
1401
     * @psalm-mutation-free
1402
     *
1403
     * @return static
1404
     *                <p>A Stringy object produced according to the formatting string
1405
     *                format.</p>
1406
     */
1407
    public function format(...$args): self
1408
    {
1409
        // init
1410
        $str = $this->str;
10✔
1411

1412
        if (\strpos($this->str, '%:') !== false) {
10✔
1413
            $offset = null;
8✔
1414
            $replacement = null;
8✔
1415
            /** @noinspection AlterInForeachInspection */
1416
            foreach ($args as $key => &$arg) {
8✔
1417
                if (!\is_array($arg)) {
8✔
1418
                    continue;
4✔
1419
                }
1420

1421
                foreach ($arg as $name => $param) {
8✔
1422
                    $name = (string) $name;
8✔
1423

1424
                    if (\strpos($name, '%:') !== 0) {
8✔
1425
                        $nameTmp = '%:' . $name;
8✔
1426
                    } else {
1427
                        $nameTmp = $name;
×
1428
                    }
1429

1430
                    if ($offset === null) {
8✔
1431
                        $offset = \strpos($str, $nameTmp);
8✔
1432
                    } else {
1433
                        $offset = \strpos($str, $nameTmp, (int) $offset + \strlen((string) $replacement));
6✔
1434
                    }
1435
                    if ($offset === false) {
8✔
1436
                        continue;
4✔
1437
                    }
1438

1439
                    unset($arg[$name]);
8✔
1440

1441
                    $str = \substr_replace($str, (string) $param, (int) $offset, \strlen($nameTmp));
8✔
1442
                }
1443

1444
                unset($args[$key]);
8✔
1445
            }
1446
        }
1447

1448
        $str = \str_replace('%:', '%%:', $str);
10✔
1449

1450
        return static::create(
10✔
1451
            \sprintf($str, ...$args),
10✔
1452
            $this->encoding
10✔
1453
        );
10✔
1454
    }
1455

1456
    /**
1457
     * Returns the encoding used by the Stringy object.
1458
     *
1459
     * EXAMPLE: <code>
1460
     * s('fòôbàř', 'UTF-8')->getEncoding(); // 'UTF-8'
1461
     * </code>
1462
     *
1463
     * @psalm-mutation-free
1464
     *
1465
     * @return string
1466
     *                <p>The current value of the $encoding property.</p>
1467
     */
1468
    public function getEncoding(): string
1469
    {
1470
        return $this->encoding;
25✔
1471
    }
1472

1473
    /**
1474
     * Returns a new ArrayIterator, thus implementing the IteratorAggregate
1475
     * interface. The ArrayIterator's constructor is passed an array of chars
1476
     * in the multibyte string. This enables the use of foreach with instances
1477
     * of Stringy\Stringy.
1478
     *
1479
     * EXAMPLE: <code>
1480
     * </code>
1481
     *
1482
     * @psalm-mutation-free
1483
     *
1484
     * @return \ArrayIterator
1485
     *                        <p>An iterator for the characters in the string.</p>
1486
     *
1487
     * @phpstan-return \ArrayIterator<array-key,string>
1488
     */
1489
    public function getIterator(): \ArrayIterator
1490
    {
1491
        return new \ArrayIterator($this->chars());
3✔
1492
    }
1493

1494
    /**
1495
     * Wrap the string after an exact number of characters.
1496
     *
1497
     * EXAMPLE: <code>
1498
     * </code>
1499
     *
1500
     * @param int    $width <p>Number of characters at which to wrap.</p>
1501
     * @param string $break [optional] <p>Character used to break the string. | Default: "\n"</p>
1502
     *
1503
     * @psalm-mutation-free
1504
     *
1505
     * @return static
1506
     */
1507
    public function hardWrap($width, $break = "\n"): self
1508
    {
1509
        return $this->lineWrap($width, $break, false);
2✔
1510
    }
1511

1512
    /**
1513
     * Returns true if the string contains a lower case char, false otherwise
1514
     *
1515
     * EXAMPLE: <code>
1516
     * s('fòôbàř')->hasLowerCase(); // true
1517
     * </code>
1518
     *
1519
     * @psalm-mutation-free
1520
     *
1521
     * @return bool
1522
     *              <p>Whether or not the string contains a lower case character.</p>
1523
     */
1524
    public function hasLowerCase(): bool
1525
    {
1526
        return $this->utf8::has_lowercase($this->str);
36✔
1527
    }
1528

1529
    /**
1530
     * Returns true if the string contains an upper case char, false otherwise.
1531
     *
1532
     * EXAMPLE: <code>
1533
     * s('fòôbàř')->hasUpperCase(); // false
1534
     * </code>
1535
     *
1536
     * @psalm-mutation-free
1537
     *
1538
     * @return bool
1539
     *              <p>Whether or not the string contains an upper case character.</p>
1540
     */
1541
    public function hasUpperCase(): bool
1542
    {
1543
        return $this->utf8::has_uppercase($this->str);
36✔
1544
    }
1545

1546
    /**
1547
     * Generate a hash value (message digest).
1548
     *
1549
     * EXAMPLE: <code>
1550
     * </code>
1551
     *
1552
     * @see https://php.net/manual/en/function.hash.php
1553
     *
1554
     * @param string $algorithm
1555
     *                          <p>Name of selected hashing algorithm (i.e. "md5", "sha256", "haval160,4", etc..)</p>
1556
     *
1557
     * @psalm-mutation-free
1558
     *
1559
     * @return static
1560
     */
1561
    public function hash($algorithm): self
1562
    {
1563
        return static::create(\hash($algorithm, $this->str), $this->encoding);
8✔
1564
    }
1565

1566
    /**
1567
     * Decode the string from hex.
1568
     *
1569
     * EXAMPLE: <code>
1570
     * </code>
1571
     *
1572
     * @psalm-mutation-free
1573
     *
1574
     * @return static
1575
     */
1576
    public function hexDecode(): self
1577
    {
1578
        $string = \preg_replace_callback(
2✔
1579
            '/\\\\x([0-9A-Fa-f]+)/',
2✔
1580
            function (array $matched) {
2✔
1581
                return (string) $this->utf8::hex_to_chr($matched[1]);
2✔
1582
            },
2✔
1583
            $this->str
2✔
1584
        );
2✔
1585

1586
        return static::create(
2✔
1587
            $string,
2✔
1588
            $this->encoding
2✔
1589
        );
2✔
1590
    }
1591

1592
    /**
1593
     * Encode string to hex.
1594
     *
1595
     * EXAMPLE: <code>
1596
     * </code>
1597
     *
1598
     * @psalm-mutation-free
1599
     *
1600
     * @return static
1601
     */
1602
    public function hexEncode(): self
1603
    {
1604
        $string = \array_reduce(
2✔
1605
            $this->chars(),
2✔
1606
            function (string $str, string $char) {
2✔
1607
                return $str . $this->utf8::chr_to_hex($char);
2✔
1608
            },
2✔
1609
            ''
2✔
1610
        );
2✔
1611

1612
        return static::create(
2✔
1613
            $string,
2✔
1614
            $this->encoding
2✔
1615
        );
2✔
1616
    }
1617

1618
    /**
1619
     * Convert all HTML entities to their applicable characters.
1620
     *
1621
     * EXAMPLE: <code>
1622
     * s('&amp;')->htmlDecode(); // '&'
1623
     * </code>
1624
     *
1625
     * @param int $flags [optional] <p>
1626
     *                   A bitmask of one or more of the following flags, which specify how to handle quotes and
1627
     *                   which document type to use. The default is ENT_COMPAT.
1628
     *                   <table>
1629
     *                   Available <i>flags</i> constants
1630
     *                   <tr valign="top">
1631
     *                   <td>Constant Name</td>
1632
     *                   <td>Description</td>
1633
     *                   </tr>
1634
     *                   <tr valign="top">
1635
     *                   <td><b>ENT_COMPAT</b></td>
1636
     *                   <td>Will convert double-quotes and leave single-quotes alone.</td>
1637
     *                   </tr>
1638
     *                   <tr valign="top">
1639
     *                   <td><b>ENT_QUOTES</b></td>
1640
     *                   <td>Will convert both double and single quotes.</td>
1641
     *                   </tr>
1642
     *                   <tr valign="top">
1643
     *                   <td><b>ENT_NOQUOTES</b></td>
1644
     *                   <td>Will leave both double and single quotes unconverted.</td>
1645
     *                   </tr>
1646
     *                   <tr valign="top">
1647
     *                   <td><b>ENT_HTML401</b></td>
1648
     *                   <td>
1649
     *                   Handle code as HTML 4.01.
1650
     *                   </td>
1651
     *                   </tr>
1652
     *                   <tr valign="top">
1653
     *                   <td><b>ENT_XML1</b></td>
1654
     *                   <td>
1655
     *                   Handle code as XML 1.
1656
     *                   </td>
1657
     *                   </tr>
1658
     *                   <tr valign="top">
1659
     *                   <td><b>ENT_XHTML</b></td>
1660
     *                   <td>
1661
     *                   Handle code as XHTML.
1662
     *                   </td>
1663
     *                   </tr>
1664
     *                   <tr valign="top">
1665
     *                   <td><b>ENT_HTML5</b></td>
1666
     *                   <td>
1667
     *                   Handle code as HTML 5.
1668
     *                   </td>
1669
     *                   </tr>
1670
     *                   </table>
1671
     *                   </p>
1672
     *
1673
     * @psalm-mutation-free
1674
     *
1675
     * @return static
1676
     *                <p>Object with the resulting $str after being html decoded.</p>
1677
     */
1678
    public function htmlDecode(int $flags = \ENT_COMPAT): self
1679
    {
1680
        return static::create(
15✔
1681
            $this->utf8::html_entity_decode(
15✔
1682
                $this->str,
15✔
1683
                $flags,
15✔
1684
                $this->encoding
15✔
1685
            ),
15✔
1686
            $this->encoding
15✔
1687
        );
15✔
1688
    }
1689

1690
    /**
1691
     * Convert all applicable characters to HTML entities.
1692
     *
1693
     * EXAMPLE: <code>
1694
     * s('&')->htmlEncode(); // '&amp;'
1695
     * </code>
1696
     *
1697
     * @param int $flags [optional] <p>
1698
     *                   A bitmask of one or more of the following flags, which specify how to handle quotes and
1699
     *                   which document type to use. The default is ENT_COMPAT.
1700
     *                   <table>
1701
     *                   Available <i>flags</i> constants
1702
     *                   <tr valign="top">
1703
     *                   <td>Constant Name</td>
1704
     *                   <td>Description</td>
1705
     *                   </tr>
1706
     *                   <tr valign="top">
1707
     *                   <td><b>ENT_COMPAT</b></td>
1708
     *                   <td>Will convert double-quotes and leave single-quotes alone.</td>
1709
     *                   </tr>
1710
     *                   <tr valign="top">
1711
     *                   <td><b>ENT_QUOTES</b></td>
1712
     *                   <td>Will convert both double and single quotes.</td>
1713
     *                   </tr>
1714
     *                   <tr valign="top">
1715
     *                   <td><b>ENT_NOQUOTES</b></td>
1716
     *                   <td>Will leave both double and single quotes unconverted.</td>
1717
     *                   </tr>
1718
     *                   <tr valign="top">
1719
     *                   <td><b>ENT_HTML401</b></td>
1720
     *                   <td>
1721
     *                   Handle code as HTML 4.01.
1722
     *                   </td>
1723
     *                   </tr>
1724
     *                   <tr valign="top">
1725
     *                   <td><b>ENT_XML1</b></td>
1726
     *                   <td>
1727
     *                   Handle code as XML 1.
1728
     *                   </td>
1729
     *                   </tr>
1730
     *                   <tr valign="top">
1731
     *                   <td><b>ENT_XHTML</b></td>
1732
     *                   <td>
1733
     *                   Handle code as XHTML.
1734
     *                   </td>
1735
     *                   </tr>
1736
     *                   <tr valign="top">
1737
     *                   <td><b>ENT_HTML5</b></td>
1738
     *                   <td>
1739
     *                   Handle code as HTML 5.
1740
     *                   </td>
1741
     *                   </tr>
1742
     *                   </table>
1743
     *                   </p>
1744
     *
1745
     * @psalm-mutation-free
1746
     *
1747
     * @return static
1748
     *                <p>Object with the resulting $str after being html encoded.</p>
1749
     */
1750
    public function htmlEncode(int $flags = \ENT_COMPAT): self
1751
    {
1752
        return static::create(
15✔
1753
            $this->utf8::htmlentities(
15✔
1754
                $this->str,
15✔
1755
                $flags,
15✔
1756
                $this->encoding
15✔
1757
            ),
15✔
1758
            $this->encoding
15✔
1759
        );
15✔
1760
    }
1761

1762
    /**
1763
     * Capitalizes the first word of the string, replaces underscores with
1764
     * spaces, and strips '_id'.
1765
     *
1766
     * EXAMPLE: <code>
1767
     * s('author_id')->humanize(); // 'Author'
1768
     * </code>
1769
     *
1770
     * @psalm-mutation-free
1771
     *
1772
     * @return static
1773
     *                <p>Object with a humanized $str.</p>
1774
     */
1775
    public function humanize(): self
1776
    {
1777
        return static::create(
9✔
1778
            $this->utf8::str_humanize($this->str),
9✔
1779
            $this->encoding
9✔
1780
        );
9✔
1781
    }
1782

1783
    /**
1784
     * Determine if the current string exists in another string. By
1785
     * default, the comparison is case-sensitive, but can be made insensitive
1786
     * by setting $caseSensitive to false.
1787
     *
1788
     * EXAMPLE: <code>
1789
     * </code>
1790
     *
1791
     * @param string $str           <p>The string to compare against.</p>
1792
     * @param bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
1793
     *
1794
     * @psalm-mutation-free
1795
     *
1796
     * @return bool
1797
     */
1798
    public function in(string $str, bool $caseSensitive = true): bool
1799
    {
1800
        if ($caseSensitive) {
3✔
1801
            return \strpos($str, $this->str) !== false;
2✔
1802
        }
1803

1804
        return \stripos($str, $this->str) !== false;
1✔
1805
    }
1806

1807
    /**
1808
     * Returns the index of the first occurrence of $needle in the string,
1809
     * and false if not found. Accepts an optional offset from which to begin
1810
     * the search.
1811
     *
1812
     * EXAMPLE: <code>
1813
     * s('string')->indexOf('ing'); // 3
1814
     * </code>
1815
     *
1816
     * @param string $needle <p>Substring to look for.</p>
1817
     * @param int    $offset [optional] <p>Offset from which to search. Default: 0</p>
1818
     *
1819
     * @psalm-mutation-free
1820
     *
1821
     * @return false|int
1822
     *                   <p>The occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
1823
     */
1824
    public function indexOf(string $needle, int $offset = 0)
1825
    {
1826
        return $this->utf8::strpos(
31✔
1827
            $this->str,
31✔
1828
            $needle,
31✔
1829
            $offset,
31✔
1830
            $this->encoding
31✔
1831
        );
31✔
1832
    }
1833

1834
    /**
1835
     * Returns the index of the first occurrence of $needle in the string,
1836
     * and false if not found. Accepts an optional offset from which to begin
1837
     * the search.
1838
     *
1839
     * EXAMPLE: <code>
1840
     * s('string')->indexOfIgnoreCase('ING'); // 3
1841
     * </code>
1842
     *
1843
     * @param string $needle <p>Substring to look for.</p>
1844
     * @param int    $offset [optional] <p>Offset from which to search. Default: 0</p>
1845
     *
1846
     * @psalm-mutation-free
1847
     *
1848
     * @return false|int
1849
     *                   <p>The occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
1850
     */
1851
    public function indexOfIgnoreCase(string $needle, int $offset = 0)
1852
    {
1853
        return $this->utf8::stripos(
20✔
1854
            $this->str,
20✔
1855
            $needle,
20✔
1856
            $offset,
20✔
1857
            $this->encoding
20✔
1858
        );
20✔
1859
    }
1860

1861
    /**
1862
     * Returns the index of the last occurrence of $needle in the string,
1863
     * and false if not found. Accepts an optional offset from which to begin
1864
     * the search. Offsets may be negative to count from the last character
1865
     * in the string.
1866
     *
1867
     * EXAMPLE: <code>
1868
     * s('foobarfoo')->indexOfLast('foo'); // 10
1869
     * </code>
1870
     *
1871
     * @param string $needle <p>Substring to look for.</p>
1872
     * @param int    $offset [optional] <p>Offset from which to search. Default: 0</p>
1873
     *
1874
     * @psalm-mutation-free
1875
     *
1876
     * @return false|int
1877
     *                   <p>The last occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
1878
     */
1879
    public function indexOfLast(string $needle, int $offset = 0)
1880
    {
1881
        return $this->utf8::strrpos(
31✔
1882
            $this->str,
31✔
1883
            $needle,
31✔
1884
            $offset,
31✔
1885
            $this->encoding
31✔
1886
        );
31✔
1887
    }
1888

1889
    /**
1890
     * Returns the index of the last occurrence of $needle in the string,
1891
     * and false if not found. Accepts an optional offset from which to begin
1892
     * the search. Offsets may be negative to count from the last character
1893
     * in the string.
1894
     *
1895
     * EXAMPLE: <code>
1896
     * s('fooBarFoo')->indexOfLastIgnoreCase('foo'); // 10
1897
     * </code>
1898
     *
1899
     * @param string $needle <p>Substring to look for.</p>
1900
     * @param int    $offset [optional] <p>Offset from which to search. Default: 0</p>
1901
     *
1902
     * @psalm-mutation-free
1903
     *
1904
     * @return false|int
1905
     *                   <p>The last occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
1906
     */
1907
    public function indexOfLastIgnoreCase(string $needle, int $offset = 0)
1908
    {
1909
        return $this->utf8::strripos(
20✔
1910
            $this->str,
20✔
1911
            $needle,
20✔
1912
            $offset,
20✔
1913
            $this->encoding
20✔
1914
        );
20✔
1915
    }
1916

1917
    /**
1918
     * Inserts $substring into the string at the $index provided.
1919
     *
1920
     * EXAMPLE: <code>
1921
     * s('fòôbř')->insert('à', 4); // 'fòôbàř'
1922
     * </code>
1923
     *
1924
     * @param string $substring <p>String to be inserted.</p>
1925
     * @param int    $index     <p>The index at which to insert the substring.</p>
1926
     *
1927
     * @psalm-mutation-free
1928
     *
1929
     * @return static
1930
     *                <p>Object with the resulting $str after the insertion.</p>
1931
     */
1932
    public function insert(string $substring, int $index): self
1933
    {
1934
        return static::create(
24✔
1935
            $this->utf8::str_insert(
24✔
1936
                $this->str,
24✔
1937
                $substring,
24✔
1938
                $index,
24✔
1939
                $this->encoding
24✔
1940
            ),
24✔
1941
            $this->encoding
24✔
1942
        );
24✔
1943
    }
1944

1945
    /**
1946
     * Returns true if the string contains the $pattern, otherwise false.
1947
     *
1948
     * WARNING: Asterisks ("*") are translated into (".*") zero-or-more regular
1949
     * expression wildcards.
1950
     *
1951
     * EXAMPLE: <code>
1952
     * s('Foo\\Bar\\Lall')->is('*\\Bar\\*'); // true
1953
     * </code>
1954
     *
1955
     * @credit Originally from Laravel, thanks Taylor.
1956
     *
1957
     * @param string $pattern <p>The string or pattern to match against.</p>
1958
     *
1959
     * @psalm-mutation-free
1960
     *
1961
     * @return bool
1962
     *              <p>Whether or not we match the provided pattern.</p>
1963
     */
1964
    public function is(string $pattern): bool
1965
    {
1966
        if ($this->toString() === $pattern) {
26✔
1967
            return true;
2✔
1968
        }
1969

1970
        $quotedPattern = \preg_quote($pattern, '/');
24✔
1971
        $replaceWildCards = \str_replace('\*', '.*', $quotedPattern);
24✔
1972

1973
        return $this->matchesPattern('^' . $replaceWildCards . '\z');
24✔
1974
    }
1975

1976
    /**
1977
     * Returns true if the string contains only alphabetic chars, false otherwise.
1978
     *
1979
     * EXAMPLE: <code>
1980
     * s('丹尼爾')->isAlpha(); // true
1981
     * </code>
1982
     *
1983
     * @psalm-mutation-free
1984
     *
1985
     * @return bool
1986
     *              <p>Whether or not $str contains only alphabetic chars.</p>
1987
     */
1988
    public function isAlpha(): bool
1989
    {
1990
        return $this->utf8::is_alpha($this->str);
30✔
1991
    }
1992

1993
    /**
1994
     * Returns true if the string contains only alphabetic and numeric chars, false otherwise.
1995
     *
1996
     * EXAMPLE: <code>
1997
     * s('دانيال1')->isAlphanumeric(); // true
1998
     * </code>
1999
     *
2000
     * @psalm-mutation-free
2001
     *
2002
     * @return bool
2003
     *              <p>Whether or not $str contains only alphanumeric chars.</p>
2004
     */
2005
    public function isAlphanumeric(): bool
2006
    {
2007
        return $this->utf8::is_alphanumeric($this->str);
39✔
2008
    }
2009

2010
    /**
2011
     * Checks if a string is 7 bit ASCII.
2012
     *
2013
     * EXAMPLE: <code>s('白')->isAscii; // false</code>
2014
     *
2015
     * @psalm-mutation-free
2016
     *
2017
     * @return bool
2018
     *              <p>
2019
     *              <strong>true</strong> if it is ASCII<br>
2020
     *              <strong>false</strong> otherwise
2021
     *              </p>
2022
     *
2023
     * @noinspection GetSetMethodCorrectnessInspection
2024
     */
2025
    public function isAscii(): bool
2026
    {
2027
        return $this->utf8::is_ascii($this->str);
×
2028
    }
2029

2030
    /**
2031
     * Returns true if the string is base64 encoded, false otherwise.
2032
     *
2033
     * EXAMPLE: <code>
2034
     * s('Zm9vYmFy')->isBase64(); // true
2035
     * </code>
2036
     *
2037
     * @param bool $emptyStringIsValid
2038
     *
2039
     * @psalm-mutation-free
2040
     *
2041
     * @return bool
2042
     *              <p>Whether or not $str is base64 encoded.</p>
2043
     */
2044
    public function isBase64($emptyStringIsValid = true): bool
2045
    {
2046
        return $this->utf8::is_base64($this->str, $emptyStringIsValid);
21✔
2047
    }
2048

2049
    /**
2050
     * Check if the input is binary... (is look like a hack).
2051
     *
2052
     * EXAMPLE: <code>s(01)->isBinary(); // true</code>
2053
     *
2054
     * @psalm-mutation-free
2055
     *
2056
     * @return bool
2057
     */
2058
    public function isBinary(): bool
2059
    {
2060
        return $this->utf8::is_binary($this->str);
1✔
2061
    }
2062

2063
    /**
2064
     * Returns true if the string contains only whitespace chars, false otherwise.
2065
     *
2066
     * EXAMPLE: <code>
2067
     * s("\n\t  \v\f")->isBlank(); // true
2068
     * </code>
2069
     *
2070
     * @psalm-mutation-free
2071
     *
2072
     * @return bool
2073
     *              <p>Whether or not $str contains only whitespace characters.</p>
2074
     */
2075
    public function isBlank(): bool
2076
    {
2077
        return $this->utf8::is_blank($this->str);
45✔
2078
    }
2079

2080
    /**
2081
     * Checks if the given string is equal to any "Byte Order Mark".
2082
     *
2083
     * WARNING: Use "s::string_has_bom()" if you will check BOM in a string.
2084
     *
2085
     * EXAMPLE: <code>s->("\xef\xbb\xbf")->isBom(); // true</code>
2086
     *
2087
     * @psalm-mutation-free
2088
     *
2089
     * @return bool
2090
     *              <p><strong>true</strong> if the $utf8_chr is Byte Order Mark, <strong>false</strong> otherwise.</p>
2091
     */
2092
    public function isBom(): bool
2093
    {
2094
        return $this->utf8::is_bom($this->str);
×
2095
    }
2096

2097
    /**
2098
     * Returns true if the string contains a valid E-Mail address, false otherwise.
2099
     *
2100
     * EXAMPLE: <code>
2101
     * s('lars@moelleken.org')->isEmail(); // true
2102
     * </code>
2103
     *
2104
     * @param bool $useExampleDomainCheck   [optional] <p>Default: false</p>
2105
     * @param bool $useTypoInDomainCheck    [optional] <p>Default: false</p>
2106
     * @param bool $useTemporaryDomainCheck [optional] <p>Default: false</p>
2107
     * @param bool $useDnsCheck             [optional] <p>Default: false</p>
2108
     *
2109
     * @psalm-mutation-free
2110
     *
2111
     * @return bool
2112
     *              <p>Whether or not $str contains a valid E-Mail address.</p>
2113
     */
2114
    public function isEmail(
2115
        bool $useExampleDomainCheck = false,
2116
        bool $useTypoInDomainCheck = false,
2117
        bool $useTemporaryDomainCheck = false,
2118
        bool $useDnsCheck = false
2119
    ): bool {
2120
        /**
2121
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the email-check class
2122
         */
2123
        return EmailCheck::isValid($this->str, $useExampleDomainCheck, $useTypoInDomainCheck, $useTemporaryDomainCheck, $useDnsCheck);
2✔
2124
    }
2125

2126
    /**
2127
     * Determine whether the string is considered to be empty.
2128
     *
2129
     * A variable is considered empty if it does not exist or if its value equals FALSE.
2130
     *
2131
     * EXAMPLE: <code>
2132
     * s('')->isEmpty(); // true
2133
     * </code>
2134
     *
2135
     * @psalm-mutation-free
2136
     *
2137
     * @return bool
2138
     *              <p>Whether or not $str is empty().</p>
2139
     */
2140
    public function isEmpty(): bool
2141
    {
2142
        return $this->utf8::is_empty($this->str);
10✔
2143
    }
2144

2145
    /**
2146
     * Determine whether the string is equals to $str.
2147
     * Alias for isEqualsCaseSensitive()
2148
     *
2149
     * EXAMPLE: <code>
2150
     * s('foo')->isEquals('foo'); // true
2151
     * </code>
2152
     *
2153
     * @param string|Stringy ...$str
2154
     *
2155
     * @psalm-mutation-free
2156
     *
2157
     * @return bool
2158
     */
2159
    public function isEquals(...$str): bool
2160
    {
2161
        return $this->isEqualsCaseSensitive(...$str);
13✔
2162
    }
2163

2164
    /**
2165
     * Determine whether the string is equals to $str.
2166
     *
2167
     * EXAMPLE: <code>
2168
     * </code>
2169
     *
2170
     * @param float|int|string|Stringy ...$str <p>The string to compare.</p>
2171
     *
2172
     * @psalm-mutation-free
2173
     *
2174
     * @return bool
2175
     *              <p>Whether or not $str is equals.</p>
2176
     */
2177
    public function isEqualsCaseInsensitive(...$str): bool
2178
    {
2179
        $strUpper = $this->toUpperCase()->str;
3✔
2180

2181
        foreach ($str as $strTmp) {
3✔
2182
            /**
2183
             * @psalm-suppress RedundantConditionGivenDocblockType - wait for union-types :)
2184
             */
2185
            if ($strTmp instanceof self) {
3✔
2186
                if ($strUpper !== $strTmp->toUpperCase()->str) {
×
2187
                    return false;
×
2188
                }
2189
            } elseif (\is_scalar($strTmp)) {
3✔
2190
                if ($strUpper !== $this->utf8::strtoupper((string) $strTmp, $this->encoding)) {
3✔
2191
                    return false;
3✔
2192
                }
2193
            } else {
2194
                throw new \InvalidArgumentException('expected: int|float|string|Stringy -> given: ' . \print_r($strTmp, true) . ' [' . \gettype($strTmp) . ']');
×
2195
            }
2196
        }
2197

2198
        return true;
3✔
2199
    }
2200

2201
    /**
2202
     * Determine whether the string is equals to $str.
2203
     *
2204
     * EXAMPLE: <code>
2205
     * </code>
2206
     *
2207
     * @param float|int|string|Stringy ...$str <p>The string to compare.</p>
2208
     *
2209
     * @psalm-mutation-free
2210
     *
2211
     * @return bool
2212
     *              <p>Whether or not $str is equals.</p>
2213
     */
2214
    public function isEqualsCaseSensitive(...$str): bool
2215
    {
2216
        foreach ($str as $strTmp) {
14✔
2217
            /**
2218
             * @psalm-suppress RedundantConditionGivenDocblockType - wait for union-types :)
2219
             */
2220
            if ($strTmp instanceof self) {
14✔
2221
                if ($this->str !== $strTmp->str) {
2✔
2222
                    return false;
1✔
2223
                }
2224
            } elseif (\is_scalar($strTmp)) {
12✔
2225
                if ($this->str !== (string) $strTmp) {
12✔
2226
                    return false;
10✔
2227
                }
2228
            } else {
2229
                throw new \InvalidArgumentException('expected: int|float|string|Stringy -> given: ' . \print_r($strTmp, true) . ' [' . \gettype($strTmp) . ']');
×
2230
            }
2231
        }
2232

2233
        return true;
3✔
2234
    }
2235

2236
    /**
2237
     * Returns true if the string contains only hexadecimal chars, false otherwise.
2238
     *
2239
     * EXAMPLE: <code>
2240
     * s('A102F')->isHexadecimal(); // true
2241
     * </code>
2242
     *
2243
     * @psalm-mutation-free
2244
     *
2245
     * @return bool
2246
     *              <p>Whether or not $str contains only hexadecimal chars.</p>
2247
     */
2248
    public function isHexadecimal(): bool
2249
    {
2250
        return $this->utf8::is_hexadecimal($this->str);
39✔
2251
    }
2252

2253
    /**
2254
     * Returns true if the string contains HTML-Tags, false otherwise.
2255
     *
2256
     * EXAMPLE: <code>
2257
     * s('<h1>foo</h1>')->isHtml(); // true
2258
     * </code>
2259
     *
2260
     * @psalm-mutation-free
2261
     *
2262
     * @return bool
2263
     *              <p>Whether or not $str contains HTML-Tags.</p>
2264
     */
2265
    public function isHtml(): bool
2266
    {
2267
        return $this->utf8::is_html($this->str);
2✔
2268
    }
2269

2270
    /**
2271
     * Returns true if the string is JSON, false otherwise. Unlike json_decode
2272
     * in PHP 5.x, this method is consistent with PHP 7 and other JSON parsers,
2273
     * in that an empty string is not considered valid JSON.
2274
     *
2275
     * EXAMPLE: <code>
2276
     * s('{"foo":"bar"}')->isJson(); // true
2277
     * </code>
2278
     *
2279
     * @param bool $onlyArrayOrObjectResultsAreValid
2280
     *
2281
     * @psalm-mutation-free
2282
     *
2283
     * @return bool
2284
     *              <p>Whether or not $str is JSON.</p>
2285
     */
2286
    public function isJson($onlyArrayOrObjectResultsAreValid = false): bool
2287
    {
2288
        /**
2289
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to vendor stuff?
2290
         */
2291
        return $this->utf8::is_json(
60✔
2292
            $this->str,
60✔
2293
            $onlyArrayOrObjectResultsAreValid
60✔
2294
        );
60✔
2295
    }
2296

2297
    /**
2298
     * Returns true if the string contains only lower case chars, false otherwise.
2299
     *
2300
     * EXAMPLE: <code>
2301
     * s('fòôbàř')->isLowerCase(); // true
2302
     * </code>
2303
     *
2304
     * @psalm-mutation-free
2305
     *
2306
     * @return bool
2307
     *              <p>Whether or not $str contains only lower case characters.</p>
2308
     */
2309
    public function isLowerCase(): bool
2310
    {
2311
        return $this->utf8::is_lowercase($this->str);
24✔
2312
    }
2313

2314
    /**
2315
     * Determine whether the string is considered to be NOT empty.
2316
     *
2317
     * A variable is considered NOT empty if it does exist or if its value equals TRUE.
2318
     *
2319
     * EXAMPLE: <code>
2320
     * s('')->isNotEmpty(); // false
2321
     * </code>
2322
     *
2323
     * @psalm-mutation-free
2324
     *
2325
     * @return bool
2326
     *              <p>Whether or not $str is empty().</p>
2327
     */
2328
    public function isNotEmpty(): bool
2329
    {
2330
        return !$this->utf8::is_empty($this->str);
10✔
2331
    }
2332

2333
    /**
2334
     * Determine if the string is composed of numeric characters.
2335
     *
2336
     * EXAMPLE: <code>
2337
     * </code>
2338
     *
2339
     * @psalm-mutation-free
2340
     *
2341
     * @return bool
2342
     */
2343
    public function isNumeric(): bool
2344
    {
2345
        return \is_numeric($this->str);
4✔
2346
    }
2347

2348
    /**
2349
     * Determine if the string is composed of printable (non-invisible) characters.
2350
     *
2351
     * EXAMPLE: <code>
2352
     * </code>
2353
     *
2354
     * @psalm-mutation-free
2355
     *
2356
     * @return bool
2357
     */
2358
    public function isPrintable(): bool
2359
    {
2360
        return $this->utf8::is_printable($this->str);
3✔
2361
    }
2362

2363
    /**
2364
     * Determine if the string is composed of punctuation characters.
2365
     *
2366
     * EXAMPLE: <code>
2367
     * </code>
2368
     *
2369
     * @psalm-mutation-free
2370
     *
2371
     * @return bool
2372
     */
2373
    public function isPunctuation(): bool
2374
    {
2375
        return $this->utf8::is_punctuation($this->str);
3✔
2376
    }
2377

2378
    /**
2379
     * Returns true if the string is serialized, false otherwise.
2380
     *
2381
     * EXAMPLE: <code>
2382
     * s('a:1:{s:3:"foo";s:3:"bar";}')->isSerialized(); // true
2383
     * </code>
2384
     *
2385
     * @psalm-mutation-free
2386
     *
2387
     * @return bool
2388
     *              <p>Whether or not $str is serialized.</p>
2389
     */
2390
    public function isSerialized(): bool
2391
    {
2392
        return $this->utf8::is_serialized($this->str);
21✔
2393
    }
2394

2395
    /**
2396
     * Check if two strings are similar.
2397
     *
2398
     * EXAMPLE: <code>
2399
     * </code>
2400
     *
2401
     * @param string $str                     <p>The string to compare against.</p>
2402
     * @param float  $minPercentForSimilarity [optional] <p>The percentage of needed similarity. | Default: 80%</p>
2403
     *
2404
     * @psalm-mutation-free
2405
     *
2406
     * @return bool
2407
     */
2408
    public function isSimilar(string $str, float $minPercentForSimilarity = 80.0): bool
2409
    {
2410
        return $this->similarity($str) >= $minPercentForSimilarity;
2✔
2411
    }
2412

2413
    /**
2414
     * Returns true if the string contains only lower case chars, false
2415
     * otherwise.
2416
     *
2417
     * EXAMPLE: <code>
2418
     * s('FÒÔBÀŘ')->isUpperCase(); // true
2419
     * </code>
2420
     *
2421
     * @psalm-mutation-free
2422
     *
2423
     * @return bool
2424
     *              <p>Whether or not $str contains only lower case characters.</p>
2425
     */
2426
    public function isUpperCase(): bool
2427
    {
2428
        return $this->utf8::is_uppercase($this->str);
24✔
2429
    }
2430

2431
    /**
2432
     * /**
2433
     * Check if $url is an correct url.
2434
     *
2435
     * @param bool $disallow_localhost
2436
     *
2437
     * @psalm-mutation-free
2438
     *
2439
     * @return bool
2440
     */
2441
    public function isUrl(bool $disallow_localhost = false): bool
2442
    {
2443
        return $this->utf8::is_url($this->str, $disallow_localhost);
×
2444
    }
2445

2446
    /**
2447
     * Check if the string is UTF-16.
2448
     *
2449
     * @psalm-mutation-free
2450
     *
2451
     * @return false|int
2452
     *                   <strong>false</strong> if is't not UTF-16,<br>
2453
     *                   <strong>1</strong> for UTF-16LE,<br>
2454
     *                   <strong>2</strong> for UTF-16BE
2455
     */
2456
    public function isUtf16()
2457
    {
2458
        return $this->utf8::is_utf16($this->str);
×
2459
    }
2460

2461
    /**
2462
     * Check if the string is UTF-32.
2463
     *
2464
     * @psalm-mutation-free
2465
     *
2466
     * @return false|int
2467
     *                   <strong>false</strong> if is't not UTF-32,<br>
2468
     *                   <strong>1</strong> for UTF-32LE,<br>
2469
     *                   <strong>2</strong> for UTF-32BE
2470
     */
2471
    public function isUtf32()
2472
    {
2473
        return $this->utf8::is_utf32($this->str);
×
2474
    }
2475

2476
    /**
2477
     * Checks whether the passed input contains only byte sequences that appear valid UTF-8.
2478
     *
2479
     * EXAMPLE: <code>
2480
     * s('Iñtërnâtiônàlizætiøn')->isUtf8(); // true
2481
     * //
2482
     * s("Iñtërnâtiônàlizætiøn\xA0\xA1")->isUtf8(); // false
2483
     * </code>
2484
     *
2485
     * @param bool $strict <p>Check also if the string is not UTF-16 or UTF-32.</p>
2486
     *
2487
     * @psalm-mutation-free
2488
     *
2489
     * @return bool
2490
     */
2491
    public function isUtf8(bool $strict = false): bool
2492
    {
2493
        return $this->utf8::is_utf8($this->str, $strict);
×
2494
    }
2495

2496
    /**
2497
     * Returns true if the string contains only whitespace chars, false otherwise.
2498
     *
2499
     * EXAMPLE: <code>
2500
     * </code>
2501
     *
2502
     * @psalm-mutation-free
2503
     *
2504
     * @return bool
2505
     *              <p>Whether or not $str contains only whitespace characters.</p>
2506
     */
2507
    public function isWhitespace(): bool
2508
    {
2509
        return $this->isBlank();
30✔
2510
    }
2511

2512
    /**
2513
     * Returns value which can be serialized by json_encode().
2514
     *
2515
     * EXAMPLE: <code>
2516
     * </code>
2517
     *
2518
     * @noinspection ReturnTypeCanBeDeclaredInspection
2519
     *
2520
     * @psalm-mutation-free
2521
     *
2522
     * @return string The current value of the $str property
2523
     */
2524
    #[\ReturnTypeWillChange]
2525
    public function jsonSerialize()
2526
    {
2527
        return (string) $this;
2✔
2528
    }
2529

2530
    /**
2531
     * Convert the string to kebab-case.
2532
     *
2533
     * EXAMPLE: <code>
2534
     * </code>
2535
     *
2536
     * @psalm-mutation-free
2537
     *
2538
     * @return static
2539
     */
2540
    public function kebabCase(): self
2541
    {
2542
        $words = \array_map(
3✔
2543
            static function (self $word) {
3✔
2544
                return $word->toLowerCase();
3✔
2545
            },
3✔
2546
            $this->words('', true)
3✔
2547
        );
3✔
2548

2549
        return new static(\implode('-', $words), $this->encoding);
3✔
2550
    }
2551

2552
    /**
2553
     * Returns the last $n characters of the string.
2554
     *
2555
     * EXAMPLE: <code>
2556
     * s('fòôbàř')->last(3); // 'bàř'
2557
     * </code>
2558
     *
2559
     * @param int $n <p>Number of characters to retrieve from the end.</p>
2560
     *
2561
     * @psalm-mutation-free
2562
     *
2563
     * @return static
2564
     *                <p>Object with its $str being the last $n chars.</p>
2565
     */
2566
    public function last(int $n): self
2567
    {
2568
        return static::create(
36✔
2569
            $this->utf8::str_last_char(
36✔
2570
                $this->str,
36✔
2571
                $n,
36✔
2572
                $this->encoding
36✔
2573
            ),
36✔
2574
            $this->encoding
36✔
2575
        );
36✔
2576
    }
2577

2578
    /**
2579
     * Gets the substring after (or before via "$beforeNeedle") the last occurrence of the "$needle".
2580
     * If no match is found returns new empty Stringy object.
2581
     *
2582
     * EXAMPLE: <code>
2583
     * </code>
2584
     *
2585
     * @param string $needle       <p>The string to look for.</p>
2586
     * @param bool   $beforeNeedle [optional] <p>Default: false</p>
2587
     *
2588
     * @psalm-mutation-free
2589
     *
2590
     * @return static
2591
     */
2592
    public function lastSubstringOf(string $needle, bool $beforeNeedle = false): self
2593
    {
2594
        return static::create(
4✔
2595
            $this->utf8::str_substr_last(
4✔
2596
                $this->str,
4✔
2597
                $needle,
4✔
2598
                $beforeNeedle,
4✔
2599
                $this->encoding
4✔
2600
            ),
4✔
2601
            $this->encoding
4✔
2602
        );
4✔
2603
    }
2604

2605
    /**
2606
     * Gets the substring after (or before via "$beforeNeedle") the last occurrence of the "$needle".
2607
     * If no match is found returns new empty Stringy object.
2608
     *
2609
     * EXAMPLE: <code>
2610
     * </code>
2611
     *
2612
     * @param string $needle       <p>The string to look for.</p>
2613
     * @param bool   $beforeNeedle [optional] <p>Default: false</p>
2614
     *
2615
     * @psalm-mutation-free
2616
     *
2617
     * @return static
2618
     */
2619
    public function lastSubstringOfIgnoreCase(string $needle, bool $beforeNeedle = false): self
2620
    {
2621
        return static::create(
2✔
2622
            $this->utf8::str_isubstr_last(
2✔
2623
                $this->str,
2✔
2624
                $needle,
2✔
2625
                $beforeNeedle,
2✔
2626
                $this->encoding
2✔
2627
            ),
2✔
2628
            $this->encoding
2✔
2629
        );
2✔
2630
    }
2631

2632
    /**
2633
     * Returns the length of the string.
2634
     *
2635
     * EXAMPLE: <code>
2636
     * s('fòôbàř')->length(); // 6
2637
     * </code>
2638
     *
2639
     * @psalm-mutation-free
2640
     *
2641
     * @return int
2642
     *             <p>The number of characters in $str given the encoding.</p>
2643
     */
2644
    public function length(): int
2645
    {
2646
        return (int) $this->utf8::strlen($this->str, $this->encoding);
29✔
2647
    }
2648

2649
    /**
2650
     * Line-Wrap the string after $limit, but also after the next word.
2651
     *
2652
     * EXAMPLE: <code>
2653
     * </code>
2654
     *
2655
     * @param int         $limit           [optional] <p>The column width.</p>
2656
     * @param string      $break           [optional] <p>The line is broken using the optional break parameter.</p>
2657
     * @param bool        $add_final_break [optional] <p>
2658
     *                                     If this flag is true, then the method will add a $break at the end
2659
     *                                     of the result string.
2660
     *                                     </p>
2661
     * @param string|null $delimiter       [optional] <p>
2662
     *                                     You can change the default behavior, where we split the string by newline.
2663
     *                                     </p>
2664
     *
2665
     * @psalm-mutation-free
2666
     *
2667
     * @return static
2668
     */
2669
    public function lineWrap(
2670
        int $limit,
2671
        string $break = "\n",
2672
        bool $add_final_break = true,
2673
        ?string $delimiter = null
2674
    ): self {
2675
        if ($limit <= 0) {
5✔
2676
            return static::create('', $this->encoding);
1✔
2677
        }
2678

2679
        $delimiter = $delimiter === '' ? null : $delimiter;
4✔
2680

2681
        return static::create(
4✔
2682
            $this->utf8::wordwrap_per_line(
4✔
2683
                $this->str,
4✔
2684
                $limit,
4✔
2685
                $break,
4✔
2686
                true,
4✔
2687
                $add_final_break,
4✔
2688
                $delimiter
4✔
2689
            ),
4✔
2690
            $this->encoding
4✔
2691
        );
4✔
2692
    }
2693

2694
    /**
2695
     * Line-Wrap the string after $limit, but also after the next word.
2696
     *
2697
     * EXAMPLE: <code>
2698
     * </code>
2699
     *
2700
     * @param int         $limit           [optional] <p>The column width.</p>
2701
     * @param string      $break           [optional] <p>The line is broken using the optional break parameter.</p>
2702
     * @param bool        $add_final_break [optional] <p>
2703
     *                                     If this flag is true, then the method will add a $break at the end
2704
     *                                     of the result string.
2705
     *                                     </p>
2706
     * @param string|null $delimiter       [optional] <p>
2707
     *                                     You can change the default behavior, where we split the string by newline.
2708
     *                                     </p>
2709
     *
2710
     * @psalm-mutation-free
2711
     *
2712
     * @return static
2713
     */
2714
    public function lineWrapAfterWord(
2715
        int $limit,
2716
        string $break = "\n",
2717
        bool $add_final_break = true,
2718
        ?string $delimiter = null
2719
    ): self {
2720
        if ($limit <= 0) {
8✔
2721
            return static::create('', $this->encoding);
2✔
2722
        }
2723

2724
        $delimiter = $delimiter === '' ? null : $delimiter;
6✔
2725

2726
        return static::create(
6✔
2727
            $this->utf8::wordwrap_per_line(
6✔
2728
                $this->str,
6✔
2729
                $limit,
6✔
2730
                $break,
6✔
2731
                false,
6✔
2732
                $add_final_break,
6✔
2733
                $delimiter
6✔
2734
            ),
6✔
2735
            $this->encoding
6✔
2736
        );
6✔
2737
    }
2738

2739
    /**
2740
     * Splits on newlines and carriage returns, returning an array of Stringy
2741
     * objects corresponding to the lines in the string.
2742
     *
2743
     * EXAMPLE: <code>
2744
     * s("fòô\r\nbàř\n")->lines(); // ['fòô', 'bàř', '']
2745
     * </code>
2746
     *
2747
     * @psalm-mutation-free
2748
     *
2749
     * @return static[]
2750
     *                  <p>An array of Stringy objects.</p>
2751
     *
2752
     * @phpstan-return array<int,static>
2753
     */
2754
    public function lines(): array
2755
    {
2756
        if ($this->str === '') {
51✔
2757
            return [static::create('')];
3✔
2758
        }
2759

2760
        $strings = $this->utf8::str_to_lines($this->str);
48✔
2761
        /** @noinspection AlterInForeachInspection */
2762
        foreach ($strings as &$str) {
48✔
2763
            $str = static::create($str, $this->encoding);
48✔
2764
        }
2765

2766
        /** @noinspection PhpSillyAssignmentInspection */
2767
        /** @var static[] $strings */
2768
        $strings = $strings;
48✔
2769

2770
        return $strings;
48✔
2771
    }
2772

2773
    /**
2774
     * Splits on newlines and carriage returns, returning an array of Stringy
2775
     * objects corresponding to the lines in the string.
2776
     *
2777
     * EXAMPLE: <code>
2778
     * </code>
2779
     *
2780
     * @psalm-mutation-free
2781
     *
2782
     * @return CollectionStringy|static[]
2783
     *                                    <p>An collection of Stringy objects.</p>
2784
     *
2785
     * @phpstan-return CollectionStringy<int,static>
2786
     */
2787
    public function linesCollection(): CollectionStringy
2788
    {
2789
        /**
2790
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the collection class
2791
         */
2792
        return CollectionStringy::create(
34✔
2793
            $this->lines()
34✔
2794
        );
34✔
2795
    }
2796

2797
    /**
2798
     * Returns the longest common prefix between the string and $otherStr.
2799
     *
2800
     * EXAMPLE: <code>
2801
     * s('foobar')->longestCommonPrefix('foobaz'); // 'fooba'
2802
     * </code>
2803
     *
2804
     * @param string $otherStr <p>Second string for comparison.</p>
2805
     *
2806
     * @psalm-mutation-free
2807
     *
2808
     * @return static
2809
     *                <p>Object with its $str being the longest common prefix.</p>
2810
     */
2811
    public function longestCommonPrefix(string $otherStr): self
2812
    {
2813
        return static::create(
30✔
2814
            $this->utf8::str_longest_common_prefix(
30✔
2815
                $this->str,
30✔
2816
                $otherStr,
30✔
2817
                $this->encoding
30✔
2818
            ),
30✔
2819
            $this->encoding
30✔
2820
        );
30✔
2821
    }
2822

2823
    /**
2824
     * Returns the longest common substring between the string and $otherStr.
2825
     * In the case of ties, it returns that which occurs first.
2826
     *
2827
     * EXAMPLE: <code>
2828
     * s('foobar')->longestCommonSubstring('boofar'); // 'oo'
2829
     * </code>
2830
     *
2831
     * @param string $otherStr <p>Second string for comparison.</p>
2832
     *
2833
     * @psalm-mutation-free
2834
     *
2835
     * @return static
2836
     *                <p>Object with its $str being the longest common substring.</p>
2837
     */
2838
    public function longestCommonSubstring(string $otherStr): self
2839
    {
2840
        return static::create(
30✔
2841
            $this->utf8::str_longest_common_substring(
30✔
2842
                $this->str,
30✔
2843
                $otherStr,
30✔
2844
                $this->encoding
30✔
2845
            ),
30✔
2846
            $this->encoding
30✔
2847
        );
30✔
2848
    }
2849

2850
    /**
2851
     * Returns the longest common suffix between the string and $otherStr.
2852
     *
2853
     * EXAMPLE: <code>
2854
     * s('fòôbàř')->longestCommonSuffix('fòrbàř'); // 'bàř'
2855
     * </code>
2856
     *
2857
     * @param string $otherStr <p>Second string for comparison.</p>
2858
     *
2859
     * @psalm-mutation-free
2860
     *
2861
     * @return static
2862
     *                <p>Object with its $str being the longest common suffix.</p>
2863
     */
2864
    public function longestCommonSuffix(string $otherStr): self
2865
    {
2866
        return static::create(
30✔
2867
            $this->utf8::str_longest_common_suffix(
30✔
2868
                $this->str,
30✔
2869
                $otherStr,
30✔
2870
                $this->encoding
30✔
2871
            ),
30✔
2872
            $this->encoding
30✔
2873
        );
30✔
2874
    }
2875

2876
    /**
2877
     * Converts the first character of the string to lower case.
2878
     *
2879
     * EXAMPLE: <code>
2880
     * s('Σ Foo')->lowerCaseFirst(); // 'σ Foo'
2881
     * </code>
2882
     *
2883
     * @psalm-mutation-free
2884
     *
2885
     * @return static
2886
     *                <p>Object with the first character of $str being lower case.</p>
2887
     */
2888
    public function lowerCaseFirst(): self
2889
    {
2890
        return static::create(
16✔
2891
            $this->utf8::lcfirst($this->str, $this->encoding),
16✔
2892
            $this->encoding
16✔
2893
        );
16✔
2894
    }
2895

2896
    /**
2897
     * Determine if the string matches another string regardless of case.
2898
     * Alias for isEqualsCaseInsensitive()
2899
     *
2900
     * EXAMPLE: <code>
2901
     * </code>
2902
     *
2903
     * @psalm-mutation-free
2904
     *
2905
     * @param string|Stringy ...$str
2906
     *                               <p>The string to compare against.</p>
2907
     *
2908
     * @psalm-mutation-free
2909
     *
2910
     * @return bool
2911
     */
2912
    public function matchCaseInsensitive(...$str): bool
2913
    {
2914
        return $this->isEqualsCaseInsensitive(...$str);
3✔
2915
    }
2916

2917
    /**
2918
     * Determine if the string matches another string.
2919
     * Alias for isEqualsCaseSensitive()
2920
     *
2921
     * EXAMPLE: <code>
2922
     * </code>
2923
     *
2924
     * @psalm-mutation-free
2925
     *
2926
     * @param string|Stringy ...$str
2927
     *                               <p>The string to compare against.</p>
2928
     *
2929
     * @psalm-mutation-free
2930
     *
2931
     * @return bool
2932
     */
2933
    public function matchCaseSensitive(...$str): bool
2934
    {
2935
        return $this->isEqualsCaseSensitive(...$str);
7✔
2936
    }
2937

2938
    /**
2939
     * Create a md5 hash from the current string.
2940
     *
2941
     * @psalm-mutation-free
2942
     *
2943
     * @return static
2944
     */
2945
    public function md5(): self
2946
    {
2947
        return static::create($this->hash('md5'), $this->encoding);
2✔
2948
    }
2949

2950
    /**
2951
     * Replace all breaks [<br> | \r\n | \r | \n | ...] into "<br>".
2952
     *
2953
     * EXAMPLE: <code>
2954
     * </code>
2955
     *
2956
     * @return static
2957
     */
2958
    public function newLineToHtmlBreak(): self
2959
    {
2960
        return $this->removeHtmlBreak('<br>');
1✔
2961
    }
2962

2963
    /**
2964
     * Get every nth character of the string.
2965
     *
2966
     * EXAMPLE: <code>
2967
     * </code>
2968
     *
2969
     * @param int $step   <p>The number of characters to step.</p>
2970
     * @param int $offset [optional] <p>The string offset to start at.</p>
2971
     *
2972
     * @psalm-mutation-free
2973
     *
2974
     * @return static
2975
     */
2976
    public function nth(int $step, int $offset = 0): self
2977
    {
2978
        $length = $step - 1;
4✔
2979
        $substring = $this->substr($offset)->toString();
4✔
2980

2981
        if ($substring === '') {
4✔
2982
            return new static('', $this->encoding);
×
2983
        }
2984

2985
        \preg_match_all(
4✔
2986
            "/(?:^|(?:.|\p{L}|\w){" . $length . "})(.|\p{L}|\w)/u",
4✔
2987
            $substring,
4✔
2988
            $matches
4✔
2989
        );
4✔
2990

2991
        return new static(\implode('', $matches[1] ?? []), $this->encoding);
4✔
2992
    }
2993

2994
    /**
2995
     * Returns the integer value of the current string.
2996
     *
2997
     * EXAMPLE: <code>
2998
     * s('foo1 ba2r')->extractIntegers(); // '12'
2999
     * </code>
3000
     *
3001
     * @psalm-mutation-free
3002
     *
3003
     * @return static
3004
     */
3005
    public function extractIntegers(): self
3006
    {
3007
        if ($this->str === '') {
1✔
3008
            return new static('', $this->encoding);
1✔
3009
        }
3010

3011
        \preg_match_all('/(?<integers>\d+)/', $this->str, $matches);
1✔
3012

3013
        return static::create(
1✔
3014
            \implode('', $matches['integers']),
1✔
3015
            $this->encoding
1✔
3016
        );
1✔
3017
    }
3018

3019
    /**
3020
     * Returns the special chars of the current string.
3021
     *
3022
     * EXAMPLE: <code>
3023
     * s('foo1 ba2!r')->extractSpecialCharacters(); // '!'
3024
     * </code>
3025
     *
3026
     * @psalm-mutation-free
3027
     *
3028
     * @return static
3029
     */
3030
    public function extractSpecialCharacters(): self
3031
    {
3032
        if ($this->str === '') {
1✔
3033
            return new static('', $this->encoding);
1✔
3034
        }
3035

3036
        // no letter, no digit, no space
3037
        \preg_match_all('/((?![\p{L}0-9\s]+).)/u', $this->str, $matches);
1✔
3038

3039
        return static::create(
1✔
3040
            \implode('', $matches[0]),
1✔
3041
            $this->encoding
1✔
3042
        );
1✔
3043
    }
3044

3045
    /**
3046
     * Returns whether or not a character exists at an index. Offsets may be
3047
     * negative to count from the last character in the string. Implements
3048
     * part of the ArrayAccess interface.
3049
     *
3050
     * EXAMPLE: <code>
3051
     * </code>
3052
     *
3053
     * @param int $offset <p>The index to check.</p>
3054
     *
3055
     * @psalm-mutation-free
3056
     *
3057
     * @return bool
3058
     *              <p>Whether or not the index exists.</p>
3059
     */
3060
    public function offsetExists($offset): bool
3061
    {
3062
        return $this->utf8::str_offset_exists(
18✔
3063
            $this->str,
18✔
3064
            $offset,
18✔
3065
            $this->encoding
18✔
3066
        );
18✔
3067
    }
3068

3069
    /**
3070
     * Returns the character at the given index. Offsets may be negative to
3071
     * count from the last character in the string. Implements part of the
3072
     * ArrayAccess interface, and throws an OutOfBoundsException if the index
3073
     * does not exist.
3074
     *
3075
     * EXAMPLE: <code>
3076
     * </code>
3077
     *
3078
     * @param int $offset <p>The <strong>index</strong> from which to retrieve the char.</p>
3079
     *
3080
     * @throws \OutOfBoundsException
3081
     *                               <p>If the positive or negative offset does not exist.</p>
3082
     *
3083
     * @return string
3084
     *                <p>The character at the specified index.</p>
3085
     *
3086
     * @psalm-mutation-free
3087
     */
3088
    public function offsetGet($offset): string
3089
    {
3090
        $length = $this->length();
10✔
3091

3092
        if (
3093
            ($offset >= 0 && $length <= $offset)
10✔
3094
            ||
3095
            $length < \abs($offset)
10✔
3096
        ) {
3097
            throw new \OutOfBoundsException('No character exists at the index');
5✔
3098
        }
3099

3100
        if ($this->encoding === 'UTF-8') {
5✔
3101
            return (string) \mb_substr($this->str, $offset, 1);
5✔
3102
        }
3103

3104
        return (string) $this->utf8::substr($this->str, $offset, 1, $this->encoding);
×
3105
    }
3106

3107
    /**
3108
     * Implements part of the ArrayAccess interface, but throws an exception
3109
     * when called. This maintains the immutability of Stringy objects.
3110
     *
3111
     * EXAMPLE: <code>
3112
     * </code>
3113
     *
3114
     * @param int   $offset <p>The index of the character.</p>
3115
     * @param mixed $value  <p>Value to set.</p>
3116
     *
3117
     * @throws \Exception
3118
     *                    <p>When called.</p>
3119
     *
3120
     * @return void
3121
     */
3122
    #[\ReturnTypeWillChange]
3123
    public function offsetSet($offset, $value)
3124
    {
3125
        // Stringy is immutable, cannot directly set char
3126
        throw new \Exception('Stringy object is immutable, cannot modify char');
3✔
3127
    }
3128

3129
    /**
3130
     * Implements part of the ArrayAccess interface, but throws an exception
3131
     * when called. This maintains the immutability of Stringy objects.
3132
     *
3133
     * EXAMPLE: <code>
3134
     * </code>
3135
     *
3136
     * @param int $offset <p>The index of the character.</p>
3137
     *
3138
     * @throws \Exception
3139
     *                    <p>When called.</p>
3140
     *
3141
     * @return void
3142
     */
3143
    #[\ReturnTypeWillChange]
3144
    public function offsetUnset($offset)
3145
    {
3146
        // Don't allow directly modifying the string
3147
        throw new \Exception('Stringy object is immutable, cannot unset char');
3✔
3148
    }
3149

3150
    /**
3151
     * Pads the string to a given length with $padStr. If length is less than
3152
     * or equal to the length of the string, no padding takes places. The
3153
     * default string used for padding is a space, and the default type (one of
3154
     * 'left', 'right', 'both') is 'right'. Throws an InvalidArgumentException
3155
     * if $padType isn't one of those 3 values.
3156
     *
3157
     * EXAMPLE: <code>
3158
     * s('fòôbàř')->pad(9, '-/', 'left'); // '-/-fòôbàř'
3159
     * </code>
3160
     *
3161
     * @param int    $length  <p>Desired string length after padding.</p>
3162
     * @param string $padStr  [optional] <p>String used to pad, defaults to space. Default: ' '</p>
3163
     * @param string $padType [optional] <p>One of 'left', 'right', 'both'. Default: 'right'</p>
3164
     *
3165
     * @throws \InvalidArgumentException
3166
     *                                   <p>If $padType isn't one of 'right', 'left' or 'both'.</p>
3167
     *
3168
     * @return static
3169
     *                <p>Object with a padded $str.</p>
3170
     *
3171
     * @psalm-mutation-free
3172
     */
3173
    public function pad(int $length, string $padStr = ' ', string $padType = 'right'): self
3174
    {
3175
        return static::create(
39✔
3176
            $this->utf8::str_pad(
39✔
3177
                $this->str,
39✔
3178
                $length,
39✔
3179
                $padStr,
39✔
3180
                $padType,
39✔
3181
                $this->encoding
39✔
3182
            )
39✔
3183
        );
39✔
3184
    }
3185

3186
    /**
3187
     * Returns a new string of a given length such that both sides of the
3188
     * string are padded. Alias for pad() with a $padType of 'both'.
3189
     *
3190
     * EXAMPLE: <code>
3191
     * s('foo bar')->padBoth(9, ' '); // ' foo bar '
3192
     * </code>
3193
     *
3194
     * @param int    $length <p>Desired string length after padding.</p>
3195
     * @param string $padStr [optional] <p>String used to pad, defaults to space. Default: ' '</p>
3196
     *
3197
     * @psalm-mutation-free
3198
     *
3199
     * @return static
3200
     *                <p>String with padding applied.</p>
3201
     */
3202
    public function padBoth(int $length, string $padStr = ' '): self
3203
    {
3204
        return static::create(
33✔
3205
            $this->utf8::str_pad_both(
33✔
3206
                $this->str,
33✔
3207
                $length,
33✔
3208
                $padStr,
33✔
3209
                $this->encoding
33✔
3210
            )
33✔
3211
        );
33✔
3212
    }
3213

3214
    /**
3215
     * Returns a new string of a given length such that the beginning of the
3216
     * string is padded. Alias for pad() with a $padType of 'left'.
3217
     *
3218
     * EXAMPLE: <code>
3219
     * s('foo bar')->padLeft(9, ' '); // '  foo bar'
3220
     * </code>
3221
     *
3222
     * @param int    $length <p>Desired string length after padding.</p>
3223
     * @param string $padStr [optional] <p>String used to pad, defaults to space. Default: ' '</p>
3224
     *
3225
     * @psalm-mutation-free
3226
     *
3227
     * @return static
3228
     *                <p>String with left padding.</p>
3229
     */
3230
    public function padLeft(int $length, string $padStr = ' '): self
3231
    {
3232
        return static::create(
21✔
3233
            $this->utf8::str_pad_left(
21✔
3234
                $this->str,
21✔
3235
                $length,
21✔
3236
                $padStr,
21✔
3237
                $this->encoding
21✔
3238
            )
21✔
3239
        );
21✔
3240
    }
3241

3242
    /**
3243
     * Returns a new string of a given length such that the end of the string
3244
     * is padded. Alias for pad() with a $padType of 'right'.
3245
     *
3246
     * EXAMPLE: <code>
3247
     * s('foo bar')->padRight(10, '_*'); // 'foo bar_*_'
3248
     * </code>
3249
     *
3250
     * @param int    $length <p>Desired string length after padding.</p>
3251
     * @param string $padStr [optional] <p>String used to pad, defaults to space. Default: ' '</p>
3252
     *
3253
     * @psalm-mutation-free
3254
     *
3255
     * @return static
3256
     *                <p>String with right padding.</p>
3257
     */
3258
    public function padRight(int $length, string $padStr = ' '): self
3259
    {
3260
        return static::create(
21✔
3261
            $this->utf8::str_pad_right(
21✔
3262
                $this->str,
21✔
3263
                $length,
21✔
3264
                $padStr,
21✔
3265
                $this->encoding
21✔
3266
            )
21✔
3267
        );
21✔
3268
    }
3269

3270
    /**
3271
     * Convert the string to PascalCase.
3272
     * Alias for studlyCase()
3273
     *
3274
     * EXAMPLE: <code>
3275
     * </code>
3276
     *
3277
     * @psalm-mutation-free
3278
     *
3279
     * @return static
3280
     */
3281
    public function pascalCase(): self
3282
    {
3283
        return $this->studlyCase();
3✔
3284
    }
3285

3286
    /**
3287
     * Returns a new string starting with $prefix.
3288
     *
3289
     * EXAMPLE: <code>
3290
     * s('bàř')->prepend('fòô'); // 'fòôbàř'
3291
     * </code>
3292
     *
3293
     * @param string ...$prefix <p>The string to append.</p>
3294
     *
3295
     * @psalm-mutation-free
3296
     *
3297
     * @return static
3298
     *                <p>Object with appended $prefix.</p>
3299
     */
3300
    public function prepend(string ...$prefix): self
3301
    {
3302
        if (\count($prefix) <= 1) {
8✔
3303
            $prefix = $prefix[0];
6✔
3304
        } else {
3305
            $prefix = \implode('', $prefix);
2✔
3306
        }
3307

3308
        return static::create($prefix . $this->str, $this->encoding);
8✔
3309
    }
3310

3311
    /**
3312
     * Returns a new string starting with $prefix.
3313
     *
3314
     * EXAMPLE: <code>
3315
     * </code>
3316
     *
3317
     * @param CollectionStringy|static ...$prefix <p>The Stringy objects to append.</p>
3318
     *
3319
     * @phpstan-param CollectionStringy<int,static>|static ...$prefix
3320
     *
3321
     * @psalm-mutation-free
3322
     *
3323
     * @return static
3324
     *                <p>Object with appended $prefix.</p>
3325
     */
3326
    public function prependStringy(...$prefix): self
3327
    {
3328
        $prefixStr = '';
1✔
3329
        foreach ($prefix as $prefixTmp) {
1✔
3330
            if ($prefixTmp instanceof CollectionStringy) {
1✔
3331
                $prefixStr .= $prefixTmp->implode('');
1✔
3332
            } else {
3333
                $prefixStr .= $prefixTmp->toString();
1✔
3334
            }
3335
        }
3336

3337
        return static::create($prefixStr . $this->str, $this->encoding);
1✔
3338
    }
3339

3340
    /**
3341
     * Replaces all occurrences of $pattern in $str by $replacement.
3342
     *
3343
     * EXAMPLE: <code>
3344
     * s('fòô ')->regexReplace('f[òô]+\s', 'bàř'); // 'bàř'
3345
     * s('fò')->regexReplace('(ò)', '\\1ô'); // 'fòô'
3346
     * </code>
3347
     *
3348
     * @param string $pattern     <p>The regular expression pattern.</p>
3349
     * @param string $replacement <p>The string to replace with.</p>
3350
     * @param string $options     [optional] <p>Matching conditions to be used.</p>
3351
     * @param string $delimiter   [optional] <p>Delimiter the the regex. Default: '/'</p>
3352
     *
3353
     * @psalm-mutation-free
3354
     *
3355
     * @return static
3356
     *                <p>Object with the result2ing $str after the replacements.</p>
3357
     */
3358
    public function regexReplace(
3359
        string $pattern,
3360
        string $replacement,
3361
        string $options = '',
3362
        string $delimiter = '/'
3363
    ): self {
3364
        return static::create(
29✔
3365
            $this->utf8::regex_replace(
29✔
3366
                $this->str,
29✔
3367
                $pattern,
29✔
3368
                $replacement,
29✔
3369
                $options,
29✔
3370
                $delimiter
29✔
3371
            ),
29✔
3372
            $this->encoding
29✔
3373
        );
29✔
3374
    }
3375

3376
    /**
3377
     * Remove html via "strip_tags()" from the string.
3378
     *
3379
     * EXAMPLE: <code>
3380
     * s('řàb <ô>òf\', ô<br/>foo <a href="#">lall</a>')->removeHtml('<br><br/>'); // 'řàb òf\', ô<br/>foo lall'
3381
     * </code>
3382
     *
3383
     * @param string $allowableTags [optional] <p>You can use the optional second parameter to specify tags which should
3384
     *                              not be stripped. Default: null
3385
     *                              </p>
3386
     *
3387
     * @psalm-mutation-free
3388
     *
3389
     * @return static
3390
     */
3391
    public function removeHtml(string $allowableTags = ''): self
3392
    {
3393
        return static::create(
12✔
3394
            $this->utf8::remove_html($this->str, $allowableTags),
12✔
3395
            $this->encoding
12✔
3396
        );
12✔
3397
    }
3398

3399
    /**
3400
     * Remove all breaks [<br> | \r\n | \r | \n | ...] from the string.
3401
     *
3402
     * EXAMPLE: <code>
3403
     * s('řàb <ô>òf\', ô<br/>foo <a href="#">lall</a>')->removeHtmlBreak(''); // 'řàb <ô>òf\', ô< foo <a href="#">lall</a>'
3404
     * </code>
3405
     *
3406
     * @param string $replacement [optional] <p>Default is a empty string.</p>
3407
     *
3408
     * @psalm-mutation-free
3409
     *
3410
     * @return static
3411
     */
3412
    public function removeHtmlBreak(string $replacement = ''): self
3413
    {
3414
        return static::create(
13✔
3415
            $this->utf8::remove_html_breaks($this->str, $replacement),
13✔
3416
            $this->encoding
13✔
3417
        );
13✔
3418
    }
3419

3420
    /**
3421
     * Returns a new string with the prefix $substring removed, if present.
3422
     *
3423
     * EXAMPLE: <code>
3424
     * s('fòôbàř')->removeLeft('fòô'); // 'bàř'
3425
     * </code>
3426
     *
3427
     * @param string $substring <p>The prefix to remove.</p>
3428
     *
3429
     * @psalm-mutation-free
3430
     *
3431
     * @return static
3432
     *                <p>Object having a $str without the prefix $substring.</p>
3433
     */
3434
    public function removeLeft(string $substring): self
3435
    {
3436
        return static::create(
36✔
3437
            $this->utf8::remove_left($this->str, $substring, $this->encoding),
36✔
3438
            $this->encoding
36✔
3439
        );
36✔
3440
    }
3441

3442
    /**
3443
     * Returns a new string with the suffix $substring removed, if present.
3444
     *
3445
     * EXAMPLE: <code>
3446
     * s('fòôbàř')->removeRight('bàř'); // 'fòô'
3447
     * </code>
3448
     *
3449
     * @param string $substring <p>The suffix to remove.</p>
3450
     *
3451
     * @psalm-mutation-free
3452
     *
3453
     * @return static
3454
     *                <p>Object having a $str without the suffix $substring.</p>
3455
     */
3456
    public function removeRight(string $substring): self
3457
    {
3458
        return static::create(
36✔
3459
            $this->utf8::remove_right($this->str, $substring, $this->encoding),
36✔
3460
            $this->encoding
36✔
3461
        );
36✔
3462
    }
3463

3464
    /**
3465
     * Try to remove all XSS-attacks from the string.
3466
     *
3467
     * EXAMPLE: <code>
3468
     * s('<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>')->removeXss(); // '<IMG >'
3469
     * </code>
3470
     *
3471
     * @psalm-mutation-free
3472
     *
3473
     * @return static
3474
     */
3475
    public function removeXss(): self
3476
    {
3477
        /**
3478
         * @var AntiXSS|null
3479
         *
3480
         * @psalm-suppress ImpureStaticVariable
3481
         */
3482
        static $antiXss = null;
12✔
3483

3484
        if ($antiXss === null) {
12✔
3485
            $antiXss = new AntiXSS();
1✔
3486
        }
3487

3488
        /**
3489
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the anti-xss class
3490
         */
3491
        $str = $antiXss->xss_clean($this->str);
12✔
3492

3493
        return static::create($str, $this->encoding);
12✔
3494
    }
3495

3496
    /**
3497
     * Returns a repeated string given a multiplier.
3498
     *
3499
     * EXAMPLE: <code>
3500
     * s('α')->repeat(3); // 'ααα'
3501
     * </code>
3502
     *
3503
     * @param int $multiplier <p>The number of times to repeat the string.</p>
3504
     *
3505
     * @psalm-mutation-free
3506
     *
3507
     * @return static
3508
     *                <p>Object with a repeated str.</p>
3509
     */
3510
    public function repeat(int $multiplier): self
3511
    {
3512
        return static::create(
21✔
3513
            \str_repeat($this->str, $multiplier),
21✔
3514
            $this->encoding
21✔
3515
        );
21✔
3516
    }
3517

3518
    /**
3519
     * Replaces all occurrences of $search in $str by $replacement.
3520
     *
3521
     * EXAMPLE: <code>
3522
     * s('fòô bàř fòô bàř')->replace('fòô ', ''); // 'bàř bàř'
3523
     * </code>
3524
     *
3525
     * @param string $search        <p>The needle to search for.</p>
3526
     * @param string $replacement   <p>The string to replace with.</p>
3527
     * @param bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
3528
     *
3529
     * @psalm-mutation-free
3530
     *
3531
     * @return static
3532
     *                <p>Object with the resulting $str after the replacements.</p>
3533
     */
3534
    public function replace(string $search, string $replacement, bool $caseSensitive = true): self
3535
    {
3536
        if ($search === '' && $replacement === '') {
76✔
3537
            return static::create($this->str, $this->encoding);
16✔
3538
        }
3539

3540
        if ($this->str === '' && $search === '') {
60✔
3541
            return static::create($replacement, $this->encoding);
2✔
3542
        }
3543

3544
        if ($caseSensitive) {
58✔
3545
            return static::create(
48✔
3546
                \str_replace($search, $replacement, $this->str),
48✔
3547
                $this->encoding
48✔
3548
            );
48✔
3549
        }
3550

3551
        return static::create(
10✔
3552
            $this->utf8::str_ireplace($search, $replacement, $this->str),
10✔
3553
            $this->encoding
10✔
3554
        );
10✔
3555
    }
3556

3557
    /**
3558
     * Replaces all occurrences of $search in $str by $replacement.
3559
     *
3560
     * EXAMPLE: <code>
3561
     * s('fòô bàř lall bàř')->replaceAll(['fòÔ ', 'lall'], '', false); // 'bàř bàř'
3562
     * </code>
3563
     *
3564
     * @param string[]        $search        <p>The elements to search for.</p>
3565
     * @param string|string[] $replacement   <p>The string to replace with.</p>
3566
     * @param bool            $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
3567
     *
3568
     * @psalm-mutation-free
3569
     *
3570
     * @return static
3571
     *                <p>Object with the resulting $str after the replacements.</p>
3572
     */
3573
    public function replaceAll(array $search, $replacement, bool $caseSensitive = true): self
3574
    {
3575
        if ($caseSensitive) {
61✔
3576
            return static::create(
47✔
3577
                \str_replace($search, $replacement, $this->str),
47✔
3578
                $this->encoding
47✔
3579
            );
47✔
3580
        }
3581

3582
        return static::create(
14✔
3583
            $this->utf8::str_ireplace($search, $replacement, $this->str),
14✔
3584
            $this->encoding
14✔
3585
        );
14✔
3586
    }
3587

3588
    /**
3589
     * Replaces all occurrences of $search from the beginning of string with $replacement.
3590
     *
3591
     * EXAMPLE: <code>
3592
     * s('fòô bàř fòô bàř')->replaceBeginning('fòô', ''); // ' bàř bàř'
3593
     * </code>
3594
     *
3595
     * @param string $search      <p>The string to search for.</p>
3596
     * @param string $replacement <p>The replacement.</p>
3597
     *
3598
     * @psalm-mutation-free
3599
     *
3600
     * @return static
3601
     *                <p>Object with the resulting $str after the replacements.</p>
3602
     */
3603
    public function replaceBeginning(string $search, string $replacement): self
3604
    {
3605
        return static::create(
32✔
3606
            $this->utf8::str_replace_beginning($this->str, $search, $replacement),
32✔
3607
            $this->encoding
32✔
3608
        );
32✔
3609
    }
3610

3611
    /**
3612
     * Replaces all occurrences of $search from the ending of string with $replacement.
3613
     *
3614
     * EXAMPLE: <code>
3615
     * s('fòô bàř fòô bàř')->replaceEnding('bàř', ''); // 'fòô bàř fòô '
3616
     * </code>
3617
     *
3618
     * @param string $search      <p>The string to search for.</p>
3619
     * @param string $replacement <p>The replacement.</p>
3620
     *
3621
     * @psalm-mutation-free
3622
     *
3623
     * @return static
3624
     *                <p>Object with the resulting $str after the replacements.</p>
3625
     */
3626
    public function replaceEnding(string $search, string $replacement): self
3627
    {
3628
        return static::create(
32✔
3629
            $this->utf8::str_replace_ending($this->str, $search, $replacement),
32✔
3630
            $this->encoding
32✔
3631
        );
32✔
3632
    }
3633

3634
    /**
3635
     * Replaces first occurrences of $search from the beginning of string with $replacement.
3636
     *
3637
     * EXAMPLE: <code>
3638
     * </code>
3639
     *
3640
     * @param string $search      <p>The string to search for.</p>
3641
     * @param string $replacement <p>The replacement.</p>
3642
     *
3643
     * @psalm-mutation-free
3644
     *
3645
     * @return static
3646
     *                <p>Object with the resulting $str after the replacements.</p>
3647
     */
3648
    public function replaceFirst(string $search, string $replacement): self
3649
    {
3650
        return static::create(
32✔
3651
            $this->utf8::str_replace_first($search, $replacement, $this->str),
32✔
3652
            $this->encoding
32✔
3653
        );
32✔
3654
    }
3655

3656
    /**
3657
     * Replaces last occurrences of $search from the ending of string with $replacement.
3658
     *
3659
     * EXAMPLE: <code>
3660
     * </code>
3661
     *
3662
     * @param string $search      <p>The string to search for.</p>
3663
     * @param string $replacement <p>The replacement.</p>
3664
     *
3665
     * @psalm-mutation-free
3666
     *
3667
     * @return static
3668
     *                <p>Object with the resulting $str after the replacements.</p>
3669
     */
3670
    public function replaceLast(string $search, string $replacement): self
3671
    {
3672
        return static::create(
30✔
3673
            $this->utf8::str_replace_last($search, $replacement, $this->str),
30✔
3674
            $this->encoding
30✔
3675
        );
30✔
3676
    }
3677

3678
    /**
3679
     * Returns a reversed string. A multibyte version of strrev().
3680
     *
3681
     * EXAMPLE: <code>
3682
     * s('fòôbàř')->reverse(); // 'řàbôòf'
3683
     * </code>
3684
     *
3685
     * @psalm-mutation-free
3686
     *
3687
     * @return static
3688
     *                <p>Object with a reversed $str.</p>
3689
     */
3690
    public function reverse(): self
3691
    {
3692
        return static::create($this->utf8::strrev($this->str), $this->encoding);
15✔
3693
    }
3694

3695
    /**
3696
     * Truncates the string to a given length, while ensuring that it does not
3697
     * split words. If $substring is provided, and truncating occurs, the
3698
     * string is further truncated so that the substring may be appended without
3699
     * exceeding the desired length.
3700
     *
3701
     * EXAMPLE: <code>
3702
     * s('What are your plans today?')->safeTruncate(22, '...'); // 'What are your plans...'
3703
     * </code>
3704
     *
3705
     * @param int    $length                          <p>Desired length of the truncated string.</p>
3706
     * @param string $substring                       [optional] <p>The substring to append if it can fit. Default: ''</p>
3707
     * @param bool   $ignoreDoNotSplitWordsForOneWord
3708
     *
3709
     * @psalm-mutation-free
3710
     *
3711
     * @return static
3712
     *                <p>Object with the resulting $str after truncating.</p>
3713
     */
3714
    public function safeTruncate(
3715
        int $length,
3716
        string $substring = '',
3717
        bool $ignoreDoNotSplitWordsForOneWord = true
3718
    ): self {
3719
        return static::create(
68✔
3720
            $this->utf8::str_truncate_safe(
68✔
3721
                $this->str,
68✔
3722
                $length,
68✔
3723
                $substring,
68✔
3724
                $this->encoding,
68✔
3725
                $ignoreDoNotSplitWordsForOneWord
68✔
3726
            ),
68✔
3727
            $this->encoding
68✔
3728
        );
68✔
3729
    }
3730

3731
    /**
3732
     * Set the internal character encoding.
3733
     *
3734
     * EXAMPLE: <code>
3735
     * </code>
3736
     *
3737
     * @param string $new_encoding <p>The desired character encoding.</p>
3738
     *
3739
     * @psalm-mutation-free
3740
     *
3741
     * @return static
3742
     */
3743
    public function setInternalEncoding(string $new_encoding): self
3744
    {
3745
        return new static($this->str, $new_encoding);
1✔
3746
    }
3747

3748
    /**
3749
     * Create a sha1 hash from the current string.
3750
     *
3751
     * EXAMPLE: <code>
3752
     * </code>
3753
     *
3754
     * @psalm-mutation-free
3755
     *
3756
     * @return static
3757
     */
3758
    public function sha1(): self
3759
    {
3760
        return static::create($this->hash('sha1'), $this->encoding);
2✔
3761
    }
3762

3763
    /**
3764
     * Create a sha256 hash from the current string.
3765
     *
3766
     * EXAMPLE: <code>
3767
     * </code>
3768
     *
3769
     * @psalm-mutation-free
3770
     *
3771
     * @return static
3772
     */
3773
    public function sha256(): self
3774
    {
3775
        return static::create($this->hash('sha256'), $this->encoding);
2✔
3776
    }
3777

3778
    /**
3779
     * Create a sha512 hash from the current string.
3780
     *
3781
     * EXAMPLE: <code>
3782
     * </code>
3783
     *
3784
     * @psalm-mutation-free
3785
     *
3786
     * @return static
3787
     */
3788
    public function sha512(): self
3789
    {
3790
        return static::create($this->hash('sha512'), $this->encoding);
2✔
3791
    }
3792

3793
    /**
3794
     * Shorten the string after $length, but also after the next word.
3795
     *
3796
     * EXAMPLE: <code>
3797
     * s('this is a test')->shortenAfterWord(2, '...'); // 'this...'
3798
     * </code>
3799
     *
3800
     * @param int    $length   <p>The given length.</p>
3801
     * @param string $strAddOn [optional] <p>Default: '…'</p>
3802
     *
3803
     * @psalm-mutation-free
3804
     *
3805
     * @return static
3806
     */
3807
    public function shortenAfterWord(int $length, string $strAddOn = '…'): self
3808
    {
3809
        if ($length <= 0) {
12✔
3810
            return static::create('', $this->encoding);
4✔
3811
        }
3812

3813
        return static::create(
8✔
3814
            $this->utf8::str_limit_after_word($this->str, $length, $strAddOn),
8✔
3815
            $this->encoding
8✔
3816
        );
8✔
3817
    }
3818

3819
    /**
3820
     * A multibyte string shuffle function. It returns a string with its
3821
     * characters in random order.
3822
     *
3823
     * EXAMPLE: <code>
3824
     * s('fòôbàř')->shuffle(); // 'àôřbòf'
3825
     * </code>
3826
     *
3827
     * @return static
3828
     *                <p>Object with a shuffled $str.</p>
3829
     */
3830
    public function shuffle(): self
3831
    {
3832
        return static::create($this->utf8::str_shuffle($this->str), $this->encoding);
9✔
3833
    }
3834

3835
    /**
3836
     * Calculate the similarity between two strings.
3837
     *
3838
     * EXAMPLE: <code>
3839
     * </code>
3840
     *
3841
     * @param string $str <p>The delimiting string.</p>
3842
     *
3843
     * @psalm-mutation-free
3844
     *
3845
     * @return float
3846
     */
3847
    public function similarity(string $str): float
3848
    {
3849
        \similar_text($this->str, $str, $percent);
2✔
3850

3851
        return $percent;
2✔
3852
    }
3853

3854
    /**
3855
     * Returns the substring beginning at $start, and up to, but not including
3856
     * the index specified by $end. If $end is omitted, the function extracts
3857
     * the remaining string. If $end is negative, it is computed from the end
3858
     * of the string.
3859
     *
3860
     * EXAMPLE: <code>
3861
     * s('fòôbàř')->slice(3, -1); // 'bà'
3862
     * </code>
3863
     *
3864
     * @param int $start <p>Initial index from which to begin extraction.</p>
3865
     * @param int $end   [optional] <p>Index at which to end extraction. Default: null</p>
3866
     *
3867
     * @psalm-mutation-free
3868
     *
3869
     * @return static
3870
     *                <p>Object with its $str being the extracted substring.</p>
3871
     */
3872
    public function slice(int $start, ?int $end = null): self
3873
    {
3874
        return static::create(
50✔
3875
            $this->utf8::str_slice($this->str, $start, $end, $this->encoding),
50✔
3876
            $this->encoding
50✔
3877
        );
50✔
3878
    }
3879

3880
    /**
3881
     * Converts the string into an URL slug. This includes replacing non-ASCII
3882
     * characters with their closest ASCII equivalents, removing remaining
3883
     * non-ASCII and non-alphanumeric characters, and replacing whitespace with
3884
     * $separator. The separator defaults to a single dash, and the string
3885
     * is also converted to lowercase. The language of the source string can
3886
     * also be supplied for language-specific transliteration.
3887
     *
3888
     * EXAMPLE: <code>
3889
     * s('Using strings like fòô bàř')->slugify(); // 'using-strings-like-foo-bar'
3890
     * </code>
3891
     *
3892
     * @param string                $separator             [optional] <p>The string used to replace whitespace.</p>
3893
     * @param string                $language              [optional] <p>Language of the source string.</p>
3894
     * @param array<string, string> $replacements          [optional] <p>A map of replaceable strings.</p>
3895
     * @param bool                  $replace_extra_symbols [optional]  <p>Add some more replacements e.g. "£" with "
3896
     *                                                     pound ".</p>
3897
     * @param bool                  $use_str_to_lower      [optional] <p>Use "string to lower" for the input.</p>
3898
     * @param bool                  $use_transliterate     [optional]  <p>Use ASCII::to_transliterate() for unknown
3899
     *                                                     chars.</p>
3900
     *
3901
     * @psalm-mutation-free
3902
     *
3903
     * @return static
3904
     *                <p>Object whose $str has been converted to an URL slug.</p>
3905
     *
3906
     * @phpstan-param ASCII::*_LANGUAGE_CODE $language
3907
     *
3908
     * @noinspection PhpTooManyParametersInspection
3909
     */
3910
    public function slugify(
3911
        string $separator = '-',
3912
        string $language = 'en',
3913
        array $replacements = [],
3914
        bool $replace_extra_symbols = true,
3915
        bool $use_str_to_lower = true,
3916
        bool $use_transliterate = false
3917
    ): self {
3918
        return static::create(
17✔
3919
            $this->ascii::to_slugify(
17✔
3920
                $this->str,
17✔
3921
                $separator,
17✔
3922
                $language,
17✔
3923
                $replacements,
17✔
3924
                $replace_extra_symbols,
17✔
3925
                $use_str_to_lower,
17✔
3926
                $use_transliterate
17✔
3927
            ),
17✔
3928
            $this->encoding
17✔
3929
        );
17✔
3930
    }
3931

3932
    /**
3933
     * Convert the string to snake_case.
3934
     *
3935
     * EXAMPLE: <code>
3936
     * </code>
3937
     *
3938
     * @psalm-mutation-free
3939
     *
3940
     * @return static
3941
     */
3942
    public function snakeCase(): self
3943
    {
3944
        $words = \array_map(
3✔
3945
            static function (self $word) {
3✔
3946
                return $word->toLowerCase();
3✔
3947
            },
3✔
3948
            $this->words('', true)
3✔
3949
        );
3✔
3950

3951
        return new static(\implode('_', $words), $this->encoding);
3✔
3952
    }
3953

3954
    /**
3955
     * Convert a string to snake_case.
3956
     *
3957
     * EXAMPLE: <code>
3958
     * s('foo1 Bar')->snakeize(); // 'foo_1_bar'
3959
     * </code>
3960
     *
3961
     * @psalm-mutation-free
3962
     *
3963
     * @return static
3964
     *                <p>Object with $str in snake_case.</p>
3965
     */
3966
    public function snakeize(): self
3967
    {
3968
        return static::create(
40✔
3969
            $this->utf8::str_snakeize($this->str, $this->encoding),
40✔
3970
            $this->encoding
40✔
3971
        );
40✔
3972
    }
3973

3974
    /**
3975
     * Wrap the string after the first whitespace character after a given number
3976
     * of characters.
3977
     *
3978
     * EXAMPLE: <code>
3979
     * </code>
3980
     *
3981
     * @param int    $width <p>Number of characters at which to wrap.</p>
3982
     * @param string $break [optional] <p>Character used to break the string. | Default "\n"</p>
3983
     *
3984
     * @psalm-mutation-free
3985
     *
3986
     * @return static
3987
     */
3988
    public function softWrap(int $width, string $break = "\n"): self
3989
    {
3990
        return $this->lineWrapAfterWord($width, $break, false);
2✔
3991
    }
3992

3993
    /**
3994
     * Splits the string with the provided regular expression, returning an
3995
     * array of Stringy objects. An optional integer $limit will truncate the
3996
     * results.
3997
     *
3998
     * EXAMPLE: <code>
3999
     * s('foo,bar,baz')->split(',', 2); // ['foo', 'bar']
4000
     * </code>
4001
     *
4002
     * @param string $pattern <p>The regex with which to split the string.</p>
4003
     * @param int    $limit   [optional] <p>Maximum number of results to return. Default: -1 === no
4004
     *                        limit</p>
4005
     *
4006
     * @psalm-mutation-free
4007
     *
4008
     * @return static[]
4009
     *                  <p>An array of Stringy objects.</p>
4010
     *
4011
     * @phpstan-return array<int,static>
4012
     */
4013
    public function split(string $pattern, ?int $limit = null): array
4014
    {
4015
        if ($this->str === '') {
51✔
4016
            return [];
×
4017
        }
4018

4019
        if ($limit === null) {
51✔
4020
            $limit = -1;
7✔
4021
        }
4022

4023
        $array = $this->utf8::str_split_pattern($this->str, $pattern, $limit);
51✔
4024
        foreach ($array as &$value) {
51✔
4025
            $value = static::create($value, $this->encoding);
45✔
4026
        }
4027

4028
        /** @noinspection PhpSillyAssignmentInspection */
4029
        /** @var static[] $array */
4030
        $array = $array;
51✔
4031

4032
        return $array;
51✔
4033
    }
4034

4035
    /**
4036
     * Splits the string with the provided regular expression, returning an
4037
     * collection of Stringy objects. An optional integer $limit will truncate the
4038
     * results.
4039
     *
4040
     * EXAMPLE: <code>
4041
     * </code>
4042
     *
4043
     * @param string $pattern <p>The regex with which to split the string.</p>
4044
     * @param int    $limit   [optional] <p>Maximum number of results to return. Default: -1 === no
4045
     *                        limit</p>
4046
     *
4047
     * @psalm-mutation-free
4048
     *
4049
     * @return CollectionStringy|static[]
4050
     *                                    <p>An collection of Stringy objects.</p>
4051
     *
4052
     * @phpstan-return CollectionStringy<int,static>
4053
     */
4054
    public function splitCollection(string $pattern, ?int $limit = null): CollectionStringy
4055
    {
4056
        /**
4057
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the collection class
4058
         */
4059
        return CollectionStringy::create(
35✔
4060
            $this->split($pattern, $limit)
35✔
4061
        );
35✔
4062
    }
4063

4064
    /**
4065
     * Returns true if the string begins with $substring, false otherwise. By
4066
     * default, the comparison is case-sensitive, but can be made insensitive
4067
     * by setting $caseSensitive to false.
4068
     *
4069
     * EXAMPLE: <code>
4070
     * s('FÒÔbàřbaz')->startsWith('fòôbàř', false); // true
4071
     * </code>
4072
     *
4073
     * @param string $substring     <p>The substring to look for.</p>
4074
     * @param bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
4075
     *
4076
     * @psalm-mutation-free
4077
     *
4078
     * @return bool
4079
     *              <p>Whether or not $str starts with $substring.</p>
4080
     */
4081
    public function startsWith(string $substring, bool $caseSensitive = true): bool
4082
    {
4083
        if ($caseSensitive) {
99✔
4084
            return $this->utf8::str_starts_with($this->str, $substring);
53✔
4085
        }
4086

4087
        return $this->utf8::str_istarts_with($this->str, $substring);
46✔
4088
    }
4089

4090
    /**
4091
     * Returns true if the string begins with any of $substrings, false otherwise.
4092
     * By default the comparison is case-sensitive, but can be made insensitive by
4093
     * setting $caseSensitive to false.
4094
     *
4095
     * EXAMPLE: <code>
4096
     * s('FÒÔbàřbaz')->startsWithAny(['fòô', 'bàř'], false); // true
4097
     * </code>
4098
     *
4099
     * @param string[] $substrings    <p>Substrings to look for.</p>
4100
     * @param bool     $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
4101
     *
4102
     * @psalm-mutation-free
4103
     *
4104
     * @return bool
4105
     *              <p>Whether or not $str starts with $substring.</p>
4106
     */
4107
    public function startsWithAny(array $substrings, bool $caseSensitive = true): bool
4108
    {
4109
        if ($caseSensitive) {
35✔
4110
            return $this->utf8::str_starts_with_any($this->str, $substrings);
23✔
4111
        }
4112

4113
        return $this->utf8::str_istarts_with_any($this->str, $substrings);
12✔
4114
    }
4115

4116
    /**
4117
     * Remove one or more strings from the string.
4118
     *
4119
     * EXAMPLE: <code>
4120
     * </code>
4121
     *
4122
     * @param string|string[] $search One or more strings to be removed
4123
     *
4124
     * @psalm-mutation-free
4125
     *
4126
     * @return static
4127
     */
4128
    public function strip($search): self
4129
    {
4130
        if (\is_array($search)) {
3✔
4131
            return $this->replaceAll($search, '');
1✔
4132
        }
4133

4134
        return $this->replace($search, '');
2✔
4135
    }
4136

4137
    /**
4138
     * Strip all whitespace characters. This includes tabs and newline characters,
4139
     * as well as multibyte whitespace such as the thin space and ideographic space.
4140
     *
4141
     * EXAMPLE: <code>
4142
     * s('   Ο     συγγραφέας  ')->stripWhitespace(); // 'Οσυγγραφέας'
4143
     * </code>
4144
     *
4145
     * @psalm-mutation-free
4146
     *
4147
     * @return static
4148
     */
4149
    public function stripWhitespace(): self
4150
    {
4151
        return static::create(
36✔
4152
            $this->utf8::strip_whitespace($this->str),
36✔
4153
            $this->encoding
36✔
4154
        );
36✔
4155
    }
4156

4157
    /**
4158
     * Remove css media-queries.
4159
     *
4160
     * EXAMPLE: <code>
4161
     * s('test @media (min-width:660px){ .des-cla #mv-tiles{width:480px} } test ')->stripeCssMediaQueries(); // 'test  test '
4162
     * </code>
4163
     *
4164
     * @psalm-mutation-free
4165
     *
4166
     * @return static
4167
     */
4168
    public function stripeCssMediaQueries(): self
4169
    {
4170
        return static::create(
2✔
4171
            $this->utf8::css_stripe_media_queries($this->str),
2✔
4172
            $this->encoding
2✔
4173
        );
2✔
4174
    }
4175

4176
    /**
4177
     * Remove empty html-tag.
4178
     *
4179
     * EXAMPLE: <code>
4180
     * s('foo<h1></h1>bar')->stripeEmptyHtmlTags(); // 'foobar'
4181
     * </code>
4182
     *
4183
     * @psalm-mutation-free
4184
     *
4185
     * @return static
4186
     */
4187
    public function stripeEmptyHtmlTags(): self
4188
    {
4189
        return static::create(
2✔
4190
            $this->utf8::html_stripe_empty_tags($this->str),
2✔
4191
            $this->encoding
2✔
4192
        );
2✔
4193
    }
4194

4195
    /**
4196
     * Convert the string to StudlyCase.
4197
     *
4198
     * EXAMPLE: <code>
4199
     * </code>
4200
     *
4201
     * @psalm-mutation-free
4202
     *
4203
     * @return static
4204
     */
4205
    public function studlyCase(): self
4206
    {
4207
        $words = \array_map(
6✔
4208
            static function (self $word) {
6✔
4209
                return $word->substr(0, 1)
6✔
4210
                    ->toUpperCase()
6✔
4211
                    ->appendStringy($word->substr(1));
6✔
4212
            },
6✔
4213
            $this->words('', true)
6✔
4214
        );
6✔
4215

4216
        return new static(\implode('', $words), $this->encoding);
6✔
4217
    }
4218

4219
    /**
4220
     * Returns the substring beginning at $start with the specified $length.
4221
     * It differs from the $this->utf8::substr() function in that providing a $length of
4222
     * null will return the rest of the string, rather than an empty string.
4223
     *
4224
     * EXAMPLE: <code>
4225
     * </code>
4226
     *
4227
     * @param int $start  <p>Position of the first character to use.</p>
4228
     * @param int $length [optional] <p>Maximum number of characters used. Default: null</p>
4229
     *
4230
     * @psalm-mutation-free
4231
     *
4232
     * @return static
4233
     *                <p>Object with its $str being the substring.</p>
4234
     */
4235
    public function substr(int $start, ?int $length = null): self
4236
    {
4237
        return static::create(
40✔
4238
            $this->utf8::substr(
40✔
4239
                $this->str,
40✔
4240
                $start,
40✔
4241
                $length,
40✔
4242
                $this->encoding
40✔
4243
            ),
40✔
4244
            $this->encoding
40✔
4245
        );
40✔
4246
    }
4247

4248
    /**
4249
     * Return part of the string.
4250
     * Alias for substr()
4251
     *
4252
     * EXAMPLE: <code>
4253
     * s('fòôbàř')->substring(2, 3); // 'ôbà'
4254
     * </code>
4255
     *
4256
     * @param int $start  <p>Starting position of the substring.</p>
4257
     * @param int $length [optional] <p>Length of substring.</p>
4258
     *
4259
     * @psalm-mutation-free
4260
     *
4261
     * @return static
4262
     */
4263
    public function substring(int $start, ?int $length = null): self
4264
    {
4265
        if ($length === null) {
3✔
4266
            return $this->substr($start);
2✔
4267
        }
4268

4269
        return $this->substr($start, $length);
3✔
4270
    }
4271

4272
    /**
4273
     * Gets the substring after (or before via "$beforeNeedle") the first occurrence of the "$needle".
4274
     * If no match is found returns new empty Stringy object.
4275
     *
4276
     * EXAMPLE: <code>
4277
     * </code>
4278
     *
4279
     * @param string $needle       <p>The string to look for.</p>
4280
     * @param bool   $beforeNeedle [optional] <p>Default: false</p>
4281
     *
4282
     * @psalm-mutation-free
4283
     *
4284
     * @return static
4285
     */
4286
    public function substringOf(string $needle, bool $beforeNeedle = false): self
4287
    {
4288
        return static::create(
4✔
4289
            $this->utf8::str_substr_first(
4✔
4290
                $this->str,
4✔
4291
                $needle,
4✔
4292
                $beforeNeedle,
4✔
4293
                $this->encoding
4✔
4294
            ),
4✔
4295
            $this->encoding
4✔
4296
        );
4✔
4297
    }
4298

4299
    /**
4300
     * Gets the substring after (or before via "$beforeNeedle") the first occurrence of the "$needle".
4301
     * If no match is found returns new empty Stringy object.
4302
     *
4303
     * EXAMPLE: <code>
4304
     * </code>
4305
     *
4306
     * @param string $needle       <p>The string to look for.</p>
4307
     * @param bool   $beforeNeedle [optional] <p>Default: false</p>
4308
     *
4309
     * @psalm-mutation-free
4310
     *
4311
     * @return static
4312
     */
4313
    public function substringOfIgnoreCase(string $needle, bool $beforeNeedle = false): self
4314
    {
4315
        return static::create(
4✔
4316
            $this->utf8::str_isubstr_first(
4✔
4317
                $this->str,
4✔
4318
                $needle,
4✔
4319
                $beforeNeedle,
4✔
4320
                $this->encoding
4✔
4321
            ),
4✔
4322
            $this->encoding
4✔
4323
        );
4✔
4324
    }
4325

4326
    /**
4327
     * Surrounds $str with the given substring.
4328
     *
4329
     * EXAMPLE: <code>
4330
     * s(' ͜ ')->surround('ʘ'); // 'ʘ ͜ ʘ'
4331
     * </code>
4332
     *
4333
     * @param string $substring <p>The substring to add to both sides.</P>
4334
     *
4335
     * @psalm-mutation-free
4336
     *
4337
     * @return static
4338
     *                <p>Object whose $str had the substring both prepended and appended.</p>
4339
     */
4340
    public function surround(string $substring): self
4341
    {
4342
        return static::create(
15✔
4343
            $substring . $this->str . $substring,
15✔
4344
            $this->encoding
15✔
4345
        );
15✔
4346
    }
4347

4348
    /**
4349
     * Returns a case swapped version of the string.
4350
     *
4351
     * EXAMPLE: <code>
4352
     * s('Ντανιλ')->swapCase(); // 'νΤΑΝΙΛ'
4353
     * </code>
4354
     *
4355
     * @psalm-mutation-free
4356
     *
4357
     * @return static
4358
     *                <p>Object whose $str has each character's case swapped.</P>
4359
     */
4360
    public function swapCase(): self
4361
    {
4362
        return static::create(
15✔
4363
            $this->utf8::swapCase($this->str, $this->encoding),
15✔
4364
            $this->encoding
15✔
4365
        );
15✔
4366
    }
4367

4368
    /**
4369
     * Returns a string with smart quotes, ellipsis characters, and dashes from
4370
     * Windows-1252 (commonly used in Word documents) replaced by their ASCII
4371
     * equivalents.
4372
     *
4373
     * EXAMPLE: <code>
4374
     * s('“I see…”')->tidy(); // '"I see..."'
4375
     * </code>
4376
     *
4377
     * @psalm-mutation-free
4378
     *
4379
     * @return static
4380
     *                <p>Object whose $str has those characters removed.</p>
4381
     */
4382
    public function tidy(): self
4383
    {
4384
        return static::create(
12✔
4385
            $this->ascii::normalize_msword($this->str),
12✔
4386
            $this->encoding
12✔
4387
        );
12✔
4388
    }
4389

4390
    /**
4391
     * Returns a trimmed string with the first letter of each word capitalized.
4392
     * Also accepts an array, $ignore, allowing you to list words not to be
4393
     * capitalized.
4394
     *
4395
     * EXAMPLE: <code>
4396
     * $ignore = ['at', 'by', 'for', 'in', 'of', 'on', 'out', 'to', 'the'];
4397
     * s('i like to watch television')->titleize($ignore); // 'I Like to Watch Television'
4398
     * </code>
4399
     *
4400
     * @param string[]|null $ignore            [optional] <p>An array of words not to capitalize or null.
4401
     *                                         Default: null</p>
4402
     * @param string|null   $word_define_chars [optional] <p>An string of chars that will be used as whitespace
4403
     *                                         separator === words.</p>
4404
     * @param string|null   $language          [optional] <p>Language of the source string.</p>
4405
     *
4406
     * @psalm-mutation-free
4407
     *
4408
     * @return static
4409
     *                <p>Object with a titleized $str.</p>
4410
     */
4411
    public function titleize(
4412
        ?array $ignore = null,
4413
        ?string $word_define_chars = null,
4414
        ?string $language = null
4415
    ): self {
4416
        return static::create(
23✔
4417
            $this->utf8::str_titleize(
23✔
4418
                $this->str,
23✔
4419
                $ignore,
23✔
4420
                $this->encoding,
23✔
4421
                false,
23✔
4422
                $language,
23✔
4423
                false,
23✔
4424
                true,
23✔
4425
                $word_define_chars
23✔
4426
            ),
23✔
4427
            $this->encoding
23✔
4428
        );
23✔
4429
    }
4430

4431
    /**
4432
     * Returns a trimmed string in proper title case: Also accepts an array, $ignore, allowing you to list words not to
4433
     * be capitalized.
4434
     *
4435
     * EXAMPLE: <code>
4436
     * </code>
4437
     *
4438
     * Adapted from John Gruber's script.
4439
     *
4440
     * @see https://gist.github.com/gruber/9f9e8650d68b13ce4d78
4441
     *
4442
     * @param string[] $ignore <p>An array of words not to capitalize.</p>
4443
     *
4444
     * @psalm-mutation-free
4445
     *
4446
     * @return static
4447
     *                <p>Object with a titleized $str</p>
4448
     */
4449
    public function titleizeForHumans(array $ignore = []): self
4450
    {
4451
        return static::create(
70✔
4452
            $this->utf8::str_titleize_for_humans(
70✔
4453
                $this->str,
70✔
4454
                $ignore,
70✔
4455
                $this->encoding
70✔
4456
            ),
70✔
4457
            $this->encoding
70✔
4458
        );
70✔
4459
    }
4460

4461
    /**
4462
     * Returns an ASCII version of the string. A set of non-ASCII characters are
4463
     * replaced with their closest ASCII counterparts, and the rest are removed
4464
     * by default. The language or locale of the source string can be supplied
4465
     * for language-specific transliteration in any of the following formats:
4466
     * en, en_GB, or en-GB. For example, passing "de" results in "äöü" mapping
4467
     * to "aeoeue" rather than "aou" as in other languages.
4468
     *
4469
     * EXAMPLE: <code>
4470
     * s('fòôbàř')->toAscii(); // 'foobar'
4471
     * </code>
4472
     *
4473
     * @param string $language          [optional] <p>Language of the source string.</p>
4474
     * @param bool   $removeUnsupported [optional] <p>Whether or not to remove the
4475
     *                                  unsupported characters.</p>
4476
     *
4477
     * @psalm-mutation-free
4478
     *
4479
     * @return static
4480
     *                <p>Object whose $str contains only ASCII characters.</p>
4481
     *
4482
     * @phpstan-param ASCII::*_LANGUAGE_CODE $language
4483
     */
4484
    public function toAscii(string $language = 'en', bool $removeUnsupported = true): self
4485
    {
4486
        return static::create(
23✔
4487
            $this->ascii::to_ascii(
23✔
4488
                $this->str,
23✔
4489
                $language,
23✔
4490
                $removeUnsupported
23✔
4491
            ),
23✔
4492
            $this->encoding
23✔
4493
        );
23✔
4494
    }
4495

4496
    /**
4497
     * Returns a boolean representation of the given logical string value.
4498
     * For example, <strong>'true', '1', 'on' and 'yes'</strong> will return true. <strong>'false', '0',
4499
     * 'off', and 'no'</strong> will return false. In all instances, case is ignored.
4500
     * For other numeric strings, their sign will determine the return value.
4501
     * In addition, blank strings consisting of only whitespace will return
4502
     * false. For all other strings, the return value is a result of a
4503
     * boolean cast.
4504
     *
4505
     * EXAMPLE: <code>
4506
     * s('OFF')->toBoolean(); // false
4507
     * </code>
4508
     *
4509
     * @psalm-mutation-free
4510
     *
4511
     * @return bool
4512
     *              <p>A boolean value for the string.</p>
4513
     */
4514
    public function toBoolean(): bool
4515
    {
4516
        /**
4517
         * @psalm-suppress ArgumentTypeCoercion -> maybe the string looks like an int ;)
4518
         * @phpstan-ignore-next-line
4519
         */
4520
        return $this->utf8::to_boolean($this->str);
45✔
4521
    }
4522

4523
    /**
4524
     * Converts all characters in the string to lowercase.
4525
     *
4526
     * EXAMPLE: <code>
4527
     * s('FÒÔBÀŘ')->toLowerCase(); // 'fòôbàř'
4528
     * </code>
4529
     *
4530
     * @param bool        $tryToKeepStringLength [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
4531
     * @param string|null $lang                  [optional] <p>Set the language for special cases: az, el, lt, tr</p>
4532
     *
4533
     * @psalm-mutation-free
4534
     *
4535
     * @return static
4536
     *                <p>Object with all characters of $str being lowercase.</p>
4537
     */
4538
    public function toLowerCase($tryToKeepStringLength = false, $lang = null): self
4539
    {
4540
        return static::create(
23✔
4541
            $this->utf8::strtolower(
23✔
4542
                $this->str,
23✔
4543
                $this->encoding,
23✔
4544
                false,
23✔
4545
                $lang,
23✔
4546
                $tryToKeepStringLength
23✔
4547
            ),
23✔
4548
            $this->encoding
23✔
4549
        );
23✔
4550
    }
4551

4552
    /**
4553
     * Converts each tab in the string to some number of spaces, as defined by
4554
     * $tabLength. By default, each tab is converted to 4 consecutive spaces.
4555
     *
4556
     * EXAMPLE: <code>
4557
     * s(' String speech = "Hi"')->toSpaces(); // '    String speech = "Hi"'
4558
     * </code>
4559
     *
4560
     * @param int $tabLength [optional] <p>Number of spaces to replace each tab with. Default: 4</p>
4561
     *
4562
     * @psalm-mutation-free
4563
     *
4564
     * @return static
4565
     *                <p>Object whose $str has had tabs switched to spaces.</p>
4566
     */
4567
    public function toSpaces(int $tabLength = 4): self
4568
    {
4569
        if ($tabLength === 4) {
18✔
4570
            $tab = '    ';
9✔
4571
        } elseif ($tabLength === 2) {
9✔
4572
            $tab = '  ';
3✔
4573
        } else {
4574
            $tab = \str_repeat(' ', $tabLength);
6✔
4575
        }
4576

4577
        return static::create(
18✔
4578
            \str_replace("\t", $tab, $this->str),
18✔
4579
            $this->encoding
18✔
4580
        );
18✔
4581
    }
4582

4583
    /**
4584
     * Return Stringy object as string, but you can also use (string) for automatically casting the object into a
4585
     * string.
4586
     *
4587
     * EXAMPLE: <code>
4588
     * s('fòôbàř')->toString(); // 'fòôbàř'
4589
     * </code>
4590
     *
4591
     * @psalm-mutation-free
4592
     *
4593
     * @return string
4594
     */
4595
    public function toString(): string
4596
    {
4597
        return (string) $this->str;
2,234✔
4598
    }
4599

4600
    /**
4601
     * Converts each occurrence of some consecutive number of spaces, as
4602
     * defined by $tabLength, to a tab. By default, each 4 consecutive spaces
4603
     * are converted to a tab.
4604
     *
4605
     * EXAMPLE: <code>
4606
     * s('    fòô    bàř')->toTabs(); // '   fòô bàř'
4607
     * </code>
4608
     *
4609
     * @param int $tabLength [optional] <p>Number of spaces to replace with a tab. Default: 4</p>
4610
     *
4611
     * @psalm-mutation-free
4612
     *
4613
     * @return static
4614
     *                <p>Object whose $str has had spaces switched to tabs.</p>
4615
     */
4616
    public function toTabs(int $tabLength = 4): self
4617
    {
4618
        if ($tabLength === 4) {
15✔
4619
            $tab = '    ';
9✔
4620
        } elseif ($tabLength === 2) {
6✔
4621
            $tab = '  ';
3✔
4622
        } else {
4623
            $tab = \str_repeat(' ', $tabLength);
3✔
4624
        }
4625

4626
        return static::create(
15✔
4627
            \str_replace($tab, "\t", $this->str),
15✔
4628
            $this->encoding
15✔
4629
        );
15✔
4630
    }
4631

4632
    /**
4633
     * Converts the first character of each word in the string to uppercase
4634
     * and all other chars to lowercase.
4635
     *
4636
     * EXAMPLE: <code>
4637
     * s('fòô bàř')->toTitleCase(); // 'Fòô Bàř'
4638
     * </code>
4639
     *
4640
     * @psalm-mutation-free
4641
     *
4642
     * @return static
4643
     *                <p>Object with all characters of $str being title-cased.</p>
4644
     */
4645
    public function toTitleCase(): self
4646
    {
4647
        return static::create(
15✔
4648
            $this->utf8::titlecase($this->str, $this->encoding),
15✔
4649
            $this->encoding
15✔
4650
        );
15✔
4651
    }
4652

4653
    /**
4654
     * Returns an ASCII version of the string. A set of non-ASCII characters are
4655
     * replaced with their closest ASCII counterparts, and the rest are removed
4656
     * unless instructed otherwise.
4657
     *
4658
     * EXAMPLE: <code>
4659
     * </code>
4660
     *
4661
     * @param bool   $strict  [optional] <p>Use "transliterator_transliterate()" from PHP-Intl | WARNING: bad
4662
     *                        performance | Default: false</p>
4663
     * @param string $unknown [optional] <p>Character use if character unknown. (default is ?)</p>
4664
     *
4665
     * @psalm-mutation-free
4666
     *
4667
     * @return static
4668
     *                <p>Object whose $str contains only ASCII characters.</p>
4669
     */
4670
    public function toTransliterate(bool $strict = false, string $unknown = '?'): self
4671
    {
4672
        return static::create(
34✔
4673
            $this->ascii::to_transliterate($this->str, $unknown, $strict),
34✔
4674
            $this->encoding
34✔
4675
        );
34✔
4676
    }
4677

4678
    /**
4679
     * Converts all characters in the string to uppercase.
4680
     *
4681
     * EXAMPLE: <code>
4682
     * s('fòôbàř')->toUpperCase(); // 'FÒÔBÀŘ'
4683
     * </code>
4684
     *
4685
     * @param bool        $tryToKeepStringLength [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
4686
     * @param string|null $lang                  [optional] <p>Set the language for special cases: az, el, lt, tr</p>
4687
     *
4688
     * @psalm-mutation-free
4689
     *
4690
     * @return static
4691
     *                <p>Object with all characters of $str being uppercase.</p>
4692
     */
4693
    public function toUpperCase($tryToKeepStringLength = false, $lang = null): self
4694
    {
4695
        return static::create(
26✔
4696
            $this->utf8::strtoupper($this->str, $this->encoding, false, $lang, $tryToKeepStringLength),
26✔
4697
            $this->encoding
26✔
4698
        );
26✔
4699
    }
4700

4701
    /**
4702
     * Returns a string with whitespace removed from the start and end of the
4703
     * string. Supports the removal of unicode whitespace. Accepts an optional
4704
     * string of characters to strip instead of the defaults.
4705
     *
4706
     * EXAMPLE: <code>
4707
     * s('  fòôbàř  ')->trim(); // 'fòôbàř'
4708
     * </code>
4709
     *
4710
     * @param string $chars [optional] <p>String of characters to strip. Default: null</p>
4711
     *
4712
     * @psalm-mutation-free
4713
     *
4714
     * @return static
4715
     *                <p>Object with a trimmed $str.</p>
4716
     */
4717
    public function trim(?string $chars = null): self
4718
    {
4719
        return static::create(
36✔
4720
            $this->utf8::trim($this->str, $chars),
36✔
4721
            $this->encoding
36✔
4722
        );
36✔
4723
    }
4724

4725
    /**
4726
     * Returns a string with whitespace removed from the start of the string.
4727
     * Supports the removal of unicode whitespace. Accepts an optional
4728
     * string of characters to strip instead of the defaults.
4729
     *
4730
     * EXAMPLE: <code>
4731
     * s('  fòôbàř  ')->trimLeft(); // 'fòôbàř  '
4732
     * </code>
4733
     *
4734
     * @param string $chars [optional] <p>Optional string of characters to strip. Default: null</p>
4735
     *
4736
     * @psalm-mutation-free
4737
     *
4738
     * @return static
4739
     *                <p>Object with a trimmed $str.</p>
4740
     */
4741
    public function trimLeft(?string $chars = null): self
4742
    {
4743
        return static::create(
39✔
4744
            $this->utf8::ltrim($this->str, $chars),
39✔
4745
            $this->encoding
39✔
4746
        );
39✔
4747
    }
4748

4749
    /**
4750
     * Returns a string with whitespace removed from the end of the string.
4751
     * Supports the removal of unicode whitespace. Accepts an optional
4752
     * string of characters to strip instead of the defaults.
4753
     *
4754
     * EXAMPLE: <code>
4755
     * s('  fòôbàř  ')->trimRight(); // '  fòôbàř'
4756
     * </code>
4757
     *
4758
     * @param string $chars [optional] <p>Optional string of characters to strip. Default: null</p>
4759
     *
4760
     * @psalm-mutation-free
4761
     *
4762
     * @return static
4763
     *                <p>Object with a trimmed $str.</p>
4764
     */
4765
    public function trimRight(?string $chars = null): self
4766
    {
4767
        return static::create(
39✔
4768
            $this->utf8::rtrim($this->str, $chars),
39✔
4769
            $this->encoding
39✔
4770
        );
39✔
4771
    }
4772

4773
    /**
4774
     * Truncates the string to a given length. If $substring is provided, and
4775
     * truncating occurs, the string is further truncated so that the substring
4776
     * may be appended without exceeding the desired length.
4777
     *
4778
     * EXAMPLE: <code>
4779
     * s('What are your plans today?')->truncate(19, '...'); // 'What are your pl...'
4780
     * </code>
4781
     *
4782
     * @param int    $length    <p>Desired length of the truncated string.</p>
4783
     * @param string $substring [optional] <p>The substring to append if it can fit. Default: ''</p>
4784
     *
4785
     * @psalm-mutation-free
4786
     *
4787
     * @return static
4788
     *                <p>Object with the resulting $str after truncating.</p>
4789
     */
4790
    public function truncate(int $length, string $substring = ''): self
4791
    {
4792
        return static::create(
66✔
4793
            $this->utf8::str_truncate($this->str, $length, $substring, $this->encoding),
66✔
4794
            $this->encoding
66✔
4795
        );
66✔
4796
    }
4797

4798
    /**
4799
     * Returns a lowercase and trimmed string separated by underscores.
4800
     * Underscores are inserted before uppercase characters (with the exception
4801
     * of the first character of the string), and in place of spaces as well as
4802
     * dashes.
4803
     *
4804
     * EXAMPLE: <code>
4805
     * s('TestUCase')->underscored(); // 'test_u_case'
4806
     * </code>
4807
     *
4808
     * @psalm-mutation-free
4809
     *
4810
     * @return static
4811
     *                <p>Object with an underscored $str.</p>
4812
     */
4813
    public function underscored(): self
4814
    {
4815
        return $this->delimit('_');
48✔
4816
    }
4817

4818
    /**
4819
     * Returns an UpperCamelCase version of the supplied string. It trims
4820
     * surrounding spaces, capitalizes letters following digits, spaces, dashes
4821
     * and underscores, and removes spaces, dashes, underscores.
4822
     *
4823
     * EXAMPLE: <code>
4824
     * s('Upper Camel-Case')->upperCamelize(); // 'UpperCamelCase'
4825
     * </code>
4826
     *
4827
     * @psalm-mutation-free
4828
     *
4829
     * @return static
4830
     *                <p>Object with $str in UpperCamelCase.</p>
4831
     */
4832
    public function upperCamelize(): self
4833
    {
4834
        $str = $this->str;
47✔
4835

4836
        if (\preg_match('/[-_\s]/', $str)) {
47✔
4837
            $str = $this->utf8::strtolower($str, $this->encoding);
41✔
4838
        }
4839

4840
        return static::create(
47✔
4841
            $this->utf8::str_upper_camelize($str, $this->encoding),
47✔
4842
            $this->encoding
47✔
4843
        );
47✔
4844
    }
4845

4846
    /**
4847
     * Converts the first character of the supplied string to upper case.
4848
     *
4849
     * EXAMPLE: <code>
4850
     * s('σ foo')->upperCaseFirst(); // 'Σ foo'
4851
     * </code>
4852
     *
4853
     * @psalm-mutation-free
4854
     *
4855
     * @return static
4856
     *                <p>Object with the first character of $str being upper case.</p>
4857
     */
4858
    public function upperCaseFirst(): self
4859
    {
4860
        return static::create($this->utf8::ucfirst($this->str, $this->encoding), $this->encoding);
18✔
4861
    }
4862

4863
    /**
4864
     * Simple url-decoding.
4865
     *
4866
     * e.g:
4867
     * 'test+test' => 'test test'
4868
     *
4869
     * EXAMPLE: <code>
4870
     * </code>
4871
     *
4872
     * @psalm-mutation-free
4873
     *
4874
     * @return static
4875
     */
4876
    public function urlDecode(): self
4877
    {
4878
        return static::create(\urldecode($this->str));
1✔
4879
    }
4880

4881
    /**
4882
     * Multi url-decoding + decode HTML entity + fix urlencoded-win1252-chars.
4883
     *
4884
     * e.g:
4885
     * 'test+test'                     => 'test test'
4886
     * 'D&#252;sseldorf'               => 'Düsseldorf'
4887
     * 'D%FCsseldorf'                  => 'Düsseldorf'
4888
     * 'D&#xFC;sseldorf'               => 'Düsseldorf'
4889
     * 'D%26%23xFC%3Bsseldorf'         => 'Düsseldorf'
4890
     * 'Düsseldorf'                   => 'Düsseldorf'
4891
     * 'D%C3%BCsseldorf'               => 'Düsseldorf'
4892
     * 'D%C3%83%C2%BCsseldorf'         => 'Düsseldorf'
4893
     * 'D%25C3%2583%25C2%25BCsseldorf' => 'Düsseldorf'
4894
     *
4895
     * EXAMPLE: <code>
4896
     * </code>
4897
     *
4898
     * @psalm-mutation-free
4899
     *
4900
     * @return static
4901
     */
4902
    public function urlDecodeMulti(): self
4903
    {
4904
        return static::create($this->utf8::urldecode($this->str));
1✔
4905
    }
4906

4907
    /**
4908
     * Simple url-decoding.
4909
     *
4910
     * e.g:
4911
     * 'test+test' => 'test+test
4912
     *
4913
     * EXAMPLE: <code>
4914
     * </code>
4915
     *
4916
     * @psalm-mutation-free
4917
     *
4918
     * @return static
4919
     */
4920
    public function urlDecodeRaw(): self
4921
    {
4922
        return static::create(\rawurldecode($this->str));
1✔
4923
    }
4924

4925
    /**
4926
     * Multi url-decoding + decode HTML entity + fix urlencoded-win1252-chars.
4927
     *
4928
     * e.g:
4929
     * 'test+test'                     => 'test+test'
4930
     * 'D&#252;sseldorf'               => 'Düsseldorf'
4931
     * 'D%FCsseldorf'                  => 'Düsseldorf'
4932
     * 'D&#xFC;sseldorf'               => 'Düsseldorf'
4933
     * 'D%26%23xFC%3Bsseldorf'         => 'Düsseldorf'
4934
     * 'Düsseldorf'                   => 'Düsseldorf'
4935
     * 'D%C3%BCsseldorf'               => 'Düsseldorf'
4936
     * 'D%C3%83%C2%BCsseldorf'         => 'Düsseldorf'
4937
     * 'D%25C3%2583%25C2%25BCsseldorf' => 'Düsseldorf'
4938
     *
4939
     * EXAMPLE: <code>
4940
     * </code>
4941
     *
4942
     * @psalm-mutation-free
4943
     *
4944
     * @return static
4945
     */
4946
    public function urlDecodeRawMulti(): self
4947
    {
4948
        return static::create($this->utf8::rawurldecode($this->str));
1✔
4949
    }
4950

4951
    /**
4952
     * Simple url-encoding.
4953
     *
4954
     * e.g:
4955
     * 'test test' => 'test+test'
4956
     *
4957
     * EXAMPLE: <code>
4958
     * </code>
4959
     *
4960
     * @psalm-mutation-free
4961
     *
4962
     * @return static
4963
     */
4964
    public function urlEncode(): self
4965
    {
4966
        return static::create(\urlencode($this->str));
1✔
4967
    }
4968

4969
    /**
4970
     * Simple url-encoding.
4971
     *
4972
     * e.g:
4973
     * 'test test' => 'test%20test'
4974
     *
4975
     * EXAMPLE: <code>
4976
     * </code>
4977
     *
4978
     * @psalm-mutation-free
4979
     *
4980
     * @return static
4981
     */
4982
    public function urlEncodeRaw(): self
4983
    {
4984
        return static::create(\rawurlencode($this->str));
1✔
4985
    }
4986

4987
    /**
4988
     * Converts the string into an URL slug. This includes replacing non-ASCII
4989
     * characters with their closest ASCII equivalents, removing remaining
4990
     * non-ASCII and non-alphanumeric characters, and replacing whitespace with
4991
     * $separator. The separator defaults to a single dash, and the string
4992
     * is also converted to lowercase.
4993
     *
4994
     * EXAMPLE: <code>
4995
     * s('Using strings like fòô bàř - 1$')->urlify(); // 'using-strings-like-foo-bar-1-dollar'
4996
     * </code>
4997
     *
4998
     * @param string                $separator    [optional] <p>The string used to replace whitespace. Default: '-'</p>
4999
     * @param string                $language     [optional] <p>The language for the url. Default: 'en'</p>
5000
     * @param array<string, string> $replacements [optional] <p>A map of replaceable strings.</p>
5001
     * @param bool                  $strToLower   [optional] <p>string to lower. Default: true</p>
5002
     *
5003
     * @psalm-mutation-free
5004
     *
5005
     * @return static
5006
     *                <p>Object whose $str has been converted to an URL slug.</p>
5007
     *
5008
     * @psalm-suppress ImpureMethodCall :/
5009
     */
5010
    public function urlify(
5011
        string $separator = '-',
5012
        string $language = 'en',
5013
        array $replacements = [],
5014
        bool $strToLower = true
5015
    ): self {
5016
        // init
5017
        $str = $this->str;
32✔
5018

5019
        foreach ($replacements as $from => $to) {
32✔
5020
            $str = \str_replace($from, $to, $str);
32✔
5021
        }
5022

5023
        return static::create(
32✔
5024
            URLify::slug(
32✔
5025
                $str,
32✔
5026
                $language,
32✔
5027
                $separator,
32✔
5028
                $strToLower
32✔
5029
            ),
32✔
5030
            $this->encoding
32✔
5031
        );
32✔
5032
    }
5033

5034
    /**
5035
     * Converts the string into an valid UTF-8 string.
5036
     *
5037
     * EXAMPLE: <code>
5038
     * s('Düsseldorf')->utf8ify(); // 'Düsseldorf'
5039
     * </code>
5040
     *
5041
     * @psalm-mutation-free
5042
     *
5043
     * @return static
5044
     */
5045
    public function utf8ify(): self
5046
    {
5047
        return static::create($this->utf8::cleanup($this->str), $this->encoding);
2✔
5048
    }
5049

5050
    /**
5051
     * Convert a string into an array of words.
5052
     *
5053
     * EXAMPLE: <code>
5054
     * </code>
5055
     *
5056
     * @param string   $char_list           [optional] <p>Additional chars for the definition of "words".</p>
5057
     * @param bool     $remove_empty_values [optional] <p>Remove empty values.</p>
5058
     * @param int|null $remove_short_values [optional] <p>The min. string length or null to disable</p>
5059
     *
5060
     * @psalm-mutation-free
5061
     *
5062
     * @return static[]
5063
     *
5064
     * @phpstan-return array<int,static>
5065
     */
5066
    public function words(
5067
        string $char_list = '',
5068
        bool $remove_empty_values = false,
5069
        ?int $remove_short_values = null
5070
    ): array {
5071
        if ($remove_short_values === null) {
14✔
5072
            $strings = $this->utf8::str_to_words(
14✔
5073
                $this->str,
14✔
5074
                $char_list,
14✔
5075
                $remove_empty_values
14✔
5076
            );
14✔
5077
        } else {
5078
            $strings = $this->utf8::str_to_words(
2✔
5079
                $this->str,
2✔
5080
                $char_list,
2✔
5081
                $remove_empty_values,
2✔
5082
                $remove_short_values
2✔
5083
            );
2✔
5084
        }
5085

5086
        /** @noinspection AlterInForeachInspection */
5087
        foreach ($strings as &$string) {
14✔
5088
            $string = static::create($string);
14✔
5089
        }
5090

5091
        /** @noinspection PhpSillyAssignmentInspection */
5092
        /** @var static[] $strings */
5093
        $strings = $strings;
14✔
5094

5095
        return $strings;
14✔
5096
    }
5097

5098
    /**
5099
     * Convert a string into an collection of words.
5100
     *
5101
     * EXAMPLE: <code>
5102
     * S::create('中文空白 oöäü#s')->wordsCollection('#', true)->toStrings(); // ['中文空白', 'oöäü#s']
5103
     * </code>
5104
     *
5105
     * @param string   $char_list           [optional] <p>Additional chars for the definition of "words".</p>
5106
     * @param bool     $remove_empty_values [optional] <p>Remove empty values.</p>
5107
     * @param int|null $remove_short_values [optional] <p>The min. string length or null to disable</p>
5108
     *
5109
     * @psalm-mutation-free
5110
     *
5111
     * @return CollectionStringy|static[]
5112
     *                                    <p>An collection of Stringy objects.</p>
5113
     *
5114
     * @phpstan-return CollectionStringy<int,static>
5115
     */
5116
    public function wordsCollection(
5117
        string $char_list = '',
5118
        bool $remove_empty_values = false,
5119
        ?int $remove_short_values = null
5120
    ): CollectionStringy {
5121
        /**
5122
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the collection class
5123
         */
5124
        return CollectionStringy::create(
2✔
5125
            $this->words(
2✔
5126
                $char_list,
2✔
5127
                $remove_empty_values,
2✔
5128
                $remove_short_values
2✔
5129
            )
2✔
5130
        );
2✔
5131
    }
5132

5133
    /**
5134
     * Surrounds $str with the given substring.
5135
     *
5136
     * EXAMPLE: <code>
5137
     * </code>
5138
     *
5139
     * @param string $substring <p>The substring to add to both sides.</P>
5140
     *
5141
     * @psalm-mutation-free
5142
     *
5143
     * @return static
5144
     *                <p>Object whose $str had the substring both prepended and appended.</p>
5145
     */
5146
    public function wrap(string $substring): self
5147
    {
5148
        return $this->surround($substring);
10✔
5149
    }
5150

5151
    /**
5152
     * Returns the replacements for the toAscii() method.
5153
     *
5154
     * @psalm-mutation-free
5155
     *
5156
     * @return array<string, array<int, string>>
5157
     *                                           <p>An array of replacements.</p>
5158
     *
5159
     * @deprecated   this is only here for backward-compatibly reasons
5160
     */
5161
    protected function charsArray(): array
5162
    {
5163
        return $this->ascii::charsArrayWithMultiLanguageValues();
1✔
5164
    }
5165

5166
    /**
5167
     * Returns true if $str matches the supplied pattern, false otherwise.
5168
     *
5169
     * @param string $pattern <p>Regex pattern to match against.</p>
5170
     *
5171
     * @psalm-mutation-free
5172
     *
5173
     * @return bool
5174
     *              <p>Whether or not $str matches the pattern.</p>
5175
     */
5176
    protected function matchesPattern(string $pattern): bool
5177
    {
5178
        return $this->utf8::str_matches_pattern($this->str, $pattern);
24✔
5179
    }
5180
}
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