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

voku / Stringy / 24214457785

09 Apr 2026 09:33PM UTC coverage: 97.411% (-0.4%) from 97.847%
24214457785

Pull #46

github

web-flow
Merge 23fa43193 into 3fdf6d5be
Pull Request #46: Fix CI PHPStan level 8 findings and eliminate warning-prone edge cases

24 of 29 new or added lines in 2 files covered. (82.76%)

1 existing line in 1 file now uncovered.

1016 of 1043 relevant lines covered (97.41%)

409.12 hits per line

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

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

3
declare(strict_types=1);
4

5
namespace Stringy;
6

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

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

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

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

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

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

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

96
        $this->str = (string) $str;
21,822✔
97

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

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

110
        if ($encoding !== 'UTF-8') {
21,822✔
111
            $this->encoding = $this->utf8::normalize_encoding($encoding, 'UTF-8');
14,796✔
112
        } else {
113
            $this->encoding = $encoding;
16,230✔
114
        }
115
    }
116

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

732
        if ($this->str === '') {
78✔
733
            return [];
12✔
734
        }
735

736
        $chunks = $this->utf8::str_split($this->str, $length);
66✔
737

738
        foreach ($chunks as &$value) {
66✔
739
            $value = static::create($value, $this->encoding);
66✔
740
        }
741

742
        /** @noinspection PhpSillyAssignmentInspection */
743
        /** @var static[] $chunks */
744
        $chunks = $chunks;
66✔
745

746
        return $chunks;
66✔
747
    }
748

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1097
        return new static($str, $new_encoding);
12✔
1098
    }
1099

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

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

1146
        return $this->utf8::str_iends_with($this->str, $substring);
264✔
1147
    }
1148

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

1172
        return $this->utf8::str_iends_with_any($this->str, $substrings);
72✔
1173
    }
1174

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

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

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

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

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

1275
        return \array_map(
18✔
1276
            function ($str) {
18✔
1277
                return new static($str, $this->encoding);
18✔
1278
            },
18✔
1279
            $strings
18✔
1280
        );
18✔
1281
    }
1282

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

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

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

1365
        return static::create(
150✔
1366
            $this->utf8::first_char($this->str, $n, $this->encoding),
150✔
1367
            $this->encoding
150✔
1368
        );
150✔
1369
    }
1370

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

1406
        if (\strpos($this->str, '%:') !== false) {
60✔
1407
            $offset = null;
48✔
1408
            $replacement = null;
48✔
1409
            /** @noinspection AlterInForeachInspection */
1410
            foreach ($args as $key => &$arg) {
48✔
1411
                if (!\is_array($arg)) {
48✔
1412
                    continue;
24✔
1413
                }
1414

1415
                foreach ($arg as $name => $param) {
48✔
1416
                    $name = (string) $name;
48✔
1417

1418
                    if (\strpos($name, '%:') !== 0) {
48✔
1419
                        $nameTmp = '%:' . $name;
48✔
1420
                    } else {
1421
                        $nameTmp = $name;
×
1422
                    }
1423

1424
                    if ($offset === null) {
48✔
1425
                        $offset = \strpos($str, $nameTmp);
48✔
1426
                    } else {
1427
                        $offset = \strpos($str, $nameTmp, (int) $offset + \strlen((string) $replacement));
36✔
1428
                    }
1429
                    if ($offset === false) {
48✔
1430
                        continue;
24✔
1431
                    }
1432

1433
                    unset($arg[$name]);
48✔
1434

1435
                    $str = \substr_replace($str, (string) $param, (int) $offset, \strlen($nameTmp));
48✔
1436
                }
1437

1438
                unset($args[$key]);
48✔
1439
            }
1440
        }
1441

1442
        $str = \str_replace('%:', '%%:', $str);
60✔
1443

1444
        return static::create(
60✔
1445
            \sprintf($str, ...$args),
60✔
1446
            $this->encoding
60✔
1447
        );
60✔
1448
    }
1449

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

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

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

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

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

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

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

1580
        return static::create(
12✔
1581
            $string,
12✔
1582
            $this->encoding
12✔
1583
        );
12✔
1584
    }
1585

1586
    /**
1587
     * Encode string to hex.
1588
     *
1589
     * EXAMPLE: <code>
1590
     * </code>
1591
     *
1592
     * @psalm-mutation-free
1593
     *
1594
     * @return static
1595
     */
1596
    public function hexEncode(): self
1597
    {
1598
        $string = \array_reduce(
12✔
1599
            $this->chars(),
12✔
1600
            function (string $str, string $char) {
12✔
1601
                return $str . $this->utf8::chr_to_hex($char);
12✔
1602
            },
12✔
1603
            ''
12✔
1604
        );
12✔
1605

1606
        return static::create(
12✔
1607
            $string,
12✔
1608
            $this->encoding
12✔
1609
        );
12✔
1610
    }
1611

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

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

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

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

1798
        return \stripos($str, $this->str) !== false;
6✔
1799
    }
1800

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

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

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

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

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

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

1964
        $quotedPattern = \preg_quote($pattern, '/');
144✔
1965
        $replaceWildCards = \str_replace('\*', '.*', $quotedPattern);
144✔
1966

1967
        return $this->matchesPattern('^' . $replaceWildCards . '\z');
144✔
1968
    }
1969

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

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

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

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

2043
    /**
2044
     * Check if the input is binary... (is look like a hack).
2045
     *
2046
     * EXAMPLE: <code>s(01)->isBinary(); // true</code>
2047
     *
2048
     * @psalm-mutation-free
2049
     *
2050
     * @return bool
2051
     */
2052
    public function isBinary(): bool
2053
    {
2054
        return $this->utf8::is_binary($this->str);
6✔
2055
    }
2056

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

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

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

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

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

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

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

2192
        return true;
18✔
2193
    }
2194

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

2227
        return true;
18✔
2228
    }
2229

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

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

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

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

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

2327
    /**
2328
     * Determine if the string is composed of numeric characters.
2329
     *
2330
     * EXAMPLE: <code>
2331
     * </code>
2332
     *
2333
     * @psalm-mutation-free
2334
     *
2335
     * @return bool
2336
     */
2337
    public function isNumeric(): bool
2338
    {
2339
        return \is_numeric($this->str);
24✔
2340
    }
2341

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

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

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

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

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

2425
    /**
2426
     * /**
2427
     * Check if $url is an correct url.
2428
     *
2429
     * @param bool $disallow_localhost
2430
     *
2431
     * @psalm-mutation-free
2432
     *
2433
     * @return bool
2434
     */
2435
    public function isUrl(bool $disallow_localhost = false): bool
2436
    {
2437
        return $this->utf8::is_url($this->str, $disallow_localhost);
×
2438
    }
2439

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

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

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

2490
    /**
2491
     * Returns true if the string contains only whitespace chars, false otherwise.
2492
     *
2493
     * EXAMPLE: <code>
2494
     * </code>
2495
     *
2496
     * @psalm-mutation-free
2497
     *
2498
     * @return bool
2499
     *              <p>Whether or not $str contains only whitespace characters.</p>
2500
     */
2501
    public function isWhitespace(): bool
2502
    {
2503
        return $this->isBlank();
180✔
2504
    }
2505

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

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

2543
        return new static(\implode('-', $words), $this->encoding);
18✔
2544
    }
2545

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

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

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

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

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

2673
        $delimiter = $delimiter === '' ? null : $delimiter;
18✔
2674

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

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

2718
        $delimiter = $delimiter === '' ? null : $delimiter;
24✔
2719

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

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

2754
        $strings = $this->utf8::str_to_lines($this->str);
288✔
2755
        /** @noinspection AlterInForeachInspection */
2756
        foreach ($strings as &$str) {
288✔
2757
            $str = static::create($str, $this->encoding);
288✔
2758
        }
2759

2760
        /** @noinspection PhpSillyAssignmentInspection */
2761
        /** @var static[] $strings */
2762
        $strings = $strings;
288✔
2763

2764
        return $strings;
288✔
2765
    }
2766

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

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

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

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

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

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

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

2932
    /**
2933
     * Create a md5 hash from the current string.
2934
     *
2935
     * @psalm-mutation-free
2936
     *
2937
     * @return static
2938
     */
2939
    public function md5(): self
2940
    {
2941
        return static::create($this->hash('md5'), $this->encoding);
12✔
2942
    }
2943

2944
    /**
2945
     * Replace all breaks [<br> | \r\n | \r | \n | ...] into "<br>".
2946
     *
2947
     * EXAMPLE: <code>
2948
     * </code>
2949
     *
2950
     * @return static
2951
     */
2952
    public function newLineToHtmlBreak(): self
2953
    {
2954
        return $this->removeHtmlBreak('<br>');
6✔
2955
    }
2956

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

2975
        if ($substring === '') {
24✔
2976
            return new static('', $this->encoding);
×
2977
        }
2978

2979
        \preg_match_all(
24✔
2980
            "/(?:^|(?:.|\p{L}|\w){" . $length . "})(.|\p{L}|\w)/u",
24✔
2981
            $substring,
24✔
2982
            $matches
24✔
2983
        );
24✔
2984

2985
        return new static(\implode('', $matches[1] ?? []), $this->encoding);
24✔
2986
    }
2987

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

3005
        \preg_match_all('/(?<integers>\d+)/', $this->str, $matches);
6✔
3006

3007
        return static::create(
6✔
3008
            \implode('', $matches['integers']),
6✔
3009
            $this->encoding
6✔
3010
        );
6✔
3011
    }
3012

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

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

3033
        return static::create(
6✔
3034
            \implode('', $matches[0]),
6✔
3035
            $this->encoding
6✔
3036
        );
6✔
3037
    }
3038

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

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

3086
        if (
3087
            ($offset >= 0 && $length <= $offset)
36✔
3088
            ||
3089
            $length < \abs($offset)
36✔
3090
        ) {
3091
            throw new \OutOfBoundsException('No character exists at the index');
18✔
3092
        }
3093

3094
        if ($this->encoding === 'UTF-8') {
18✔
3095
            return (string) \mb_substr($this->str, $offset, 1);
18✔
3096
        }
3097

NEW
3098
        return (string) $this->utf8::substr($this->str, $offset, 1, $this->encoding);
×
3099
    }
3100

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

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

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

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

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

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

3264
    /**
3265
     * Convert the string to PascalCase.
3266
     * Alias for studlyCase()
3267
     *
3268
     * EXAMPLE: <code>
3269
     * </code>
3270
     *
3271
     * @psalm-mutation-free
3272
     *
3273
     * @return static
3274
     */
3275
    public function pascalCase(): self
3276
    {
3277
        return $this->studlyCase();
18✔
3278
    }
3279

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

3302
        return static::create($prefix . $this->str, $this->encoding);
48✔
3303
    }
3304

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

3331
        return static::create($prefixStr . $this->str, $this->encoding);
6✔
3332
    }
3333

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

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

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

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

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

3458
    /**
3459
     * Try to remove all XSS-attacks from the string.
3460
     *
3461
     * EXAMPLE: <code>
3462
     * 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 >'
3463
     * </code>
3464
     *
3465
     * @psalm-mutation-free
3466
     *
3467
     * @return static
3468
     */
3469
    public function removeXss(): self
3470
    {
3471
        /**
3472
         * @var AntiXSS|null
3473
         *
3474
         * @psalm-suppress ImpureStaticVariable
3475
         */
3476
        static $antiXss = null;
72✔
3477

3478
        if ($antiXss === null) {
72✔
3479
            $antiXss = new AntiXSS();
6✔
3480
        }
3481

3482
        /**
3483
         * @psalm-suppress ImpureMethodCall -> add more psalm stuff to the anti-xss class
3484
         */
3485
        $str = $antiXss->xss_clean($this->str);
72✔
3486

3487
        return static::create($str, $this->encoding);
72✔
3488
    }
3489

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

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

3534
        if ($this->str === '' && $search === '') {
360✔
3535
            return static::create($replacement, $this->encoding);
12✔
3536
        }
3537

3538
        if ($caseSensitive) {
348✔
3539
            return static::create(
288✔
3540
                \str_replace($search, $replacement, $this->str),
288✔
3541
                $this->encoding
288✔
3542
            );
288✔
3543
        }
3544

3545
        return static::create(
60✔
3546
            $this->utf8::str_ireplace($search, $replacement, $this->str),
60✔
3547
            $this->encoding
60✔
3548
        );
60✔
3549
    }
3550

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

3576
        return static::create(
84✔
3577
            $this->utf8::str_ireplace($search, $replacement, $this->str),
84✔
3578
            $this->encoding
84✔
3579
        );
84✔
3580
    }
3581

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

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

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

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

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

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

3725
    /**
3726
     * Set the internal character encoding.
3727
     *
3728
     * EXAMPLE: <code>
3729
     * </code>
3730
     *
3731
     * @param string $new_encoding <p>The desired character encoding.</p>
3732
     *
3733
     * @psalm-mutation-free
3734
     *
3735
     * @return static
3736
     */
3737
    public function setInternalEncoding(string $new_encoding): self
3738
    {
3739
        return new static($this->str, $new_encoding);
6✔
3740
    }
3741

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

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

3772
    /**
3773
     * Create a sha512 hash from the current string.
3774
     *
3775
     * EXAMPLE: <code>
3776
     * </code>
3777
     *
3778
     * @psalm-mutation-free
3779
     *
3780
     * @return static
3781
     */
3782
    public function sha512(): self
3783
    {
3784
        return static::create($this->hash('sha512'), $this->encoding);
12✔
3785
    }
3786

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

3807
        return static::create(
48✔
3808
            $this->utf8::str_limit_after_word($this->str, $length, $strAddOn),
48✔
3809
            $this->encoding
48✔
3810
        );
48✔
3811
    }
3812

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

3829
    /**
3830
     * Calculate the similarity between two strings.
3831
     *
3832
     * EXAMPLE: <code>
3833
     * </code>
3834
     *
3835
     * @param string $str <p>The delimiting string.</p>
3836
     *
3837
     * @psalm-mutation-free
3838
     *
3839
     * @return float
3840
     */
3841
    public function similarity(string $str): float
3842
    {
3843
        \similar_text($this->str, $str, $percent);
12✔
3844

3845
        return $percent;
12✔
3846
    }
3847

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

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

3926
    /**
3927
     * Convert the string to snake_case.
3928
     *
3929
     * EXAMPLE: <code>
3930
     * </code>
3931
     *
3932
     * @psalm-mutation-free
3933
     *
3934
     * @return static
3935
     */
3936
    public function snakeCase(): self
3937
    {
3938
        $words = \array_map(
18✔
3939
            static function (self $word) {
18✔
3940
                return $word->toLowerCase();
18✔
3941
            },
18✔
3942
            $this->words('', true)
18✔
3943
        );
18✔
3944

3945
        return new static(\implode('_', $words), $this->encoding);
18✔
3946
    }
3947

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

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

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

4013
        if ($limit === null) {
306✔
4014
            $limit = -1;
42✔
4015
        }
4016

4017
        $array = $this->utf8::str_split_pattern($this->str, $pattern, $limit);
306✔
4018
        foreach ($array as &$value) {
306✔
4019
            $value = static::create($value, $this->encoding);
270✔
4020
        }
4021

4022
        /** @noinspection PhpSillyAssignmentInspection */
4023
        /** @var static[] $array */
4024
        $array = $array;
306✔
4025

4026
        return $array;
306✔
4027
    }
4028

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

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

4081
        return $this->utf8::str_istarts_with($this->str, $substring);
276✔
4082
    }
4083

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

4107
        return $this->utf8::str_istarts_with_any($this->str, $substrings);
72✔
4108
    }
4109

4110
    /**
4111
     * Remove one or more strings from the string.
4112
     *
4113
     * EXAMPLE: <code>
4114
     * </code>
4115
     *
4116
     * @param string|string[] $search One or more strings to be removed
4117
     *
4118
     * @psalm-mutation-free
4119
     *
4120
     * @return static
4121
     */
4122
    public function strip($search): self
4123
    {
4124
        if (\is_array($search)) {
18✔
4125
            return $this->replaceAll($search, '');
6✔
4126
        }
4127

4128
        return $this->replace($search, '');
12✔
4129
    }
4130

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

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

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

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

4210
        return new static(\implode('', $words), $this->encoding);
36✔
4211
    }
4212

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

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

4263
        return $this->substr($start, $length);
18✔
4264
    }
4265

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

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

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

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

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

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

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

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

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

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

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

4571
        return static::create(
108✔
4572
            \str_replace("\t", $tab, $this->str),
108✔
4573
            $this->encoding
108✔
4574
        );
108✔
4575
    }
4576

4577
    /**
4578
     * Return Stringy object as string, but you can also use (string) for automatically casting the object into a
4579
     * string.
4580
     *
4581
     * EXAMPLE: <code>
4582
     * s('fòôbàř')->toString(); // 'fòôbàř'
4583
     * </code>
4584
     *
4585
     * @psalm-mutation-free
4586
     *
4587
     * @return string
4588
     */
4589
    public function toString(): string
4590
    {
4591
        return (string) $this->str;
13,206✔
4592
    }
4593

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

4620
        return static::create(
90✔
4621
            \str_replace($tab, "\t", $this->str),
90✔
4622
            $this->encoding
90✔
4623
        );
90✔
4624
    }
4625

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

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

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

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

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

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

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

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

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

4834
    /**
4835
     * Converts the first character of the supplied string to upper case.
4836
     *
4837
     * EXAMPLE: <code>
4838
     * s('σ foo')->upperCaseFirst(); // 'Σ foo'
4839
     * </code>
4840
     *
4841
     * @psalm-mutation-free
4842
     *
4843
     * @return static
4844
     *                <p>Object with the first character of $str being upper case.</p>
4845
     */
4846
    public function upperCaseFirst(): self
4847
    {
4848
        return static::create($this->utf8::ucfirst($this->str, $this->encoding), $this->encoding);
108✔
4849
    }
4850

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

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

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

4913
    /**
4914
     * Multi url-decoding + decode HTML entity + fix urlencoded-win1252-chars.
4915
     *
4916
     * e.g:
4917
     * 'test+test'                     => 'test+test'
4918
     * 'D&#252;sseldorf'               => 'Düsseldorf'
4919
     * 'D%FCsseldorf'                  => 'Düsseldorf'
4920
     * 'D&#xFC;sseldorf'               => 'Düsseldorf'
4921
     * 'D%26%23xFC%3Bsseldorf'         => 'Düsseldorf'
4922
     * 'Düsseldorf'                   => 'Düsseldorf'
4923
     * 'D%C3%BCsseldorf'               => 'Düsseldorf'
4924
     * 'D%C3%83%C2%BCsseldorf'         => 'Düsseldorf'
4925
     * 'D%25C3%2583%25C2%25BCsseldorf' => 'Düsseldorf'
4926
     *
4927
     * EXAMPLE: <code>
4928
     * </code>
4929
     *
4930
     * @psalm-mutation-free
4931
     *
4932
     * @return static
4933
     */
4934
    public function urlDecodeRawMulti(): self
4935
    {
4936
        return static::create($this->utf8::rawurldecode($this->str));
6✔
4937
    }
4938

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

4957
    /**
4958
     * Simple url-encoding.
4959
     *
4960
     * e.g:
4961
     * 'test test' => 'test%20test'
4962
     *
4963
     * EXAMPLE: <code>
4964
     * </code>
4965
     *
4966
     * @psalm-mutation-free
4967
     *
4968
     * @return static
4969
     */
4970
    public function urlEncodeRaw(): self
4971
    {
4972
        return static::create(\rawurlencode($this->str));
6✔
4973
    }
4974

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

5007
        foreach ($replacements as $from => $to) {
192✔
5008
            $str = \str_replace($from, $to, $str);
192✔
5009
        }
5010

5011
        return static::create(
192✔
5012
            URLify::slug(
192✔
5013
                $str,
192✔
5014
                $language,
192✔
5015
                $separator,
192✔
5016
                $strToLower
192✔
5017
            ),
192✔
5018
            $this->encoding
192✔
5019
        );
192✔
5020
    }
5021

5022
    /**
5023
     * Converts the string into an valid UTF-8 string.
5024
     *
5025
     * EXAMPLE: <code>
5026
     * s('Düsseldorf')->utf8ify(); // 'Düsseldorf'
5027
     * </code>
5028
     *
5029
     * @psalm-mutation-free
5030
     *
5031
     * @return static
5032
     */
5033
    public function utf8ify(): self
5034
    {
5035
        return static::create($this->utf8::cleanup($this->str), $this->encoding);
12✔
5036
    }
5037

5038
    /**
5039
     * Convert a string into an array of words.
5040
     *
5041
     * EXAMPLE: <code>
5042
     * </code>
5043
     *
5044
     * @param string   $char_list           [optional] <p>Additional chars for the definition of "words".</p>
5045
     * @param bool     $remove_empty_values [optional] <p>Remove empty values.</p>
5046
     * @param int|null $remove_short_values [optional] <p>The min. string length or null to disable</p>
5047
     *
5048
     * @psalm-mutation-free
5049
     *
5050
     * @return static[]
5051
     *
5052
     * @phpstan-return array<int,static>
5053
     */
5054
    public function words(
5055
        string $char_list = '',
5056
        bool $remove_empty_values = false,
5057
        int $remove_short_values = null
5058
    ): array {
5059
        if ($remove_short_values === null) {
84✔
5060
            $strings = $this->utf8::str_to_words(
84✔
5061
                $this->str,
84✔
5062
                $char_list,
84✔
5063
                $remove_empty_values
84✔
5064
            );
84✔
5065
        } else {
5066
            $strings = $this->utf8::str_to_words(
12✔
5067
                $this->str,
12✔
5068
                $char_list,
12✔
5069
                $remove_empty_values,
12✔
5070
                $remove_short_values
12✔
5071
            );
12✔
5072
        }
5073

5074
        /** @noinspection AlterInForeachInspection */
5075
        foreach ($strings as &$string) {
84✔
5076
            $string = static::create($string);
84✔
5077
        }
5078

5079
        /** @noinspection PhpSillyAssignmentInspection */
5080
        /** @var static[] $strings */
5081
        $strings = $strings;
84✔
5082

5083
        return $strings;
84✔
5084
    }
5085

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

5121
    /**
5122
     * Surrounds $str with the given substring.
5123
     *
5124
     * EXAMPLE: <code>
5125
     * </code>
5126
     *
5127
     * @param string $substring <p>The substring to add to both sides.</P>
5128
     *
5129
     * @psalm-mutation-free
5130
     *
5131
     * @return static
5132
     *                <p>Object whose $str had the substring both prepended and appended.</p>
5133
     */
5134
    public function wrap(string $substring): self
5135
    {
5136
        return $this->surround($substring);
60✔
5137
    }
5138

5139
    /**
5140
     * Returns the replacements for the toAscii() method.
5141
     *
5142
     * @psalm-mutation-free
5143
     *
5144
     * @return array<string, array<int, string>>
5145
     *                                           <p>An array of replacements.</p>
5146
     *
5147
     * @deprecated   this is only here for backward-compatibly reasons
5148
     */
5149
    protected function charsArray(): array
5150
    {
5151
        return $this->ascii::charsArrayWithMultiLanguageValues();
6✔
5152
    }
5153

5154
    /**
5155
     * Returns true if $str matches the supplied pattern, false otherwise.
5156
     *
5157
     * @param string $pattern <p>Regex pattern to match against.</p>
5158
     *
5159
     * @psalm-mutation-free
5160
     *
5161
     * @return bool
5162
     *              <p>Whether or not $str matches the pattern.</p>
5163
     */
5164
    protected function matchesPattern(string $pattern): bool
5165
    {
5166
        return $this->utf8::str_matches_pattern($this->str, $pattern);
144✔
5167
    }
5168
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc