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

voku / Stringy / 36025920523

24 Sep 2026 04:14PM UTC coverage: 97.891%. Remained the same
36025920523

Pull #57

github

web-flow
Merge 76607567f into 164ef293a
Pull Request #57: chore: raise covered infection MSI threshold

41 of 42 new or added lines in 1 file covered. (97.62%)

13 existing lines in 1 file now uncovered.

1021 of 1043 relevant lines covered (97.89%)

70.02 hits per line

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

97.86
/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,714✔
83
            throw new \InvalidArgumentException(
3✔
84
                'Passed value cannot be an array'
3✔
85
            );
3✔
86
        }
87

88
        if (
89
            \is_object($str)
3,711✔
90
            &&
91
            !\method_exists($str, '__toString')
3,711✔
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,708✔
99

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

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

112
        if ($encoding !== 'UTF-8') {
3,708✔
113
            $this->encoding = $this->utf8::normalize_encoding($encoding, 'UTF-8');
2,524✔
114
        } else {
115
            $this->encoding = $encoding;
2,768✔
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,168✔
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
        // fast path for UTF-8; the generic path below returns the same result
398
        // @infection-ignore-all
399
        if ($this->encoding === 'UTF-8') {
32✔
400
            return static::create((string) \mb_substr($this->str, $index, 1), $this->encoding);
32✔
401
        }
402

403
        return static::create($this->utf8::substr($this->str, $index, 1, $this->encoding), $this->encoding);
×
404
    }
405

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

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

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

469
    /**
470
     * Return part of the string occurring before a specific string.
471
     *
472
     * EXAMPLE: <code>
473
     * </code>
474
     *
475
     * @param string $string <p>The delimiting string.</p>
476
     *
477
     * @psalm-mutation-free
478
     *
479
     * @return static
480
     */
481
    public function before(string $string): self
482
    {
483
        // only the first part is used, so any limit >= 1 gives the same result
484
        // @infection-ignore-all
485
        $strArray = UTF8::str_split_pattern(
6✔
486
            $this->str,
6✔
487
            $string,
6✔
488
            1
6✔
489
        );
6✔
490

491
        return new static(
6✔
492
            $strArray[0] ?? '',
6✔
493
            $this->encoding
6✔
494
        );
6✔
495
    }
496

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

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

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

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

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

626
        return static::create($str, $this->encoding);
48✔
627
    }
628

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

650
        return static::create(
2✔
651
            $str,
2✔
652
            $this->encoding
2✔
653
        );
2✔
654
    }
655

656
    /**
657
     * Returns a camelCase version of the string. Trims surrounding spaces,
658
     * capitalizes letters following digits, spaces, dashes and underscores,
659
     * and removes spaces, dashes, as well as underscores.
660
     *
661
     * EXAMPLE: <code>
662
     * s('Camel-Case')->camelize(); // 'camelCase'
663
     * </code>
664
     *
665
     * @psalm-mutation-free
666
     *
667
     * @return static
668
     *                <p>Object with $str in camelCase.</p>
669
     */
670
    public function camelize(): self
671
    {
672
        return static::create(
67✔
673
            $this->utf8::str_camelize($this->str, $this->encoding),
67✔
674
            $this->encoding
67✔
675
        );
67✔
676
    }
677

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

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

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

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

741
        foreach ($chunks as &$value) {
15✔
742
            $value = static::create($value, $this->encoding);
13✔
743
        }
744

745
        /** @noinspection PhpSillyAssignmentInspection */
746
        /** @var static[] $chunks */
747
        $chunks = $chunks;
15✔
748

749
        return $chunks;
15✔
750
    }
751

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1071
    /**
1072
     * Encode the given string into the given $encoding + set the internal character encoding.
1073
     *
1074
     * EXAMPLE: <code>
1075
     * </code>
1076
     *
1077
     * @param string $new_encoding         <p>The desired character encoding.</p>
1078
     * @param bool   $auto_detect_encoding [optional] <p>Auto-detect the current string-encoding</p>
1079
     *
1080
     * @psalm-mutation-free
1081
     *
1082
     * @return static
1083
     */
1084
    public function encode(string $new_encoding, bool $auto_detect_encoding = false): self
1085
    {
1086
        $str = $this->utf8::encode(
3✔
1087
            $new_encoding,
3✔
1088
            $this->str,
3✔
1089
            $auto_detect_encoding,
3✔
1090
            $auto_detect_encoding ? '' : $this->encoding
3✔
1091
        );
3✔
1092

1093
        return new static($str, $new_encoding);
3✔
1094
    }
1095

1096
    /**
1097
     * Encrypt the string.
1098
     *
1099
     * EXAMPLE: <code>
1100
     * </code>
1101
     *
1102
     * @param string $password <p>The key for encrypting</p>
1103
     *
1104
     * @psalm-mutation-free
1105
     *
1106
     * @return static
1107
     */
1108
    public function encrypt(string $password): self
1109
    {
1110
        /**
1111
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to vendor stuff
1112
         */
1113
        return new static(
4✔
1114
            Crypto::encryptWithPassword($this->str, $password),
4✔
1115
            $this->encoding
4✔
1116
        );
4✔
1117
    }
1118

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

1142
        return $this->utf8::str_iends_with($this->str, $substring);
44✔
1143
    }
1144

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

1168
        return $this->utf8::str_iends_with_any($this->str, $substrings);
12✔
1169
    }
1170

1171
    /**
1172
     * Ensures that the string begins with $substring. If it doesn't, it's
1173
     * prepended.
1174
     *
1175
     * EXAMPLE: <code>
1176
     * s('foobar')->ensureLeft('http://'); // 'http://foobar'
1177
     * </code>
1178
     *
1179
     * @param string $substring <p>The substring to add if not present.</p>
1180
     *
1181
     * @psalm-mutation-free
1182
     *
1183
     * @return static
1184
     *                <p>Object with its $str prefixed by the $substring.</p>
1185
     */
1186
    public function ensureLeft(string $substring): self
1187
    {
1188
        return static::create(
30✔
1189
            $this->utf8::str_ensure_left($this->str, $substring),
30✔
1190
            $this->encoding
30✔
1191
        );
30✔
1192
    }
1193

1194
    /**
1195
     * Ensures that the string ends with $substring. If it doesn't, it's appended.
1196
     *
1197
     * EXAMPLE: <code>
1198
     * s('foobar')->ensureRight('.com'); // 'foobar.com'
1199
     * </code>
1200
     *
1201
     * @param string $substring <p>The substring to add if not present.</p>
1202
     *
1203
     * @psalm-mutation-free
1204
     *
1205
     * @return static
1206
     *                <p>Object with its $str suffixed by the $substring.</p>
1207
     */
1208
    public function ensureRight(string $substring): self
1209
    {
1210
        return static::create(
30✔
1211
            $this->utf8::str_ensure_right($this->str, $substring),
30✔
1212
            $this->encoding
30✔
1213
        );
30✔
1214
    }
1215

1216
    /**
1217
     * Create a escape html version of the string via "htmlspecialchars()".
1218
     *
1219
     * EXAMPLE: <code>
1220
     * s('<∂∆ onerror="alert(xss)">')->escape(); // '&lt;∂∆ onerror=&quot;alert(xss)&quot;&gt;'
1221
     * </code>
1222
     *
1223
     * @psalm-mutation-free
1224
     *
1225
     * @return static
1226
     */
1227
    public function escape(): self
1228
    {
1229
        return static::create(
12✔
1230
            $this->utf8::htmlspecialchars(
12✔
1231
                $this->str,
12✔
1232
                \ENT_QUOTES | \ENT_SUBSTITUTE,
12✔
1233
                $this->encoding
12✔
1234
            ),
12✔
1235
            $this->encoding
12✔
1236
        );
12✔
1237
    }
1238

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

1264
        /** @phpstan-ignore-next-line - FP -> non-empty-string is already checked */
1265
        $strings = \explode($delimiter, $this->str, $limit);
3✔
1266
        /** @phpstan-ignore-next-line - if "$delimiter" is an empty string, then "explode()" will return "false" */
1267
        if ($strings === false) {
3✔
1268
            $strings = [];
×
1269
        }
1270

1271
        return \array_map(
3✔
1272
            function ($str) {
3✔
1273
                return new static($str, $this->encoding);
3✔
1274
            },
3✔
1275
            $strings
3✔
1276
        );
3✔
1277
    }
1278

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

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

1341
    /**
1342
     * Returns the first $n characters of the string.
1343
     *
1344
     * EXAMPLE: <code>
1345
     * s('fòôbàř')->first(3); // 'fòô'
1346
     * </code>
1347
     *
1348
     * @param int $n <p>Number of characters to retrieve from the start.</p>
1349
     *
1350
     * @psalm-mutation-free
1351
     *
1352
     * @return static
1353
     *                <p>Object with its $str being the first $n chars.</p>
1354
     */
1355
    public function first(int $n): self
1356
    {
1357
        if ($n <= 0) {
37✔
1358
            return static::create('', $this->encoding);
12✔
1359
        }
1360

1361
        return static::create(
25✔
1362
            $this->utf8::first_char($this->str, $n, $this->encoding),
25✔
1363
            $this->encoding
25✔
1364
        );
25✔
1365
    }
1366

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

1402
        if (\strpos($this->str, '%:') !== false) {
14✔
1403
            $namedArgs = [];
12✔
1404
            foreach ($args as $key => $arg) {
12✔
1405
                if (!\is_array($arg)) {
12✔
1406
                    continue;
4✔
1407
                }
1408

1409
                foreach ($arg as $name => $param) {
12✔
1410
                    $name = (string) $name;
12✔
1411

1412
                    if (\strpos($name, '%:') === 0) {
12✔
NEW
1413
                        $name = (string) \substr($name, 2);
×
1414
                    }
1415

1416
                    // the same name in several arrays fills the next occurrence of that placeholder
1417
                    $namedArgs[$name][] = $param;
12✔
1418
                }
1419

1420
                unset($args[$key]);
12✔
1421
            }
1422

1423
            if ($namedArgs !== []) {
12✔
1424
                $names = \array_map('strval', \array_keys($namedArgs));
12✔
1425
                // prefer the longest name, e.g. "%:foo-bar" over "%:foo-"
1426
                \usort(
12✔
1427
                    $names,
12✔
1428
                    static function (string $a, string $b): int {
12✔
1429
                        return \strlen($b) <=> \strlen($a);
10✔
1430
                    }
12✔
1431
                );
12✔
1432

1433
                $namePatterns = [];
12✔
1434
                foreach ($names as $name) {
12✔
1435
                    // "%:foo" must not match the beginning of "%:foo_bar"
1436
                    $namePatterns[] = \preg_quote($name, '/') . (\preg_match('/\w$/', $name) === 1 ? '(?!\w)' : '');
12✔
1437
                }
1438

1439
                // single pass: replaced values are never expanded again
1440
                $formattedStr = \preg_replace_callback(
12✔
1441
                    '/%:(' . \implode('|', $namePatterns) . ')/',
12✔
1442
                    static function (array $matches) use (&$namedArgs): string {
12✔
1443
                        if ($namedArgs[$matches[1]] === []) {
12✔
1444
                            return $matches[0];
3✔
1445
                        }
1446

1447
                        return (string) \array_shift($namedArgs[$matches[1]]);
12✔
1448
                    },
12✔
1449
                    $str
12✔
1450
                );
12✔
1451
                if ($formattedStr !== null) {
12✔
1452
                    $str = $formattedStr;
12✔
1453
                }
1454
            }
1455
        }
1456

1457
        $str = \str_replace('%:', '%%:', $str);
14✔
1458

1459
        return static::create(
14✔
1460
            \sprintf($str, ...$args),
14✔
1461
            $this->encoding
14✔
1462
        );
14✔
1463
    }
1464

1465
    /**
1466
     * Returns the encoding used by the Stringy object.
1467
     *
1468
     * EXAMPLE: <code>
1469
     * s('fòôbàř', 'UTF-8')->getEncoding(); // 'UTF-8'
1470
     * </code>
1471
     *
1472
     * @psalm-mutation-free
1473
     *
1474
     * @return string
1475
     *                <p>The current value of the $encoding property.</p>
1476
     */
1477
    public function getEncoding(): string
1478
    {
1479
        return $this->encoding;
26✔
1480
    }
1481

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

1503
    /**
1504
     * Wrap the string after an exact number of characters.
1505
     *
1506
     * EXAMPLE: <code>
1507
     * </code>
1508
     *
1509
     * @param int    $width <p>Number of characters at which to wrap.</p>
1510
     * @param string $break [optional] <p>Character used to break the string. | Default: "\n"</p>
1511
     *
1512
     * @psalm-mutation-free
1513
     *
1514
     * @return static
1515
     */
1516
    public function hardWrap($width, $break = "\n"): self
1517
    {
1518
        return $this->lineWrap($width, $break, false);
2✔
1519
    }
1520

1521
    /**
1522
     * Returns true if the string contains a lower case char, false otherwise
1523
     *
1524
     * EXAMPLE: <code>
1525
     * s('fòôbàř')->hasLowerCase(); // true
1526
     * </code>
1527
     *
1528
     * @psalm-mutation-free
1529
     *
1530
     * @return bool
1531
     *              <p>Whether or not the string contains a lower case character.</p>
1532
     */
