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

voku / Stringy / 25994924888

17 May 2026 03:27PM UTC coverage: 97.959% (+0.07%) from 97.891%
25994924888

Pull #55

github

web-flow
Merge e6c2254bc into 3357517e5
Pull Request #55: Restore Stringy fast paths and remove mutation-only regressions

38 of 39 new or added lines in 1 file covered. (97.44%)

1 existing line in 1 file now uncovered.

1008 of 1029 relevant lines covered (97.96%)

70.91 hits per line

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

97.93
/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
    use JsonSerializableReturnTypeTrait;
37

38
    /**
39
     * An instance's string.
40
     *
41
     * @var string
42
     */
43
    protected $str;
44

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

53
    /**
54
     * @var UTF8
55
     */
56
    private $utf8;
57

58
    /**
59
     * @var ASCII
60
     */
61
    private $ascii;
62

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

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

98
        $this->str = (string) $str;
3,706✔
99

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

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

112
        if ($encoding !== 'UTF-8') {
3,706✔
113
            $this->encoding = $this->utf8::normalize_encoding($encoding, 'UTF-8');
2,522✔
114
        } else {
115
            $this->encoding = $encoding;
2,766✔
116
        }
117
    }
118

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

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

155
        unset($strArray[0]);
4✔
156

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

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

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

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

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

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

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

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

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

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

329
        return $this->append($str);
6✔
330
    }
331

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

358
        return static::create($this->str . $suffixStr, $this->encoding);
8✔
359
    }
360

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

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

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

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

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

466
    /**
467
     * Return part of the string occurring before a specific string.
468
     *
469
     * EXAMPLE: <code>
470
     * </code>
471
     *
472
     * @param string $string <p>The delimiting string.</p>
473
     *
474
     * @psalm-mutation-free
475
     *
476
     * @return static
477
     */
478
    public function before(string $string): self
479
    {
480
        return $this->beforeFirst($string);
5✔
481
    }
482

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

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

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

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

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

612
        return static::create($str, $this->encoding);
48✔
613
    }
614

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

636
        return static::create(
2✔
637
            $str,
2✔
638
            $this->encoding
2✔
639
        );
2✔
640
    }
641

642
    /**
643
     * Returns a camelCase version of the string. Trims surrounding spaces,
644
     * capitalizes letters following digits, spaces, dashes and underscores,
645
     * and removes spaces, dashes, as well as underscores.
646
     *
647
     * EXAMPLE: <code>
648
     * s('Camel-Case')->camelize(); // 'camelCase'
649
     * </code>
650
     *
651
     * @psalm-mutation-free
652
     *
653
     * @return static
654
     *                <p>Object with $str in camelCase.</p>
655
     */
656
    public function camelize(): self
657
    {
658
        return static::create(
67✔
659
            $this->utf8::str_camelize($this->str, $this->encoding),
67✔
660
            $this->encoding
67✔
661
        );
67✔
662
    }
663

664
    /**
665
     * Returns the string with the first letter of each word capitalized,
666
     * except for when the word is a name which shouldn't be capitalized.
667
     *
668
     * EXAMPLE: <code>
669
     * s('jaap de hoop scheffer')->capitalizePersonName(); // 'Jaap de Hoop Scheffer'
670
     * </code>
671
     *
672
     * @psalm-mutation-free
673
     *
674
     * @return static
675
     *                <p>Object with $str capitalized.</p>
676
     */
677
    public function capitalizePersonalName(): self
678
    {
679
        return static::create(
78✔
680
            $this->utf8::str_capitalize_name($this->str),
78✔
681
            $this->encoding
78✔
682
        );
78✔
683
    }
684

685
    /**
686
     * Returns an array consisting of the characters in the string.
687
     *
688
     * EXAMPLE: <code>
689
     * s('fòôbàř')->chars(); // ['f', 'ò', 'ô', 'b', 'à', 'ř']
690
     * </code>
691
     *
692
     * @psalm-mutation-free
693
     *
694
     * @return string[]
695
     *                  <p>An array of string chars.</p>
696
     */
697
    public function chars(): array
698
    {
699
        /** @var string[] */
700
        return $this->utf8::str_split($this->str);
14✔
701
    }
702

703
    /**
704
     * Splits the string into chunks of Stringy objects.
705
     *
706
     * EXAMPLE: <code>
707
     * s('foobar')->chunk(3); // ['foo', 'bar']
708
     * </code>
709
     *
710
     * @param int $length [optional] <p>Max character length of each array element.</p>
711
     *
712
     * @psalm-mutation-free
713
     *
714
     * @return static[]
715
     *                  <p>An array of Stringy objects.</p>
716
     *
717
     * @phpstan-return array<int,static>
718
     */
719
    public function chunk(int $length = 1): array
720
    {
721
        if ($length < 1) {
15✔
722
            throw new \InvalidArgumentException('The chunk length must be greater than zero.');
×
723
        }
724

725
        $chunks = $this->utf8::str_split($this->str, $length);
15✔
726

727
        foreach ($chunks as &$value) {
15✔
728
            $value = static::create($value, $this->encoding);
13✔
729
        }
730

731
        /** @noinspection PhpSillyAssignmentInspection */
732
        /** @var static[] $chunks */
733
        $chunks = $chunks;
15✔
734

735
        return $chunks;
15✔
736
    }
737

738
    /**
739
     * Splits the string into chunks of Stringy objects collection.
740
     *
741
     * EXAMPLE: <code>
742
     * </code>
743
     *
744
     * @param int $length [optional] <p>Max character length of each array element.</p>
745
     *
746
     * @psalm-mutation-free
747
     *
748
     * @return CollectionStringy|static[]
749
     *                                    <p>An collection of Stringy objects.</p>
750
     *
751
     * @phpstan-return CollectionStringy<int,static>
752
     */
753
    public function chunkCollection(int $length = 1): CollectionStringy
754
    {
755
        /**
756
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the collection class
757
         */
758
        return CollectionStringy::create(
8✔
759
            $this->chunk($length)
8✔
760
        );
8✔
761
    }
762

763
    /**
764
     * Trims the string and replaces consecutive whitespace characters with a
765
     * single space. This includes tabs and newline characters, as well as
766
     * multibyte whitespace such as the thin space and ideographic space.
767
     *
768
     * EXAMPLE: <code>
769
     * s('   Ο     συγγραφέας  ')->collapseWhitespace(); // 'Ο συγγραφέας'
770
     * </code>
771
     *
772
     * @psalm-mutation-free
773
     *
774
     * @return static
775
     *                <p>Object with a trimmed $str and condensed whitespace.</p>
776
     */
777
    public function collapseWhitespace(): self
778
    {
779
        return static::create(
39✔
780
            $this->utf8::collapse_whitespace($this->str),
39✔
781
            $this->encoding
39✔
782
        );
39✔
783
    }
784

785
    /**
786
     * Returns true if the string contains $needle, false otherwise. By default
787
     * the comparison is case-sensitive, but can be made insensitive by setting
788
     * $caseSensitive to false.
789
     *
790
     * EXAMPLE: <code>
791
     * s('Ο συγγραφέας είπε')->contains('συγγραφέας'); // true
792
     * </code>
793
     *
794
     * @param string $needle        <p>Substring to look for.</p>
795
     * @param bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
796
     *
797
     * @psalm-mutation-free
798
     *
799
     * @return bool
800
     *              <p>Whether or not $str contains $needle.</p>
801
     */
802
    public function contains(string $needle, bool $caseSensitive = true): bool
803
    {
804
        return $this->utf8::str_contains(
64✔
805
            $this->str,
64✔
806
            $needle,
64✔
807
            $caseSensitive
64✔
808
        );
64✔
809
    }
810

811
    /**
812
     * Returns true if the string contains all $needles, false otherwise. By
813
     * default the comparison is case-sensitive, but can be made insensitive by
814
     * setting $caseSensitive to false.
815
     *
816
     * EXAMPLE: <code>
817
     * s('foo & bar')->containsAll(['foo', 'bar']); // true
818
     * </code>
819
     *
820
     * @param string[] $needles       <p>SubStrings to look for.</p>
821
     * @param bool     $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
822
     *
823
     * @psalm-mutation-free
824
     *
825
     * @return bool
826
     *              <p>Whether or not $str contains $needle.</p>
827
     */
828
    public function containsAll(array $needles, bool $caseSensitive = true): bool
829
    {
830
        return $this->utf8::str_contains_all(
132✔
831
            $this->str,
132✔
832
            $needles,
132✔
833
            $caseSensitive
132✔
834
        );
132✔
835
    }
836

837
    /**
838
     * Returns true if the string contains any $needles, false otherwise. By
839
     * default the comparison is case-sensitive, but can be made insensitive by
840
     * setting $caseSensitive to false.
841
     *
842
     * EXAMPLE: <code>
843
     * s('str contains foo')->containsAny(['foo', 'bar']); // true
844
     * </code>
845
     *
846
     * @param string[] $needles       <p>SubStrings to look for.</p>
847
     * @param bool     $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
848
     *
849
     * @psalm-mutation-free
850
     *
851
     * @return bool
852
     *              <p>Whether or not $str contains $needle.</p>
853
     */
854
    public function containsAny(array $needles, bool $caseSensitive = true): bool
855
    {
856
        return $this->utf8::str_contains_any(
130✔
857
            $this->str,
130✔
858
            $needles,
130✔
859
            $caseSensitive
130✔
860
        );
130✔
861
    }
862

863
    /**
864
     * Checks if string starts with "BOM" (Byte Order Mark Character) character.
865
     *
866
     * EXAMPLE: <code>s("\xef\xbb\xbf foobar")->containsBom(); // true</code>
867
     *
868
     * @psalm-mutation-free
869
     *
870
     * @return bool
871
     *              <strong>true</strong> if the string has BOM at the start,<br>
872
     *              <strong>false</strong> otherwise
873
     */
874
    public function containsBom(): bool
875
    {
876
        return $this->utf8::string_has_bom($this->str);
×
877
    }
878

879
    /**
880
     * Returns the length of the string, implementing the countable interface.
881
     *
882
     * EXAMPLE: <code>
883
     * </code>
884
     *
885
     * @psalm-mutation-free
886
     *
887
     * @return int
888
     *             <p>The number of characters in the string, given the encoding.</p>
889
     */
890
    public function count(): int
891
    {
892
        return $this->length();
3✔
893
    }
894

895
    /**
896
     * Returns the number of occurrences of $substring in the given string.
897
     * By default, the comparison is case-sensitive, but can be made insensitive
898
     * by setting $caseSensitive to false.
899
     *
900
     * EXAMPLE: <code>
901
     * s('Ο συγγραφέας είπε')->countSubstr('α'); // 2
902
     * </code>
903
     *
904
     * @param string $substring     <p>The substring to search for.</p>
905
     * @param bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
906
     *
907
     * @psalm-mutation-free
908
     *
909
     * @return int
910
     */
911
    public function countSubstr(string $substring, bool $caseSensitive = true): int
912
    {
913
        return $this->utf8::substr_count_simple(
46✔
914
            $this->str,
46✔
915
            $substring,
46✔
916
            $caseSensitive,
46✔
917
            $this->encoding
46✔
918
        );
46✔
919
    }
920

921
    /**
922
     * Calculates the crc32 polynomial of a string.
923
     *
924
     * EXAMPLE: <code>
925
     * </code>
926
     *
927
     * @psalm-mutation-free
928
     *
929
     * @return int
930
     */
931
    public function crc32(): int
932
    {
933
        return \crc32($this->str);
2✔
934
    }
935

936
    /**
937
     * Creates a Stringy object and assigns both str and encoding properties
938
     * the supplied values. $str is cast to a string prior to assignment, and if
939
     * $encoding is not specified, it defaults to mb_internal_encoding(). It
940
     * then returns the initialized object. Throws an InvalidArgumentException
941
     * if the first argument is an array or object without a __toString method.
942
     *
943
     * @param mixed  $str      [optional] <p>Value to modify, after being cast to string. Default: ''</p>
944
     * @param string $encoding [optional] <p>The character encoding. Fallback: 'UTF-8'</p>
945
     *
946
     * @throws \InvalidArgumentException
947
     *                                   <p>if an array or object without a
948
     *                                   __toString method is passed as the first argument</p>
949
     *
950
     * @return static
951
     *                <p>A Stringy object.</p>
952
     */
953
    public static function create($str = '', ?string $encoding = null): self
954
    {
955
        return new static($str, $encoding);
3,641✔
956
    }
957

958
    /**
959
     * One-way string encryption (hashing).
960
     *
961
     * Hash the string using the standard Unix DES-based algorithm or an
962
     * alternative algorithm that may be available on the system.
963
     *
964
     * PS: if you need encrypt / decrypt, please use ```static::encrypt($password)```
965
     *     and ```static::decrypt($password)```
966
     *
967
     * EXAMPLE: <code>
968
     * </code>
969
     *
970
     * @param string $salt <p>A salt string to base the hashing on.</p>
971
     *
972
     * @psalm-mutation-free
973
     *
974
     * @return static
975
     */
976
    public function crypt(string $salt): self
977
    {
978
        return new static(
3✔
979
            \crypt(
3✔
980
                $this->str,
3✔
981
                $salt
3✔
982
            ),
3✔
983
            $this->encoding
3✔
984
        );
3✔
985
    }
986

987
    /**
988
     * Returns a lowercase and trimmed string separated by dashes. Dashes are
989
     * inserted before uppercase characters (with the exception of the first
990
     * character of the string), and in place of spaces as well as underscores.
991
     *
992
     * EXAMPLE: <code>
993
     * s('fooBar')->dasherize(); // 'foo-bar'
994
     * </code>
995
     *
996
     * @psalm-mutation-free
997
     *
998
     * @return static
999
     *                <p>Object with a dasherized $str</p>
1000
     */
1001
    public function dasherize(): self
1002
    {
1003
        return static::create(
57✔
1004
            $this->utf8::str_dasherize($this->str),
57✔
1005
            $this->encoding
57✔
1006
        );
57✔
1007
    }
1008

1009
    /**
1010
     * Decrypt the string.
1011
     *
1012
     * EXAMPLE: <code>
1013
     * </code>
1014
     *
1015
     * @param string $password The key for decrypting
1016
     *
1017
     * @psalm-mutation-free
1018
     *
1019
     * @return static
1020
     */
1021
    public function decrypt(string $password): self
1022
    {
1023
        /**
1024
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to vendor stuff
1025
         */
1026
        return new static(
5✔
1027
            Crypto::decryptWithPassword($this->str, $password),
5✔
1028
            $this->encoding
5✔
1029
        );
5✔
1030
    }
1031

1032
    /**
1033
     * Returns a lowercase and trimmed string separated by the given delimiter.
1034
     * Delimiters are inserted before uppercase characters (with the exception
1035
     * of the first character of the string), and in place of spaces, dashes,
1036
     * and underscores. Alpha delimiters are not converted to lowercase.
1037
     *
1038
     * EXAMPLE: <code>
1039
     * s('fooBar')->delimit('::'); // 'foo::bar'
1040
     * </code>
1041
     *
1042
     * @param string $delimiter <p>Sequence used to separate parts of the string.</p>
1043
     *
1044
     * @psalm-mutation-free
1045
     *
1046
     * @return static
1047
     *                <p>Object with a delimited $str.</p>
1048
     */
1049
    public function delimit(string $delimiter): self
1050
    {
1051
        return static::create(
90✔
1052
            $this->utf8::str_delimit($this->str, $delimiter),
90✔
1053
            $this->encoding
90✔
1054
        );
90✔
1055
    }
1056

1057
    /**
1058
     * Encode the given string into the given $encoding + set the internal character encoding.
1059
     *
1060
     * EXAMPLE: <code>
1061
     * </code>
1062
     *
1063
     * @param string $new_encoding         <p>The desired character encoding.</p>
1064
     * @param bool   $auto_detect_encoding [optional] <p>Auto-detect the current string-encoding</p>
1065
     *
1066
     * @psalm-mutation-free
1067
     *
1068
     * @return static
1069
     */
1070
    public function encode(string $new_encoding, bool $auto_detect_encoding = false): self
1071
    {
1072
        $str = $this->utf8::encode(
3✔
1073
            $new_encoding,
3✔
1074
            $this->str,
3✔
1075
            $auto_detect_encoding,
3✔
1076
            $auto_detect_encoding ? '' : $this->encoding
3✔
1077
        );
3✔
1078

1079
        return new static($str, $new_encoding);
3✔
1080
    }
1081

1082
    /**
1083
     * Encrypt the string.
1084
     *
1085
     * EXAMPLE: <code>
1086
     * </code>
1087
     *
1088
     * @param string $password <p>The key for encrypting</p>
1089
     *
1090
     * @psalm-mutation-free
1091
     *
1092
     * @return static
1093
     */
1094
    public function encrypt(string $password): self
1095
    {
1096
        /**
1097
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to vendor stuff
1098
         */
1099
        return new static(
4✔
1100
            Crypto::encryptWithPassword($this->str, $password),
4✔
1101
            $this->encoding
4✔
1102
        );
4✔
1103
    }
1104

1105
    /**
1106
     * Returns true if the string ends with $substring, false otherwise. By
1107
     * default, the comparison is case-sensitive, but can be made insensitive
1108
     * by setting $caseSensitive to false.
1109
     *
1110
     * EXAMPLE: <code>
1111
     * s('fòôbàř')->endsWith('bàř', true); // true
1112
     * </code>
1113
     *
1114
     * @param string $substring     <p>The substring to look for.</p>
1115
     * @param bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
1116
     *
1117
     * @psalm-mutation-free
1118
     *
1119
     * @return bool
1120
     *              <p>Whether or not $str ends with $substring.</p>
1121
     */
1122
    public function endsWith(string $substring, bool $caseSensitive = true): bool
1123
    {
1124
        if ($caseSensitive) {
97✔
1125
            return $this->utf8::str_ends_with($this->str, $substring);
53✔
1126
        }
1127

1128
        return $this->utf8::str_iends_with($this->str, $substring);
44✔
1129
    }
1130

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

1154
        return $this->utf8::str_iends_with_any($this->str, $substrings);
12✔
1155
    }
1156

1157
    /**
1158
     * Ensures that the string begins with $substring. If it doesn't, it's
1159
     * prepended.
1160
     *
1161
     * EXAMPLE: <code>
1162
     * s('foobar')->ensureLeft('http://'); // 'http://foobar'
1163
     * </code>
1164
     *
1165
     * @param string $substring <p>The substring to add if not present.</p>
1166
     *
1167
     * @psalm-mutation-free
1168
     *
1169
     * @return static
1170
     *                <p>Object with its $str prefixed by the $substring.</p>
1171
     */
1172
    public function ensureLeft(string $substring): self
1173
    {
1174
        return static::create(
30✔
1175
            $this->utf8::str_ensure_left($this->str, $substring),
30✔
1176
            $this->encoding
30✔
1177
        );
30✔
1178
    }
1179

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

1202
    /**
1203
     * Create a escape html version of the string via "htmlspecialchars()".
1204
     *
1205
     * EXAMPLE: <code>
1206
     * s('<∂∆ onerror="alert(xss)">')->escape(); // '&lt;∂∆ onerror=&quot;alert(xss)&quot;&gt;'
1207
     * </code>
1208
     *
1209
     * @psalm-mutation-free
1210
     *
1211
     * @return static
1212
     */
1213
    public function escape(): self
1214
    {
1215
        return static::create(
12✔
1216
            $this->utf8::htmlspecialchars(
12✔
1217
                $this->str,
12✔
1218
                \ENT_QUOTES | \ENT_SUBSTITUTE,
12✔
1219
                $this->encoding
12✔
1220
            ),
12✔
1221
            $this->encoding
12✔
1222
        );
12✔
1223
    }
1224

1225
    /**
1226
     * Split a string by a string.
1227
     *
1228
     * EXAMPLE: <code>
1229
     * </code>
1230
     *
1231
     * @param string $delimiter <p>The boundary string</p>
1232
     * @param int    $limit     [optional] <p>The maximum number of elements in the exploded
1233
     *                          collection.</p>
1234
     *
1235
     *   - If limit is set and positive, the returned collection will contain a maximum of limit elements with the last
1236
     *   element containing the rest of string.
1237
     *   - If the limit parameter is negative, all components except the last -limit are returned.
1238
     *   - If the limit parameter is zero, then this is treated as 1
1239
     *
1240
     * @psalm-mutation-free
1241
     *
1242
     * @return array<int,static>
1243
     */
1244
    public function explode(string $delimiter, int $limit = \PHP_INT_MAX): array
1245
    {
1246
        if ($this->str === '') {
3✔
1247
            return [];
×
1248
        }
1249

1250
        /** @phpstan-ignore-next-line - FP -> non-empty-string is already checked */
1251
        $strings = \explode($delimiter, $this->str, $limit);
3✔
1252
        /** @phpstan-ignore-next-line - if "$delimiter" is an empty string, then "explode()" will return "false" */
1253
        if ($strings === false) {
3✔
1254
            $strings = [];
×
1255
        }
1256

1257
        return \array_map(
3✔
1258
            function ($str) {
3✔
1259
                return new static($str, $this->encoding);
3✔
1260
            },
3✔
1261
            $strings
3✔
1262
        );
3✔
1263
    }
1264

1265
    /**
1266
     * Split a string by a string.
1267
     *
1268
     * EXAMPLE: <code>
1269
     * </code>
1270
     *
1271
     * @param string $delimiter <p>The boundary string</p>
1272
     * @param int    $limit     [optional] <p>The maximum number of elements in the exploded
1273
     *                          collection.</p>
1274
     *
1275
     *   - If limit is set and positive, the returned collection will contain a maximum of limit elements with the last
1276
     *   element containing the rest of string.
1277
     *   - If the limit parameter is negative, all components except the last -limit are returned.
1278
     *   - If the limit parameter is zero, then this is treated as 1
1279
     *
1280
     * @psalm-mutation-free
1281
     *
1282
     * @return CollectionStringy|static[]
1283
     *                                    <p>An collection of Stringy objects.</p>
1284
     *
1285
     * @phpstan-return CollectionStringy<int,static>
1286
     */
1287
    public function explodeCollection(string $delimiter, int $limit = \PHP_INT_MAX): CollectionStringy
1288
    {
1289
        /**
1290
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the collection class
1291
         */
1292
        return CollectionStringy::create(
1✔
1293
            $this->explode($delimiter, $limit)
1✔
1294
        );
1✔
1295
    }
1296

1297
    /**
1298
     * Create an extract from a sentence, so if the search-string was found, it try to centered in the output.
1299
     *
1300
     * EXAMPLE: <code>
1301
     * $sentence = 'This is only a Fork of Stringy, take a look at the new features.';
1302
     * s($sentence)->extractText('Stringy'); // '...Fork of Stringy...'
1303
     * </code>
1304
     *
1305
     * @param string   $search
1306
     * @param int|null $length                 [optional] <p>Default: null === text->length / 2</p>
1307
     * @param string   $replacerForSkippedText [optional] <p>Default: …</p>
1308
     *
1309
     * @psalm-mutation-free
1310
     *
1311
     * @return static
1312
     */
1313
    public function extractText(string $search = '', ?int $length = null, string $replacerForSkippedText = '…'): self
1314
    {
1315
        return static::create(
2✔
1316
            $this->utf8::extract_text(
2✔
1317
                $this->str,
2✔
1318
                $search,
2✔
1319
                $length,
2✔
1320
                $replacerForSkippedText,
2✔
1321
                $this->encoding
2✔
1322
            ),
2✔
1323
            $this->encoding
2✔
1324
        );
2✔
1325
    }
1326

1327
    /**
1328
     * Returns the first $n characters of the string.
1329
     *
1330
     * EXAMPLE: <code>
1331
     * s('fòôbàř')->first(3); // 'fòô'
1332
     * </code>
1333
     *
1334
     * @param int $n <p>Number of characters to retrieve from the start.</p>
1335
     *
1336
     * @psalm-mutation-free
1337
     *
1338
     * @return static
1339
     *                <p>Object with its $str being the first $n chars.</p>
1340
     */
1341
    public function first(int $n): self
1342
    {
1343
        if ($n <= 0) {
37✔
1344
            return static::create('', $this->encoding);
12✔
1345
        }
1346

1347
        return static::create(
25✔
1348
            $this->utf8::first_char($this->str, $n, $this->encoding),
25✔
1349
            $this->encoding
25✔
1350
        );
25✔
1351
    }
1352

1353
    /**
1354
     * Return a formatted string via sprintf + named parameters via array syntax.
1355
     *
1356
     * <p>
1357
     * <br>
1358
     * It will use "sprintf()" so you can use e.g.:
1359
     * <br>
1360
     * <br><pre>s('There are %d monkeys in the %s')->format(5, 'tree');</pre>
1361
     * <br>
1362
     * <br><pre>s('There are %2$d monkeys in the %1$s')->format('tree', 5);</pre>
1363
     * <br>
1364
     * <br>
1365
     * But you can also use named parameter via array syntax e.g.:
1366
     * <br>
1367
     * <br><pre>s('There are %:count monkeys in the %:location')->format(['count' => 5, 'location' => 'tree');</pre>
1368
     * </p>
1369
     *
1370
     * EXAMPLE: <code>
1371
     * $input = 'one: %2$d, %1$s: 2, %:text_three: %3$d';
1372
     * s($input)->format(['text_three' => '%4$s'], 'two', 1, 3, 'three'); // 'One: 1, two: 2, three: 3'
1373
     * </code>
1374
     *
1375
     * @param mixed ...$args [optional]
1376
     *
1377
     * @psalm-mutation-free
1378
     *
1379
     * @return static
1380
     *                <p>A Stringy object produced according to the formatting string
1381
     *                format.</p>
1382
     */
1383
    public function format(...$args): self
1384
    {
1385
        // init
1386
        $str = $this->str;
13✔
1387

1388
        if (\strpos($this->str, '%:') !== false) {
13✔
1389
            $namedArgs = [];
11✔
1390
            /** @noinspection AlterInForeachInspection */
1391
            foreach ($args as $key => &$arg) {
11✔
1392
                if (!\is_array($arg)) {
11✔
1393
                    continue;
4✔
1394
                }
1395

1396
                foreach ($arg as $name => $param) {
11✔
1397
                    $name = (string) $name;
11✔
1398

1399
                    if (\strpos($name, '%:') === 0) {
11✔
NEW
1400
                        $name = (string) \substr($name, 2);
×
1401
                    }
1402

1403
                    if (\array_key_exists($name, $namedArgs)) {
11✔
UNCOV
1404
                        continue;
×
1405
                    }
1406

1407
                    $namedArgs[$name] = (string) $param;
11✔
1408
                }
1409

1410
                unset($args[$key]);
11✔
1411
            }
1412

1413
            if ($namedArgs !== []) {
11✔
1414
                $usedNames = [];
11✔
1415
                $formattedStr = \preg_replace_callback(
11✔
1416
                    '/%:([0-9A-Za-z_]+)/',
11✔
1417
                    static function (array $matches) use (&$namedArgs, &$usedNames): string {
11✔
1418
                        $name = $matches[1];
11✔
1419
                        if (($usedNames[$name] ?? false) === true || !\array_key_exists($name, $namedArgs)) {
11✔
1420
                            return $matches[0];
6✔
1421
                        }
1422

1423
                        $usedNames[$name] = true;
11✔
1424

1425
                        return $namedArgs[$name];
11✔
1426
                    },
11✔
1427
                    $str
11✔
1428
                );
11✔
1429
                if ($formattedStr !== null) {
11✔
1430
                    $str = $formattedStr;
11✔
1431
                }
1432
            }
1433
        }
1434

1435
        $str = \str_replace('%:', '%%:', $str);
13✔
1436

1437
        return static::create(
13✔
1438
            \sprintf($str, ...$args),
13✔
1439
            $this->encoding
13✔
1440
        );
13✔
1441
    }
1442

1443
    /**
1444
     * Returns the encoding used by the Stringy object.
1445
     *
1446
     * EXAMPLE: <code>
1447
     * s('fòôbàř', 'UTF-8')->getEncoding(); // 'UTF-8'
1448
     * </code>
1449
     *
1450
     * @psalm-mutation-free
1451
     *
1452
     * @return string
1453
     *                <p>The current value of the $encoding property.</p>
1454
     */
1455
    public function getEncoding(): string
1456
    {
1457
        return $this->encoding;
25✔
1458
    }
1459

1460
    /**
1461
     * Returns a new ArrayIterator, thus implementing the IteratorAggregate
1462
     * interface. The ArrayIterator's constructor is passed an array of chars
1463
     * in the multibyte string. This enables the use of foreach with instances
1464
     * of Stringy\Stringy.
1465
     *
1466
     * EXAMPLE: <code>
1467
     * </code>
1468
     *
1469
     * @psalm-mutation-free
1470
     *
1471
     * @return \ArrayIterator
1472
     *                        <p>An iterator for the characters in the string.</p>
1473
     *
1474
     * @phpstan-return \ArrayIterator<array-key,string>
1475
     */
1476
    public function getIterator(): \ArrayIterator
1477
    {
1478
        return new \ArrayIterator($this->chars());
3✔
1479
    }
1480

1481
    /**
1482
     * Wrap the string after an exact number of characters.
1483
     *
1484
     * EXAMPLE: <code>
1485
     * </code>
1486
     *
1487
     * @param int    $width <p>Number of characters at which to wrap.</p>
1488
     * @param string $break [optional] <p>Character used to break the string. | Default: "\n"</p>
1489
     *
1490
     * @psalm-mutation-free
1491
     *
1492
     * @return static
1493
     */
1494
    public function hardWrap($width, $break = "\n"): self
1495
    {
1496
        return $this->lineWrap($width, $break, false);
2✔
1497
    }
1498

1499
    /**
1500
     * Returns true if the string contains a lower case char, false otherwise
1501
     *
1502
     * EXAMPLE: <code>
1503
     * s('fòôbàř')->hasLowerCase(); // true
1504
     * </code>
1505
     *
1506
     * @psalm-mutation-free
1507
     *
1508
     * @return bool
1509
     *              <p>Whether or not the string contains a lower case character.</p>
1510
     */
1511
    public function hasLowerCase(): bool
1512
    {
1513
        return $this->utf8::has_lowercase($this->str);
36✔
1514
    }
1515

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

1533
    /**
1534
     * Generate a hash value (message digest).
1535
     *
1536
     * EXAMPLE: <code>
1537
     * </code>
1538
     *
1539
     * @see https://php.net/manual/en/function.hash.php
1540
     *
1541
     * @param string $algorithm
1542
     *                          <p>Name of selected hashing algorithm (i.e. "md5", "sha256", "haval160,4", etc..)</p>
1543
     *
1544
     * @psalm-mutation-free
1545
     *
1546
     * @return static
1547
     */
1548
    public function hash($algorithm): self
1549
    {
1550
        return static::create(\hash($algorithm, $this->str), $this->encoding);
8✔
1551
    }
1552

1553
    /**
1554
     * Decode the string from hex.
1555
     *
1556
     * EXAMPLE: <code>
1557
     * </code>
1558
     *
1559
     * @psalm-mutation-free
1560
     *
1561
     * @return static
1562
     */
1563
    public function hexDecode(): self
1564
    {
1565
        $string = \preg_replace_callback(
3✔
1566
            '/\\\\x(?<hex>[0-9A-Fa-f]+)/',
3✔
1567
            function (array $matched) {
3✔
1568
                return $this->utf8::hex_to_chr($matched['hex']);
3✔
1569
            },
3✔
1570
            $this->str
3✔
1571
        );
3✔
1572

1573
        return static::create(
3✔
1574
            $string,
3✔
1575
            $this->encoding
3✔
1576
        );
3✔
1577
    }
1578

1579
    /**
1580
     * Encode string to hex.
1581
     *
1582
     * EXAMPLE: <code>
1583
     * </code>
1584
     *
1585
     * @psalm-mutation-free
1586
     *
1587
     * @return static
1588
     */
1589
    public function hexEncode(): self
1590
    {
1591
        $string = \array_reduce(
2✔
1592
            $this->chars(),
2✔
1593
            function (string $str, string $char) {
2✔
1594
                return $str . $this->utf8::chr_to_hex($char);
2✔
1595
            },
2✔
1596
            ''
2✔
1597
        );
2✔
1598

1599
        return static::create(
2✔
1600
            $string,
2✔
1601
            $this->encoding
2✔
1602
        );
2✔
1603
    }
1604

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

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

1749
    /**
1750
     * Capitalizes the first word of the string, replaces underscores with
1751
     * spaces, and strips '_id'.
1752
     *
1753
     * EXAMPLE: <code>
1754
     * s('author_id')->humanize(); // 'Author'
1755
     * </code>
1756
     *
1757
     * @psalm-mutation-free
1758
     *
1759
     * @return static
1760
     *                <p>Object with a humanized $str.</p>
1761
     */
1762
    public function humanize(): self
1763
    {
1764
        return static::create(
9✔
1765
            $this->utf8::str_humanize($this->str),
9✔
1766
            $this->encoding
9✔
1767
        );
9✔
1768
    }
1769

1770
    /**
1771
     * Determine if the current string exists in another string. By
1772
     * default, the comparison is case-sensitive, but can be made insensitive
1773
     * by setting $caseSensitive to false.
1774
     *
1775
     * EXAMPLE: <code>
1776
     * </code>
1777
     *
1778
     * @param string $str           <p>The string to compare against.</p>
1779
     * @param bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
1780
     *
1781
     * @psalm-mutation-free
1782
     *
1783
     * @return bool
1784
     */
1785
    public function in(string $str, bool $caseSensitive = true): bool
1786
    {
1787
        if ($caseSensitive) {
4✔
1788
            return \strpos($str, $this->str) !== false;
3✔
1789
        }
1790

1791
        return \stripos($str, $this->str) !== false;
1✔
1792
    }
1793

1794
    /**
1795
     * Returns the index of the first occurrence of $needle in the string,
1796
     * and false if not found. Accepts an optional offset from which to begin
1797
     * the search.
1798
     *
1799
     * EXAMPLE: <code>
1800
     * s('string')->indexOf('ing'); // 3
1801
     * </code>
1802
     *
1803
     * @param string $needle <p>Substring to look for.</p>
1804
     * @param int    $offset [optional] <p>Offset from which to search. Default: 0</p>
1805
     *
1806
     * @psalm-mutation-free
1807
     *
1808
     * @return false|int
1809
     *                   <p>The occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
1810
     */
1811
    public function indexOf(string $needle, int $offset = 0)
1812
    {
1813
        return $this->utf8::strpos(
32✔
1814
            $this->str,
32✔
1815
            $needle,
32✔
1816
            $offset,
32✔
1817
            $this->encoding
32✔
1818
        );
32✔
1819
    }
1820

1821
    /**
1822
     * Returns the index of the first occurrence of $needle in the string,
1823
     * and false if not found. Accepts an optional offset from which to begin
1824
     * the search.
1825
     *
1826
     * EXAMPLE: <code>
1827
     * s('string')->indexOfIgnoreCase('ING'); // 3
1828
     * </code>
1829
     *
1830
     * @param string $needle <p>Substring to look for.</p>
1831
     * @param int    $offset [optional] <p>Offset from which to search. Default: 0</p>
1832
     *
1833
     * @psalm-mutation-free
1834
     *
1835
     * @return false|int
1836
     *                   <p>The occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
1837
     */
1838
    public function indexOfIgnoreCase(string $needle, int $offset = 0)
1839
    {
1840
        return $this->utf8::stripos(
21✔
1841
            $this->str,
21✔
1842
            $needle,
21✔
1843
            $offset,
21✔
1844
            $this->encoding
21✔
1845
        );
21✔
1846
    }
1847

1848
    /**
1849
     * Returns the index of the last occurrence of $needle in the string,
1850
     * and false if not found. Accepts an optional offset from which to begin
1851
     * the search. Offsets may be negative to count from the last character
1852
     * in the string.
1853
     *
1854
     * EXAMPLE: <code>
1855
     * s('foobarfoo')->indexOfLast('foo'); // 10
1856
     * </code>
1857
     *
1858
     * @param string $needle <p>Substring to look for.</p>
1859
     * @param int    $offset [optional] <p>Offset from which to search. Default: 0</p>
1860
     *
1861
     * @psalm-mutation-free
1862
     *
1863
     * @return false|int
1864
     *                   <p>The last occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
1865
     */
1866
    public function indexOfLast(string $needle, int $offset = 0)
1867
    {
1868
        return $this->utf8::strrpos(
32✔
1869
            $this->str,
32✔
1870
            $needle,
32✔
1871
            $offset,
32✔
1872
            $this->encoding
32✔
1873
        );
32✔
1874
    }
1875

1876
    /**
1877
     * Returns the index of the last occurrence of $needle in the string,
1878
     * and false if not found. Accepts an optional offset from which to begin
1879
     * the search. Offsets may be negative to count from the last character
1880
     * in the string.
1881
     *
1882
     * EXAMPLE: <code>
1883
     * s('fooBarFoo')->indexOfLastIgnoreCase('foo'); // 10
1884
     * </code>
1885
     *
1886
     * @param string $needle <p>Substring to look for.</p>
1887
     * @param int    $offset [optional] <p>Offset from which to search. Default: 0</p>
1888
     *
1889
     * @psalm-mutation-free
1890
     *
1891
     * @return false|int
1892
     *                   <p>The last occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
1893
     */
1894
    public function indexOfLastIgnoreCase(string $needle, int $offset = 0)
1895
    {
1896
        return $this->utf8::strripos(
21✔
1897
            $this->str,
21✔
1898
            $needle,
21✔
1899
            $offset,
21✔
1900
            $this->encoding
21✔
1901
        );
21✔
1902
    }
1903

1904
    /**
1905
     * Inserts $substring into the string at the $index provided.
1906
     *
1907
     * EXAMPLE: <code>
1908
     * s('fòôbř')->insert('à', 4); // 'fòôbàř'
1909
     * </code>
1910
     *
1911
     * @param string $substring <p>String to be inserted.</p>
1912
     * @param int    $index     <p>The index at which to insert the substring.</p>
1913
     *
1914
     * @psalm-mutation-free
1915
     *
1916
     * @return static
1917
     *                <p>Object with the resulting $str after the insertion.</p>
1918
     */
1919
    public function insert(string $substring, int $index): self
1920
    {
1921
        return static::create(
24✔
1922
            $this->utf8::str_insert(
24✔
1923
                $this->str,
24✔
1924
                $substring,
24✔
1925
                $index,
24✔
1926
                $this->encoding
24✔
1927
            ),
24✔
1928
            $this->encoding
24✔
1929
        );
24✔
1930
    }
1931

1932
    /**
1933
     * Returns true if the string contains the $pattern, otherwise false.
1934
     *
1935
     * WARNING: Asterisks ("*") are translated into (".*") zero-or-more regular
1936
     * expression wildcards.
1937
     *
1938
     * EXAMPLE: <code>
1939
     * s('Foo\\Bar\\Lall')->is('*\\Bar\\*'); // true
1940
     * </code>
1941
     *
1942
     * @credit Originally from Laravel, thanks Taylor.
1943
     *
1944
     * @param string $pattern <p>The string or pattern to match against.</p>
1945
     *
1946
     * @psalm-mutation-free
1947
     *
1948
     * @return bool
1949
     *              <p>Whether or not we match the provided pattern.</p>
1950
     */
1951
    public function is(string $pattern): bool
1952
    {
1953
        if ($this->toString() === $pattern) {
27✔
1954
            return true;
3✔
1955
        }
1956

1957
        $quotedPattern = \preg_quote($pattern, '/');
25✔
1958
        $replaceWildCards = \str_replace('\*', '.*', $quotedPattern);
25✔
1959

1960
        return $this->matchesPattern('^' . $replaceWildCards . '\z');
25✔
1961
    }
1962

1963
    /**
1964
     * Returns true if the string contains only alphabetic chars, false otherwise.
1965
     *
1966
     * EXAMPLE: <code>
1967
     * s('丹尼爾')->isAlpha(); // true
1968
     * </code>
1969
     *
1970
     * @psalm-mutation-free
1971
     *
1972
     * @return bool
1973
     *              <p>Whether or not $str contains only alphabetic chars.</p>
1974
     */
1975
    public function isAlpha(): bool
1976
    {
1977
        return $this->utf8::is_alpha($this->str);
30✔
1978
    }
1979

1980
    /**
1981
     * Returns true if the string contains only alphabetic and numeric chars, false otherwise.
1982
     *
1983
     * EXAMPLE: <code>
1984
     * s('دانيال1')->isAlphanumeric(); // true
1985
     * </code>
1986
     *
1987
     * @psalm-mutation-free
1988
     *
1989
     * @return bool
1990
     *              <p>Whether or not $str contains only alphanumeric chars.</p>
1991
     */
1992
    public function isAlphanumeric(): bool
1993
    {
1994
        return $this->utf8::is_alphanumeric($this->str);
39✔
1995
    }
1996

1997
    /**
1998
     * Checks if a string is 7 bit ASCII.
1999
     *
2000
     * EXAMPLE: <code>s('白')->isAscii; // false</code>
2001
     *
2002
     * @psalm-mutation-free
2003
     *
2004
     * @return bool
2005
     *              <p>
2006
     *              <strong>true</strong> if it is ASCII<br>
2007
     *              <strong>false</strong> otherwise
2008
     *              </p>
2009
     *
2010
     * @noinspection GetSetMethodCorrectnessInspection
2011
     */
2012
    public function isAscii(): bool
2013
    {
2014
        return $this->utf8::is_ascii($this->str);
×
2015
    }
2016

2017
    /**
2018
     * Returns true if the string is base64 encoded, false otherwise.
2019
     *
2020
     * EXAMPLE: <code>
2021
     * s('Zm9vYmFy')->isBase64(); // true
2022
     * </code>
2023
     *
2024
     * @param bool $emptyStringIsValid
2025
     *
2026
     * @psalm-mutation-free
2027
     *
2028
     * @return bool
2029
     *              <p>Whether or not $str is base64 encoded.</p>
2030
     */
2031
    public function isBase64($emptyStringIsValid = true): bool
2032
    {
2033
        return $this->utf8::is_base64($this->str, $emptyStringIsValid);
21✔
2034
    }
2035

2036
    /**
2037
     * Check if the input is binary... (is look like a hack).
2038
     *
2039
     * EXAMPLE: <code>s(01)->isBinary(); // true</code>
2040
     *
2041
     * @psalm-mutation-free
2042
     *
2043
     * @return bool
2044
     */
2045
    public function isBinary(): bool
2046
    {
2047
        return $this->utf8::is_binary($this->str);
1✔
2048
    }
2049

2050
    /**
2051
     * Returns true if the string contains only whitespace chars, false otherwise.
2052
     *
2053
     * EXAMPLE: <code>
2054
     * s("\n\t  \v\f")->isBlank(); // true
2055
     * </code>
2056
     *
2057
     * @psalm-mutation-free
2058
     *
2059
     * @return bool
2060
     *              <p>Whether or not $str contains only whitespace characters.</p>
2061
     */
2062
    public function isBlank(): bool
2063
    {
2064
        return $this->utf8::is_blank($this->str);
45✔
2065
    }
2066

2067
    /**
2068
     * Checks if the given string is equal to any "Byte Order Mark".
2069
     *
2070
     * WARNING: Use "s::string_has_bom()" if you will check BOM in a string.
2071
     *
2072
     * EXAMPLE: <code>s->("\xef\xbb\xbf")->isBom(); // true</code>
2073
     *
2074
     * @psalm-mutation-free
2075
     *
2076
     * @return bool
2077
     *              <p><strong>true</strong> if the $utf8_chr is Byte Order Mark, <strong>false</strong> otherwise.</p>
2078
     */
2079
    public function isBom(): bool
2080
    {
2081
        return $this->utf8::is_bom($this->str);
×
2082
    }
2083

2084
    /**
2085
     * Returns true if the string contains a valid E-Mail address, false otherwise.
2086
     *
2087
     * EXAMPLE: <code>
2088
     * s('lars@moelleken.org')->isEmail(); // true
2089
     * </code>
2090
     *
2091
     * @param bool $useExampleDomainCheck   [optional] <p>Default: false</p>
2092
     * @param bool $useTypoInDomainCheck    [optional] <p>Default: false</p>
2093
     * @param bool $useTemporaryDomainCheck [optional] <p>Default: false</p>
2094
     * @param bool $useDnsCheck             [optional] <p>Default: false</p>
2095
     *
2096
     * @psalm-mutation-free
2097
     *
2098
     * @return bool
2099
     *              <p>Whether or not $str contains a valid E-Mail address.</p>
2100
     */
2101
    public function isEmail(
2102
        bool $useExampleDomainCheck = false,
2103
        bool $useTypoInDomainCheck = false,
2104
        bool $useTemporaryDomainCheck = false,
2105
        bool $useDnsCheck = false
2106
    ): bool {
2107
        /**
2108
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the email-check class
2109
         */
2110
        return EmailCheck::isValid($this->str, $useExampleDomainCheck, $useTypoInDomainCheck, $useTemporaryDomainCheck, $useDnsCheck);
2✔
2111
    }
2112

2113
    /**
2114
     * Determine whether the string is considered to be empty.
2115
     *
2116
     * A variable is considered empty if it does not exist or if its value equals FALSE.
2117
     *
2118
     * EXAMPLE: <code>
2119
     * s('')->isEmpty(); // true
2120
     * </code>
2121
     *
2122
     * @psalm-mutation-free
2123
     *
2124
     * @return bool
2125
     *              <p>Whether or not $str is empty().</p>
2126
     */
2127
    public function isEmpty(): bool
2128
    {
2129
        return $this->utf8::is_empty($this->str);
10✔
2130
    }
2131

2132
    /**
2133
     * Determine whether the string is equals to $str.
2134
     * Alias for isEqualsCaseSensitive()
2135
     *
2136
     * EXAMPLE: <code>
2137
     * s('foo')->isEquals('foo'); // true
2138
     * </code>
2139
     *
2140
     * @param string|Stringy ...$str
2141
     *
2142
     * @psalm-mutation-free
2143
     *
2144
     * @return bool
2145
     */
2146
    public function isEquals(...$str): bool
2147
    {
2148
        return $this->isEqualsCaseSensitive(...$str);
13✔
2149
    }
2150

2151
    /**
2152
     * Determine whether the string is equals to $str.
2153
     *
2154
     * EXAMPLE: <code>
2155
     * </code>
2156
     *
2157
     * @param float|int|string|Stringy ...$str <p>The string to compare.</p>
2158
     *
2159
     * @psalm-mutation-free
2160
     *
2161
     * @return bool
2162
     *              <p>Whether or not $str is equals.</p>
2163
     */
2164
    public function isEqualsCaseInsensitive(...$str): bool
2165
    {
2166
        $strUpper = $this->toUpperCase()->str;
4✔
2167

2168
        foreach ($str as $strTmp) {
4✔
2169
            /**
2170
             * @psalm-suppress RedundantConditionGivenDocblockType - wait for union-types :)
2171
             */
2172
            if ($strTmp instanceof self) {
4✔
2173
                if ($strUpper !== $strTmp->toUpperCase()->str) {
×
2174
                    return false;
×
2175
                }
2176
            } elseif (\is_scalar($strTmp)) {
4✔
2177
                if ($strUpper !== $this->utf8::strtoupper((string) $strTmp, $this->encoding)) {
4✔
2178
                    return false;
3✔
2179
                }
2180
            } else {
2181
                throw new \InvalidArgumentException('expected: int|float|string|Stringy -> given: ' . \print_r($strTmp, true) . ' [' . \gettype($strTmp) . ']');
×
2182
            }
2183
        }
2184

2185
        return true;
4✔
2186
    }
2187

2188
    /**
2189
     * Determine whether the string is equals to $str.
2190
     *
2191
     * EXAMPLE: <code>
2192
     * </code>
2193
     *
2194
     * @param float|int|string|Stringy ...$str <p>The string to compare.</p>
2195
     *
2196
     * @psalm-mutation-free
2197
     *
2198
     * @return bool
2199
     *              <p>Whether or not $str is equals.</p>
2200
     */
2201
    public function isEqualsCaseSensitive(...$str): bool
2202
    {
2203
        foreach ($str as $strTmp) {
15✔
2204
            /**
2205
             * @psalm-suppress RedundantConditionGivenDocblockType - wait for union-types :)
2206
             */
2207
            if ($strTmp instanceof self) {
15✔
2208
                if ($this->str !== $strTmp->str) {
2✔
2209
                    return false;
1✔
2210
                }
2211
            } elseif (\is_scalar($strTmp)) {
13✔
2212
                if ($this->str !== (string) $strTmp) {
13✔
2213
                    return false;
10✔
2214
                }
2215
            } else {
2216
                throw new \InvalidArgumentException('expected: int|float|string|Stringy -> given: ' . \print_r($strTmp, true) . ' [' . \gettype($strTmp) . ']');
×
2217
            }
2218
        }
2219

2220
        return true;
4✔
2221
    }
2222

2223
    /**
2224
     * Returns true if the string contains only hexadecimal chars, false otherwise.
2225
     *
2226
     * EXAMPLE: <code>
2227
     * s('A102F')->isHexadecimal(); // true
2228
     * </code>
2229
     *
2230
     * @psalm-mutation-free
2231
     *
2232
     * @return bool
2233
     *              <p>Whether or not $str contains only hexadecimal chars.</p>
2234
     */
2235
    public function isHexadecimal(): bool
2236
    {
2237
        return $this->utf8::is_hexadecimal($this->str);
39✔
2238
    }
2239

2240
    /**
2241
     * Returns true if the string contains HTML-Tags, false otherwise.
2242
     *
2243
     * EXAMPLE: <code>
2244
     * s('<h1>foo</h1>')->isHtml(); // true
2245
     * </code>
2246
     *
2247
     * @psalm-mutation-free
2248
     *
2249
     * @return bool
2250
     *              <p>Whether or not $str contains HTML-Tags.</p>
2251
     */
2252
    public function isHtml(): bool
2253
    {
2254
        return $this->utf8::is_html($this->str);
2✔
2255
    }
2256

2257
    /**
2258
     * Returns true if the string is JSON, false otherwise. Unlike json_decode
2259
     * in PHP 5.x, this method is consistent with PHP 7 and other JSON parsers,
2260
     * in that an empty string is not considered valid JSON.
2261
     *
2262
     * EXAMPLE: <code>
2263
     * s('{"foo":"bar"}')->isJson(); // true
2264
     * </code>
2265
     *
2266
     * @param bool $onlyArrayOrObjectResultsAreValid
2267
     *
2268
     * @psalm-mutation-free
2269
     *
2270
     * @return bool
2271
     *              <p>Whether or not $str is JSON.</p>
2272
     */
2273
    public function isJson($onlyArrayOrObjectResultsAreValid = false): bool
2274
    {
2275
        /**
2276
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to vendor stuff?
2277
         */
2278
        return $this->utf8::is_json(
60✔
2279
            $this->str,
60✔
2280
            $onlyArrayOrObjectResultsAreValid
60✔
2281
        );
60✔
2282
    }
2283

2284
    /**
2285
     * Returns true if the string contains only lower case chars, false otherwise.
2286
     *
2287
     * EXAMPLE: <code>
2288
     * s('fòôbàř')->isLowerCase(); // true
2289
     * </code>
2290
     *
2291
     * @psalm-mutation-free
2292
     *
2293
     * @return bool
2294
     *              <p>Whether or not $str contains only lower case characters.</p>
2295
     */
2296
    public function isLowerCase(): bool
2297
    {
2298
        return $this->utf8::is_lowercase($this->str);
24✔
2299
    }
2300

2301
    /**
2302
     * Determine whether the string is considered to be NOT empty.
2303
     *
2304
     * A variable is considered NOT empty if it does exist or if its value equals TRUE.
2305
     *
2306
     * EXAMPLE: <code>
2307
     * s('')->isNotEmpty(); // false
2308
     * </code>
2309
     *
2310
     * @psalm-mutation-free
2311
     *
2312
     * @return bool
2313
     *              <p>Whether or not $str is empty().</p>
2314
     */
2315
    public function isNotEmpty(): bool
2316
    {
2317
        return !$this->utf8::is_empty($this->str);
10✔
2318
    }
2319

2320
    /**
2321
     * Determine if the string is composed of numeric characters.
2322
     *
2323
     * EXAMPLE: <code>
2324
     * </code>
2325
     *
2326
     * @psalm-mutation-free
2327
     *
2328
     * @return bool
2329
     */
2330
    public function isNumeric(): bool
2331
    {
2332
        return \is_numeric($this->str);
4✔
2333
    }
2334

2335
    /**
2336
     * Determine if the string is composed of printable (non-invisible) characters.
2337
     *
2338
     * EXAMPLE: <code>
2339
     * </code>
2340
     *
2341
     * @psalm-mutation-free
2342
     *
2343
     * @return bool
2344
     */
2345
    public function isPrintable(): bool
2346
    {
2347
        return $this->utf8::is_printable($this->str);
3✔
2348
    }
2349

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

2365
    /**
2366
     * Returns true if the string is serialized, false otherwise.
2367
     *
2368
     * EXAMPLE: <code>
2369
     * s('a:1:{s:3:"foo";s:3:"bar";}')->isSerialized(); // true
2370
     * </code>
2371
     *
2372
     * @psalm-mutation-free
2373
     *
2374
     * @return bool
2375
     *              <p>Whether or not $str is serialized.</p>
2376
     */
2377
    public function isSerialized(): bool
2378
    {
2379
        return $this->utf8::is_serialized($this->str);
21✔
2380
    }
2381

2382
    /**
2383
     * Check if two strings are similar.
2384
     *
2385
     * EXAMPLE: <code>
2386
     * </code>
2387
     *
2388
     * @param string $str                     <p>The string to compare against.</p>
2389
     * @param float  $minPercentForSimilarity [optional] <p>The percentage of needed similarity. | Default: 80%</p>
2390
     *
2391
     * @psalm-mutation-free
2392
     *
2393
     * @return bool
2394
     */
2395
    public function isSimilar(string $str, float $minPercentForSimilarity = 80.0): bool
2396
    {
2397
        return $this->similarity($str) >= $minPercentForSimilarity;
3✔
2398
    }
2399

2400
    /**
2401
     * Returns true if the string contains only lower case chars, false
2402
     * otherwise.
2403
     *
2404
     * EXAMPLE: <code>
2405
     * s('FÒÔBÀŘ')->isUpperCase(); // true
2406
     * </code>
2407
     *
2408
     * @psalm-mutation-free
2409
     *
2410
     * @return bool
2411
     *              <p>Whether or not $str contains only lower case characters.</p>
2412
     */
2413
    public function isUpperCase(): bool
2414
    {
2415
        return $this->utf8::is_uppercase($this->str);
24✔
2416
    }
2417

2418
    /**
2419
     * /**
2420
     * Check if $url is an correct url.
2421
     *
2422
     * @param bool $disallow_localhost
2423
     *
2424
     * @psalm-mutation-free
2425
     *
2426
     * @return bool
2427
     */
2428
    public function isUrl(bool $disallow_localhost = false): bool
2429
    {
2430
        return $this->utf8::is_url($this->str, $disallow_localhost);
×
2431
    }
2432

2433
    /**
2434
     * Check if the string is UTF-16.
2435
     *
2436
     * @psalm-mutation-free
2437
     *
2438
     * @return false|int
2439
     *                   <strong>false</strong> if is't not UTF-16,<br>
2440
     *                   <strong>1</strong> for UTF-16LE,<br>
2441
     *                   <strong>2</strong> for UTF-16BE
2442
     */
2443
    public function isUtf16()
2444
    {
2445
        return $this->utf8::is_utf16($this->str);
×
2446
    }
2447

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

2463
    /**
2464
     * Checks whether the passed input contains only byte sequences that appear valid UTF-8.
2465
     *
2466
     * EXAMPLE: <code>
2467
     * s('Iñtërnâtiônàlizætiøn')->isUtf8(); // true
2468
     * //
2469
     * s("Iñtërnâtiônàlizætiøn\xA0\xA1")->isUtf8(); // false
2470
     * </code>
2471
     *
2472
     * @param bool $strict <p>Check also if the string is not UTF-16 or UTF-32.</p>
2473
     *
2474
     * @psalm-mutation-free
2475
     *
2476
     * @return bool
2477
     */
2478
    public function isUtf8(bool $strict = false): bool
2479
    {
2480
        return $this->utf8::is_utf8($this->str, $strict);
×
2481
    }
2482

2483
    /**
2484
     * Returns true if the string contains only whitespace chars, false otherwise.
2485
     *
2486
     * EXAMPLE: <code>
2487
     * </code>
2488
     *
2489
     * @psalm-mutation-free
2490
     *
2491
     * @return bool
2492
     *              <p>Whether or not $str contains only whitespace characters.</p>
2493
     */
2494
    public function isWhitespace(): bool
2495
    {
2496
        return $this->isBlank();
30✔
2497
    }
2498

2499
    /**
2500
     * Convert the string to kebab-case.
2501
     *
2502
     * EXAMPLE: <code>
2503
     * </code>
2504
     *
2505
     * @psalm-mutation-free
2506
     *
2507
     * @return static
2508
     */
2509
    public function kebabCase(): self
2510
    {
2511
        $words = \array_map(
4✔
2512
            static function (self $word) {
4✔
2513
                return $word->toLowerCase();
4✔
2514
            },
4✔
2515
            $this->words('', true)
4✔
2516
        );
4✔
2517

2518
        return new static(\implode('-', $words), $this->encoding);
4✔
2519
    }
2520

2521
    /**
2522
     * Returns the last $n characters of the string.
2523
     *
2524
     * EXAMPLE: <code>
2525
     * s('fòôbàř')->last(3); // 'bàř'
2526
     * </code>
2527
     *
2528
     * @param int $n <p>Number of characters to retrieve from the end.</p>
2529
     *
2530
     * @psalm-mutation-free
2531
     *
2532
     * @return static
2533
     *                <p>Object with its $str being the last $n chars.</p>
2534
     */
2535
    public function last(int $n): self
2536
    {
2537
        return static::create(
36✔
2538
            $this->utf8::str_last_char(
36✔
2539
                $this->str,
36✔
2540
                $n,
36✔
2541
                $this->encoding
36✔
2542
            ),
36✔
2543
            $this->encoding
36✔
2544
        );
36✔
2545
    }
2546

2547
    /**
2548
     * Gets the substring after (or before via "$beforeNeedle") the last occurrence of the "$needle".
2549
     * If no match is found returns new empty Stringy object.
2550
     *
2551
     * EXAMPLE: <code>
2552
     * </code>
2553
     *
2554
     * @param string $needle       <p>The string to look for.</p>
2555
     * @param bool   $beforeNeedle [optional] <p>Default: false</p>
2556
     *
2557
     * @psalm-mutation-free
2558
     *
2559
     * @return static
2560
     */
2561
    public function lastSubstringOf(string $needle, bool $beforeNeedle = false): self
2562
    {
2563
        return static::create(
5✔
2564
            $this->utf8::str_substr_last(
5✔
2565
                $this->str,
5✔
2566
                $needle,
5✔
2567
                $beforeNeedle,
5✔
2568
                $this->encoding
5✔
2569
            ),
5✔
2570
            $this->encoding
5✔
2571
        );
5✔
2572
    }
2573

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

2601
    /**
2602
     * Returns the length of the string.
2603
     *
2604
     * EXAMPLE: <code>
2605
     * s('fòôbàř')->length(); // 6
2606
     * </code>
2607
     *
2608
     * @psalm-mutation-free
2609
     *
2610
     * @return int
2611
     *             <p>The number of characters in $str given the encoding.</p>
2612
     */
2613
    public function length(): int
2614
    {
2615
        return (int) $this->utf8::strlen($this->str, $this->encoding);
30✔
2616
    }
2617

2618
    /**
2619
     * Line-Wrap the string after $limit, but also after the next word.
2620
     *
2621
     * EXAMPLE: <code>
2622
     * </code>
2623
     *
2624
     * @param int         $limit           [optional] <p>The column width.</p>
2625
     * @param string      $break           [optional] <p>The line is broken using the optional break parameter.</p>
2626
     * @param bool        $add_final_break [optional] <p>
2627
     *                                     If this flag is true, then the method will add a $break at the end
2628
     *                                     of the result string.
2629
     *                                     </p>
2630
     * @param string|null $delimiter       [optional] <p>
2631
     *                                     You can change the default behavior, where we split the string by newline.
2632
     *                                     </p>
2633
     *
2634
     * @psalm-mutation-free
2635
     *
2636
     * @return static
2637
     */
2638
    public function lineWrap(
2639
        int $limit,
2640
        string $break = "\n",
2641
        bool $add_final_break = true,
2642
        ?string $delimiter = null
2643
    ): self {
2644
        if ($limit <= 0) {
5✔
2645
            return static::create('', $this->encoding);
1✔
2646
        }
2647

2648
        $delimiter = $delimiter === '' ? null : $delimiter;
4✔
2649

2650
        return static::create(
4✔
2651
            $this->utf8::wordwrap_per_line(
4✔
2652
                $this->str,
4✔
2653
                $limit,
4✔
2654
                $break,
4✔
2655
                true,
4✔
2656
                $add_final_break,
4✔
2657
                $delimiter
4✔
2658
            ),
4✔
2659
            $this->encoding
4✔
2660
        );
4✔
2661
    }
2662

2663
    /**
2664
     * Line-Wrap the string after $limit, but also after the next word.
2665
     *
2666
     * EXAMPLE: <code>
2667
     * </code>
2668
     *
2669
     * @param int         $limit           [optional] <p>The column width.</p>
2670
     * @param string      $break           [optional] <p>The line is broken using the optional break parameter.</p>
2671
     * @param bool        $add_final_break [optional] <p>
2672
     *                                     If this flag is true, then the method will add a $break at the end
2673
     *                                     of the result string.
2674
     *                                     </p>
2675
     * @param string|null $delimiter       [optional] <p>
2676
     *                                     You can change the default behavior, where we split the string by newline.
2677
     *                                     </p>
2678
     *
2679
     * @psalm-mutation-free
2680
     *
2681
     * @return static
2682
     */
2683
    public function lineWrapAfterWord(
2684
        int $limit,
2685
        string $break = "\n",
2686
        bool $add_final_break = true,
2687
        ?string $delimiter = null
2688
    ): self {
2689
        if ($limit <= 0) {
8✔
2690
            return static::create('', $this->encoding);
2✔
2691
        }
2692

2693
        $delimiter = $delimiter === '' ? null : $delimiter;
6✔
2694

2695
        return static::create(
6✔
2696
            $this->utf8::wordwrap_per_line(
6✔
2697
                $this->str,
6✔
2698
                $limit,
6✔
2699
                $break,
6✔
2700
                false,
6✔
2701
                $add_final_break,
6✔
2702
                $delimiter
6✔
2703
            ),
6✔
2704
            $this->encoding
6✔
2705
        );
6✔
2706
    }
2707

2708
    /**
2709
     * Splits on newlines and carriage returns, returning an array of Stringy
2710
     * objects corresponding to the lines in the string.
2711
     *
2712
     * EXAMPLE: <code>
2713
     * s("fòô\r\nbàř\n")->lines(); // ['fòô', 'bàř', '']
2714
     * </code>
2715
     *
2716
     * @psalm-mutation-free
2717
     *
2718
     * @return static[]
2719
     *                  <p>An array of Stringy objects.</p>
2720
     *
2721
     * @phpstan-return array<int,static>
2722
     */
2723
    public function lines(): array
2724
    {
2725
        $strings = $this->utf8::str_to_lines($this->str);
52✔
2726
        /** @noinspection AlterInForeachInspection */
2727
        foreach ($strings as &$str) {
52✔
2728
            $str = static::create($str, $this->encoding);
52✔
2729
        }
2730

2731
        /** @noinspection PhpSillyAssignmentInspection */
2732
        /** @var static[] $strings */
2733
        $strings = $strings;
52✔
2734

2735
        return $strings;
52✔
2736
    }
2737

2738
    /**
2739
     * Splits on newlines and carriage returns, returning an array of Stringy
2740
     * objects corresponding to the lines in the string.
2741
     *
2742
     * EXAMPLE: <code>
2743
     * </code>
2744
     *
2745
     * @psalm-mutation-free
2746
     *
2747
     * @return CollectionStringy|static[]
2748
     *                                    <p>An collection of Stringy objects.</p>
2749
     *
2750
     * @phpstan-return CollectionStringy<int,static>
2751
     */
2752
    public function linesCollection(): CollectionStringy
2753
    {
2754
        /**
2755
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the collection class
2756
         */
2757
        return CollectionStringy::create(
34✔
2758
            $this->lines()
34✔
2759
        );
34✔
2760
    }
2761

2762
    /**
2763
     * Returns the longest common prefix between the string and $otherStr.
2764
     *
2765
     * EXAMPLE: <code>
2766
     * s('foobar')->longestCommonPrefix('foobaz'); // 'fooba'
2767
     * </code>
2768
     *
2769
     * @param string $otherStr <p>Second string for comparison.</p>
2770
     *
2771
     * @psalm-mutation-free
2772
     *
2773
     * @return static
2774
     *                <p>Object with its $str being the longest common prefix.</p>
2775
     */
2776
    public function longestCommonPrefix(string $otherStr): self
2777
    {
2778
        return static::create(
30✔
2779
            $this->utf8::str_longest_common_prefix(
30✔
2780
                $this->str,
30✔
2781
                $otherStr,
30✔
2782
                $this->encoding
30✔
2783
            ),
30✔
2784
            $this->encoding
30✔
2785
        );
30✔
2786
    }
2787

2788
    /**
2789
     * Returns the longest common substring between the string and $otherStr.
2790
     * In the case of ties, it returns that which occurs first.
2791
     *
2792
     * EXAMPLE: <code>
2793
     * s('foobar')->longestCommonSubstring('boofar'); // 'oo'
2794
     * </code>
2795
     *
2796
     * @param string $otherStr <p>Second string for comparison.</p>
2797
     *
2798
     * @psalm-mutation-free
2799
     *
2800
     * @return static
2801
     *                <p>Object with its $str being the longest common substring.</p>
2802
     */
2803
    public function longestCommonSubstring(string $otherStr): self
2804
    {
2805
        return static::create(
30✔
2806
            $this->utf8::str_longest_common_substring(
30✔
2807
                $this->str,
30✔
2808
                $otherStr,
30✔
2809
                $this->encoding
30✔
2810
            ),
30✔
2811
            $this->encoding
30✔
2812
        );
30✔
2813
    }
2814

2815
    /**
2816
     * Returns the longest common suffix between the string and $otherStr.
2817
     *
2818
     * EXAMPLE: <code>
2819
     * s('fòôbàř')->longestCommonSuffix('fòrbàř'); // 'bàř'
2820
     * </code>
2821
     *
2822
     * @param string $otherStr <p>Second string for comparison.</p>
2823
     *
2824
     * @psalm-mutation-free
2825
     *
2826
     * @return static
2827
     *                <p>Object with its $str being the longest common suffix.</p>
2828
     */
2829
    public function longestCommonSuffix(string $otherStr): self
2830
    {
2831
        return static::create(
30✔
2832
            $this->utf8::str_longest_common_suffix(
30✔
2833
                $this->str,
30✔
2834
                $otherStr,
30✔
2835
                $this->encoding
30✔
2836
            ),
30✔
2837
            $this->encoding
30✔
2838
        );
30✔
2839
    }
2840

2841
    /**
2842
     * Converts the first character of the string to lower case.
2843
     *
2844
     * EXAMPLE: <code>
2845
     * s('Σ Foo')->lowerCaseFirst(); // 'σ Foo'
2846
     * </code>
2847
     *
2848
     * @psalm-mutation-free
2849
     *
2850
     * @return static
2851
     *                <p>Object with the first character of $str being lower case.</p>
2852
     */
2853
    public function lowerCaseFirst(): self
2854
    {
2855
        return static::create(
16✔
2856
            $this->utf8::lcfirst($this->str, $this->encoding),
16✔
2857
            $this->encoding
16✔
2858
        );
16✔
2859
    }
2860

2861
    /**
2862
     * Determine if the string matches another string regardless of case.
2863
     * Alias for isEqualsCaseInsensitive()
2864
     *
2865
     * EXAMPLE: <code>
2866
     * </code>
2867
     *
2868
     * @psalm-mutation-free
2869
     *
2870
     * @param string|Stringy ...$str
2871
     *                               <p>The string to compare against.</p>
2872
     *
2873
     * @psalm-mutation-free
2874
     *
2875
     * @return bool
2876
     */
2877
    public function matchCaseInsensitive(...$str): bool
2878
    {
2879
        return $this->isEqualsCaseInsensitive(...$str);
3✔
2880
    }
2881

2882
    /**
2883
     * Determine if the string matches another string.
2884
     * Alias for isEqualsCaseSensitive()
2885
     *
2886
     * EXAMPLE: <code>
2887
     * </code>
2888
     *
2889
     * @psalm-mutation-free
2890
     *
2891
     * @param string|Stringy ...$str
2892
     *                               <p>The string to compare against.</p>
2893
     *
2894
     * @psalm-mutation-free
2895
     *
2896
     * @return bool
2897
     */
2898
    public function matchCaseSensitive(...$str): bool
2899
    {
2900
        return $this->isEqualsCaseSensitive(...$str);
7✔
2901
    }
2902

2903
    /**
2904
     * Create a md5 hash from the current string.
2905
     *
2906
     * @psalm-mutation-free
2907
     *
2908
     * @return static
2909
     */
2910
    public function md5(): self
2911
    {
2912
        return static::create($this->hash('md5'), $this->encoding);
2✔
2913
    }
2914

2915
    /**
2916
     * Replace all breaks [<br> | \r\n | \r | \n | ...] into "<br>".
2917
     *
2918
     * EXAMPLE: <code>
2919
     * </code>
2920
     *
2921
     * @return static
2922
     */
2923
    public function newLineToHtmlBreak(): self
2924
    {
2925
        return $this->removeHtmlBreak('<br>');
1✔
2926
    }
2927

2928
    /**
2929
     * Get every nth character of the string.
2930
     *
2931
     * EXAMPLE: <code>
2932
     * </code>
2933
     *
2934
     * @param int $step   <p>The number of characters to step.</p>
2935
     * @param int $offset [optional] <p>The string offset to start at.</p>
2936
     *
2937
     * @psalm-mutation-free
2938
     *
2939
     * @return static
2940
     */
2941
    public function nth(int $step, int $offset = 0): self
2942
    {
2943
        $length = $step - 1;
4✔
2944
        $substring = $this->substr($offset)->toString();
4✔
2945

2946
        if ($substring === '') {
4✔
2947
            return new static('', $this->encoding);
×
2948
        }
2949

2950
        \preg_match_all(
4✔
2951
            "/(?:^|(?:.|\p{L}|\w){" . $length . "})(.|\p{L}|\w)/u",
4✔
2952
            $substring,
4✔
2953
            $matches
4✔
2954
        );
4✔
2955

2956
        return new static(\implode('', $matches[1] ?? []), $this->encoding);
4✔
2957
    }
2958

2959
    /**
2960
     * Returns the integer value of the current string.
2961
     *
2962
     * EXAMPLE: <code>
2963
     * s('foo1 ba2r')->extractIntegers(); // '12'
2964
     * </code>
2965
     *
2966
     * @psalm-mutation-free
2967
     *
2968
     * @return static
2969
     */
2970
    public function extractIntegers(): self
2971
    {
2972
        \preg_match_all('/(?<integers>\d+)/', $this->str, $matches);
1✔
2973

2974
        return static::create(
1✔
2975
            \implode('', $matches['integers']),
1✔
2976
            $this->encoding
1✔
2977
        );
1✔
2978
    }
2979

2980
    /**
2981
     * Returns the special chars of the current string.
2982
     *
2983
     * EXAMPLE: <code>
2984
     * s('foo1 ba2!r')->extractSpecialCharacters(); // '!'
2985
     * </code>
2986
     *
2987
     * @psalm-mutation-free
2988
     *
2989
     * @return static
2990
     */
2991
    public function extractSpecialCharacters(): self
2992
    {
2993
        // no letter, no digit, no space
2994
        \preg_match_all('/[^\p{L}0-9\s]/u', $this->str, $matches);
1✔
2995

2996
        return static::create(
1✔
2997
            \implode('', $matches[0]),
1✔
2998
            $this->encoding
1✔
2999
        );
1✔
3000
    }
3001

3002
    /**
3003
     * Returns whether or not a character exists at an index. Offsets may be
3004
     * negative to count from the last character in the string. Implements
3005
     * part of the ArrayAccess interface.
3006
     *
3007
     * EXAMPLE: <code>
3008
     * </code>
3009
     *
3010
     * @param int $offset <p>The index to check.</p>
3011
     *
3012
     * @psalm-mutation-free
3013
     *
3014
     * @return bool
3015
     *              <p>Whether or not the index exists.</p>
3016
     */
3017
    public function offsetExists($offset): bool
3018
    {
3019
        return $this->utf8::str_offset_exists(
18✔
3020
            $this->str,
18✔
3021
            $offset,
18✔
3022
            $this->encoding
18✔
3023
        );
18✔
3024
    }
3025

3026
    /**
3027
     * Returns the character at the given index. Offsets may be negative to
3028
     * count from the last character in the string. Implements part of the
3029
     * ArrayAccess interface, and throws an OutOfBoundsException if the index
3030
     * does not exist.
3031
     *
3032
     * EXAMPLE: <code>
3033
     * </code>
3034
     *
3035
     * @param int $offset <p>The <strong>index</strong> from which to retrieve the char.</p>
3036
     *
3037
     * @throws \OutOfBoundsException
3038
     *                               <p>If the positive or negative offset does not exist.</p>
3039
     *
3040
     * @return string
3041
     *                <p>The character at the specified index.</p>
3042
     *
3043
     * @psalm-mutation-free
3044
     */
3045
    public function offsetGet($offset): string
3046
    {
3047
        $length = $this->length();
11✔
3048

3049
        if (
3050
            ($offset >= 0 && $length <= $offset)
11✔
3051
            ||
3052
            $length < \abs($offset)
11✔
3053
        ) {
3054
            throw new \OutOfBoundsException('No character exists at the index');
6✔
3055
        }
3056

3057
        return (string) $this->utf8::substr($this->str, $offset, 1, $this->encoding);
5✔
3058
    }
3059

3060
    /**
3061
     * Implements part of the ArrayAccess interface, but throws an exception
3062
     * when called. This maintains the immutability of Stringy objects.
3063
     *
3064
     * EXAMPLE: <code>
3065
     * </code>
3066
     *
3067
     * @param int   $offset <p>The index of the character.</p>
3068
     * @param mixed $value  <p>Value to set.</p>
3069
     *
3070
     * @throws \Exception
3071
     *                    <p>When called.</p>
3072
     *
3073
     * @return void
3074
     */
3075
    public function offsetSet($offset, $value): void
3076
    {
3077
        // Stringy is immutable, cannot directly set char
3078
        throw new \Exception('Stringy object is immutable, cannot modify char');
3✔
3079
    }
3080

3081
    /**
3082
     * Implements part of the ArrayAccess interface, but throws an exception
3083
     * when called. This maintains the immutability of Stringy objects.
3084
     *
3085
     * EXAMPLE: <code>
3086
     * </code>
3087
     *
3088
     * @param int $offset <p>The index of the character.</p>
3089
     *
3090
     * @throws \Exception
3091
     *                    <p>When called.</p>
3092
     *
3093
     * @return void
3094
     */
3095
    public function offsetUnset($offset): void
3096
    {
3097
        // Don't allow directly modifying the string
3098
        throw new \Exception('Stringy object is immutable, cannot unset char');
3✔
3099
    }
3100

3101
    /**
3102
     * Pads the string to a given length with $padStr. If length is less than
3103
     * or equal to the length of the string, no padding takes places. The
3104
     * default string used for padding is a space, and the default type (one of
3105
     * 'left', 'right', 'both') is 'right'. Throws an InvalidArgumentException
3106
     * if $padType isn't one of those 3 values.
3107
     *
3108
     * EXAMPLE: <code>
3109
     * s('fòôbàř')->pad(9, '-/', 'left'); // '-/-fòôbàř'
3110
     * </code>
3111
     *
3112
     * @param int    $length  <p>Desired string length after padding.</p>
3113
     * @param string $padStr  [optional] <p>String used to pad, defaults to space. Default: ' '</p>
3114
     * @param string $padType [optional] <p>One of 'left', 'right', 'both'. Default: 'right'</p>
3115
     *
3116
     * @throws \InvalidArgumentException
3117
     *                                   <p>If $padType isn't one of 'right', 'left' or 'both'.</p>
3118
     *
3119
     * @return static
3120
     *                <p>Object with a padded $str.</p>
3121
     *
3122
     * @psalm-mutation-free
3123
     */
3124
    public function pad(int $length, string $padStr = ' ', string $padType = 'right'): self
3125
    {
3126
        return static::create(
39✔
3127
            $this->utf8::str_pad(
39✔
3128
                $this->str,
39✔
3129
                $length,
39✔
3130
                $padStr,
39✔
3131
                $padType,
39✔
3132
                $this->encoding
39✔
3133
            )
39✔
3134
        );
39✔
3135
    }
3136

3137
    /**
3138
     * Returns a new string of a given length such that both sides of the
3139
     * string are padded. Alias for pad() with a $padType of 'both'.
3140
     *
3141
     * EXAMPLE: <code>
3142
     * s('foo bar')->padBoth(9, ' '); // ' foo bar '
3143
     * </code>
3144
     *
3145
     * @param int    $length <p>Desired string length after padding.</p>
3146
     * @param string $padStr [optional] <p>String used to pad, defaults to space. Default: ' '</p>
3147
     *
3148
     * @psalm-mutation-free
3149
     *
3150
     * @return static
3151
     *                <p>String with padding applied.</p>
3152
     */
3153
    public function padBoth(int $length, string $padStr = ' '): self
3154
    {
3155
        return static::create(
33✔
3156
            $this->utf8::str_pad_both(
33✔
3157
                $this->str,
33✔
3158
                $length,
33✔
3159
                $padStr,
33✔
3160
                $this->encoding
33✔
3161
            )
33✔
3162
        );
33✔
3163
    }
3164

3165
    /**
3166
     * Returns a new string of a given length such that the beginning of the
3167
     * string is padded. Alias for pad() with a $padType of 'left'.
3168
     *
3169
     * EXAMPLE: <code>
3170
     * s('foo bar')->padLeft(9, ' '); // '  foo bar'
3171
     * </code>
3172
     *
3173
     * @param int    $length <p>Desired string length after padding.</p>
3174
     * @param string $padStr [optional] <p>String used to pad, defaults to space. Default: ' '</p>
3175
     *
3176
     * @psalm-mutation-free
3177
     *
3178
     * @return static
3179
     *                <p>String with left padding.</p>
3180
     */
3181
    public function padLeft(int $length, string $padStr = ' '): self
3182
    {
3183
        return static::create(
21✔
3184
            $this->utf8::str_pad_left(
21✔
3185
                $this->str,
21✔
3186
                $length,
21✔
3187
                $padStr,
21✔
3188
                $this->encoding
21✔
3189
            )
21✔
3190
        );
21✔
3191
    }
3192

3193
    /**
3194
     * Returns a new string of a given length such that the end of the string
3195
     * is padded. Alias for pad() with a $padType of 'right'.
3196
     *
3197
     * EXAMPLE: <code>
3198
     * s('foo bar')->padRight(10, '_*'); // 'foo bar_*_'
3199
     * </code>
3200
     *
3201
     * @param int    $length <p>Desired string length after padding.</p>
3202
     * @param string $padStr [optional] <p>String used to pad, defaults to space. Default: ' '</p>
3203
     *
3204
     * @psalm-mutation-free
3205
     *
3206
     * @return static
3207
     *                <p>String with right padding.</p>
3208
     */
3209
    public function padRight(int $length, string $padStr = ' '): self
3210
    {
3211
        return static::create(
21✔
3212
            $this->utf8::str_pad_right(
21✔
3213
                $this->str,
21✔
3214
                $length,
21✔
3215
                $padStr,
21✔
3216
                $this->encoding
21✔
3217
            )
21✔
3218
        );
21✔
3219
    }
3220

3221
    /**
3222
     * Convert the string to PascalCase.
3223
     * Alias for studlyCase()
3224
     *
3225
     * EXAMPLE: <code>
3226
     * </code>
3227
     *
3228
     * @psalm-mutation-free
3229
     *
3230
     * @return static
3231
     */
3232
    public function pascalCase(): self
3233
    {
3234
        return $this->studlyCase();
3✔
3235
    }
3236

3237
    /**
3238
     * Returns a new string starting with $prefix.
3239
     *
3240
     * EXAMPLE: <code>
3241
     * s('bàř')->prepend('fòô'); // 'fòôbàř'
3242
     * </code>
3243
     *
3244
     * @param string ...$prefix <p>The string to append.</p>
3245
     *
3246
     * @psalm-mutation-free
3247
     *
3248
     * @return static
3249
     *                <p>Object with appended $prefix.</p>
3250
     */
3251
    public function prepend(string ...$prefix): self
3252
    {
3253
        if (\count($prefix) <= 1) {
8✔
3254
            $prefix = $prefix[0];
6✔
3255
        } else {
3256
            $prefix = \implode('', $prefix);
2✔
3257
        }
3258

3259
        return static::create($prefix . $this->str, $this->encoding);
8✔
3260
    }
3261

3262
    /**
3263
     * Returns a new string starting with $prefix.
3264
     *
3265
     * EXAMPLE: <code>
3266
     * </code>
3267
     *
3268
     * @param CollectionStringy|static ...$prefix <p>The Stringy objects to append.</p>
3269
     *
3270
     * @phpstan-param CollectionStringy<int,static>|static ...$prefix
3271
     *
3272
     * @psalm-mutation-free
3273
     *
3274
     * @return static
3275
     *                <p>Object with appended $prefix.</p>
3276
     */
3277
    public function prependStringy(...$prefix): self
3278
    {
3279
        $prefixStr = '';
2✔
3280
        foreach ($prefix as $prefixTmp) {
2✔
3281
            if ($prefixTmp instanceof CollectionStringy) {
2✔
3282
                $prefixStr .= $prefixTmp->implode('');
2✔
3283
            } else {
3284
                $prefixStr .= $prefixTmp->toString();
2✔
3285
            }
3286
        }
3287

3288
        return static::create($prefixStr . $this->str, $this->encoding);
2✔
3289
    }
3290

3291
    /**
3292
     * Replaces all occurrences of $pattern in $str by $replacement.
3293
     *
3294
     * EXAMPLE: <code>
3295
     * s('fòô ')->regexReplace('f[òô]+\s', 'bàř'); // 'bàř'
3296
     * s('fò')->regexReplace('(ò)', '\\1ô'); // 'fòô'
3297
     * </code>
3298
     *
3299
     * @param string $pattern     <p>The regular expression pattern.</p>
3300
     * @param string $replacement <p>The string to replace with.</p>
3301
     * @param string $options     [optional] <p>Matching conditions to be used.</p>
3302
     * @param string $delimiter   [optional] <p>Delimiter the the regex. Default: '/'</p>
3303
     *
3304
     * @psalm-mutation-free
3305
     *
3306
     * @return static
3307
     *                <p>Object with the result2ing $str after the replacements.</p>
3308
     */
3309
    public function regexReplace(
3310
        string $pattern,
3311
        string $replacement,
3312
        string $options = '',
3313
        string $delimiter = '/'
3314
    ): self {
3315
        return static::create(
29✔
3316
            $this->utf8::regex_replace(
29✔
3317
                $this->str,
29✔
3318
                $pattern,
29✔
3319
                $replacement,
29✔
3320
                $options,
29✔
3321
                $delimiter
29✔
3322
            ),
29✔
3323
            $this->encoding
29✔
3324
        );
29✔
3325
    }
3326

3327
    /**
3328
     * Remove html via "strip_tags()" from the string.
3329
     *
3330
     * EXAMPLE: <code>
3331
     * s('řàb <ô>òf\', ô<br/>foo <a href="#">lall</a>')->removeHtml('<br><br/>'); // 'řàb òf\', ô<br/>foo lall'
3332
     * </code>
3333
     *
3334
     * @param string $allowableTags [optional] <p>You can use the optional second parameter to specify tags which should
3335
     *                              not be stripped. Default: null
3336
     *                              </p>
3337
     *
3338
     * @psalm-mutation-free
3339
     *
3340
     * @return static
3341
     */
3342
    public function removeHtml(string $allowableTags = ''): self
3343
    {
3344
        return static::create(
12✔
3345
            $this->utf8::remove_html($this->str, $allowableTags),
12✔
3346
            $this->encoding
12✔
3347
        );
12✔
3348
    }
3349

3350
    /**
3351
     * Remove all breaks [<br> | \r\n | \r | \n | ...] from the string.
3352
     *
3353
     * EXAMPLE: <code>
3354
     * s('řàb <ô>òf\', ô<br/>foo <a href="#">lall</a>')->removeHtmlBreak(''); // 'řàb <ô>òf\', ô< foo <a href="#">lall</a>'
3355
     * </code>
3356
     *
3357
     * @param string $replacement [optional] <p>Default is a empty string.</p>
3358
     *
3359
     * @psalm-mutation-free
3360
     *
3361
     * @return static
3362
     */
3363
    public function removeHtmlBreak(string $replacement = ''): self
3364
    {
3365
        return static::create(
13✔
3366
            $this->utf8::remove_html_breaks($this->str, $replacement),
13✔
3367
            $this->encoding
13✔
3368
        );
13✔
3369
    }
3370

3371
    /**
3372
     * Returns a new string with the prefix $substring removed, if present.
3373
     *
3374
     * EXAMPLE: <code>
3375
     * s('fòôbàř')->removeLeft('fòô'); // 'bàř'
3376
     * </code>
3377
     *
3378
     * @param string $substring <p>The prefix to remove.</p>
3379
     *
3380
     * @psalm-mutation-free
3381
     *
3382
     * @return static
3383
     *                <p>Object having a $str without the prefix $substring.</p>
3384
     */
3385
    public function removeLeft(string $substring): self
3386
    {
3387
        return static::create(
36✔
3388
            $this->utf8::remove_left($this->str, $substring, $this->encoding),
36✔
3389
            $this->encoding
36✔
3390
        );
36✔
3391
    }
3392

3393
    /**
3394
     * Returns a new string with the suffix $substring removed, if present.
3395
     *
3396
     * EXAMPLE: <code>
3397
     * s('fòôbàř')->removeRight('bàř'); // 'fòô'
3398
     * </code>
3399
     *
3400
     * @param string $substring <p>The suffix to remove.</p>
3401
     *
3402
     * @psalm-mutation-free
3403
     *
3404
     * @return static
3405
     *                <p>Object having a $str without the suffix $substring.</p>
3406
     */
3407
    public function removeRight(string $substring): self
3408
    {
3409
        return static::create(
36✔
3410
            $this->utf8::remove_right($this->str, $substring, $this->encoding),
36✔
3411
            $this->encoding
36✔
3412
        );
36✔
3413
    }
3414

3415
    /**
3416
     * Try to remove all XSS-attacks from the string.
3417
     *
3418
     * EXAMPLE: <code>
3419
     * 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 >'
3420
     * </code>
3421
     *
3422
     * @psalm-mutation-free
3423
     *
3424
     * @return static
3425
     */
3426
    public function removeXss(): self
3427
    {
3428
        /**
3429
         * @var AntiXSS|null
3430
         *
3431
         * @psalm-suppress ImpureStaticVariable
3432
         */
3433
        static $antiXss = null;
12✔
3434

3435
        if ($antiXss === null) {
12✔
3436
            $antiXss = new AntiXSS();
1✔
3437
        }
3438

3439
        /**
3440
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the anti-xss class
3441
         */
3442
        $str = $antiXss->xss_clean($this->str);
12✔
3443

3444
        return static::create($str, $this->encoding);
12✔
3445
    }
3446

3447
    /**
3448
     * Returns a repeated string given a multiplier.
3449
     *
3450
     * EXAMPLE: <code>
3451
     * s('α')->repeat(3); // 'ααα'
3452
     * </code>
3453
     *
3454
     * @param int $multiplier <p>The number of times to repeat the string.</p>
3455
     *
3456
     * @psalm-mutation-free
3457
     *
3458
     * @return static
3459
     *                <p>Object with a repeated str.</p>
3460
     */
3461
    public function repeat(int $multiplier): self
3462
    {
3463
        return static::create(
21✔
3464
            \str_repeat($this->str, $multiplier),
21✔
3465
            $this->encoding
21✔
3466
        );
21✔
3467
    }
3468

3469
    /**
3470
     * Replaces all occurrences of $search in $str by $replacement.
3471
     *
3472
     * EXAMPLE: <code>
3473
     * s('fòô bàř fòô bàř')->replace('fòô ', ''); // 'bàř bàř'
3474
     * </code>
3475
     *
3476
     * @param string $search        <p>The needle to search for.</p>
3477
     * @param string $replacement   <p>The string to replace with.</p>
3478
     * @param bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
3479
     *
3480
     * @infection-ignore-all
3481
     *
3482
     * @psalm-mutation-free
3483
     *
3484
     * @return static
3485
     *                <p>Object with the resulting $str after the replacements.</p>
3486
     */
3487
    public function replace(string $search, string $replacement, bool $caseSensitive = true): self
3488
    {
3489
        if ($search === '' && $replacement === '') {
77✔
3490
            return static::create($this->str, $this->encoding);
16✔
3491
        }
3492

3493
        if ($this->str === '' && $search === '') {
61✔
3494
            return static::create($replacement, $this->encoding);
2✔
3495
        }
3496

3497
        if ($caseSensitive) {
59✔
3498
            return static::create(
49✔
3499
                \str_replace($search, $replacement, $this->str),
49✔
3500
                $this->encoding
49✔
3501
            );
49✔
3502
        }
3503

3504
        return static::create(
10✔
3505
            $this->utf8::str_ireplace($search, $replacement, $this->str),
10✔
3506
            $this->encoding
10✔
3507
        );
10✔
3508
    }
3509

3510
    /**
3511
     * Replaces all occurrences of $search in $str by $replacement.
3512
     *
3513
     * EXAMPLE: <code>
3514
     * s('fòô bàř lall bàř')->replaceAll(['fòÔ ', 'lall'], '', false); // 'bàř bàř'
3515
     * </code>
3516
     *
3517
     * @param string[]        $search        <p>The elements to search for.</p>
3518
     * @param string|string[] $replacement   <p>The string to replace with.</p>
3519
     * @param bool            $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
3520
     *
3521
     * @psalm-mutation-free
3522
     *
3523
     * @return static
3524
     *                <p>Object with the resulting $str after the replacements.</p>
3525
     */
3526
    public function replaceAll(array $search, $replacement, bool $caseSensitive = true): self
3527
    {
3528
        if ($caseSensitive) {
62✔
3529
            return static::create(
48✔
3530
                \str_replace($search, $replacement, $this->str),
48✔
3531
                $this->encoding
48✔
3532
            );
48✔
3533
        }
3534

3535
        return static::create(
14✔
3536
            $this->utf8::str_ireplace($search, $replacement, $this->str),
14✔
3537
            $this->encoding
14✔
3538
        );
14✔
3539
    }
3540

3541
    /**
3542
     * Replaces all occurrences of $search from the beginning of string with $replacement.
3543
     *
3544
     * EXAMPLE: <code>
3545
     * s('fòô bàř fòô bàř')->replaceBeginning('fòô', ''); // ' bàř bàř'
3546
     * </code>
3547
     *
3548
     * @param string $search      <p>The string to search for.</p>
3549
     * @param string $replacement <p>The replacement.</p>
3550
     *
3551
     * @psalm-mutation-free
3552
     *
3553
     * @return static
3554
     *                <p>Object with the resulting $str after the replacements.</p>
3555
     */
3556
    public function replaceBeginning(string $search, string $replacement): self
3557
    {
3558
        return static::create(
32✔
3559
            $this->utf8::str_replace_beginning($this->str, $search, $replacement),
32✔
3560
            $this->encoding
32✔
3561
        );
32✔
3562
    }
3563

3564
    /**
3565
     * Replaces all occurrences of $search from the ending of string with $replacement.
3566
     *
3567
     * EXAMPLE: <code>
3568
     * s('fòô bàř fòô bàř')->replaceEnding('bàř', ''); // 'fòô bàř fòô '
3569
     * </code>
3570
     *
3571
     * @param string $search      <p>The string to search for.</p>
3572
     * @param string $replacement <p>The replacement.</p>
3573
     *
3574
     * @psalm-mutation-free
3575
     *
3576
     * @return static
3577
     *                <p>Object with the resulting $str after the replacements.</p>
3578
     */
3579
    public function replaceEnding(string $search, string $replacement): self
3580
    {
3581
        return static::create(
32✔
3582
            $this->utf8::str_replace_ending($this->str, $search, $replacement),
32✔
3583
            $this->encoding
32✔
3584
        );
32✔
3585
    }
3586

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

3609
    /**
3610
     * Replaces last occurrences of $search from the ending of string with $replacement.
3611
     *
3612
     * EXAMPLE: <code>
3613
     * </code>
3614
     *
3615
     * @param string $search      <p>The string to search for.</p>
3616
     * @param string $replacement <p>The replacement.</p>
3617
     *
3618
     * @psalm-mutation-free
3619
     *
3620
     * @return static
3621
     *                <p>Object with the resulting $str after the replacements.</p>
3622
     */
3623
    public function replaceLast(string $search, string $replacement): self
3624
    {
3625
        return static::create(
30✔
3626
            $this->utf8::str_replace_last($search, $replacement, $this->str),
30✔
3627
            $this->encoding
30✔
3628
        );
30✔
3629
    }
3630

3631
    /**
3632
     * Returns a reversed string. A multibyte version of strrev().
3633
     *
3634
     * EXAMPLE: <code>
3635
     * s('fòôbàř')->reverse(); // 'řàbôòf'
3636
     * </code>
3637
     *
3638
     * @psalm-mutation-free
3639
     *
3640
     * @return static
3641
     *                <p>Object with a reversed $str.</p>
3642
     */
3643
    public function reverse(): self
3644
    {
3645
        return static::create($this->utf8::strrev($this->str), $this->encoding);
15✔
3646
    }
3647

3648
    /**
3649
     * Truncates the string to a given length, while ensuring that it does not
3650
     * split words. If $substring is provided, and truncating occurs, the
3651
     * string is further truncated so that the substring may be appended without
3652
     * exceeding the desired length.
3653
     *
3654
     * EXAMPLE: <code>
3655
     * s('What are your plans today?')->safeTruncate(22, '...'); // 'What are your plans...'
3656
     * </code>
3657
     *
3658
     * @param int    $length                          <p>Desired length of the truncated string.</p>
3659
     * @param string $substring                       [optional] <p>The substring to append if it can fit. Default: ''</p>
3660
     * @param bool   $ignoreDoNotSplitWordsForOneWord
3661
     *
3662
     * @psalm-mutation-free
3663
     *
3664
     * @return static
3665
     *                <p>Object with the resulting $str after truncating.</p>
3666
     */
3667
    public function safeTruncate(
3668
        int $length,
3669
        string $substring = '',
3670
        bool $ignoreDoNotSplitWordsForOneWord = true
3671
    ): self {
3672
        return static::create(
68✔
3673
            $this->utf8::str_truncate_safe(
68✔
3674
                $this->str,
68✔
3675
                $length,
68✔
3676
                $substring,
68✔
3677
                $this->encoding,
68✔
3678
                $ignoreDoNotSplitWordsForOneWord
68✔
3679
            ),
68✔
3680
            $this->encoding
68✔
3681
        );
68✔
3682
    }
3683

3684
    /**
3685
     * Set the internal character encoding.
3686
     *
3687
     * EXAMPLE: <code>
3688
     * </code>
3689
     *
3690
     * @param string $new_encoding <p>The desired character encoding.</p>
3691
     *
3692
     * @psalm-mutation-free
3693
     *
3694
     * @return static
3695
     */
3696
    public function setInternalEncoding(string $new_encoding): self
3697
    {
3698
        return new static($this->str, $new_encoding);
1✔
3699
    }
3700

3701
    /**
3702
     * Create a sha1 hash from the current string.
3703
     *
3704
     * EXAMPLE: <code>
3705
     * </code>
3706
     *
3707
     * @psalm-mutation-free
3708
     *
3709
     * @return static
3710
     */
3711
    public function sha1(): self
3712
    {
3713
        return static::create($this->hash('sha1'), $this->encoding);
2✔
3714
    }
3715

3716
    /**
3717
     * Create a sha256 hash from the current string.
3718
     *
3719
     * EXAMPLE: <code>
3720
     * </code>
3721
     *
3722
     * @psalm-mutation-free
3723
     *
3724
     * @return static
3725
     */
3726
    public function sha256(): self
3727
    {
3728
        return static::create($this->hash('sha256'), $this->encoding);
2✔
3729
    }
3730

3731
    /**
3732
     * Create a sha512 hash from the current string.
3733
     *
3734
     * EXAMPLE: <code>
3735
     * </code>
3736
     *
3737
     * @psalm-mutation-free
3738
     *
3739
     * @return static
3740
     */
3741
    public function sha512(): self
3742
    {
3743
        return static::create($this->hash('sha512'), $this->encoding);
2✔
3744
    }
3745

3746
    /**
3747
     * Shorten the string after $length, but also after the next word.
3748
     *
3749
     * EXAMPLE: <code>
3750
     * s('this is a test')->shortenAfterWord(2, '...'); // 'this...'
3751
     * </code>
3752
     *
3753
     * @param int    $length   <p>The given length.</p>
3754
     * @param string $strAddOn [optional] <p>Default: '…'</p>
3755
     *
3756
     * @psalm-mutation-free
3757
     *
3758
     * @return static
3759
     */
3760
    public function shortenAfterWord(int $length, string $strAddOn = '…'): self
3761
    {
3762
        if ($length <= 0) {
12✔
3763
            return static::create('', $this->encoding);
4✔
3764
        }
3765

3766
        return static::create(
8✔
3767
            $this->utf8::str_limit_after_word($this->str, $length, $strAddOn),
8✔
3768
            $this->encoding
8✔
3769
        );
8✔
3770
    }
3771

3772
    /**
3773
     * A multibyte string shuffle function. It returns a string with its
3774
     * characters in random order.
3775
     *
3776
     * EXAMPLE: <code>
3777
     * s('fòôbàř')->shuffle(); // 'àôřbòf'
3778
     * </code>
3779
     *
3780
     * @return static
3781
     *                <p>Object with a shuffled $str.</p>
3782
     */
3783
    public function shuffle(): self
3784
    {
3785
        return static::create($this->utf8::str_shuffle($this->str), $this->encoding);
9✔
3786
    }
3787

3788
    /**
3789
     * Calculate the similarity between two strings.
3790
     *
3791
     * EXAMPLE: <code>
3792
     * </code>
3793
     *
3794
     * @param string $str <p>The delimiting string.</p>
3795
     *
3796
     * @psalm-mutation-free
3797
     *
3798
     * @return float
3799
     */
3800
    public function similarity(string $str): float
3801
    {
3802
        \similar_text($this->str, $str, $percent);
3✔
3803

3804
        return $percent;
3✔
3805
    }
3806

3807
    /**
3808
     * Returns the substring beginning at $start, and up to, but not including
3809
     * the index specified by $end. If $end is omitted, the function extracts
3810
     * the remaining string. If $end is negative, it is computed from the end
3811
     * of the string.
3812
     *
3813
     * EXAMPLE: <code>
3814
     * s('fòôbàř')->slice(3, -1); // 'bà'
3815
     * </code>
3816
     *
3817
     * @param int $start <p>Initial index from which to begin extraction.</p>
3818
     * @param int $end   [optional] <p>Index at which to end extraction. Default: null</p>
3819
     *
3820
     * @psalm-mutation-free
3821
     *
3822
     * @return static
3823
     *                <p>Object with its $str being the extracted substring.</p>
3824
     */
3825
    public function slice(int $start, ?int $end = null): self
3826
    {
3827
        return static::create(
51✔
3828
            $this->utf8::str_slice($this->str, $start, $end, $this->encoding),
51✔
3829
            $this->encoding
51✔
3830
        );
51✔
3831
    }
3832

3833
    /**
3834
     * Converts the string into an URL slug. This includes replacing non-ASCII
3835
     * characters with their closest ASCII equivalents, removing remaining
3836
     * non-ASCII and non-alphanumeric characters, and replacing whitespace with
3837
     * $separator. The separator defaults to a single dash, and the string
3838
     * is also converted to lowercase. The language of the source string can
3839
     * also be supplied for language-specific transliteration.
3840
     *
3841
     * EXAMPLE: <code>
3842
     * s('Using strings like fòô bàř')->slugify(); // 'using-strings-like-foo-bar'
3843
     * </code>
3844
     *
3845
     * @param string                $separator             [optional] <p>The string used to replace whitespace.</p>
3846
     * @param string                $language              [optional] <p>Language of the source string.</p>
3847
     * @param array<string, string> $replacements          [optional] <p>A map of replaceable strings.</p>
3848
     * @param bool                  $replace_extra_symbols [optional]  <p>Add some more replacements e.g. "£" with "
3849
     *                                                     pound ".</p>
3850
     * @param bool                  $use_str_to_lower      [optional] <p>Use "string to lower" for the input.</p>
3851
     * @param bool                  $use_transliterate     [optional]  <p>Use ASCII::to_transliterate() for unknown
3852
     *                                                     chars.</p>
3853
     *
3854
     * @psalm-mutation-free
3855
     *
3856
     * @return static
3857
     *                <p>Object whose $str has been converted to an URL slug.</p>
3858
     *
3859
     * @phpstan-param ASCII::*_LANGUAGE_CODE $language
3860
     *
3861
     * @noinspection PhpTooManyParametersInspection
3862
     */
3863
    public function slugify(
3864
        string $separator = '-',
3865
        string $language = 'en',
3866
        array $replacements = [],
3867
        bool $replace_extra_symbols = true,
3868
        bool $use_str_to_lower = true,
3869
        bool $use_transliterate = false
3870
    ): self {
3871
        return static::create(
18✔
3872
            $this->ascii::to_slugify(
18✔
3873
                $this->str,
18✔
3874
                $separator,
18✔
3875
                $language,
18✔
3876
                $replacements,
18✔
3877
                $replace_extra_symbols,
18✔
3878
                $use_str_to_lower,
18✔
3879
                $use_transliterate
18✔
3880
            ),
18✔
3881
            $this->encoding
18✔
3882
        );
18✔
3883
    }
3884

3885
    /**
3886
     * Convert the string to snake_case.
3887
     *
3888
     * EXAMPLE: <code>
3889
     * </code>
3890
     *
3891
     * @psalm-mutation-free
3892
     *
3893
     * @return static
3894
     */
3895
    public function snakeCase(): self
3896
    {
3897
        $words = \array_map(
4✔
3898
            static function (self $word) {
4✔
3899
                return $word->toLowerCase();
4✔
3900
            },
4✔
3901
            $this->words('', true)
4✔
3902
        );
4✔
3903

3904
        return new static(\implode('_', $words), $this->encoding);
4✔
3905
    }
3906

3907
    /**
3908
     * Convert a string to snake_case.
3909
     *
3910
     * EXAMPLE: <code>
3911
     * s('foo1 Bar')->snakeize(); // 'foo_1_bar'
3912
     * </code>
3913
     *
3914
     * @psalm-mutation-free
3915
     *
3916
     * @return static
3917
     *                <p>Object with $str in snake_case.</p>
3918
     */
3919
    public function snakeize(): self
3920
    {
3921
        return static::create(
40✔
3922
            $this->utf8::str_snakeize($this->str, $this->encoding),
40✔
3923
            $this->encoding
40✔
3924
        );
40✔
3925
    }
3926

3927
    /**
3928
     * Wrap the string after the first whitespace character after a given number
3929
     * of characters.
3930
     *
3931
     * EXAMPLE: <code>
3932
     * </code>
3933
     *
3934
     * @param int    $width <p>Number of characters at which to wrap.</p>
3935
     * @param string $break [optional] <p>Character used to break the string. | Default "\n"</p>
3936
     *
3937
     * @psalm-mutation-free
3938
     *
3939
     * @return static
3940
     */
3941
    public function softWrap(int $width, string $break = "\n"): self
3942
    {
3943
        return $this->lineWrapAfterWord($width, $break, false);
2✔
3944
    }
3945

3946
    /**
3947
     * Splits the string with the provided regular expression, returning an
3948
     * array of Stringy objects. An optional integer $limit will truncate the
3949
     * results.
3950
     *
3951
     * EXAMPLE: <code>
3952
     * s('foo,bar,baz')->split(',', 2); // ['foo', 'bar']
3953
     * </code>
3954
     *
3955
     * @param string $pattern <p>The regex with which to split the string.</p>
3956
     * @param int    $limit   [optional] <p>Maximum number of results to return. Default: -1 === no
3957
     *                        limit</p>
3958
     *
3959
     * @psalm-mutation-free
3960
     *
3961
     * @return static[]
3962
     *                  <p>An array of Stringy objects.</p>
3963
     *
3964
     * @phpstan-return array<int,static>
3965
     */
3966
    public function split(string $pattern, ?int $limit = null): array
3967
    {
3968
        if ($this->str === '') {
53✔
3969
            return [];
×
3970
        }
3971

3972
        if ($limit === null) {
53✔
3973
            $array = $this->utf8::str_split_pattern($this->str, $pattern);
9✔
3974
        } else {
3975
            $array = $this->utf8::str_split_pattern($this->str, $pattern, $limit);
44✔
3976
        }
3977

3978
        foreach ($array as &$value) {
53✔
3979
            $value = static::create($value, $this->encoding);
47✔
3980
        }
3981

3982
        /** @noinspection PhpSillyAssignmentInspection */
3983
        /** @var static[] $array */
3984
        $array = $array;
53✔
3985

3986
        return $array;
53✔
3987
    }
3988

3989
    /**
3990
     * Splits the string with the provided regular expression, returning an
3991
     * collection of Stringy objects. An optional integer $limit will truncate the
3992
     * results.
3993
     *
3994
     * EXAMPLE: <code>
3995
     * </code>
3996
     *
3997
     * @param string $pattern <p>The regex with which to split the string.</p>
3998
     * @param int    $limit   [optional] <p>Maximum number of results to return. Default: -1 === no
3999
     *                        limit</p>
4000
     *
4001
     * @psalm-mutation-free
4002
     *
4003
     * @return CollectionStringy|static[]
4004
     *                                    <p>An collection of Stringy objects.</p>
4005
     *
4006
     * @phpstan-return CollectionStringy<int,static>
4007
     */
4008
    public function splitCollection(string $pattern, ?int $limit = null): CollectionStringy
4009
    {
4010
        /**
4011
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the collection class
4012
         */
4013
        return CollectionStringy::create(
35✔
4014
            $this->split($pattern, $limit)
35✔
4015
        );
35✔
4016
    }
4017

4018
    /**
4019
     * Returns true if the string begins with $substring, false otherwise. By
4020
     * default, the comparison is case-sensitive, but can be made insensitive
4021
     * by setting $caseSensitive to false.
4022
     *
4023
     * EXAMPLE: <code>
4024
     * s('FÒÔbàřbaz')->startsWith('fòôbàř', false); // true
4025
     * </code>
4026
     *
4027
     * @param string $substring     <p>The substring to look for.</p>
4028
     * @param bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
4029
     *
4030
     * @psalm-mutation-free
4031
     *
4032
     * @return bool
4033
     *              <p>Whether or not $str starts with $substring.</p>
4034
     */
4035
    public function startsWith(string $substring, bool $caseSensitive = true): bool
4036
    {
4037
        if ($caseSensitive) {
99✔
4038
            return $this->utf8::str_starts_with($this->str, $substring);
53✔
4039
        }
4040

4041
        return $this->utf8::str_istarts_with($this->str, $substring);
46✔
4042
    }
4043

4044
    /**
4045
     * Returns true if the string begins with any of $substrings, false otherwise.
4046
     * By default the comparison is case-sensitive, but can be made insensitive by
4047
     * setting $caseSensitive to false.
4048
     *
4049
     * EXAMPLE: <code>
4050
     * s('FÒÔbàřbaz')->startsWithAny(['fòô', 'bàř'], false); // true
4051
     * </code>
4052
     *
4053
     * @param string[] $substrings    <p>Substrings to look for.</p>
4054
     * @param bool     $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
4055
     *
4056
     * @psalm-mutation-free
4057
     *
4058
     * @return bool
4059
     *              <p>Whether or not $str starts with $substring.</p>
4060
     */
4061
    public function startsWithAny(array $substrings, bool $caseSensitive = true): bool
4062
    {
4063
        if ($caseSensitive) {
36✔
4064
            return $this->utf8::str_starts_with_any($this->str, $substrings);
24✔
4065
        }
4066

4067
        return $this->utf8::str_istarts_with_any($this->str, $substrings);
12✔
4068
    }
4069

4070
    /**
4071
     * Remove one or more strings from the string.
4072
     *
4073
     * EXAMPLE: <code>
4074
     * </code>
4075
     *
4076
     * @param string|string[] $search One or more strings to be removed
4077
     *
4078
     * @psalm-mutation-free
4079
     *
4080
     * @return static
4081
     */
4082
    public function strip($search): self
4083
    {
4084
        if (\is_array($search)) {
3✔
4085
            return $this->replaceAll($search, '');
1✔
4086
        }
4087

4088
        return $this->replace($search, '');
2✔
4089
    }
4090

4091
    /**
4092
     * Strip all whitespace characters. This includes tabs and newline characters,
4093
     * as well as multibyte whitespace such as the thin space and ideographic space.
4094
     *
4095
     * EXAMPLE: <code>
4096
     * s('   Ο     συγγραφέας  ')->stripWhitespace(); // 'Οσυγγραφέας'
4097
     * </code>
4098
     *
4099
     * @psalm-mutation-free
4100
     *
4101
     * @return static
4102
     */
4103
    public function stripWhitespace(): self
4104
    {
4105
        return static::create(
36✔
4106
            $this->utf8::strip_whitespace($this->str),
36✔
4107
            $this->encoding
36✔
4108
        );
36✔
4109
    }
4110

4111
    /**
4112
     * Remove css media-queries.
4113
     *
4114
     * EXAMPLE: <code>
4115
     * s('test @media (min-width:660px){ .des-cla #mv-tiles{width:480px} } test ')->stripeCssMediaQueries(); // 'test  test '
4116
     * </code>
4117
     *
4118
     * @psalm-mutation-free
4119
     *
4120
     * @return static
4121
     */
4122
    public function stripeCssMediaQueries(): self
4123
    {
4124
        return static::create(
2✔
4125
            $this->utf8::css_stripe_media_queries($this->str),
2✔
4126
            $this->encoding
2✔
4127
        );
2✔
4128
    }
4129

4130
    /**
4131
     * Remove empty html-tag.
4132
     *
4133
     * EXAMPLE: <code>
4134
     * s('foo<h1></h1>bar')->stripeEmptyHtmlTags(); // 'foobar'
4135
     * </code>
4136
     *
4137
     * @psalm-mutation-free
4138
     *
4139
     * @return static
4140
     */
4141
    public function stripeEmptyHtmlTags(): self
4142
    {
4143
        return static::create(
2✔
4144
            $this->utf8::html_stripe_empty_tags($this->str),
2✔
4145
            $this->encoding
2✔
4146
        );
2✔
4147
    }
4148

4149
    /**
4150
     * Convert the string to StudlyCase.
4151
     *
4152
     * EXAMPLE: <code>
4153
     * </code>
4154
     *
4155
     * @psalm-mutation-free
4156
     *
4157
     * @return static
4158
     */
4159
    public function studlyCase(): self
4160
    {
4161
        $words = \array_map(
6✔
4162
            static function (self $word) {
6✔
4163
                return $word->substr(0, 1)
6✔
4164
                    ->toUpperCase()
6✔
4165
                    ->appendStringy($word->substr(1));
6✔
4166
            },
6✔
4167
            $this->words('', true)
6✔
4168
        );
6✔
4169

4170
        return new static(\implode('', $words), $this->encoding);
6✔
4171
    }
4172

4173
    /**
4174
     * Returns the substring beginning at $start with the specified $length.
4175
     * It differs from the $this->utf8::substr() function in that providing a $length of
4176
     * null will return the rest of the string, rather than an empty string.
4177
     *
4178
     * EXAMPLE: <code>
4179
     * </code>
4180
     *
4181
     * @param int $start  <p>Position of the first character to use.</p>
4182
     * @param int $length [optional] <p>Maximum number of characters used. Default: null</p>
4183
     *
4184
     * @psalm-mutation-free
4185
     *
4186
     * @return static
4187
     *                <p>Object with its $str being the substring.</p>
4188
     */
4189
    public function substr(int $start, ?int $length = null): self
4190
    {
4191
        return static::create(
41✔
4192
            $this->utf8::substr(
41✔
4193
                $this->str,
41✔
4194
                $start,
41✔
4195
                $length,
41✔
4196
                $this->encoding
41✔
4197
            ),
41✔
4198
            $this->encoding
41✔
4199
        );
41✔
4200
    }
4201

4202
    /**
4203
     * Return part of the string.
4204
     * Alias for substr()
4205
     *
4206
     * EXAMPLE: <code>
4207
     * s('fòôbàř')->substring(2, 3); // 'ôbà'
4208
     * </code>
4209
     *
4210
     * @param int $start  <p>Starting position of the substring.</p>
4211
     * @param int $length [optional] <p>Length of substring.</p>
4212
     *
4213
     * @psalm-mutation-free
4214
     *
4215
     * @return static
4216
     */
4217
    public function substring(int $start, ?int $length = null): self
4218
    {
4219
        return $this->substr($start, $length);
4✔
4220
    }
4221

4222
    /**
4223
     * Gets the substring after (or before via "$beforeNeedle") the first occurrence of the "$needle".
4224
     * If no match is found returns new empty Stringy object.
4225
     *
4226
     * EXAMPLE: <code>
4227
     * </code>
4228
     *
4229
     * @param string $needle       <p>The string to look for.</p>
4230
     * @param bool   $beforeNeedle [optional] <p>Default: false</p>
4231
     *
4232
     * @psalm-mutation-free
4233
     *
4234
     * @return static
4235
     */
4236
    public function substringOf(string $needle, bool $beforeNeedle = false): self
4237
    {
4238
        return static::create(
5✔
4239
            $this->utf8::str_substr_first(
5✔
4240
                $this->str,
5✔
4241
                $needle,
5✔
4242
                $beforeNeedle,
5✔
4243
                $this->encoding
5✔
4244
            ),
5✔
4245
            $this->encoding
5✔
4246
        );
5✔
4247
    }
4248

4249
    /**
4250
     * Gets the substring after (or before via "$beforeNeedle") the first occurrence of the "$needle".
4251
     * If no match is found returns new empty Stringy object.
4252
     *
4253
     * EXAMPLE: <code>
4254
     * </code>
4255
     *
4256
     * @param string $needle       <p>The string to look for.</p>
4257
     * @param bool   $beforeNeedle [optional] <p>Default: false</p>
4258
     *
4259
     * @psalm-mutation-free
4260
     *
4261
     * @return static
4262
     */
4263
    public function substringOfIgnoreCase(string $needle, bool $beforeNeedle = false): self
4264
    {
4265
        return static::create(
5✔
4266
            $this->utf8::str_isubstr_first(
5✔
4267
                $this->str,
5✔
4268
                $needle,
5✔
4269
                $beforeNeedle,
5✔
4270
                $this->encoding
5✔
4271
            ),
5✔
4272
            $this->encoding
5✔
4273
        );
5✔
4274
    }
4275

4276
    /**
4277
     * Surrounds $str with the given substring.
4278
     *
4279
     * EXAMPLE: <code>
4280
     * s(' ͜ ')->surround('ʘ'); // 'ʘ ͜ ʘ'
4281
     * </code>
4282
     *
4283
     * @param string $substring <p>The substring to add to both sides.</P>
4284
     *
4285
     * @psalm-mutation-free
4286
     *
4287
     * @return static
4288
     *                <p>Object whose $str had the substring both prepended and appended.</p>
4289
     */
4290
    public function surround(string $substring): self
4291
    {
4292
        return static::create(
15✔
4293
            $substring . $this->str . $substring,
15✔
4294
            $this->encoding
15✔
4295
        );
15✔
4296
    }
4297

4298
    /**
4299
     * Returns a case swapped version of the string.
4300
     *
4301
     * EXAMPLE: <code>
4302
     * s('Ντανιλ')->swapCase(); // 'νΤΑΝΙΛ'
4303
     * </code>
4304
     *
4305
     * @psalm-mutation-free
4306
     *
4307
     * @return static
4308
     *                <p>Object whose $str has each character's case swapped.</P>
4309
     */
4310
    public function swapCase(): self
4311
    {
4312
        return static::create(
15✔
4313
            $this->utf8::swapCase($this->str, $this->encoding),
15✔
4314
            $this->encoding
15✔
4315
        );
15✔
4316
    }
4317

4318
    /**
4319
     * Returns a string with smart quotes, ellipsis characters, and dashes from
4320
     * Windows-1252 (commonly used in Word documents) replaced by their ASCII
4321
     * equivalents.
4322
     *
4323
     * EXAMPLE: <code>
4324
     * s('“I see…”')->tidy(); // '"I see..."'
4325
     * </code>
4326
     *
4327
     * @psalm-mutation-free
4328
     *
4329
     * @return static
4330
     *                <p>Object whose $str has those characters removed.</p>
4331
     */
4332
    public function tidy(): self
4333
    {
4334
        return static::create(
12✔
4335
            $this->ascii::normalize_msword($this->str),
12✔
4336
            $this->encoding
12✔
4337
        );
12✔
4338
    }
4339

4340
    /**
4341
     * Returns a trimmed string with the first letter of each word capitalized.
4342
     * Also accepts an array, $ignore, allowing you to list words not to be
4343
     * capitalized.
4344
     *
4345
     * EXAMPLE: <code>
4346
     * $ignore = ['at', 'by', 'for', 'in', 'of', 'on', 'out', 'to', 'the'];
4347
     * s('i like to watch television')->titleize($ignore); // 'I Like to Watch Television'
4348
     * </code>
4349
     *
4350
     * @param string[]|null $ignore            [optional] <p>An array of words not to capitalize or null.
4351
     *                                         Default: null</p>
4352
     * @param string|null   $word_define_chars [optional] <p>An string of chars that will be used as whitespace
4353
     *                                         separator === words.</p>
4354
     * @param string|null   $language          [optional] <p>Language of the source string.</p>
4355
     *
4356
     * @psalm-mutation-free
4357
     *
4358
     * @return static
4359
     *                <p>Object with a titleized $str.</p>
4360
     */
4361
    public function titleize(
4362
        ?array $ignore = null,
4363
        ?string $word_define_chars = null,
4364
        ?string $language = null
4365
    ): self {
4366
        return static::create(
25✔
4367
            $this->utf8::str_titleize(
25✔
4368
                $this->str,
25✔
4369
                $ignore,
25✔
4370
                $this->encoding,
25✔
4371
                false,
25✔
4372
                $language,
25✔
4373
                false,
25✔
4374
                true,
25✔
4375
                $word_define_chars
25✔
4376
            ),
25✔
4377
            $this->encoding
25✔
4378
        );
25✔
4379
    }
4380

4381
    /**
4382
     * Returns a trimmed string in proper title case: Also accepts an array, $ignore, allowing you to list words not to
4383
     * be capitalized.
4384
     *
4385
     * EXAMPLE: <code>
4386
     * </code>
4387
     *
4388
     * Adapted from John Gruber's script.
4389
     *
4390
     * @see https://gist.github.com/gruber/9f9e8650d68b13ce4d78
4391
     *
4392
     * @param string[] $ignore <p>An array of words not to capitalize.</p>
4393
     *
4394
     * @psalm-mutation-free
4395
     *
4396
     * @return static
4397
     *                <p>Object with a titleized $str</p>
4398
     */
4399
    public function titleizeForHumans(array $ignore = []): self
4400
    {
4401
        return static::create(
70✔
4402
            $this->utf8::str_titleize_for_humans(
70✔
4403
                $this->str,
70✔
4404
                $ignore,
70✔
4405
                $this->encoding
70✔
4406
            ),
70✔
4407
            $this->encoding
70✔
4408
        );
70✔
4409
    }
4410

4411
    /**
4412
     * Returns an ASCII version of the string. A set of non-ASCII characters are
4413
     * replaced with their closest ASCII counterparts, and the rest are removed
4414
     * by default. The language or locale of the source string can be supplied
4415
     * for language-specific transliteration in any of the following formats:
4416
     * en, en_GB, or en-GB. For example, passing "de" results in "äöü" mapping
4417
     * to "aeoeue" rather than "aou" as in other languages.
4418
     *
4419
     * EXAMPLE: <code>
4420
     * s('fòôbàř')->toAscii(); // 'foobar'
4421
     * </code>
4422
     *
4423
     * @param string $language          [optional] <p>Language of the source string.</p>
4424
     * @param bool   $removeUnsupported [optional] <p>Whether or not to remove the
4425
     *                                  unsupported characters.</p>
4426
     *
4427
     * @psalm-mutation-free
4428
     *
4429
     * @return static
4430
     *                <p>Object whose $str contains only ASCII characters.</p>
4431
     *
4432
     * @phpstan-param ASCII::*_LANGUAGE_CODE $language
4433
     */
4434
    public function toAscii(string $language = 'en', bool $removeUnsupported = true): self
4435
    {
4436
        return static::create(
24✔
4437
            $this->ascii::to_ascii(
24✔
4438
                $this->str,
24✔
4439
                $language,
24✔
4440
                $removeUnsupported
24✔
4441
            ),
24✔
4442
            $this->encoding
24✔
4443
        );
24✔
4444
    }
4445

4446
    /**
4447
     * Returns a boolean representation of the given logical string value.
4448
     * For example, <strong>'true', '1', 'on' and 'yes'</strong> will return true. <strong>'false', '0',
4449
     * 'off', and 'no'</strong> will return false. In all instances, case is ignored.
4450
     * For other numeric strings, their sign will determine the return value.
4451
     * In addition, blank strings consisting of only whitespace will return
4452
     * false. For all other strings, the return value is a result of a
4453
     * boolean cast.
4454
     *
4455
     * EXAMPLE: <code>
4456
     * s('OFF')->toBoolean(); // false
4457
     * </code>
4458
     *
4459
     * @psalm-mutation-free
4460
     *
4461
     * @return bool
4462
     *              <p>A boolean value for the string.</p>
4463
     */
4464
    public function toBoolean(): bool
4465
    {
4466
        /**
4467
         * @psalm-suppress ArgumentTypeCoercion -> maybe the string looks like an int ;)
4468
         * @phpstan-ignore-next-line
4469
         */
4470
        return $this->utf8::to_boolean($this->str);
45✔
4471
    }
4472

4473
    /**
4474
     * Converts all characters in the string to lowercase.
4475
     *
4476
     * EXAMPLE: <code>
4477
     * s('FÒÔBÀŘ')->toLowerCase(); // 'fòôbàř'
4478
     * </code>
4479
     *
4480
     * @param bool        $tryToKeepStringLength [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
4481
     * @param string|null $lang                  [optional] <p>Set the language for special cases: az, el, lt, tr</p>
4482
     *
4483
     * @psalm-mutation-free
4484
     *
4485
     * @return static
4486
     *                <p>Object with all characters of $str being lowercase.</p>
4487
     */
4488
    public function toLowerCase($tryToKeepStringLength = false, $lang = null): self
4489
    {
4490
        return static::create(
24✔
4491
            $this->utf8::strtolower(
24✔
4492
                $this->str,
24✔
4493
                $this->encoding,
24✔
4494
                false,
24✔
4495
                $lang,
24✔
4496
                $tryToKeepStringLength
24✔
4497
            ),
24✔
4498
            $this->encoding
24✔
4499
        );
24✔
4500
    }
4501

4502
    /**
4503
     * Converts each tab in the string to some number of spaces, as defined by
4504
     * $tabLength. By default, each tab is converted to 4 consecutive spaces.
4505
     *
4506
     * EXAMPLE: <code>
4507
     * s(' String speech = "Hi"')->toSpaces(); // '    String speech = "Hi"'
4508
     * </code>
4509
     *
4510
     * @param int $tabLength [optional] <p>Number of spaces to replace each tab with. Default: 4</p>
4511
     *
4512
     * @psalm-mutation-free
4513
     *
4514
     * @return static
4515
     *                <p>Object whose $str has had tabs switched to spaces.</p>
4516
     */
4517
    public function toSpaces(int $tabLength = 4): self
4518
    {
4519
        if ($tabLength === 4) {
19✔
4520
            $tab = '    ';
10✔
4521
        } elseif ($tabLength === 2) {
10✔
4522
            $tab = '  ';
4✔
4523
        } else {
4524
            $tab = \str_repeat(' ', $tabLength);
7✔
4525
        }
4526

4527
        return static::create(
19✔
4528
            \str_replace("\t", $tab, $this->str),
19✔
4529
            $this->encoding
19✔
4530
        );
19✔
4531
    }
4532

4533
    /**
4534
     * Return Stringy object as string, but you can also use (string) for automatically casting the object into a
4535
     * string.
4536
     *
4537
     * EXAMPLE: <code>
4538
     * s('fòôbàř')->toString(); // 'fòôbàř'
4539
     * </code>
4540
     *
4541
     * @psalm-mutation-free
4542
     *
4543
     * @return string
4544
     */
4545
    public function toString(): string
4546
    {
4547
        return (string) $this->str;
2,242✔
4548
    }
4549

4550
    /**
4551
     * Converts each occurrence of some consecutive number of spaces, as
4552
     * defined by $tabLength, to a tab. By default, each 4 consecutive spaces
4553
     * are converted to a tab.
4554
     *
4555
     * EXAMPLE: <code>
4556
     * s('    fòô    bàř')->toTabs(); // '   fòô bàř'
4557
     * </code>
4558
     *
4559
     * @param int $tabLength [optional] <p>Number of spaces to replace with a tab. Default: 4</p>
4560
     *
4561
     * @psalm-mutation-free
4562
     *
4563
     * @return static
4564
     *                <p>Object whose $str has had spaces switched to tabs.</p>
4565
     */
4566
    public function toTabs(int $tabLength = 4): self
4567
    {
4568
        if ($tabLength === 4) {
16✔
4569
            $tab = '    ';
10✔
4570
        } elseif ($tabLength === 2) {
7✔
4571
            $tab = '  ';
4✔
4572
        } else {
4573
            $tab = \str_repeat(' ', $tabLength);
4✔
4574
        }
4575

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

4582
    /**
4583
     * Converts the first character of each word in the string to uppercase
4584
     * and all other chars to lowercase.
4585
     *
4586
     * EXAMPLE: <code>
4587
     * s('fòô bàř')->toTitleCase(); // 'Fòô Bàř'
4588
     * </code>
4589
     *
4590
     * @psalm-mutation-free
4591
     *
4592
     * @return static
4593
     *                <p>Object with all characters of $str being title-cased.</p>
4594
     */
4595
    public function toTitleCase(): self
4596
    {
4597
        return static::create(
15✔
4598
            $this->utf8::titlecase($this->str, $this->encoding),
15✔
4599
            $this->encoding
15✔
4600
        );
15✔
4601
    }
4602

4603
    /**
4604
     * Returns an ASCII version of the string. A set of non-ASCII characters are
4605
     * replaced with their closest ASCII counterparts, and the rest are removed
4606
     * unless instructed otherwise.
4607
     *
4608
     * EXAMPLE: <code>
4609
     * </code>
4610
     *
4611
     * @param bool   $strict  [optional] <p>Use "transliterator_transliterate()" from PHP-Intl | WARNING: bad
4612
     *                        performance | Default: false</p>
4613
     * @param string $unknown [optional] <p>Character use if character unknown. (default is ?)</p>
4614
     *
4615
     * @psalm-mutation-free
4616
     *
4617
     * @return static
4618
     *                <p>Object whose $str contains only ASCII characters.</p>
4619
     */
4620
    public function toTransliterate(bool $strict = false, string $unknown = '?'): self
4621
    {
4622
        return static::create(
34✔
4623
            $this->ascii::to_transliterate($this->str, $unknown, $strict),
34✔
4624
            $this->encoding
34✔
4625
        );
34✔
4626
    }
4627

4628
    /**
4629
     * Converts all characters in the string to uppercase.
4630
     *
4631
     * EXAMPLE: <code>
4632
     * s('fòôbàř')->toUpperCase(); // 'FÒÔBÀŘ'
4633
     * </code>
4634
     *
4635
     * @param bool        $tryToKeepStringLength [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
4636
     * @param string|null $lang                  [optional] <p>Set the language for special cases: az, el, lt, tr</p>
4637
     *
4638
     * @psalm-mutation-free
4639
     *
4640
     * @return static
4641
     *                <p>Object with all characters of $str being uppercase.</p>
4642
     */
4643
    public function toUpperCase($tryToKeepStringLength = false, $lang = null): self
4644
    {
4645
        return static::create(
28✔
4646
            $this->utf8::strtoupper($this->str, $this->encoding, false, $lang, $tryToKeepStringLength),
28✔
4647
            $this->encoding
28✔
4648
        );
28✔
4649
    }
4650

4651
    /**
4652
     * Returns a string with whitespace removed from the start and end of the
4653
     * string. Supports the removal of unicode whitespace. Accepts an optional
4654
     * string of characters to strip instead of the defaults.
4655
     *
4656
     * EXAMPLE: <code>
4657
     * s('  fòôbàř  ')->trim(); // 'fòôbàř'
4658
     * </code>
4659
     *
4660
     * @param string $chars [optional] <p>String of characters to strip. Default: null</p>
4661
     *
4662
     * @psalm-mutation-free
4663
     *
4664
     * @return static
4665
     *                <p>Object with a trimmed $str.</p>
4666
     */
4667
    public function trim(?string $chars = null): self
4668
    {
4669
        return static::create(
36✔
4670
            $this->utf8::trim($this->str, $chars),
36✔
4671
            $this->encoding
36✔
4672
        );
36✔
4673
    }
4674

4675
    /**
4676
     * Returns a string with whitespace removed from the start of the string.
4677
     * Supports the removal of unicode whitespace. Accepts an optional
4678
     * string of characters to strip instead of the defaults.
4679
     *
4680
     * EXAMPLE: <code>
4681
     * s('  fòôbàř  ')->trimLeft(); // 'fòôbàř  '
4682
     * </code>
4683
     *
4684
     * @param string $chars [optional] <p>Optional string of characters to strip. Default: null</p>
4685
     *
4686
     * @psalm-mutation-free
4687
     *
4688
     * @return static
4689
     *                <p>Object with a trimmed $str.</p>
4690
     */
4691
    public function trimLeft(?string $chars = null): self
4692
    {
4693
        return static::create(
39✔
4694
            $this->utf8::ltrim($this->str, $chars),
39✔
4695
            $this->encoding
39✔
4696
        );
39✔
4697
    }
4698

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

4723
    /**
4724
     * Truncates the string to a given length. If $substring is provided, and
4725
     * truncating occurs, the string is further truncated so that the substring
4726
     * may be appended without exceeding the desired length.
4727
     *
4728
     * EXAMPLE: <code>
4729
     * s('What are your plans today?')->truncate(19, '...'); // 'What are your pl...'
4730
     * </code>
4731
     *
4732
     * @param int    $length    <p>Desired length of the truncated string.</p>
4733
     * @param string $substring [optional] <p>The substring to append if it can fit. Default: ''</p>
4734
     *
4735
     * @psalm-mutation-free
4736
     *
4737
     * @return static
4738
     *                <p>Object with the resulting $str after truncating.</p>
4739
     */
4740
    public function truncate(int $length, string $substring = ''): self
4741
    {
4742
        return static::create(
66✔
4743
            $this->utf8::str_truncate($this->str, $length, $substring, $this->encoding),
66✔
4744
            $this->encoding
66✔
4745
        );
66✔
4746
    }
4747

4748
    /**
4749
     * Returns a lowercase and trimmed string separated by underscores.
4750
     * Underscores are inserted before uppercase characters (with the exception
4751
     * of the first character of the string), and in place of spaces as well as
4752
     * dashes.
4753
     *
4754
     * EXAMPLE: <code>
4755
     * s('TestUCase')->underscored(); // 'test_u_case'
4756
     * </code>
4757
     *
4758
     * @psalm-mutation-free
4759
     *
4760
     * @return static
4761
     *                <p>Object with an underscored $str.</p>
4762
     */
4763
    public function underscored(): self
4764
    {
4765
        return $this->delimit('_');
48✔
4766
    }
4767

4768
    /**
4769
     * Returns an UpperCamelCase version of the supplied string. It trims
4770
     * surrounding spaces, capitalizes letters following digits, spaces, dashes
4771
     * and underscores, and removes spaces, dashes, underscores.
4772
     *
4773
     * EXAMPLE: <code>
4774
     * s('Upper Camel-Case')->upperCamelize(); // 'UpperCamelCase'
4775
     * </code>
4776
     *
4777
     * @psalm-mutation-free
4778
     *
4779
     * @return static
4780
     *                <p>Object with $str in UpperCamelCase.</p>
4781
     */
4782
    public function upperCamelize(): self
4783
    {
4784
        return static::create(
49✔
4785
            $this->utf8::str_upper_camelize($this->str, $this->encoding),
49✔
4786
            $this->encoding
49✔
4787
        );
49✔
4788
    }
4789

4790
    /**
4791
     * Converts the first character of the supplied string to upper case.
4792
     *
4793
     * EXAMPLE: <code>
4794
     * s('σ foo')->upperCaseFirst(); // 'Σ foo'
4795
     * </code>
4796
     *
4797
     * @psalm-mutation-free
4798
     *
4799
     * @return static
4800
     *                <p>Object with the first character of $str being upper case.</p>
4801
     */
4802
    public function upperCaseFirst(): self
4803
    {
4804
        return static::create($this->utf8::ucfirst($this->str, $this->encoding), $this->encoding);
18✔
4805
    }
4806

4807
    /**
4808
     * Simple url-decoding.
4809
     *
4810
     * e.g:
4811
     * 'test+test' => 'test test'
4812
     *
4813
     * EXAMPLE: <code>
4814
     * </code>
4815
     *
4816
     * @psalm-mutation-free
4817
     *
4818
     * @return static
4819
     */
4820
    public function urlDecode(): self
4821
    {
4822
        return static::create(\urldecode($this->str));
1✔
4823
    }
4824

4825
    /**
4826
     * Multi url-decoding + decode HTML entity + fix urlencoded-win1252-chars.
4827
     *
4828
     * e.g:
4829
     * 'test+test'                     => 'test test'
4830
     * 'D&#252;sseldorf'               => 'Düsseldorf'
4831
     * 'D%FCsseldorf'                  => 'Düsseldorf'
4832
     * 'D&#xFC;sseldorf'               => 'Düsseldorf'
4833
     * 'D%26%23xFC%3Bsseldorf'         => 'Düsseldorf'
4834
     * 'Düsseldorf'                   => 'Düsseldorf'
4835
     * 'D%C3%BCsseldorf'               => 'Düsseldorf'
4836
     * 'D%C3%83%C2%BCsseldorf'         => 'Düsseldorf'
4837
     * 'D%25C3%2583%25C2%25BCsseldorf' => 'Düsseldorf'
4838
     *
4839
     * EXAMPLE: <code>
4840
     * </code>
4841
     *
4842
     * @psalm-mutation-free
4843
     *
4844
     * @return static
4845
     */
4846
    public function urlDecodeMulti(): self
4847
    {
4848
        return static::create($this->utf8::urldecode($this->str));
1✔
4849
    }
4850

4851
    /**
4852
     * Simple url-decoding.
4853
     *
4854
     * e.g:
4855
     * 'test+test' => 'test+test
4856
     *
4857
     * EXAMPLE: <code>
4858
     * </code>
4859
     *
4860
     * @psalm-mutation-free
4861
     *
4862
     * @return static
4863
     */
4864
    public function urlDecodeRaw(): self
4865
    {
4866
        return static::create(\rawurldecode($this->str));
1✔
4867
    }
4868

4869
    /**
4870
     * Multi url-decoding + decode HTML entity + fix urlencoded-win1252-chars.
4871
     *
4872
     * e.g:
4873
     * 'test+test'                     => 'test+test'
4874
     * 'D&#252;sseldorf'               => 'Düsseldorf'
4875
     * 'D%FCsseldorf'                  => 'Düsseldorf'
4876
     * 'D&#xFC;sseldorf'               => 'Düsseldorf'
4877
     * 'D%26%23xFC%3Bsseldorf'         => 'Düsseldorf'
4878
     * 'Düsseldorf'                   => 'Düsseldorf'
4879
     * 'D%C3%BCsseldorf'               => 'Düsseldorf'
4880
     * 'D%C3%83%C2%BCsseldorf'         => 'Düsseldorf'
4881
     * 'D%25C3%2583%25C2%25BCsseldorf' => 'Düsseldorf'
4882
     *
4883
     * EXAMPLE: <code>
4884
     * </code>
4885
     *
4886
     * @psalm-mutation-free
4887
     *
4888
     * @return static
4889
     */
4890
    public function urlDecodeRawMulti(): self
4891
    {
4892
        return static::create($this->utf8::rawurldecode($this->str));
1✔
4893
    }
4894

4895
    /**
4896
     * Simple url-encoding.
4897
     *
4898
     * e.g:
4899
     * 'test test' => 'test+test'
4900
     *
4901
     * EXAMPLE: <code>
4902
     * </code>
4903
     *
4904
     * @psalm-mutation-free
4905
     *
4906
     * @return static
4907
     */
4908
    public function urlEncode(): self
4909
    {
4910
        return static::create(\urlencode($this->str));
1✔
4911
    }
4912

4913
    /**
4914
     * Simple url-encoding.
4915
     *
4916
     * e.g:
4917
     * 'test test' => 'test%20test'
4918
     *
4919
     * EXAMPLE: <code>
4920
     * </code>
4921
     *
4922
     * @psalm-mutation-free
4923
     *
4924
     * @return static
4925
     */
4926
    public function urlEncodeRaw(): self
4927
    {
4928
        return static::create(\rawurlencode($this->str));
1✔
4929
    }
4930

4931
    /**
4932
     * Converts the string into an URL slug. This includes replacing non-ASCII
4933
     * characters with their closest ASCII equivalents, removing remaining
4934
     * non-ASCII and non-alphanumeric characters, and replacing whitespace with
4935
     * $separator. The separator defaults to a single dash, and the string
4936
     * is also converted to lowercase.
4937
     *
4938
     * EXAMPLE: <code>
4939
     * s('Using strings like fòô bàř - 1$')->urlify(); // 'using-strings-like-foo-bar-1-dollar'
4940
     * </code>
4941
     *
4942
     * @param string                $separator    [optional] <p>The string used to replace whitespace. Default: '-'</p>
4943
     * @param string                $language     [optional] <p>The language for the url. Default: 'en'</p>
4944
     * @param array<string, string> $replacements [optional] <p>A map of replaceable strings.</p>
4945
     * @param bool                  $strToLower   [optional] <p>string to lower. Default: true</p>
4946
     *
4947
     * @psalm-mutation-free
4948
     *
4949
     * @return static
4950
     *                <p>Object whose $str has been converted to an URL slug.</p>
4951
     *
4952
     * @phpstan-param ASCII::*_LANGUAGE_CODE $language
4953
     *
4954
     * @psalm-suppress ImpureMethodCall :/
4955
     */
4956
    public function urlify(
4957
        string $separator = '-',
4958
        string $language = 'en',
4959
        array $replacements = [],
4960
        bool $strToLower = true
4961
    ): self {
4962
        // init
4963
        $str = $this->str;
32✔
4964

4965
        foreach ($replacements as $from => $to) {
32✔
4966
            $str = \str_replace($from, $to, $str);
32✔
4967
        }
4968

4969
        return static::create(
32✔
4970
            URLify::slug(
32✔
4971
                $str,
32✔
4972
                $language,
32✔
4973
                $separator,
32✔
4974
                $strToLower
32✔
4975
            ),
32✔
4976
            $this->encoding
32✔
4977
        );
32✔
4978
    }
4979

4980
    /**
4981
     * Converts the string into an valid UTF-8 string.
4982
     *
4983
     * EXAMPLE: <code>
4984
     * s('Düsseldorf')->utf8ify(); // 'Düsseldorf'
4985
     * </code>
4986
     *
4987
     * @psalm-mutation-free
4988
     *
4989
     * @return static
4990
     */
4991
    public function utf8ify(): self
4992
    {
4993
        return static::create($this->utf8::cleanup($this->str), $this->encoding);
2✔
4994
    }
4995

4996
    /**
4997
     * Convert a string into an array of words.
4998
     *
4999
     * EXAMPLE: <code>
5000
     * </code>
5001
     *
5002
     * @param string   $char_list           [optional] <p>Additional chars for the definition of "words".</p>
5003
     * @param bool     $remove_empty_values [optional] <p>Remove empty values.</p>
5004
     * @param int|null $remove_short_values [optional] <p>The min. string length or null to disable</p>
5005
     *
5006
     * @psalm-mutation-free
5007
     *
5008
     * @return static[]
5009
     *
5010
     * @phpstan-return array<int,static>
5011
     */
5012
    public function words(
5013
        string $char_list = '',
5014
        bool $remove_empty_values = false,
5015
        ?int $remove_short_values = null
5016
    ): array {
5017
        if ($remove_short_values === null) {
16✔
5018
            $strings = $this->utf8::str_to_words(
16✔
5019
                $this->str,
16✔
5020
                $char_list,
16✔
5021
                $remove_empty_values
16✔
5022
            );
16✔
5023
        } else {
5024
            $strings = $this->utf8::str_to_words(
2✔
5025
                $this->str,
2✔
5026
                $char_list,
2✔
5027
                $remove_empty_values,
2✔
5028
                $remove_short_values
2✔
5029
            );
2✔
5030
        }
5031

5032
        /** @noinspection AlterInForeachInspection */
5033
        foreach ($strings as &$string) {
16✔
5034
            $string = static::create($string);
16✔
5035
        }
5036

5037
        /** @noinspection PhpSillyAssignmentInspection */
5038
        /** @var static[] $strings */
5039
        $strings = $strings;
16✔
5040

5041
        return $strings;
16✔
5042
    }
5043

5044
    /**
5045
     * Convert a string into an collection of words.
5046
     *
5047
     * EXAMPLE: <code>
5048
     * S::create('中文空白 oöäü#s')->wordsCollection('#', true)->toStrings(); // ['中文空白', 'oöäü#s']
5049
     * </code>
5050
     *
5051
     * @param string   $char_list           [optional] <p>Additional chars for the definition of "words".</p>
5052
     * @param bool     $remove_empty_values [optional] <p>Remove empty values.</p>
5053
     * @param int|null $remove_short_values [optional] <p>The min. string length or null to disable</p>
5054
     *
5055
     * @psalm-mutation-free
5056
     *
5057
     * @return CollectionStringy|static[]
5058
     *                                    <p>An collection of Stringy objects.</p>
5059
     *
5060
     * @phpstan-return CollectionStringy<int,static>
5061
     */
5062
    public function wordsCollection(
5063
        string $char_list = '',
5064
        bool $remove_empty_values = false,
5065
        ?int $remove_short_values = null
5066
    ): CollectionStringy {
5067
        /**
5068
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the collection class
5069
         */
5070
        return CollectionStringy::create(
2✔
5071
            $this->words(
2✔
5072
                $char_list,
2✔
5073
                $remove_empty_values,
2✔
5074
                $remove_short_values
2✔
5075
            )
2✔
5076
        );
2✔
5077
    }
5078

5079
    /**
5080
     * Surrounds $str with the given substring.
5081
     *
5082
     * EXAMPLE: <code>
5083
     * </code>
5084
     *
5085
     * @param string $substring <p>The substring to add to both sides.</P>
5086
     *
5087
     * @psalm-mutation-free
5088
     *
5089
     * @return static
5090
     *                <p>Object whose $str had the substring both prepended and appended.</p>
5091
     */
5092
    public function wrap(string $substring): self
5093
    {
5094
        return $this->surround($substring);
10✔
5095
    }
5096

5097
    /**
5098
     * Returns the replacements for the toAscii() method.
5099
     *
5100
     * @psalm-mutation-free
5101
     *
5102
     * @return array<string, array<int, string>>
5103
     *                                           <p>An array of replacements.</p>
5104
     *
5105
     * @deprecated   this is only here for backward-compatibly reasons
5106
     */
5107
    protected function charsArray(): array
5108
    {
5109
        return $this->ascii::charsArrayWithMultiLanguageValues();
1✔
5110
    }
5111

5112
    /**
5113
     * Returns true if $str matches the supplied pattern, false otherwise.
5114
     *
5115
     * @param string $pattern <p>Regex pattern to match against.</p>
5116
     *
5117
     * @psalm-mutation-free
5118
     *
5119
     * @return bool
5120
     *              <p>Whether or not $str matches the pattern.</p>
5121
     */
5122
    protected function matchesPattern(string $pattern): bool
5123
    {
5124
        return $this->utf8::str_matches_pattern($this->str, $pattern);
25✔
5125
    }
5126
}
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