1533
    public function hasLowerCase(): bool
1534
    {
1535
        return $this->utf8::has_lowercase($this->str);
36✔
1536
    }
1537

1538
    /**
1539
     * Returns true if the string contains an upper case char, false otherwise.
1540
     *
1541
     * EXAMPLE: <code>
1542
     * s('fòôbàř')->hasUpperCase(); // false
1543
     * </code>
1544
     *
1545
     * @psalm-mutation-free
1546
     *
1547
     * @return bool
1548
     *              <p>Whether or not the string contains an upper case character.</p>
1549
     */
1550
    public function hasUpperCase(): bool
1551
    {
1552
        return $this->utf8::has_uppercase($this->str);
36✔
1553
    }
1554

1555
    /**
1556
     * Generate a hash value (message digest).
1557
     *
1558
     * EXAMPLE: <code>
1559
     * </code>
1560
     *
1561
     * @see https://php.net/manual/en/function.hash.php
1562
     *
1563
     * @param string $algorithm
1564
     *                          <p>Name of selected hashing algorithm (i.e. "md5", "sha256", "haval160,4", etc..)</p>
1565
     *
1566
     * @psalm-mutation-free
1567
     *
1568
     * @return static
1569
     */
1570
    public function hash($algorithm): self
1571
    {
1572
        return static::create(\hash($algorithm, $this->str), $this->encoding);
8✔
1573
    }
1574

1575
    /**
1576
     * Decode the string from hex.
1577
     *
1578
     * EXAMPLE: <code>
1579
     * </code>
1580
     *
1581
     * @psalm-mutation-free
1582
     *
1583
     * @return static
1584
     */
1585
    public function hexDecode(): self
1586
    {
1587
        $string = \preg_replace_callback(
3✔
1588
            '/\\\\x(?<hex>[0-9A-Fa-f]+)/',
3✔
1589
            function (array $matched) {
3✔
1590
                return $this->utf8::hex_to_chr($matched['hex']);
3✔
1591
            },
3✔
1592
            $this->str
3✔
1593
        );
3✔
1594

1595
        return static::create(
3✔
1596
            $string,
3✔
1597
            $this->encoding
3✔
1598
        );
3✔
1599
    }
1600

1601
    /**
1602
     * Encode string to hex.
1603
     *
1604
     * EXAMPLE: <code>
1605
     * </code>
1606
     *
1607
     * @psalm-mutation-free
1608
     *
1609
     * @return static
1610
     */
1611
    public function hexEncode(): self
1612
    {
1613
        $string = \array_reduce(
2✔
1614
            $this->chars(),
2✔
1615
            function (string $str, string $char) {
2✔
1616
                return $str . $this->utf8::chr_to_hex($char);
2✔
1617
            },
2✔
1618
            ''
2✔
1619
        );
2✔
1620

1621
        return static::create(
2✔
1622
            $string,
2✔
1623
            $this->encoding
2✔
1624
        );
2✔
1625
    }
1626

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

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

1771
    /**
1772
     * Capitalizes the first word of the string, replaces underscores with
1773
     * spaces, and strips '_id'.
1774
     *
1775
     * EXAMPLE: <code>
1776
     * s('author_id')->humanize(); // 'Author'
1777
     * </code>
1778
     *
1779
     * @psalm-mutation-free
1780
     *
1781
     * @return static
1782
     *                <p>Object with a humanized $str.</p>
1783
     */
1784
    public function humanize(): self
1785
    {
1786
        return static::create(
9✔
1787
            $this->utf8::str_humanize($this->str),
9✔
1788
            $this->encoding
9✔
1789
        );
9✔
1790
    }
1791

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

1813
        return \stripos($str, $this->str) !== false;
1✔
1814
    }
1815

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

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

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

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

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

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

1979
        $quotedPattern = \preg_quote($pattern, '/');
25✔
1980
        $replaceWildCards = \str_replace('\*', '.*', $quotedPattern);
25✔
1981

1982
        return $this->matchesPattern('^' . $replaceWildCards . '\z');
25✔
1983
    }
1984

1985
    /**
1986
     * Returns true if the string contains only alphabetic chars, false otherwise.
1987
     *
1988
     * EXAMPLE: <code>
1989
     * s('丹尼爾')->isAlpha(); // true
1990
     * </code>
1991
     *
1992
     * @psalm-mutation-free
1993
     *
1994
     * @return bool
1995
     *              <p>Whether or not $str contains only alphabetic chars.</p>
1996
     */
1997
    public function isAlpha(): bool
1998
    {
1999
        return $this->utf8::is_alpha($this->str);
30✔
2000
    }
2001

2002
    /**
2003
     * Returns true if the string contains only alphabetic and numeric chars, false otherwise.
2004
     *
2005
     * EXAMPLE: <code>
2006
     * s('دانيال1')->isAlphanumeric(); // true
2007
     * </code>
2008
     *
2009
     * @psalm-mutation-free
2010
     *
2011
     * @return bool
2012
     *              <p>Whether or not $str contains only alphanumeric chars.</p>
2013
     */
2014
    public function isAlphanumeric(): bool
2015
    {
2016
        return $this->utf8::is_alphanumeric($this->str);
39✔
2017
    }
2018

2019
    /**
2020
     * Checks if a string is 7 bit ASCII.
2021
     *
2022
     * EXAMPLE: <code>s('白')->isAscii; // false</code>
2023
     *
2024
     * @psalm-mutation-free
2025
     *
2026
     * @return bool
2027
     *              <p>
2028
     *              <strong>true</strong> if it is ASCII<br>
2029
     *              <strong>false</strong> otherwise
2030
     *              </p>
2031
     *
2032
     * @noinspection GetSetMethodCorrectnessInspection
2033
     */
2034
    public function isAscii(): bool
2035
    {
UNCOV
2036
        return $this->utf8::is_ascii($this->str);
×
2037
    }
2038

2039
    /**
2040
     * Returns true if the string is base64 encoded, false otherwise.
2041
     *
2042
     * EXAMPLE: <code>
2043
     * s('Zm9vYmFy')->isBase64(); // true
2044
     * </code>
2045
     *
2046
     * @param bool $emptyStringIsValid
2047
     *
2048
     * @psalm-mutation-free
2049
     *
2050
     * @return bool
2051
     *              <p>Whether or not $str is base64 encoded.</p>
2052
     */
2053
    public function isBase64($emptyStringIsValid = true): bool
2054
    {
2055
        return $this->utf8::is_base64($this->str, $emptyStringIsValid);
21✔
2056
    }
2057

2058
    /**
2059
     * Check if the input is binary... (is look like a hack).
2060
     *
2061
     * EXAMPLE: <code>s(01)->isBinary(); // true</code>
2062
     *
2063
     * @psalm-mutation-free
2064
     *
2065
     * @return bool
2066
     */
2067
    public function isBinary(): bool
2068
    {
2069
        return $this->utf8::is_binary($this->str);
1✔
2070
    }
2071

2072
    /**
2073
     * Returns true if the string contains only whitespace chars, false otherwise.
2074
     *
2075
     * EXAMPLE: <code>
2076
     * s("\n\t  \v\f")->isBlank(); // true
2077
     * </code>
2078
     *
2079
     * @psalm-mutation-free
2080
     *
2081
     * @return bool
2082
     *              <p>Whether or not $str contains only whitespace characters.</p>
2083
     */
2084
    public function isBlank(): bool
2085
    {
2086
        return $this->utf8::is_blank($this->str);
45✔
2087
    }
2088

2089
    /**
2090
     * Checks if the given string is equal to any "Byte Order Mark".
2091
     *
2092
     * WARNING: Use "s::string_has_bom()" if you will check BOM in a string.
2093
     *
2094
     * EXAMPLE: <code>s->("\xef\xbb\xbf")->isBom(); // true</code>
2095
     *
2096
     * @psalm-mutation-free
2097
     *
2098
     * @return bool
2099
     *              <p><strong>true</strong> if the $utf8_chr is Byte Order Mark, <strong>false</strong> otherwise.</p>
2100
     */
2101
    public function isBom(): bool
2102
    {
UNCOV
2103
        return $this->utf8::is_bom($this->str);
×
2104
    }
2105

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

2135
    /**
2136
     * Determine whether the string is considered to be empty.
2137
     *
2138
     * A variable is considered empty if it does not exist or if its value equals FALSE.
2139
     *
2140
     * EXAMPLE: <code>
2141
     * s('')->isEmpty(); // true
2142
     * </code>
2143
     *
2144
     * @psalm-mutation-free
2145
     *
2146
     * @return bool
2147
     *              <p>Whether or not $str is empty().</p>
2148
     */
2149
    public function isEmpty(): bool
2150
    {
2151
        return $this->utf8::is_empty($this->str);
10✔
2152
    }
2153

2154
    /**
2155
     * Determine whether the string is equals to $str.
2156
     * Alias for isEqualsCaseSensitive()
2157
     *
2158
     * EXAMPLE: <code>
2159
     * s('foo')->isEquals('foo'); // true
2160
     * </code>
2161
     *
2162
     * @param string|Stringy ...$str
2163
     *
2164
     * @psalm-mutation-free
2165
     *
2166
     * @return bool
2167
     */
2168
    public function isEquals(...$str): bool
2169
    {
2170
        return $this->isEqualsCaseSensitive(...$str);
13✔
2171
    }
2172

2173
    /**
2174
     * Determine whether the string is equals to $str.
2175
     *
2176
     * EXAMPLE: <code>
2177
     * </code>
2178
     *
2179
     * @param float|int|string|Stringy ...$str <p>The string to compare.</p>
2180
     *
2181
     * @psalm-mutation-free
2182
     *
2183
     * @return bool
2184
     *              <p>Whether or not $str is equals.</p>
2185
     */
2186
    public function isEqualsCaseInsensitive(...$str): bool
2187
    {
2188
        $strUpper = $this->toUpperCase()->str;
4✔
2189

2190
        foreach ($str as $strTmp) {
4✔
2191
            /**
2192
             * @psalm-suppress RedundantConditionGivenDocblockType - wait for union-types :)
2193
             */
2194
            if ($strTmp instanceof self) {
4✔
UNCOV
2195
                if ($strUpper !== $strTmp->toUpperCase()->str) {
×
UNCOV
2196
                    return false;
×
2197
                }
2198
            } elseif (\is_scalar($strTmp)) {
4✔
2199
                if ($strUpper !== $this->utf8::strtoupper((string) $strTmp, $this->encoding)) {
4✔
2200
                    return false;
3✔
2201
                }
2202
            } else {
UNCOV
2203
                throw new \InvalidArgumentException('expected: int|float|string|Stringy -> given: ' . \print_r($strTmp, true) . ' [' . \gettype($strTmp) . ']');
×
2204
            }
2205
        }
2206

2207
        return true;
4✔
2208
    }
2209

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

2242
        return true;
4✔
2243
    }
2244

2245
    /**
2246
     * Returns true if the string contains only hexadecimal chars, false otherwise.
2247
     *
2248
     * EXAMPLE: <code>
2249
     * s('A102F')->isHexadecimal(); // true
2250
     * </code>
2251
     *
2252
     * @psalm-mutation-free
2253
     *
2254
     * @return bool
2255
     *              <p>Whether or not $str contains only hexadecimal chars.</p>
2256
     */
2257
    public function isHexadecimal(): bool
2258
    {
2259
        return $this->utf8::is_hexadecimal($this->str);
39✔
2260
    }
2261

2262
    /**
2263
     * Returns true if the string contains HTML-Tags, false otherwise.
2264
     *
2265
     * EXAMPLE: <code>
2266
     * s('<h1>foo</h1>')->isHtml(); // true
2267
     * </code>
2268
     *
2269
     * @psalm-mutation-free
2270
     *
2271
     * @return bool
2272
     *              <p>Whether or not $str contains HTML-Tags.</p>
2273
     */
2274
    public function isHtml(): bool
2275
    {
2276
        return $this->utf8::is_html($this->str);
2✔
2277
    }
2278

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

2306
    /**
2307
     * Returns true if the string contains only lower case chars, false otherwise.
2308
     *
2309
     * EXAMPLE: <code>
2310
     * s('fòôbàř')->isLowerCase(); // true
2311
     * </code>
2312
     *
2313
     * @psalm-mutation-free
2314
     *
2315
     * @return bool
2316
     *              <p>Whether or not $str contains only lower case characters.</p>
2317
     */
2318
    public function isLowerCase(): bool
2319
    {
2320
        return $this->utf8::is_lowercase($this->str);
24✔
2321
    }
2322

2323
    /**
2324
     * Determine whether the string is considered to be NOT empty.
2325
     *
2326
     * A variable is considered NOT empty if it does exist or if its value equals TRUE.
2327
     *
2328
     * EXAMPLE: <code>
2329
     * s('')->isNotEmpty(); // false
2330
     * </code>
2331
     *
2332
     * @psalm-mutation-free
2333
     *
2334
     * @return bool
2335
     *              <p>Whether or not $str is empty().</p>
2336
     */
2337
    public function isNotEmpty(): bool
2338
    {
2339
        return !$this->utf8::is_empty($this->str);
10✔
2340
    }
2341

2342
    /**
2343
     * Determine if the string is composed of numeric characters.
2344
     *
2345
     * EXAMPLE: <code>
2346
     * </code>
2347
     *
2348
     * @psalm-mutation-free
2349
     *
2350
     * @return bool
2351
     */
2352
    public function isNumeric(): bool
2353
    {
2354
        return \is_numeric($this->str);
4✔
2355
    }
2356

2357
    /**
2358
     * Determine if the string is composed of printable (non-invisible) characters.
2359
     *
2360
     * EXAMPLE: <code>
2361
     * </code>
2362
     *
2363
     * @psalm-mutation-free
2364
     *
2365
     * @return bool
2366
     */
2367
    public function isPrintable(): bool
2368
    {
2369
        return $this->utf8::is_printable($this->str);
3✔
2370
    }
2371

2372
    /**
2373
     * Determine if the string is composed of punctuation characters.
2374
     *
2375
     * EXAMPLE: <code>
2376
     * </code>
2377
     *
2378
     * @psalm-mutation-free
2379
     *
2380
     * @return bool
2381
     */
2382
    public function isPunctuation(): bool
2383
    {
2384
        return $this->utf8::is_punctuation($this->str);
3✔
2385
    }
2386

2387
    /**
2388
     * Returns true if the string is serialized, false otherwise.
2389
     *
2390
     * EXAMPLE: <code>
2391
     * s('a:1:{s:3:"foo";s:3:"bar";}')->isSerialized(); // true
2392
     * </code>
2393
     *
2394
     * @psalm-mutation-free
2395
     *
2396
     * @return bool
2397
     *              <p>Whether or not $str is serialized.</p>
2398
     */
2399
    public function isSerialized(): bool
2400
    {
2401
        return $this->utf8::is_serialized($this->str);
21✔
2402
    }
2403

2404
    /**
2405
     * Check if two strings are similar.
2406
     *
2407
     * EXAMPLE: <code>
2408
     * </code>
2409
     *
2410
     * @param string $str                     <p>The string to compare against.</p>
2411
     * @param float  $minPercentForSimilarity [optional] <p>The percentage of needed similarity. | Default: 80%</p>
2412
     *
2413
     * @psalm-mutation-free
2414
     *
2415
     * @return bool
2416
     */
2417
    public function isSimilar(string $str, float $minPercentForSimilarity = 80.0): bool
2418
    {
2419
        return $this->similarity($str) >= $minPercentForSimilarity;
3✔
2420
    }
2421

2422
    /**
2423
     * Returns true if the string contains only lower case chars, false
2424
     * otherwise.
2425
     *
2426
     * EXAMPLE: <code>
2427
     * s('FÒÔBÀŘ')->isUpperCase(); // true
2428
     * </code>
2429
     *
2430
     * @psalm-mutation-free
2431
     *
2432
     * @return bool
2433
     *              <p>Whether or not $str contains only lower case characters.</p>
2434
     */
2435
    public function isUpperCase(): bool
2436
    {
2437
        return $this->utf8::is_uppercase($this->str);
24✔
2438
    }
2439

2440
    /**
2441
     * /**
2442
     * Check if $url is an correct url.
2443
     *
2444
     * @param bool $disallow_localhost
2445
     *
2446
     * @psalm-mutation-free
2447
     *
2448
     * @return bool
2449
     */
2450
    public function isUrl(bool $disallow_localhost = false): bool
2451
    {
UNCOV
2452
        return $this->utf8::is_url($this->str, $disallow_localhost);
×
2453
    }
2454

2455
    /**
2456
     * Check if the string is UTF-16.
2457
     *
2458
     * @psalm-mutation-free
2459
     *
2460
     * @return false|int
2461
     *                   <strong>false</strong> if is't not UTF-16,<br>
2462
     *                   <strong>1</strong> for UTF-16LE,<br>
2463
     *                   <strong>2</strong> for UTF-16BE
2464
     */
2465
    public function isUtf16()
2466
    {
UNCOV
2467
        return $this->utf8::is_utf16($this->str);
×
2468
    }
2469

2470
    /**
2471
     * Check if the string is UTF-32.
2472
     *
2473
     * @psalm-mutation-free
2474
     *
2475
     * @return false|int
2476
     *                   <strong>false</strong> if is't not UTF-32,<br>
2477
     *                   <strong>1</strong> for UTF-32LE,<br>
2478
     *                   <strong>2</strong> for UTF-32BE
2479
     */
2480
    public function isUtf32()
2481
    {
UNCOV
2482
        return $this->utf8::is_utf32($this->str);
×
2483
    }
2484

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

2505
    /**
2506
     * Returns true if the string contains only whitespace chars, false otherwise.
2507
     *
2508
     * EXAMPLE: <code>
2509
     * </code>
2510
     *
2511
     * @psalm-mutation-free
2512
     *
2513
     * @return bool
2514
     *              <p>Whether or not $str contains only whitespace characters.</p>
2515
     */
2516
    public function isWhitespace(): bool
2517
    {
2518
        return $this->isBlank();
30✔
2519
    }
2520

2521
    /**
2522
     * Convert the string to kebab-case.
2523
     *
2524
     * EXAMPLE: <code>
2525
     * </code>
2526
     *
2527
     * @psalm-mutation-free
2528
     *
2529
     * @return static
2530
     */
2531
    public function kebabCase(): self
2532
    {
2533
        $words = \array_map(
4✔
2534
            static function (self $word) {
4✔
2535
                return $word->toLowerCase();
4✔
2536
            },
4✔
2537
            $this->words('', true)
4✔
2538
        );
4✔
2539

2540
        return new static(\implode('-', $words), $this->encoding);
4✔
2541
    }
2542

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

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

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

2623
    /**
2624
     * Returns the length of the string.
2625
     *
2626
     * EXAMPLE: <code>
2627
     * s('fòôbàř')->length(); // 6
2628
     * </code>
2629
     *
2630
     * @psalm-mutation-free
2631
     *
2632
     * @return int
2633
     *             <p>The number of characters in $str given the encoding.</p>
2634
     */
2635
    public function length(): int
2636
    {
2637
        return (int) $this->utf8::strlen($this->str, $this->encoding);
30✔
2638
    }
2639

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

2670
        $delimiter = $delimiter === '' ? null : $delimiter;
4✔
2671

2672
        return static::create(
4✔
2673
            $this->utf8::wordwrap_per_line(
4✔
2674
                $this->str,
4✔
2675
                $limit,
4✔
2676
                $break,
4✔
2677
                true,
4✔
2678
                $add_final_break,
4✔
2679
                $delimiter
4✔
2680
            ),
4✔
2681
            $this->encoding
4✔
2682
        );
4✔
2683
    }
2684

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

2715
        $delimiter = $delimiter === '' ? null : $delimiter;
6✔
2716

2717
        return static::create(
6✔
2718
            $this->utf8::wordwrap_per_line(
6✔
2719
                $this->str,
6✔
2720
                $limit,
6✔
2721
                $break,
6✔
2722
                false,
6✔
2723
                $add_final_break,
6✔
2724
                $delimiter
6✔
2725
            ),
6✔
2726
            $this->encoding
6✔
2727
        );
6✔
2728
    }
2729

2730
    /**
2731
     * Splits on newlines and carriage returns, returning an array of Stringy
2732
     * objects corresponding to the lines in the string.
2733
     *
2734
     * EXAMPLE: <code>
2735
     * s("fòô\r\nbàř\n")->lines(); // ['fòô', 'bàř', '']
2736
     * </code>
2737
     *
2738
     * @psalm-mutation-free
2739
     *
2740
     * @return static[]
2741
     *                  <p>An array of Stringy objects.</p>
2742
     *
2743
     * @phpstan-return array<int,static>
2744
     */
2745
    public function lines(): array
2746
    {
2747
        $strings = $this->utf8::str_to_lines($this->str);
52✔
2748
        /** @noinspection AlterInForeachInspection */
2749
        foreach ($strings as &$str) {
52✔
2750
            $str = static::create($str, $this->encoding);
52✔
2751
        }
2752

2753
        /** @noinspection PhpSillyAssignmentInspection */
2754
        /** @var static[] $strings */
2755
        $strings = $strings;
52✔
2756

2757
        return $strings;
52✔
2758
    }
2759

2760
    /**
2761
     * Splits on newlines and carriage returns, returning an array of Stringy
2762
     * objects corresponding to the lines in the string.
2763
     *
2764
     * EXAMPLE: <code>
2765
     * </code>
2766
     *
2767
     * @psalm-mutation-free
2768
     *
2769
     * @return CollectionStringy|static[]
2770
     *                                    <p>An collection of Stringy objects.</p>
2771
     *
2772
     * @phpstan-return CollectionStringy<int,static>
2773
     */
2774
    public function linesCollection(): CollectionStringy
2775
    {
2776
        /**
2777
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the collection class
2778
         */
2779
        return CollectionStringy::create(
34✔
2780
            $this->lines()
34✔
2781
        );
34✔
2782
    }
2783

2784
    /**
2785
     * Returns the longest common prefix between the string and $otherStr.
2786
     *
2787
     * EXAMPLE: <code>
2788
     * s('foobar')->longestCommonPrefix('foobaz'); // 'fooba'
2789
     * </code>
2790
     *
2791
     * @param string $otherStr <p>Second string for comparison.</p>
2792
     *
2793
     * @psalm-mutation-free
2794
     *
2795
     * @return static
2796
     *                <p>Object with its $str being the longest common prefix.</p>
2797
     */
2798
    public function longestCommonPrefix(string $otherStr): self
2799
    {
2800
        return static::create(
30✔
2801
            $this->utf8::str_longest_common_prefix(
30✔
2802
                $this->str,
30✔
2803
                $otherStr,
30✔
2804
                $this->encoding
30✔
2805
            ),
30✔
2806
            $this->encoding
30✔
2807
        );
30✔
2808
    }
2809

2810
    /**
2811
     * Returns the longest common substring between the string and $otherStr.
2812
     * In the case of ties, it returns that which occurs first.
2813
     *
2814
     * EXAMPLE: <code>
2815
     * s('foobar')->longestCommonSubstring('boofar'); // 'oo'
2816
     * </code>
2817
     *
2818
     * @param string $otherStr <p>Second string for comparison.</p>
2819
     *
2820
     * @psalm-mutation-free
2821
     *
2822
     * @return static
2823
     *                <p>Object with its $str being the longest common substring.</p>
2824
     */
2825
    public function longestCommonSubstring(string $otherStr): self
2826
    {
2827
        return static::create(
30✔
2828
            $this->utf8::str_longest_common_substring(
30✔
2829
                $this->str,
30✔
2830
                $otherStr,
30✔
2831
                $this->encoding
30✔
2832
            ),
30✔
2833
            $this->encoding
30✔
2834
        );
30✔
2835
    }
2836

2837
    /**
2838
     * Returns the longest common suffix between the string and $otherStr.
2839
     *
2840
     * EXAMPLE: <code>
2841
     * s('fòôbàř')->longestCommonSuffix('fòrbàř'); // 'bàř'
2842
     * </code>
2843
     *
2844
     * @param string $otherStr <p>Second string for comparison.</p>
2845
     *
2846
     * @psalm-mutation-free
2847
     *
2848
     * @return static
2849
     *                <p>Object with its $str being the longest common suffix.</p>
2850
     */
2851
    public function longestCommonSuffix(string $otherStr): self
2852
    {
2853
        return static::create(
30✔
2854
            $this->utf8::str_longest_common_suffix(
30✔
2855
                $this->str,
30✔
2856
                $otherStr,
30✔
2857
                $this->encoding
30✔
2858
            ),
30✔
2859
            $this->encoding
30✔
2860
        );
30✔
2861
    }
2862

2863
    /**
2864
     * Converts the first character of the string to lower case.
2865
     *
2866
     * EXAMPLE: <code>
2867
     * s('Σ Foo')->lowerCaseFirst(); // 'σ Foo'
2868
     * </code>
2869
     *
2870
     * @psalm-mutation-free
2871
     *
2872
     * @return static
2873
     *                <p>Object with the first character of $str being lower case.</p>
2874
     */
2875
    public function lowerCaseFirst(): self
2876
    {
2877
        return static::create(
16✔
2878
            $this->utf8::lcfirst($this->str, $this->encoding),
16✔
2879
            $this->encoding
16✔
2880
        );
16✔
2881
    }
2882

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

2904
    /**
2905
     * Determine if the string matches another string.
2906
     * Alias for isEqualsCaseSensitive()
2907
     *
2908
     * EXAMPLE: <code>
2909
     * </code>
2910
     *
2911
     * @psalm-mutation-free
2912
     *
2913
     * @param string|Stringy ...$str
2914
     *                               <p>The string to compare against.</p>
2915
     *
2916
     * @psalm-mutation-free
2917
     *
2918
     * @return bool
2919
     */
2920
    public function matchCaseSensitive(...$str): bool
2921
    {
2922
        return $this->isEqualsCaseSensitive(...$str);
7✔
2923
    }
2924

2925
    /**
2926
     * Create a md5 hash from the current string.
2927
     *
2928
     * @psalm-mutation-free
2929
     *
2930
     * @return static
2931
     */
2932
    public function md5(): self
2933
    {
2934
        return static::create($this->hash('md5'), $this->encoding);
2✔
2935
    }
2936

2937
    /**
2938
     * Replace all breaks [<br> | \r\n | \r | \n | ...] into "<br>".
2939
     *
2940
     * EXAMPLE: <code>
2941
     * </code>
2942
     *
2943
     * @return static
2944
     */
2945
    public function newLineToHtmlBreak(): self
2946
    {
2947
        return $this->removeHtmlBreak('<br>');
1✔
2948
    }
2949

2950
    /**
2951
     * Get every nth character of the string.
2952
     *
2953
     * EXAMPLE: <code>
2954
     * </code>
2955
     *
2956
     * @param int $step   <p>The number of characters to step.</p>
2957
     * @param int $offset [optional] <p>The string offset to start at.</p>
2958
     *
2959
     * @psalm-mutation-free
2960
     *
2961
     * @return static
2962
     */
2963
    public function nth(int $step, int $offset = 0): self
2964
    {
2965
        $length = $step - 1;
4✔
2966
        $substring = $this->substr($offset)->toString();
4✔
2967

2968
        if ($substring === '') {
4✔
UNCOV
2969
            return new static('', $this->encoding);
×
2970
        }
2971

2972
        \preg_match_all(
4✔
2973
            "/(?:^|(?:.|\p{L}|\w){" . $length . "})(.|\p{L}|\w)/u",
4✔
2974
            $substring,
4✔
2975
            $matches
4✔
2976
        );
4✔
2977

2978
        return new static(\implode('', $matches[1] ?? []), $this->encoding);
4✔
2979
    }
2980

2981
    /**
2982
     * Returns the integer value of the current string.
2983
     *
2984
     * EXAMPLE: <code>
2985
     * s('foo1 ba2r')->extractIntegers(); // '12'
2986
     * </code>
2987
     *
2988
     * @psalm-mutation-free
2989
     *
2990
     * @return static
2991
     */
2992
    public function extractIntegers(): self
2993
    {
2994
        \preg_match_all('/(?<integers>\d+)/', $this->str, $matches);
1✔
2995

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

3002
    /**
3003
     * Returns the special chars of the current string.
3004
     *
3005
     * EXAMPLE: <code>
3006
     * s('foo1 ba2!r')->extractSpecialCharacters(); // '!'
3007
     * </code>
3008
     *
3009
     * @psalm-mutation-free
3010
     *
3011
     * @return static
3012
     */
3013
    public function extractSpecialCharacters(): self
3014
    {
3015
        // no letter, no digit, no space
3016
        \preg_match_all('/[^\p{L}0-9\s]/u', $this->str, $matches);
1✔
3017

3018
        return static::create(
1✔
3019
            \implode('', $matches[0]),
1✔
3020
            $this->encoding
1✔
3021
        );
1✔
3022
    }
3023

3024
    /**
3025
     * Returns whether or not a character exists at an index. Offsets may be
3026
     * negative to count from the last character in the string. Implements
3027
     * part of the ArrayAccess interface.
3028
     *
3029
     * EXAMPLE: <code>
3030
     * </code>
3031
     *
3032
     * @param int $offset <p>The index to check.</p>
3033
     *
3034
     * @psalm-mutation-free
3035
     *
3036
     * @return bool
3037
     *              <p>Whether or not the index exists.</p>
3038
     */
3039
    public function offsetExists($offset): bool
3040
    {
3041
        return $this->utf8::str_offset_exists(
18✔
3042
            $this->str,
18✔
3043
            $offset,
18✔
3044
            $this->encoding
18✔
3045
        );
18✔
3046
    }
3047

3048
    /**
3049
     * Returns the character at the given index. Offsets may be negative to
3050
     * count from the last character in the string. Implements part of the
3051
     * ArrayAccess interface, and throws an OutOfBoundsException if the index
3052
     * does not exist.
3053
     *
3054
     * EXAMPLE: <code>
3055
     * </code>
3056
     *
3057
     * @param int $offset <p>The <strong>index</strong> from which to retrieve the char.</p>
3058
     *
3059
     * @throws \OutOfBoundsException
3060
     *                               <p>If the positive or negative offset does not exist.</p>
3061
     *
3062
     * @return string
3063
     *                <p>The character at the specified index.</p>
3064
     *
3065
     * @psalm-mutation-free
3066
     */
3067
    public function offsetGet($offset): string
3068
    {
3069
        $length = $this->length();
11✔
3070

3071
        if (
3072
            ($offset >= 0 && $length <= $offset)
11✔
3073
            ||
3074
            $length < \abs($offset)
11✔
3075
        ) {
3076
            throw new \OutOfBoundsException('No character exists at the index');
6✔
3077
        }
3078

3079
        // fast path for UTF-8; the generic path below returns the same result
3080
        // @infection-ignore-all
3081
        if ($this->encoding === 'UTF-8') {
5✔
3082
            return (string) \mb_substr($this->str, $offset, 1);
5✔
3083
        }
3084

UNCOV
3085
        return (string) $this->utf8::substr($this->str, $offset, 1, $this->encoding);
×
3086
    }
3087

3088
    /**
3089
     * Implements part of the ArrayAccess interface, but throws an exception
3090
     * when called. This maintains the immutability of Stringy objects.
3091
     *
3092
     * EXAMPLE: <code>
3093
     * </code>
3094
     *
3095
     * @param int   $offset <p>The index of the character.</p>
3096
     * @param mixed $value  <p>Value to set.</p>
3097
     *
3098
     * @throws \Exception
3099
     *                    <p>When called.</p>
3100
     *
3101
     * @return void
3102
     */
3103
    public function offsetSet($offset, $value): void
3104
    {
3105
        // Stringy is immutable, cannot directly set char
3106
        throw new \Exception('Stringy object is immutable, cannot modify char');
3✔
3107
    }
3108

3109
    /**
3110
     * Implements part of the ArrayAccess interface, but throws an exception
3111
     * when called. This maintains the immutability of Stringy objects.
3112
     *
3113
     * EXAMPLE: <code>
3114
     * </code>
3115
     *
3116
     * @param int $offset <p>The index of the character.</p>
3117
     *
3118
     * @throws \Exception
3119
     *                    <p>When called.</p>
3120
     *
3121
     * @return void
3122
     */
3123
    public function offsetUnset($offset): void
3124
    {
3125
        // Don't allow directly modifying the string
3126
        throw new \Exception('Stringy object is immutable, cannot unset char');
3✔
3127
    }
3128

3129
    /**
3130
     * Pads the string to a given length with $padStr. If length is less than
3131
     * or equal to the length of the string, no padding takes places. The
3132
     * default string used for padding is a space, and the default type (one of
3133
     * 'left', 'right', 'both') is 'right'. Throws an InvalidArgumentException
3134
     * if $padType isn't one of those 3 values.
3135
     *
3136
     * EXAMPLE: <code>
3137
     * s('fòôbàř')->pad(9, '-/', 'left'); // '-/-fòôbàř'
3138
     * </code>
3139
     *
3140
     * @param int    $length  <p>Desired string length after padding.</p>
3141
     * @param string $padStr  [optional] <p>String used to pad, defaults to space. Default: ' '</p>
3142
     * @param string $padType [optional] <p>One of 'left', 'right', 'both'. Default: 'right'</p>
3143
     *
3144
     * @throws \InvalidArgumentException
3145
     *                                   <p>If $padType isn't one of 'right', 'left' or 'both'.</p>
3146
     *
3147
     * @return static
3148
     *                <p>Object with a padded $str.</p>
3149
     *
3150
     * @psalm-mutation-free
3151
     */
3152
    public function pad(int $length, string $padStr = ' ', string $padType = 'right'): self
3153
    {
3154
        return static::create(
39✔
3155
            $this->utf8::str_pad(
39✔
3156
                $this->str,
39✔
3157
                $length,
39✔
3158
                $padStr,
39✔
3159
                $padType,
39✔
3160
                $this->encoding
39✔
3161
            )
39✔
3162
        );
39✔
3163
    }
3164

3165
    /**
3166
     * Returns a new string of a given length such that both sides of the
3167
     * string are padded. Alias for pad() with a $padType of 'both'.
3168
     *
3169
     * EXAMPLE: <code>
3170
     * s('foo bar')->padBoth(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 padding applied.</p>
3180
     */
3181
    public function padBoth(int $length, string $padStr = ' '): self
3182
    {
3183
        return static::create(
33✔
3184
            $this->utf8::str_pad_both(
33✔
3185
                $this->str,
33✔
3186
                $length,
33✔
3187
                $padStr,
33✔
3188
                $this->encoding
33✔
3189
            )
33✔
3190
        );
33✔
3191
    }
3192

3193
    /**
3194
     * Returns a new string of a given length such that the beginning of the
3195
     * string is padded. Alias for pad() with a $padType of 'left'.
3196
     *
3197
     * EXAMPLE: <code>
3198
     * s('foo bar')->padLeft(9, ' '); // '  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 left padding.</p>
3208
     */
3209
    public function padLeft(int $length, string $padStr = ' '): self
3210
    {
3211
        return static::create(
21✔
3212
            $this->utf8::str_pad_left(
21✔
3213
                $this->str,
21✔
3214
                $length,
21✔
3215
                $padStr,
21✔
3216
                $this->encoding
21✔
3217
            )
21✔
3218
        );
21✔
3219
    }
3220

3221
    /**
3222
     * Returns a new string of a given length such that the end of the string
3223
     * is padded. Alias for pad() with a $padType of 'right'.
3224
     *
3225
     * EXAMPLE: <code>
3226
     * s('foo bar')->padRight(10, '_*'); // 'foo bar_*_'
3227
     * </code>
3228
     *
3229
     * @param int    $length <p>Desired string length after padding.</p>
3230
     * @param string $padStr [optional] <p>String used to pad, defaults to space. Default: ' '</p>
3231
     *
3232
     * @psalm-mutation-free
3233
     *
3234
     * @return static
3235
     *                <p>String with right padding.</p>
3236
     */
3237
    public function padRight(int $length, string $padStr = ' '): self
3238
    {
3239
        return static::create(
21✔
3240
            $this->utf8::str_pad_right(
21✔
3241
                $this->str,
21✔
3242
                $length,
21✔
3243
                $padStr,
21✔
3244
                $this->encoding
21✔
3245
            )
21✔
3246
        );
21✔
3247
    }
3248

3249
    /**
3250
     * Convert the string to PascalCase.
3251
     * Alias for studlyCase()
3252
     *
3253
     * EXAMPLE: <code>
3254
     * </code>
3255
     *
3256
     * @psalm-mutation-free
3257
     *
3258
     * @return static
3259
     */
3260
    public function pascalCase(): self
3261
    {
3262
        return $this->studlyCase();
3✔
3263
    }
3264

3265
    /**
3266
     * Returns a new string starting with $prefix.
3267
     *
3268
     * EXAMPLE: <code>
3269
     * s('bàř')->prepend('fòô'); // 'fòôbàř'
3270
     * </code>
3271
     *
3272
     * @param string ...$prefix <p>The string to append.</p>
3273
     *
3274
     * @psalm-mutation-free
3275
     *
3276
     * @return static
3277
     *                <p>Object with appended $prefix.</p>
3278
     */
3279
    public function prepend(string ...$prefix): self
3280
    {
3281
        if (\count($prefix) <= 1) {
8✔
3282
            $prefix = $prefix[0];
6✔
3283
        } else {
3284
            $prefix = \implode('', $prefix);
2✔
3285
        }
3286

3287
        return static::create($prefix . $this->str, $this->encoding);
8✔
3288
    }
3289

3290
    /**
3291
     * Returns a new string starting with $prefix.
3292
     *
3293
     * EXAMPLE: <code>
3294
     * </code>
3295
     *
3296
     * @param CollectionStringy|static ...$prefix <p>The Stringy objects to append.</p>
3297
     *
3298
     * @phpstan-param CollectionStringy<int,static>|static ...$prefix
3299
     *
3300
     * @psalm-mutation-free
3301
     *
3302
     * @return static
3303
     *                <p>Object with appended $prefix.</p>
3304
     */
3305
    public function prependStringy(...$prefix): self
3306
    {
3307
        $prefixStr = '';
2✔
3308
        foreach ($prefix as $prefixTmp) {
2✔
3309
            if ($prefixTmp instanceof CollectionStringy) {
2✔
3310
                $prefixStr .= $prefixTmp->implode('');
2✔
3311
            } else {
3312
                $prefixStr .= $prefixTmp->toString();
2✔
3313
            }
3314
        }
3315

3316
        return static::create($prefixStr . $this->str, $this->encoding);
2✔
3317
    }
3318

3319
    /**
3320
     * Replaces all occurrences of $pattern in $str by $replacement.
3321
     *
3322
     * EXAMPLE: <code>
3323
     * s('fòô ')->regexReplace('f[òô]+\s', 'bàř'); // 'bàř'
3324
     * s('fò')->regexReplace('(ò)', '\\1ô'); // 'fòô'
3325
     * </code>
3326
     *
3327
     * @param string $pattern     <p>The regular expression pattern.</p>
3328
     * @param string $replacement <p>The string to replace with.</p>
3329
     * @param string $options     [optional] <p>Matching conditions to be used.</p>
3330
     * @param string $delimiter   [optional] <p>Delimiter the the regex. Default: '/'</p>
3331
     *
3332
     * @psalm-mutation-free
3333
     *
3334
     * @return static
3335
     *                <p>Object with the result2ing $str after the replacements.</p>
3336
     */
3337
    public function regexReplace(
3338
        string $pattern,
3339
        string $replacement,
3340
        string $options = '',
3341
        string $delimiter = '/'
3342
    ): self {
3343
        return static::create(
29✔
3344
            $this->utf8::regex_replace(
29✔
3345
                $this->str,
29✔
3346
                $pattern,
29✔
3347
                $replacement,
29✔
3348
                $options,
29✔
3349
                $delimiter
29✔
3350
            ),
29✔
3351
            $this->encoding
29✔
3352
        );
29✔
3353
    }
3354

3355
    /**
3356
     * Remove html via "strip_tags()" from the string.
3357
     *
3358
     * EXAMPLE: <code>
3359
     * s('řàb <ô>òf\', ô<br/>foo <a href="#">lall</a>')->removeHtml('<br><br/>'); // 'řàb òf\', ô<br/>foo lall'
3360
     * </code>
3361
     *
3362
     * @param string $allowableTags [optional] <p>You can use the optional second parameter to specify tags which should
3363
     *                              not be stripped. Default: null
3364
     *                              </p>
3365
     *
3366
     * @psalm-mutation-free
3367
     *
3368
     * @return static
3369
     */
3370
    public function removeHtml(string $allowableTags = ''): self
3371
    {
3372
        return static::create(
12✔
3373
            $this->utf8::remove_html($this->str, $allowableTags),
12✔
3374
            $this->encoding
12✔
3375
        );
12✔
3376
    }
3377

3378
    /**
3379
     * Remove all breaks [<br> | \r\n | \r | \n | ...] from the string.
3380
     *
3381
     * EXAMPLE: <code>
3382
     * s('řàb <ô>òf\', ô<br/>foo <a href="#">lall</a>')->removeHtmlBreak(''); // 'řàb <ô>òf\', ô< foo <a href="#">lall</a>'
3383
     * </code>
3384
     *
3385
     * @param string $replacement [optional] <p>Default is a empty string.</p>
3386
     *
3387
     * @psalm-mutation-free
3388
     *
3389
     * @return static
3390
     */
3391
    public function removeHtmlBreak(string $replacement = ''): self
3392
    {
3393
        return static::create(
13✔
3394
            $this->utf8::remove_html_breaks($this->str, $replacement),
13✔
3395
            $this->encoding
13✔
3396
        );
13✔
3397
    }
3398

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

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

3443
    /**
3444
     * Try to remove all XSS-attacks from the string.
3445
     *
3446
     * EXAMPLE: <code>
3447
     * 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 >'
3448
     * </code>
3449
     *
3450
     * @psalm-mutation-free
3451
     *
3452
     * @return static
3453
     */
3454
    public function removeXss(): self
3455
    {
3456
        /**
3457
         * @var AntiXSS|null
3458
         *
3459
         * @psalm-suppress ImpureStaticVariable
3460
         */
3461
        static $antiXss = null;
12✔
3462

3463
        if ($antiXss === null) {
12✔
3464
            $antiXss = new AntiXSS();
1✔
3465
        }
3466

3467
        /**
3468
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the anti-xss class
3469
         */
3470
        $str = $antiXss->xss_clean($this->str);
12✔
3471

3472
        return static::create($str, $this->encoding);
12✔
3473
    }
3474

3475
    /**
3476
     * Returns a repeated string given a multiplier.
3477
     *
3478
     * EXAMPLE: <code>
3479
     * s('α')->repeat(3); // 'ααα'
3480
     * </code>
3481
     *
3482
     * @param int $multiplier <p>The number of times to repeat the string.</p>
3483
     *
3484
     * @psalm-mutation-free
3485
     *
3486
     * @return static
3487
     *                <p>Object with a repeated str.</p>
3488
     */
3489
    public function repeat(int $multiplier): self
3490
    {
3491
        return static::create(
21✔
3492
            \str_repeat($this->str, $multiplier),
21✔
3493
            $this->encoding
21✔
3494
        );
21✔
3495
    }
3496

3497
    /**
3498
     * Replaces all occurrences of $search in $str by $replacement.
3499
     *
3500
     * EXAMPLE: <code>
3501
     * s('fòô bàř fòô bàř')->replace('fòô ', ''); // 'bàř bàř'
3502
     * </code>
3503
     *
3504
     * @param string $search        <p>The needle to search for.</p>
3505
     * @param string $replacement   <p>The string to replace with.</p>
3506
     * @param bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
3507
     *
3508
     * @psalm-mutation-free
3509
     *
3510
     * @return static
3511
     *                <p>Object with the resulting $str after the replacements.</p>
3512
     */
3513
    public function replace(string $search, string $replacement, bool $caseSensitive = true): self
3514
    {
3515
        // no-op guard kept for older PHP versions, where str_replace() with an empty needle behaves differently
3516
        // @infection-ignore-all
3517
        if ($search === '' && $replacement === '') {
77✔
3518
            return static::create($this->str, $this->encoding);
16✔
3519
        }
3520

3521
        if ($this->str === '' && $search === '') {
61✔
3522
            return static::create($replacement, $this->encoding);
2✔
3523
        }
3524

3525
        if ($caseSensitive) {
59✔
3526
            return static::create(
49✔
3527
                \str_replace($search, $replacement, $this->str),
49✔
3528
                $this->encoding
49✔
3529
            );
49✔
3530
        }
3531

3532
        return static::create(
10✔
3533
            $this->utf8::str_ireplace($search, $replacement, $this->str),
10✔
3534
            $this->encoding
10✔
3535
        );
10✔
3536
    }
3537

3538
    /**
3539
     * Replaces all occurrences of $search in $str by $replacement.
3540
     *
3541
     * EXAMPLE: <code>
3542
     * s('fòô bàř lall bàř')->replaceAll(['fòÔ ', 'lall'], '', false); // 'bàř bàř'
3543
     * </code>
3544
     *
3545
     * @param string[]        $search        <p>The elements to search for.</p>
3546
     * @param string|string[] $replacement   <p>The string to replace with.</p>
3547
     * @param bool            $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
3548
     *
3549
     * @psalm-mutation-free
3550
     *
3551
     * @return static
3552
     *                <p>Object with the resulting $str after the replacements.</p>
3553
     */
3554
    public function replaceAll(array $search, $replacement, bool $caseSensitive = true): self
3555
    {
3556
        if ($caseSensitive) {
62✔
3557
            return static::create(
48✔
3558
                \str_replace($search, $replacement, $this->str),
48✔
3559
                $this->encoding
48✔
3560
            );
48✔
3561
        }
3562

3563
        return static::create(
14✔
3564
            $this->utf8::str_ireplace($search, $replacement, $this->str),
14✔
3565
            $this->encoding
14✔
3566
        );
14✔
3567
    }
3568

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

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

3615
    /**
3616
     * Replaces first occurrences of $search from the beginning of string with $replacement.
3617
     *
3618
     * EXAMPLE: <code>
3619
     * </code>
3620
     *
3621
     * @param string $search      <p>The string to search for.</p>
3622
     * @param string $replacement <p>The replacement.</p>
3623
     *
3624
     * @psalm-mutation-free
3625
     *
3626
     * @return static
3627
     *                <p>Object with the resulting $str after the replacements.</p>
3628
     */
3629
    public function replaceFirst(string $search, string $replacement): self
3630
    {
3631
        return static::create(
32✔
3632
            $this->utf8::str_replace_first($search, $replacement, $this->str),
32✔
3633
            $this->encoding
32✔
3634
        );
32✔
3635
    }
3636

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

3659
    /**
3660
     * Returns a reversed string. A multibyte version of strrev().
3661
     *
3662
     * EXAMPLE: <code>
3663
     * s('fòôbàř')->reverse(); // 'řàbôòf'
3664
     * </code>
3665
     *
3666
     * @psalm-mutation-free
3667
     *
3668
     * @return static
3669
     *                <p>Object with a reversed $str.</p>
3670
     */
3671
    public function reverse(): self
3672
    {
3673
        return static::create($this->utf8::strrev($this->str), $this->encoding);
15✔
3674
    }
3675

3676
    /**
3677
     * Truncates the string to a given length, while ensuring that it does not
3678
     * split words. If $substring is provided, and truncating occurs, the
3679
     * string is further truncated so that the substring may be appended without
3680
     * exceeding the desired length.
3681
     *
3682
     * EXAMPLE: <code>
3683
     * s('What are your plans today?')->safeTruncate(22, '...'); // 'What are your plans...'
3684
     * </code>
3685
     *
3686
     * @param int    $length                          <p>Desired length of the truncated string.</p>
3687
     * @param string $substring                       [optional] <p>The substring to append if it can fit. Default: ''</p>
3688
     * @param bool   $ignoreDoNotSplitWordsForOneWord
3689
     *
3690
     * @psalm-mutation-free
3691
     *
3692
     * @return static
3693
     *                <p>Object with the resulting $str after truncating.</p>
3694
     */
3695
    public function safeTruncate(
3696
        int $length,
3697
        string $substring = '',
3698
        bool $ignoreDoNotSplitWordsForOneWord = true
3699
    ): self {
3700
        return static::create(
68✔
3701
            $this->utf8::str_truncate_safe(
68✔
3702
                $this->str,
68✔
3703
                $length,
68✔
3704
                $substring,
68✔
3705
                $this->encoding,
68✔
3706
                $ignoreDoNotSplitWordsForOneWord
68✔
3707
            ),
68✔
3708
            $this->encoding
68✔
3709
        );
68✔
3710
    }
3711

3712
    /**
3713
     * Set the internal character encoding.
3714
     *
3715
     * EXAMPLE: <code>
3716
     * </code>
3717
     *
3718
     * @param string $new_encoding <p>The desired character encoding.</p>
3719
     *
3720
     * @psalm-mutation-free
3721
     *
3722
     * @return static
3723
     */
3724
    public function setInternalEncoding(string $new_encoding): self
3725
    {
3726
        return new static($this->str, $new_encoding);
1✔
3727
    }
3728

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

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

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

3774
    /**
3775
     * Shorten the string after $length, but also after the next word.
3776
     *
3777
     * EXAMPLE: <code>
3778
     * s('this is a test')->shortenAfterWord(2, '...'); // 'this...'
3779
     * </code>
3780
     *
3781
     * @param int    $length   <p>The given length.</p>
3782
     * @param string $strAddOn [optional] <p>Default: '…'</p>
3783
     *
3784
     * @psalm-mutation-free
3785
     *
3786
     * @return static
3787
     */
3788
    public function shortenAfterWord(int $length, string $strAddOn = '…'): self
3789
    {
3790
        if ($length <= 0) {
12✔
3791
            return static::create('', $this->encoding);
4✔
3792
        }
3793

3794
        return static::create(
8✔
3795
            $this->utf8::str_limit_after_word($this->str, $length, $strAddOn),
8✔
3796
            $this->encoding
8✔
3797
        );
8✔
3798
    }
3799

3800
    /**
3801
     * A multibyte string shuffle function. It returns a string with its
3802
     * characters in random order.
3803
     *
3804
     * EXAMPLE: <code>
3805
     * s('fòôbàř')->shuffle(); // 'àôřbòf'
3806
     * </code>
3807
     *
3808
     * @return static
3809
     *                <p>Object with a shuffled $str.</p>
3810
     */
3811
    public function shuffle(): self
3812
    {
3813
        return static::create($this->utf8::str_shuffle($this->str), $this->encoding);
9✔
3814
    }
3815

3816
    /**
3817
     * Calculate the similarity between two strings.
3818
     *
3819
     * EXAMPLE: <code>
3820
     * </code>
3821
     *
3822
     * @param string $str <p>The delimiting string.</p>
3823
     *
3824
     * @psalm-mutation-free
3825
     *
3826
     * @return float
3827
     */
3828
    public function similarity(string $str): float
3829
    {
3830
        \similar_text($this->str, $str, $percent);
3✔
3831

3832
        return $percent;
3✔
3833
    }
3834

3835
    /**
3836
     * Returns the substring beginning at $start, and up to, but not including
3837
     * the index specified by $end. If $end is omitted, the function extracts
3838
     * the remaining string. If $end is negative, it is computed from the end
3839
     * of the string.
3840
     *
3841
     * EXAMPLE: <code>
3842
     * s('fòôbàř')->slice(3, -1); // 'bà'
3843
     * </code>
3844
     *
3845
     * @param int $start <p>Initial index from which to begin extraction.</p>
3846
     * @param int $end   [optional] <p>Index at which to end extraction. Default: null</p>
3847
     *
3848
     * @psalm-mutation-free
3849
     *
3850
     * @return static
3851
     *                <p>Object with its $str being the extracted substring.</p>
3852
     */
3853
    public function slice(int $start, ?int $end = null): self
3854
    {
3855
        return static::create(
51✔
3856
            $this->utf8::str_slice($this->str, $start, $end, $this->encoding),
51✔
3857
            $this->encoding
51✔
3858
        );
51✔
3859
    }
3860

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

3913
    /**
3914
     * Convert the string to snake_case.
3915
     *
3916
     * EXAMPLE: <code>
3917
     * </code>
3918
     *
3919
     * @psalm-mutation-free
3920
     *
3921
     * @return static
3922
     */
3923
    public function snakeCase(): self
3924
    {
3925
        $words = \array_map(
4✔
3926
            static function (self $word) {
4✔
3927
                return $word->toLowerCase();
4✔
3928
            },
4✔
3929
            $this->words('', true)
4✔
3930
        );
4✔
3931

3932
        return new static(\implode('_', $words), $this->encoding);
4✔
3933
    }
3934

3935
    /**
3936
     * Convert a string to snake_case.
3937
     *
3938
     * EXAMPLE: <code>
3939
     * s('foo1 Bar')->snakeize(); // 'foo_1_bar'
3940
     * </code>
3941
     *
3942
     * @psalm-mutation-free
3943
     *
3944
     * @return static
3945
     *                <p>Object with $str in snake_case.</p>
3946
     */
3947
    public function snakeize(): self
3948
    {
3949
        return static::create(
40✔
3950
            $this->utf8::str_snakeize($this->str, $this->encoding),
40✔
3951
            $this->encoding
40✔
3952
        );
40✔
3953
    }
3954

3955
    /**
3956
     * Wrap the string after the first whitespace character after a given number
3957
     * of characters.
3958
     *
3959
     * EXAMPLE: <code>
3960
     * </code>
3961
     *
3962
     * @param int    $width <p>Number of characters at which to wrap.</p>
3963
     * @param string $break [optional] <p>Character used to break the string. | Default "\n"</p>
3964
     *
3965
     * @psalm-mutation-free
3966
     *
3967
     * @return static
3968
     */
3969
    public function softWrap(int $width, string $break = "\n"): self
3970
    {
3971
        return $this->lineWrapAfterWord($width, $break, false);
2✔
3972
    }
3973

3974
    /**
3975
     * Splits the string with the provided regular expression, returning an
3976
     * array of Stringy objects. An optional integer $limit will truncate the
3977
     * results.
3978
     *
3979
     * EXAMPLE: <code>
3980
     * s('foo,bar,baz')->split(',', 2); // ['foo', 'bar']
3981
     * </code>
3982
     *
3983
     * @param string $pattern <p>The regex with which to split the string.</p>
3984
     * @param int    $limit   [optional] <p>Maximum number of results to return. Default: -1 === no
3985
     *                        limit</p>
3986
     *
3987
     * @psalm-mutation-free
3988
     *
3989
     * @return static[]
3990
     *                  <p>An array of Stringy objects.</p>
3991
     *
3992
     * @phpstan-return array<int,static>
3993
     */
3994
    public function split(string $pattern, ?int $limit = null): array
3995
    {
3996
        if ($this->str === '') {
53✔
UNCOV
3997
            return [];
×
3998
        }
3999

4000
        if ($limit === null) {
53✔
4001
            $array = $this->utf8::str_split_pattern($this->str, $pattern);
9✔
4002
        } else {
4003
            $array = $this->utf8::str_split_pattern($this->str, $pattern, $limit);
44✔
4004
        }
4005

4006
        foreach ($array as &$value) {
53✔
4007
            $value = static::create($value, $this->encoding);
47✔
4008
        }
4009

4010
        /** @noinspection PhpSillyAssignmentInspection */
4011
        /** @var static[] $array */
4012
        $array = $array;
53✔
4013

4014
        return $array;
53✔
4015
    }
4016

4017
    /**
4018
     * Splits the string with the provided regular expression, returning an
4019
     * collection of Stringy objects. An optional integer $limit will truncate the
4020
     * results.
4021
     *
4022
     * EXAMPLE: <code>
4023
     * </code>
4024
     *
4025
     * @param string $pattern <p>The regex with which to split the string.</p>
4026
     * @param int    $limit   [optional] <p>Maximum number of results to return. Default: -1 === no
4027
     *                        limit</p>
4028
     *
4029
     * @psalm-mutation-free
4030
     *
4031
     * @return CollectionStringy|static[]
4032
     *                                    <p>An collection of Stringy objects.</p>
4033
     *
4034
     * @phpstan-return CollectionStringy<int,static>
4035
     */
4036
    public function splitCollection(string $pattern, ?int $limit = null): CollectionStringy
4037
    {
4038
        /**
4039
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the collection class
4040
         */
4041
        return CollectionStringy::create(
35✔
4042
            $this->split($pattern, $limit)
35✔
4043
        );
35✔
4044
    }
4045

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

4069
        return $this->utf8::str_istarts_with($this->str, $substring);
46✔
4070
    }
4071

4072
    /**
4073
     * Returns true if the string begins with any of $substrings, false otherwise.
4074
     * By default the comparison is case-sensitive, but can be made insensitive by
4075
     * setting $caseSensitive to false.
4076
     *
4077
     * EXAMPLE: <code>
4078
     * s('FÒÔbàřbaz')->startsWithAny(['fòô', 'bàř'], false); // true
4079
     * </code>
4080
     *
4081
     * @param string[] $substrings    <p>Substrings to look for.</p>
4082
     * @param bool     $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
4083
     *
4084
     * @psalm-mutation-free
4085
     *
4086
     * @return bool
4087
     *              <p>Whether or not $str starts with $substring.</p>
4088
     */
4089
    public function startsWithAny(array $substrings, bool $caseSensitive = true): bool
4090
    {
4091
        if ($caseSensitive) {
36✔
4092
            return $this->utf8::str_starts_with_any($this->str, $substrings);
24✔
4093
        }
4094

4095
        return $this->utf8::str_istarts_with_any($this->str, $substrings);
12✔
4096
    }
4097

4098
    /**
4099
     * Remove one or more strings from the string.
4100
     *
4101
     * EXAMPLE: <code>
4102
     * </code>
4103
     *
4104
     * @param string|string[] $search One or more strings to be removed
4105
     *
4106
     * @psalm-mutation-free
4107
     *
4108
     * @return static
4109
     */
4110
    public function strip($search): self
4111
    {
4112
        if (\is_array($search)) {
3✔
4113
            return $this->replaceAll($search, '');
1✔
4114
        }
4115

4116
        return $this->replace($search, '');
2✔
4117
    }
4118

4119
    /**
4120
     * Strip all whitespace characters. This includes tabs and newline characters,
4121
     * as well as multibyte whitespace such as the thin space and ideographic space.
4122
     *
4123
     * EXAMPLE: <code>
4124
     * s('   Ο     συγγραφέας  ')->stripWhitespace(); // 'Οσυγγραφέας'
4125
     * </code>
4126
     *
4127
     * @psalm-mutation-free
4128
     *
4129
     * @return static
4130
     */
4131
    public function stripWhitespace(): self
4132
    {
4133
        return static::create(
36✔
4134
            $this->utf8::strip_whitespace($this->str),
36✔
4135
            $this->encoding
36✔
4136
        );
36✔
4137
    }
4138

4139
    /**
4140
     * Remove css media-queries.
4141
     *
4142
     * EXAMPLE: <code>
4143
     * s('test @media (min-width:660px){ .des-cla #mv-tiles{width:480px} } test ')->stripeCssMediaQueries(); // 'test  test '
4144
     * </code>
4145
     *
4146
     * @psalm-mutation-free
4147
     *
4148
     * @return static
4149
     */
4150
    public function stripeCssMediaQueries(): self
4151
    {
4152
        return static::create(
2✔
4153
            $this->utf8::css_stripe_media_queries($this->str),
2✔
4154
            $this->encoding
2✔
4155
        );
2✔
4156
    }
4157

4158
    /**
4159
     * Remove empty html-tag.
4160
     *
4161
     * EXAMPLE: <code>
4162
     * s('foo<h1></h1>bar')->stripeEmptyHtmlTags(); // 'foobar'
4163
     * </code>
4164
     *
4165
     * @psalm-mutation-free
4166
     *
4167
     * @return static
4168
     */
4169
    public function stripeEmptyHtmlTags(): self
4170
    {
4171
        return static::create(
2✔
4172
            $this->utf8::html_stripe_empty_tags($this->str),
2✔
4173
            $this->encoding
2✔
4174
        );
2✔
4175
    }
4176

4177
    /**
4178
     * Convert the string to StudlyCase.
4179
     *
4180
     * EXAMPLE: <code>
4181
     * </code>
4182
     *
4183
     * @psalm-mutation-free
4184
     *
4185
     * @return static
4186
     */
4187
    public function studlyCase(): self
4188
    {
4189
        $words = \array_map(
6✔
4190
            static function (self $word) {
6✔
4191
                return $word->substr(0, 1)
6✔
4192
                    ->toUpperCase()
6✔
4193
                    ->appendStringy($word->substr(1));
6✔
4194
            },
6✔
4195
            $this->words('', true)
6✔
4196
        );
6✔
4197

4198
        return new static(\implode('', $words), $this->encoding);
6✔
4199
    }
4200

4201
    /**
4202
     * Returns the substring beginning at $start with the specified $length.
4203
     * It differs from the $this->utf8::substr() function in that providing a $length of
4204
     * null will return the rest of the string, rather than an empty string.
4205
     *
4206
     * EXAMPLE: <code>
4207
     * </code>
4208
     *
4209
     * @param int $start  <p>Position of the first character to use.</p>
4210
     * @param int $length [optional] <p>Maximum number of characters used. Default: null</p>
4211
     *
4212
     * @psalm-mutation-free
4213
     *
4214
     * @return static
4215
     *                <p>Object with its $str being the substring.</p>
4216
     */
4217
    public function substr(int $start, ?int $length = null): self
4218
    {
4219
        return static::create(
41✔
4220
            $this->utf8::substr(
41✔
4221
                $this->str,
41✔
4222
                $start,
41✔
4223
                $length,
41✔
4224
                $this->encoding
41✔
4225
            ),
41✔
4226
            $this->encoding
41✔
4227
        );
41✔
4228
    }
4229

4230
    /**
4231
     * Return part of the string.
4232
     * Alias for substr()
4233
     *
4234
     * EXAMPLE: <code>
4235
     * s('fòôbàř')->substring(2, 3); // 'ôbà'
4236
     * </code>
4237
     *
4238
     * @param int $start  <p>Starting position of the substring.</p>
4239
     * @param int $length [optional] <p>Length of substring.</p>
4240
     *
4241
     * @psalm-mutation-free
4242
     *
4243
     * @return static
4244
     */
4245
    public function substring(int $start, ?int $length = null): self
4246
    {
4247
        return $this->substr($start, $length);
4✔
4248
    }
4249

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

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

4304
    /**
4305
     * Surrounds $str with the given substring.
4306
     *
4307
     * EXAMPLE: <code>
4308
     * s(' ͜ ')->surround('ʘ'); // 'ʘ ͜ ʘ'
4309
     * </code>
4310
     *
4311
     * @param string $substring <p>The substring to add to both sides.</P>
4312
     *
4313
     * @psalm-mutation-free
4314
     *
4315
     * @return static
4316
     *                <p>Object whose $str had the substring both prepended and appended.</p>
4317
     */
4318
    public function surround(string $substring): self
4319
    {
4320
        return static::create(
15✔
4321
            $substring . $this->str . $substring,
15✔
4322
            $this->encoding
15✔
4323
        );
15✔
4324
    }
4325

4326
    /**
4327
     * Returns a case swapped version of the string.
4328
     *
4329
     * EXAMPLE: <code>
4330
     * s('Ντανιλ')->swapCase(); // 'νΤΑΝΙΛ'
4331
     * </code>
4332
     *
4333
     * @psalm-mutation-free
4334
     *
4335
     * @return static
4336
     *                <p>Object whose $str has each character's case swapped.</P>
4337
     */
4338
    public function swapCase(): self
4339
    {
4340
        return static::create(
15✔
4341
            $this->utf8::swapCase($this->str, $this->encoding),
15✔
4342
            $this->encoding
15✔
4343
        );
15✔
4344
    }
4345

4346
    /**
4347
     * Returns a string with smart quotes, ellipsis characters, and dashes from
4348
     * Windows-1252 (commonly used in Word documents) replaced by their ASCII
4349
     * equivalents.
4350
     *
4351
     * EXAMPLE: <code>
4352
     * s('“I see…”')->tidy(); // '"I see..."'
4353
     * </code>
4354
     *
4355
     * @psalm-mutation-free
4356
     *
4357
     * @return static
4358
     *                <p>Object whose $str has those characters removed.</p>
4359
     */
4360
    public function tidy(): self
4361
    {
4362
        return static::create(
12✔
4363
            $this->ascii::normalize_msword($this->str),
12✔
4364
            $this->encoding
12✔
4365
        );
12✔
4366
    }
4367

4368
    /**
4369
     * Returns a trimmed string with the first letter of each word capitalized.
4370
     * Also accepts an array, $ignore, allowing you to list words not to be
4371
     * capitalized.
4372
     *
4373
     * EXAMPLE: <code>
4374
     * $ignore = ['at', 'by', 'for', 'in', 'of', 'on', 'out', 'to', 'the'];
4375
     * s('i like to watch television')->titleize($ignore); // 'I Like to Watch Television'
4376
     * </code>
4377
     *
4378
     * @param string[]|null $ignore            [optional] <p>An array of words not to capitalize or null.
4379
     *                                         Default: null</p>
4380
     * @param string|null   $word_define_chars [optional] <p>An string of chars that will be used as whitespace
4381
     *                                         separator === words.</p>
4382
     * @param string|null   $language          [optional] <p>Language of the source string.</p>
4383
     *
4384
     * @psalm-mutation-free
4385
     *
4386
     * @return static
4387
     *                <p>Object with a titleized $str.</p>
4388
     */
4389
    public function titleize(
4390
        ?array $ignore = null,
4391
        ?string $word_define_chars = null,
4392
        ?string $language = null
4393
    ): self {
4394
        return static::create(
25✔
4395
            $this->utf8::str_titleize(
25✔
4396
                $this->str,
25✔
4397
                $ignore,
25✔
4398
                $this->encoding,
25✔
4399
                false,
25✔
4400
                $language,
25✔
4401
                false,
25✔
4402
                true,
25✔
4403
                $word_define_chars
25✔
4404
            ),
25✔
4405
            $this->encoding
25✔
4406
        );
25✔
4407
    }
4408

4409
    /**
4410
     * Returns a trimmed string in proper title case: Also accepts an array, $ignore, allowing you to list words not to
4411
     * be capitalized.
4412
     *
4413
     * EXAMPLE: <code>
4414
     * </code>
4415
     *
4416
     * Adapted from John Gruber's script.
4417
     *
4418
     * @see https://gist.github.com/gruber/9f9e8650d68b13ce4d78
4419
     *
4420
     * @param string[] $ignore <p>An array of words not to capitalize.</p>
4421
     *
4422
     * @psalm-mutation-free
4423
     *
4424
     * @return static
4425
     *                <p>Object with a titleized $str</p>
4426
     */
4427
    public function titleizeForHumans(array $ignore = []): self
4428
    {
4429
        return static::create(
70✔
4430
            $this->utf8::str_titleize_for_humans(
70✔
4431
                $this->str,
70✔
4432
                $ignore,
70✔
4433
                $this->encoding
70✔
4434
            ),
70✔
4435
            $this->encoding
70✔
4436
        );
70✔
4437
    }
4438

4439
    /**
4440
     * Returns an ASCII version of the string. A set of non-ASCII characters are
4441
     * replaced with their closest ASCII counterparts, and the rest are removed
4442
     * by default. The language or locale of the source string can be supplied
4443
     * for language-specific transliteration in any of the following formats:
4444
     * en, en_GB, or en-GB. For example, passing "de" results in "äöü" mapping
4445
     * to "aeoeue" rather than "aou" as in other languages.
4446
     *
4447
     * EXAMPLE: <code>
4448
     * s('fòôbàř')->toAscii(); // 'foobar'
4449
     * </code>
4450
     *
4451
     * @param string $language          [optional] <p>Language of the source string.</p>
4452
     * @param bool   $removeUnsupported [optional] <p>Whether or not to remove the
4453
     *                                  unsupported characters.</p>
4454
     *
4455
     * @psalm-mutation-free
4456
     *
4457
     * @return static
4458
     *                <p>Object whose $str contains only ASCII characters.</p>
4459
     *
4460
     * @phpstan-param ASCII::*_LANGUAGE_CODE $language
4461
     */
4462
    public function toAscii(string $language = 'en', bool $removeUnsupported = true): self
4463
    {
4464
        return static::create(
24✔
4465
            $this->ascii::to_ascii(
24✔
4466
                $this->str,
24✔
4467
                $language,
24✔
4468
                $removeUnsupported
24✔
4469
            ),
24✔
4470
            $this->encoding
24✔
4471
        );
24✔
4472
    }
4473

4474
    /**
4475
     * Returns a boolean representation of the given logical string value.
4476
     * For example, <strong>'true', '1', 'on' and 'yes'</strong> will return true. <strong>'false', '0',
4477
     * 'off', and 'no'</strong> will return false. In all instances, case is ignored.
4478
     * For other numeric strings, their sign will determine the return value.
4479
     * In addition, blank strings consisting of only whitespace will return
4480
     * false. For all other strings, the return value is a result of a
4481
     * boolean cast.
4482
     *
4483
     * EXAMPLE: <code>
4484
     * s('OFF')->toBoolean(); // false
4485
     * </code>
4486
     *
4487
     * @psalm-mutation-free
4488
     *
4489
     * @return bool
4490
     *              <p>A boolean value for the string.</p>
4491
     */
4492
    public function toBoolean(): bool
4493
    {
4494
        /**
4495
         * @psalm-suppress ArgumentTypeCoercion -> maybe the string looks like an int ;)
4496
         * @phpstan-ignore-next-line
4497
         */
4498
        return $this->utf8::to_boolean($this->str);
45✔
4499
    }
4500

4501
    /**
4502
     * Converts all characters in the string to lowercase.
4503
     *
4504
     * EXAMPLE: <code>
4505
     * s('FÒÔBÀŘ')->toLowerCase(); // 'fòôbàř'
4506
     * </code>
4507
     *
4508
     * @param bool        $tryToKeepStringLength [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
4509
     * @param string|null $lang                  [optional] <p>Set the language for special cases: az, el, lt, tr</p>
4510
     *
4511
     * @psalm-mutation-free
4512
     *
4513
     * @return static
4514
     *                <p>Object with all characters of $str being lowercase.</p>
4515
     */
4516
    public function toLowerCase($tryToKeepStringLength = false, $lang = null): self
4517
    {
4518
        return static::create(
24✔
4519
            $this->utf8::strtolower(
24✔
4520
                $this->str,
24✔
4521
                $this->encoding,
24✔
4522
                false,
24✔
4523
                $lang,
24✔
4524
                $tryToKeepStringLength
24✔
4525
            ),
24✔
4526
            $this->encoding
24✔
4527
        );
24✔
4528
    }
4529

4530
    /**
4531
     * Converts each tab in the string to some number of spaces, as defined by
4532
     * $tabLength. By default, each tab is converted to 4 consecutive spaces.
4533
     *
4534
     * EXAMPLE: <code>
4535
     * s(' String speech = "Hi"')->toSpaces(); // '    String speech = "Hi"'
4536
     * </code>
4537
     *
4538
     * @param int $tabLength [optional] <p>Number of spaces to replace each tab with. Default: 4</p>
4539
     *
4540
     * @psalm-mutation-free
4541
     *
4542
     * @return static
4543
     *                <p>Object whose $str has had tabs switched to spaces.</p>
4544
     */
4545
    public function toSpaces(int $tabLength = 4): self
4546
    {
4547
        if ($tabLength === 4) {
19✔
4548
            $tab = '    ';
10✔
4549
        } elseif ($tabLength === 2) {
10✔
4550
            $tab = '  ';
4✔
4551
        } else {
4552
            $tab = \str_repeat(' ', $tabLength);
7✔
4553
        }
4554

4555
        return static::create(
19✔
4556
            \str_replace("\t", $tab, $this->str),
19✔
4557
            $this->encoding
19✔
4558
        );
19✔
4559
    }
4560

4561
    /**
4562
     * Return Stringy object as string, but you can also use (string) for automatically casting the object into a
4563
     * string.
4564
     *
4565
     * EXAMPLE: <code>
4566
     * s('fòôbàř')->toString(); // 'fòôbàř'
4567
     * </code>
4568
     *
4569
     * @psalm-mutation-free
4570
     *
4571
     * @return string
4572
     */
4573
    public function toString(): string
4574
    {
4575
        return (string) $this->str;
2,243✔
4576
    }
4577

4578
    /**
4579
     * Converts each occurrence of some consecutive number of spaces, as
4580
     * defined by $tabLength, to a tab. By default, each 4 consecutive spaces
4581
     * are converted to a tab.
4582
     *
4583
     * EXAMPLE: <code>
4584
     * s('    fòô    bàř')->toTabs(); // '   fòô bàř'
4585
     * </code>
4586
     *
4587
     * @param int $tabLength [optional] <p>Number of spaces to replace with a tab. Default: 4</p>
4588
     *
4589
     * @psalm-mutation-free
4590
     *
4591
     * @return static
4592
     *                <p>Object whose $str has had spaces switched to tabs.</p>
4593
     */
4594
    public function toTabs(int $tabLength = 4): self
4595
    {
4596
        if ($tabLength === 4) {
16✔
4597
            $tab = '    ';
10✔
4598
        } elseif ($tabLength === 2) {
7✔
4599
            $tab = '  ';
4✔
4600
        } else {
4601
            $tab = \str_repeat(' ', $tabLength);
4✔
4602
        }
4603

4604
        return static::create(
16✔
4605
            \str_replace($tab, "\t", $this->str),
16✔
4606
            $this->encoding
16✔
4607
        );
16✔
4608
    }
4609

4610
    /**
4611
     * Converts the first character of each word in the string to uppercase
4612
     * and all other chars to lowercase.
4613
     *
4614
     * EXAMPLE: <code>
4615
     * s('fòô bàř')->toTitleCase(); // 'Fòô Bàř'
4616
     * </code>
4617
     *
4618
     * @psalm-mutation-free
4619
     *
4620
     * @return static
4621
     *                <p>Object with all characters of $str being title-cased.</p>
4622
     */
4623
    public function toTitleCase(): self
4624
    {
4625
        return static::create(
15✔
4626
            $this->utf8::titlecase($this->str, $this->encoding),
15✔
4627
            $this->encoding
15✔
4628
        );
15✔
4629
    }
4630

4631
    /**
4632
     * Returns an ASCII version of the string. A set of non-ASCII characters are
4633
     * replaced with their closest ASCII counterparts, and the rest are removed
4634
     * unless instructed otherwise.
4635
     *
4636
     * EXAMPLE: <code>
4637
     * </code>
4638
     *
4639
     * @param bool   $strict  [optional] <p>Use "transliterator_transliterate()" from PHP-Intl | WARNING: bad
4640
     *                        performance | Default: false</p>
4641
     * @param string $unknown [optional] <p>Character use if character unknown. (default is ?)</p>
4642
     *
4643
     * @psalm-mutation-free
4644
     *
4645
     * @return static
4646
     *                <p>Object whose $str contains only ASCII characters.</p>
4647
     */
4648
    public function toTransliterate(bool $strict = false, string $unknown = '?'): self
4649
    {
4650
        return static::create(
34✔
4651
            $this->ascii::to_transliterate($this->str, $unknown, $strict),
34✔
4652
            $this->encoding
34✔
4653
        );
34✔
4654
    }
4655

4656
    /**
4657
     * Converts all characters in the string to uppercase.
4658
     *
4659
     * EXAMPLE: <code>
4660
     * s('fòôbàř')->toUpperCase(); // 'FÒÔBÀŘ'
4661
     * </code>
4662
     *
4663
     * @param bool        $tryToKeepStringLength [optional] <p>true === try to keep the string length: e.g. ẞ -> ß</p>
4664
     * @param string|null $lang                  [optional] <p>Set the language for special cases: az, el, lt, tr</p>
4665
     *
4666
     * @psalm-mutation-free
4667
     *
4668
     * @return static
4669
     *                <p>Object with all characters of $str being uppercase.</p>
4670
     */
4671
    public function toUpperCase($tryToKeepStringLength = false, $lang = null): self
4672
    {
4673
        return static::create(
28✔
4674
            $this->utf8::strtoupper($this->str, $this->encoding, false, $lang, $tryToKeepStringLength),
28✔
4675
            $this->encoding
28✔
4676
        );
28✔
4677
    }
4678

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

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

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

4751
    /**
4752
     * Truncates the string to a given length. If $substring is provided, and
4753
     * truncating occurs, the string is further truncated so that the substring
4754
     * may be appended without exceeding the desired length.
4755
     *
4756
     * EXAMPLE: <code>
4757
     * s('What are your plans today?')->truncate(19, '...'); // 'What are your pl...'
4758
     * </code>
4759
     *
4760
     * @param int    $length    <p>Desired length of the truncated string.</p>
4761
     * @param string $substring [optional] <p>The substring to append if it can fit. Default: ''</p>
4762
     *
4763
     * @psalm-mutation-free
4764
     *
4765
     * @return static
4766
     *                <p>Object with the resulting $str after truncating.</p>
4767
     */
4768
    public function truncate(int $length, string $substring = ''): self
4769
    {
4770
        return static::create(
66✔
4771
            $this->utf8::str_truncate($this->str, $length, $substring, $this->encoding),
66✔
4772
            $this->encoding
66✔
4773
        );
66✔
4774
    }
4775

4776
    /**
4777
     * Returns a lowercase and trimmed string separated by underscores.
4778
     * Underscores are inserted before uppercase characters (with the exception
4779
     * of the first character of the string), and in place of spaces as well as
4780
     * dashes.
4781
     *
4782
     * EXAMPLE: <code>
4783
     * s('TestUCase')->underscored(); // 'test_u_case'
4784
     * </code>
4785
     *
4786
     * @psalm-mutation-free
4787
     *
4788
     * @return static
4789
     *                <p>Object with an underscored $str.</p>
4790
     */
4791
    public function underscored(): self
4792
    {
4793
        return $this->delimit('_');
48✔
4794
    }
4795

4796
    /**
4797
     * Returns an UpperCamelCase version of the supplied string. It trims
4798
     * surrounding spaces, capitalizes letters following digits, spaces, dashes
4799
     * and underscores, and removes spaces, dashes, underscores.
4800
     *
4801
     * EXAMPLE: <code>
4802
     * s('Upper Camel-Case')->upperCamelize(); // 'UpperCamelCase'
4803
     * </code>
4804
     *
4805
     * @psalm-mutation-free
4806
     *
4807
     * @return static
4808
     *                <p>Object with $str in UpperCamelCase.</p>
4809
     */
4810
    public function upperCamelize(): self
4811
    {
4812
        return static::create(
49✔
4813
            $this->utf8::str_upper_camelize($this->str, $this->encoding),
49✔
4814
            $this->encoding
49✔
4815
        );
49✔
4816
    }
4817

4818
    /**
4819
     * Converts the first character of the supplied string to upper case.
4820
     *
4821
     * EXAMPLE: <code>
4822
     * s('σ foo')->upperCaseFirst(); // 'Σ foo'
4823
     * </code>
4824
     *
4825
     * @psalm-mutation-free
4826
     *
4827
     * @return static
4828
     *                <p>Object with the first character of $str being upper case.</p>
4829
     */
4830
    public function upperCaseFirst(): self
4831
    {
4832
        return static::create($this->utf8::ucfirst($this->str, $this->encoding), $this->encoding);
18✔
4833
    }
4834

4835
    /**
4836
     * Simple url-decoding.
4837
     *
4838
     * e.g:
4839
     * 'test+test' => 'test test'
4840
     *
4841
     * EXAMPLE: <code>
4842
     * </code>
4843
     *
4844
     * @psalm-mutation-free
4845
     *
4846
     * @return static
4847
     */
4848
    public function urlDecode(): self
4849
    {
4850
        return static::create(\urldecode($this->str));
1✔
4851
    }
4852

4853
    /**
4854
     * Multi url-decoding + decode HTML entity + fix urlencoded-win1252-chars.
4855
     *
4856
     * e.g:
4857
     * 'test+test'                     => 'test test'
4858
     * 'D&#252;sseldorf'               => 'Düsseldorf'
4859
     * 'D%FCsseldorf'                  => 'Düsseldorf'
4860
     * 'D&#xFC;sseldorf'               => 'Düsseldorf'
4861
     * 'D%26%23xFC%3Bsseldorf'         => 'Düsseldorf'
4862
     * 'Düsseldorf'                   => 'Düsseldorf'
4863
     * 'D%C3%BCsseldorf'               => 'Düsseldorf'
4864
     * 'D%C3%83%C2%BCsseldorf'         => 'Düsseldorf'
4865
     * 'D%25C3%2583%25C2%25BCsseldorf' => 'Düsseldorf'
4866
     *
4867
     * EXAMPLE: <code>
4868
     * </code>
4869
     *
4870
     * @psalm-mutation-free
4871
     *
4872
     * @return static
4873
     */
4874
    public function urlDecodeMulti(): self
4875
    {
4876
        return static::create($this->utf8::urldecode($this->str));
1✔
4877
    }
4878

4879
    /**
4880
     * Simple url-decoding.
4881
     *
4882
     * e.g:
4883
     * 'test+test' => 'test+test
4884
     *
4885
     * EXAMPLE: <code>
4886
     * </code>
4887
     *
4888
     * @psalm-mutation-free
4889
     *
4890
     * @return static
4891
     */
4892
    public function urlDecodeRaw(): self
4893
    {
4894
        return static::create(\rawurldecode($this->str));
1✔
4895
    }
4896

4897
    /**
4898
     * Multi url-decoding + decode HTML entity + fix urlencoded-win1252-chars.
4899
     *
4900
     * e.g:
4901
     * 'test+test'                     => 'test+test'
4902
     * 'D&#252;sseldorf'               => 'Düsseldorf'
4903
     * 'D%FCsseldorf'                  => 'Düsseldorf'
4904
     * 'D&#xFC;sseldorf'               => 'Düsseldorf'
4905
     * 'D%26%23xFC%3Bsseldorf'         => 'Düsseldorf'
4906
     * 'Düsseldorf'                   => 'Düsseldorf'
4907
     * 'D%C3%BCsseldorf'               => 'Düsseldorf'
4908
     * 'D%C3%83%C2%BCsseldorf'         => 'Düsseldorf'
4909
     * 'D%25C3%2583%25C2%25BCsseldorf' => 'Düsseldorf'
4910
     *
4911
     * EXAMPLE: <code>
4912
     * </code>
4913
     *
4914
     * @psalm-mutation-free
4915
     *
4916
     * @return static
4917
     */
4918
    public function urlDecodeRawMulti(): self
4919
    {
4920
        return static::create($this->utf8::rawurldecode($this->str));
1✔
4921
    }
4922

4923
    /**
4924
     * Simple url-encoding.
4925
     *
4926
     * e.g:
4927
     * 'test test' => 'test+test'
4928
     *
4929
     * EXAMPLE: <code>
4930
     * </code>
4931
     *
4932
     * @psalm-mutation-free
4933
     *
4934
     * @return static
4935
     */
4936
    public function urlEncode(): self
4937
    {
4938
        return static::create(\urlencode($this->str));
1✔
4939
    }
4940

4941
    /**
4942
     * Simple url-encoding.
4943
     *
4944
     * e.g:
4945
     * 'test test' => 'test%20test'
4946
     *
4947
     * EXAMPLE: <code>
4948
     * </code>
4949
     *
4950
     * @psalm-mutation-free
4951
     *
4952
     * @return static
4953
     */
4954
    public function urlEncodeRaw(): self
4955
    {
4956
        return static::create(\rawurlencode($this->str));
1✔
4957
    }
4958

4959
    /**
4960
     * Converts the string into an URL slug. This includes replacing non-ASCII
4961
     * characters with their closest ASCII equivalents, removing remaining
4962
     * non-ASCII and non-alphanumeric characters, and replacing whitespace with
4963
     * $separator. The separator defaults to a single dash, and the string
4964
     * is also converted to lowercase.
4965
     *
4966
     * EXAMPLE: <code>
4967
     * s('Using strings like fòô bàř - 1$')->urlify(); // 'using-strings-like-foo-bar-1-dollar'
4968
     * </code>
4969
     *
4970
     * @param string                $separator    [optional] <p>The string used to replace whitespace. Default: '-'</p>
4971
     * @param string                $language     [optional] <p>The language for the url. Default: 'en'</p>
4972
     * @param array<string, string> $replacements [optional] <p>A map of replaceable strings.</p>
4973
     * @param bool                  $strToLower   [optional] <p>string to lower. Default: true</p>
4974
     *
4975
     * @psalm-mutation-free
4976
     *
4977
     * @return static
4978
     *                <p>Object whose $str has been converted to an URL slug.</p>
4979
     *
4980
     * @phpstan-param ASCII::*_LANGUAGE_CODE $language
4981
     *
4982
     * @psalm-suppress ImpureMethodCall :/
4983
     */
4984
    public function urlify(
4985
        string $separator = '-',
4986
        string $language = 'en',
4987
        array $replacements = [],
4988
        bool $strToLower = true
4989
    ): self {
4990
        // init
4991
        $str = $this->str;
32✔
4992

4993
        foreach ($replacements as $from => $to) {
32✔
4994
            $str = \str_replace($from, $to, $str);
32✔
4995
        }
4996

4997
        return static::create(
32✔
4998
            URLify::slug(
32✔
4999
                $str,
32✔
5000
                $language,
32✔
5001
                $separator,
32✔
5002
                $strToLower
32✔
5003
            ),
32✔
5004
            $this->encoding
32✔
5005
        );
32✔
5006
    }
5007

5008
    /**
5009
     * Converts the string into an valid UTF-8 string.
5010
     *
5011
     * EXAMPLE: <code>
5012
     * s('Düsseldorf')->utf8ify(); // 'Düsseldorf'
5013
     * </code>
5014
     *
5015
     * @psalm-mutation-free
5016
     *
5017
     * @return static
5018
     */
5019
    public function utf8ify(): self
5020
    {
5021
        return static::create($this->utf8::cleanup($this->str), $this->encoding);
2✔
5022
    }
5023

5024
    /**
5025
     * Convert a string into an array of words.
5026
     *
5027
     * EXAMPLE: <code>
5028
     * </code>
5029
     *
5030
     * @param string   $char_list           [optional] <p>Additional chars for the definition of "words".</p>
5031
     * @param bool     $remove_empty_values [optional] <p>Remove empty values.</p>
5032
     * @param int|null $remove_short_values [optional] <p>The min. string length or null to disable</p>
5033
     *
5034
     * @psalm-mutation-free
5035
     *
5036
     * @return static[]
5037
     *
5038
     * @phpstan-return array<int,static>
5039
     */
5040
    public function words(
5041
        string $char_list = '',
5042
        bool $remove_empty_values = false,
5043
        ?int $remove_short_values = null
5044
    ): array {
5045
        if ($remove_short_values === null) {
16✔
5046
            $strings = $this->utf8::str_to_words(
16✔
5047
                $this->str,
16✔
5048
                $char_list,
16✔
5049
                $remove_empty_values
16✔
5050
            );
16✔
5051
        } else {
5052
            $strings = $this->utf8::str_to_words(
2✔
5053
                $this->str,
2✔
5054
                $char_list,
2✔
5055
                $remove_empty_values,
2✔
5056
                $remove_short_values
2✔
5057
            );
2✔
5058
        }
5059

5060
        /** @noinspection AlterInForeachInspection */
5061
        foreach ($strings as &$string) {
16✔
5062
            $string = static::create($string);
16✔
5063
        }
5064

5065
        /** @noinspection PhpSillyAssignmentInspection */
5066
        /** @var static[] $strings */
5067
        $strings = $strings;
16✔
5068

5069
        return $strings;
16✔
5070
    }
5071

5072
    /**
5073
     * Convert a string into an collection of words.
5074
     *
5075
     * EXAMPLE: <code>
5076
     * S::create('中文空白 oöäü#s')->wordsCollection('#', true)->toStrings(); // ['中文空白', 'oöäü#s']
5077
     * </code>
5078
     *
5079
     * @param string   $char_list           [optional] <p>Additional chars for the definition of "words".</p>
5080
     * @param bool     $remove_empty_values [optional] <p>Remove empty values.</p>
5081
     * @param int|null $remove_short_values [optional] <p>The min. string length or null to disable</p>
5082
     *
5083
     * @psalm-mutation-free
5084
     *
5085
     * @return CollectionStringy|static[]
5086
     *                                    <p>An collection of Stringy objects.</p>
5087
     *
5088
     * @phpstan-return CollectionStringy<int,static>
5089
     */
5090
    public function wordsCollection(
5091
        string $char_list = '',
5092
        bool $remove_empty_values = false,
5093
        ?int $remove_short_values = null
5094
    ): CollectionStringy {
5095
        /**
5096
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the collection class
5097
         */
5098
        return CollectionStringy::create(
2✔
5099
            $this->words(
2✔
5100
                $char_list,
2✔
5101
                $remove_empty_values,
2✔
5102
                $remove_short_values
2✔
5103
            )
2✔
5104
        );
2✔
5105
    }
5106

5107
    /**
5108
     * Surrounds $str with the given substring.
5109
     *
5110
     * EXAMPLE: <code>
5111
     * </code>
5112
     *
5113
     * @param string $substring <p>The substring to add to both sides.</P>
5114
     *
5115
     * @psalm-mutation-free
5116
     *
5117
     * @return static
5118
     *                <p>Object whose $str had the substring both prepended and appended.</p>
5119
     */
5120
    public function wrap(string $substring): self
5121
    {
5122
        return $this->surround($substring);
10✔
5123
    }
5124

5125
    /**
5126
     * Returns the replacements for the toAscii() method.
5127
     *
5128
     * @psalm-mutation-free
5129
     *
5130
     * @return array<string, array<int, string>>
5131
     *                                           <p>An array of replacements.</p>
5132
     *
5133
     * @deprecated   this is only here for backward-compatibly reasons
5134
     */
5135
    protected function charsArray(): array
5136
    {
5137
        return $this->ascii::charsArrayWithMultiLanguageValues();
1✔
5138
    }
5139

5140
    /**
5141
     * Returns true if $str matches the supplied pattern, false otherwise.
5142
     *
5143
     * @param string $pattern <p>Regex pattern to match against.</p>
5144
     *
5145
     * @psalm-mutation-free
5146
     *
5147
     * @return bool
5148
     *              <p>Whether or not $str matches the pattern.</p>
5149
     */
5150
    protected function matchesPattern(string $pattern): bool
5151
    {
5152
        return $this->utf8::str_matches_pattern($this->str, $pattern);
25✔
5153
    }
5154
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc