Completed
Push — master ( 070da9...6b3f4e )
by Lars
03:15
created

Stringy::lastSubstringOf()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 12
Ratio 100 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 2
dl 12
loc 12
ccs 5
cts 6
cp 0.8333
crap 3.0416
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Stringy;
6
7
use voku\helper\AntiXSS;
8
use voku\helper\EmailCheck;
9
use voku\helper\URLify;
10
use voku\helper\UTF8;
11
12
/**
13
 * Class Stringy
14
 *
15
 * @package Stringy
16
 */
17
class Stringy implements \Countable, \IteratorAggregate, \ArrayAccess
18
{
19
  /**
20
   * An instance's string.
21
   *
22
   * @var string
23
   */
24
  protected $str;
25
26
  /**
27
   * The string's encoding, which should be one of the mbstring module's
28
   * supported encodings.
29
   *
30
   * @var string
31
   */
32
  protected $encoding;
33
34
  /**
35
   * Initializes a Stringy object and assigns both str and encoding properties
36
   * the supplied values. $str is cast to a string prior to assignment, and if
37
   * $encoding is not specified, it defaults to mb_internal_encoding(). Throws
38
   * an InvalidArgumentException if the first argument is an array or object
39
   * without a __toString method.
40
   *
41
   * @param mixed  $str      [optional] <p>Value to modify, after being cast to string. Default: ''</p>
42
   * @param string $encoding [optional] <p>The character encoding.</p>
43
   *
44
   * @throws \InvalidArgumentException <p>if an array or object without a
45
   *         __toString method is passed as the first argument</p>
46
   */
47 1117
  public function __construct($str = '', string $encoding = null)
48
  {
49 1117
    if (\is_array($str)) {
50 1
      throw new \InvalidArgumentException(
51 1
          'Passed value cannot be an array'
52
      );
53
    }
54
55
    if (
56 1116
        \is_object($str)
57
        &&
58 1116
        !\method_exists($str, '__toString')
59
    ) {
60 1
      throw new \InvalidArgumentException(
61 1
          'Passed object must have a __toString method'
62
      );
63
    }
64
65
    // init
66 1115
    UTF8::checkForSupport();
67
68 1115
    $this->str = (string)$str;
69
70 1115
    if ($encoding) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $encoding of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
71 864
      $this->encoding = $encoding;
72
    } else {
73 741
      $this->encoding = \mb_internal_encoding();
74
    }
75 1115
  }
76
77
  /**
78
   * Returns the value in $str.
79
   *
80
   * @return string <p>The current value of the $str property.</p>
81
   */
82 186
  public function __toString()
83
  {
84 186
    return (string)$this->str;
85
  }
86
87
  /**
88
   * Returns a new string with $string appended.
89
   *
90
   * @param string $string <p>The string to append.</p>
91
   *
92
   * @return static <p>Object with appended $string.</p>
93
   */
94 5
  public function append(string $string): Stringy
95
  {
96 5
    return static::create($this->str . $string, $this->encoding);
97
  }
98
99
  /**
100
   * Append an password (limited to chars that are good readable).
101
   *
102
   * @param int $length <p>Length of the random string.</p>
103
   *
104
   * @return static <p>Object with appended password.</p>
105
   */
106 1
  public function appendPassword(int $length): Stringy
107
  {
108 1
    $possibleChars = '2346789bcdfghjkmnpqrtvwxyzBCDFGHJKLMNPQRTVWXYZ';
109
110 1
    return $this->appendRandomString($length, $possibleChars);
111
  }
112
113
  /**
114
   * Append an unique identifier.
115
   *
116
   * @param string|int $entropyExtra [optional] <p>Extra entropy via a string or int value.</p>
117
   * @param bool       $md5          [optional] <p>Return the unique identifier as md5-hash? Default: true</p>
118
   *
119
   * @return static <p>Object with appended unique identifier as md5-hash.</p>
120
   */
121 1
  public function appendUniqueIdentifier($entropyExtra = '', bool $md5 = true): Stringy
122
  {
123 1
    $uniqueHelper = \mt_rand() .
124 1
                    \session_id() .
125 1
                    ($_SERVER['REMOTE_ADDR'] ?? '') .
126 1
                    ($_SERVER['SERVER_ADDR'] ?? '') .
127 1
                    $entropyExtra;
128
129 1
    $uniqueString = \uniqid($uniqueHelper, true);
130
131 1
    if ($md5) {
132 1
      $uniqueString = \md5($uniqueString . $uniqueHelper);
133
    }
134
135 1
    return $this->append($uniqueString);
136
  }
137
138
  /**
139
   * Append an random string.
140
   *
141
   * @param int    $length        <p>Length of the random string.</p>
142
   * @param string $possibleChars [optional] <p>Characters string for the random selection.</p>
143
   *
144
   * @return static <p>Object with appended random string.</p>
145
   */
146 2
  public function appendRandomString(int $length, string $possibleChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'): Stringy
147
  {
148
    // init
149 2
    $i = 0;
150 2
    $str = $this->str;
151 2
    $maxlength = UTF8::strlen($possibleChars, $this->encoding);
152
153 2
    if ($maxlength === 0) {
154 1
      return $this;
155
    }
156
157
    // add random chars
158 2
    while ($i < $length) {
159 2
      $char = UTF8::substr($possibleChars, random_int(0, $maxlength - 1), 1, $this->encoding);
160 2
      $str .= $char;
161 2
      $i++;
162
    }
163
164 2
    return $this->append($str);
165
  }
166
167
  /**
168
   * Creates a Stringy object and assigns both str and encoding properties
169
   * the supplied values. $str is cast to a string prior to assignment, and if
170
   * $encoding is not specified, it defaults to mb_internal_encoding(). It
171
   * then returns the initialized object. Throws an InvalidArgumentException
172
   * if the first argument is an array or object without a __toString method.
173
   *
174
   * @param  mixed  $str      [optional] <p>Value to modify, after being cast to string. Default: ''</p>
175
   * @param  string $encoding [optional] <p>The character encoding.</p>
176
   *
177
   * @return static <p>A Stringy object.</p>
178
   *
179
   * @throws \InvalidArgumentException <p>if an array or object without a
180
   *         __toString method is passed as the first argument</p>
181
   */
182 1107
  public static function create($str = '', string $encoding = null): Stringy
183
  {
184 1107
    return new static($str, $encoding);
185
  }
186
187
  /**
188
   * Returns the substring between $start and $end, if found, or an empty
189
   * string. An optional offset may be supplied from which to begin the
190
   * search for the start string.
191
   *
192
   * @param  string $start  <p>Delimiter marking the start of the substring.</p>
193
   * @param  string $end    <p>Delimiter marking the end of the substring.</p>
194
   * @param  int    $offset [optional] <p>Index from which to begin the search. Default: 0</p>
195
   *
196
   * @return static <p>Object whose $str is a substring between $start and $end.</p>
197
   */
198 16
  public function between(string $start, string $end, int $offset = 0): Stringy
199
  {
200 16
    $startIndex = $this->indexOf($start, $offset);
201 16
    if ($startIndex === false) {
202 2
      return static::create('', $this->encoding);
203
    }
204
205 14
    $substrIndex = $startIndex + UTF8::strlen($start, $this->encoding);
206 14
    $endIndex = $this->indexOf($end, $substrIndex);
207 14
    if ($endIndex === false) {
208 2
      return static::create('', $this->encoding);
209
    }
210
211 12
    return $this->substr($substrIndex, $endIndex - $substrIndex);
212
  }
213
214
  /**
215
   * Returns the index of the first occurrence of $needle in the string,
216
   * and false if not found. Accepts an optional offset from which to begin
217
   * the search.
218
   *
219
   * @param  string $needle <p>Substring to look for.</p>
220
   * @param  int    $offset [optional] <p>Offset from which to search. Default: 0</p>
221
   *
222
   * @return int|false <p>The occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
223
   */
224 29
  public function indexOf(string $needle, int $offset = 0)
225
  {
226 29
    return UTF8::strpos($this->str, $needle, $offset, $this->encoding);
227
  }
228
229
  /**
230
   * Returns the index of the first occurrence of $needle in the string,
231
   * and false if not found. Accepts an optional offset from which to begin
232
   * the search.
233
   *
234
   * @param  string $needle <p>Substring to look for.</p>
235
   * @param  int    $offset [optional] <p>Offset from which to search. Default: 0</p>
236
   *
237
   * @return int|false <p>The occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
238
   */
239 2
  public function indexOfIgnoreCase(string $needle, int $offset = 0)
240
  {
241 2
    return UTF8::stripos($this->str, $needle, $offset, $this->encoding);
242
  }
243
244
  /**
245
   * Returns the substring beginning at $start with the specified $length.
246
   * It differs from the UTF8::substr() function in that providing a $length of
247
   * null will return the rest of the string, rather than an empty string.
248
   *
249
   * @param int $start  <p>Position of the first character to use.</p>
250
   * @param int $length [optional] <p>Maximum number of characters used. Default: null</p>
251
   *
252
   * @return static <p>Object with its $str being the substring.</p>
253
   */
254 65
  public function substr(int $start, int $length = null): Stringy
255
  {
256 65
    if ($length === null) {
257 19
      $length = $this->length();
258
    }
259
260 65
    $str = UTF8::substr($this->str, $start, $length, $this->encoding);
261
262 65
    return static::create($str, $this->encoding);
263
  }
264
265
  /**
266
   * Returns the length of the string.
267
   *
268
   * @return int <p>The number of characters in $str given the encoding.</p>
269
   */
270 259
  public function length(): int
271
  {
272 259
    return UTF8::strlen($this->str, $this->encoding);
273
  }
274
275
  /**
276
   * Trims the string and replaces consecutive whitespace characters with a
277
   * single space. This includes tabs and newline characters, as well as
278
   * multibyte whitespace such as the thin space and ideographic space.
279
   *
280
   * @return static <p>Object with a trimmed $str and condensed whitespace.</p>
281
   */
282 52
  public function collapseWhitespace(): Stringy
283
  {
284 52
    return $this->regexReplace('[[:space:]]+', ' ')->trim();
285
  }
286
287
  /**
288
   * Returns a string with whitespace removed from the start and end of the
289
   * string. Supports the removal of unicode whitespace. Accepts an optional
290
   * string of characters to strip instead of the defaults.
291
   *
292
   * @param string $chars [optional] <p>String of characters to strip. Default: null</p>
293
   *
294
   * @return static <p>Object with a trimmed $str.</p>
295
   */
296 153 View Code Duplication
  public function trim(string $chars = null): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
297
  {
298 153
    if (!$chars) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $chars of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
299 152
      $chars = '[:space:]';
300
    } else {
301 1
      $chars = \preg_quote($chars, '/');
302
    }
303
304 153
    return $this->regexReplace("^[$chars]+|[$chars]+\$", '');
305
  }
306
307
  /**
308
   * Replaces all occurrences of $pattern in $str by $replacement.
309
   *
310
   * @param  string $pattern     <p>The regular expression pattern.</p>
311
   * @param  string $replacement <p>The string to replace with.</p>
312
   * @param  string $options     [optional] <p>Matching conditions to be used.</p>
313
   * @param  string $delimiter   [optional] <p>Delimiter the the regex. Default: '/'</p>
314
   *
315
   * @return static <p>Object with the result2ing $str after the replacements.</p>
316
   */
317 224
  public function regexReplace(string $pattern, string $replacement, string $options = '', string $delimiter = '/'): Stringy
318
  {
319 224
    if ($options === 'msr') {
320 9
      $options = 'ms';
321
    }
322
323
    // fallback
324 224
    if (!$delimiter) {
325
      $delimiter = '/';
326
    }
327
328 224
    $str = (string)\preg_replace(
329 224
        $delimiter . $pattern . $delimiter . 'u' . $options,
330 224
        $replacement,
331 224
        $this->str
332
    );
333
334 224
    return static::create($str, $this->encoding);
335
  }
336
337
  /**
338
   * Returns true if the string contains all $needles, false otherwise. By
339
   * default the comparison is case-sensitive, but can be made insensitive by
340
   * setting $caseSensitive to false.
341
   *
342
   * @param  array $needles       <p>SubStrings to look for.</p>
343
   * @param  bool  $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
344
   *
345
   * @return bool  <p>Whether or not $str contains $needle.</p>
346
   */
347 43 View Code Duplication
  public function containsAll(array $needles, bool $caseSensitive = true): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
348
  {
349
    /** @noinspection IsEmptyFunctionUsageInspection */
350 43
    if (empty($needles)) {
351 1
      return false;
352
    }
353
354 42
    foreach ($needles as $needle) {
355 42
      if (!$this->contains($needle, $caseSensitive)) {
356 42
        return false;
357
      }
358
    }
359
360 24
    return true;
361
  }
362
363
  /**
364
   * Returns true if the string contains $needle, false otherwise. By default
365
   * the comparison is case-sensitive, but can be made insensitive by setting
366
   * $caseSensitive to false.
367
   *
368
   * @param string $needle        <p>Substring to look for.</p>
369
   * @param bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
370
   *
371
   * @return bool   <p>Whether or not $str contains $needle.</p>
372
   */
373 105
  public function contains(string $needle, bool $caseSensitive = true): bool
374
  {
375 105
    $encoding = $this->encoding;
376
377 105
    if ($caseSensitive) {
378 55
      return (UTF8::strpos($this->str, $needle, 0, $encoding) !== false);
379
    }
380
381 50
    return (UTF8::stripos($this->str, $needle, 0, $encoding) !== false);
382
  }
383
384
  /**
385
   * Returns true if the string contains any $needles, false otherwise. By
386
   * default the comparison is case-sensitive, but can be made insensitive by
387
   * setting $caseSensitive to false.
388
   *
389
   * @param  array $needles       <p>SubStrings to look for.</p>
390
   * @param  bool  $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
391
   *
392
   * @return bool <p>Whether or not $str contains $needle.</p>
393
   */
394 43 View Code Duplication
  public function containsAny(array $needles, bool $caseSensitive = true): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
395
  {
396
    /** @noinspection IsEmptyFunctionUsageInspection */
397 43
    if (empty($needles)) {
398 1
      return false;
399
    }
400
401 42
    foreach ($needles as $needle) {
402 42
      if ($this->contains($needle, $caseSensitive)) {
403 42
        return true;
404
      }
405
    }
406
407 18
    return false;
408
  }
409
410
  /**
411
   * Returns the length of the string, implementing the countable interface.
412
   *
413
   * @return int <p>The number of characters in the string, given the encoding.</p>
414
   */
415 1
  public function count(): int
416
  {
417 1
    return $this->length();
418
  }
419
420
  /**
421
   * Returns the number of occurrences of $substring in the given string.
422
   * By default, the comparison is case-sensitive, but can be made insensitive
423
   * by setting $caseSensitive to false.
424
   *
425
   * @param  string $substring     <p>The substring to search for.</p>
426
   * @param  bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
427
   *
428
   * @return int|false <p>This functions returns an integer or false if there isn't a string.</p>
429
   */
430 15
  public function countSubstr(string $substring, bool $caseSensitive = true)
431
  {
432 15
    if ($caseSensitive) {
433 9
      return UTF8::substr_count($this->str, $substring, 0, null, $this->encoding);
434
    }
435
436 6
    $str = UTF8::strtoupper($this->str, $this->encoding);
437 6
    $substring = UTF8::strtoupper($substring, $this->encoding);
438
439 6
    return UTF8::substr_count($str, $substring, 0, null, $this->encoding);
440
  }
441
442
  /**
443
   * Returns a lowercase and trimmed string separated by dashes. Dashes are
444
   * inserted before uppercase characters (with the exception of the first
445
   * character of the string), and in place of spaces as well as underscores.
446
   *
447
   * @return static <p>Object with a dasherized $str</p>
448
   */
449 19
  public function dasherize(): Stringy
450
  {
451 19
    return $this->delimit('-');
452
  }
453
454
  /**
455
   * Returns a lowercase and trimmed string separated by the given delimiter.
456
   * Delimiters are inserted before uppercase characters (with the exception
457
   * of the first character of the string), and in place of spaces, dashes,
458
   * and underscores. Alpha delimiters are not converted to lowercase.
459
   *
460
   * @param string $delimiter <p>Sequence used to separate parts of the string.</p>
461
   *
462
   * @return static <p>Object with a delimited $str.</p>
463
   */
464 49
  public function delimit(string $delimiter): Stringy
465
  {
466 49
    $str = $this->trim();
467
468 49
    $str = (string)\preg_replace('/\B([A-Z])/u', '-\1', $str);
469
470 49
    $str = UTF8::strtolower($str, $this->encoding);
471
472 49
    $str = (string)\preg_replace('/[-_\s]+/u', $delimiter, $str);
473
474 49
    return static::create($str, $this->encoding);
475
  }
476
477
  /**
478
   * Ensures that the string begins with $substring. If it doesn't, it's
479
   * prepended.
480
   *
481
   * @param string $substring <p>The substring to add if not present.</p>
482
   *
483
   * @return static <p>Object with its $str prefixed by the $substring.</p>
484
   */
485 10 View Code Duplication
  public function ensureLeft(string $substring): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
486
  {
487 10
    $stringy = static::create($this->str, $this->encoding);
488
489 10
    if (!$stringy->startsWith($substring)) {
490 4
      $stringy->str = $substring . $stringy->str;
491
    }
492
493 10
    return $stringy;
494
  }
495
496
  /**
497
   * Returns true if the string begins with $substring, false otherwise. By
498
   * default, the comparison is case-sensitive, but can be made insensitive
499
   * by setting $caseSensitive to false.
500
   *
501
   * @param  string $substring     <p>The substring to look for.</p>
502
   * @param  bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
503
   *
504
   * @return bool   <p>Whether or not $str starts with $substring.</p>
505
   */
506 45
  public function startsWith(string $substring, bool $caseSensitive = true): bool
507
  {
508 45
    $str = $this->str;
509
510 45 View Code Duplication
    if (!$caseSensitive) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
511 8
      $substring = UTF8::strtolower($substring, $this->encoding);
512 8
      $str = UTF8::strtolower($this->str, $this->encoding);
513
    }
514
515 45
    return UTF8::strpos($str, $substring, 0, $this->encoding) === 0;
516
  }
517
518
  /**
519
   * Returns true if the string begins with any of $substrings, false otherwise.
520
   * By default the comparison is case-sensitive, but can be made insensitive by
521
   * setting $caseSensitive to false.
522
   *
523
   * @param  array $substrings    <p>Substrings to look for.</p>
524
   * @param  bool  $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
525
   *
526
   * @return bool  <p>Whether or not $str starts with $substring.</p>
527
   */
528 12 View Code Duplication
  public function startsWithAny(array $substrings, bool $caseSensitive = true): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
529
  {
530 12
    if (empty($substrings)) {
531
      return false;
532
    }
533
534 12
    foreach ($substrings as $substring) {
535 12
      if ($this->startsWith($substring, $caseSensitive)) {
536 12
        return true;
537
      }
538
    }
539
540 6
    return false;
541
  }
542
543
  /**
544
   * Ensures that the string ends with $substring. If it doesn't, it's appended.
545
   *
546
   * @param string $substring <p>The substring to add if not present.</p>
547
   *
548
   * @return static <p>Object with its $str suffixed by the $substring.</p>
549
   */
550 10 View Code Duplication
  public function ensureRight(string $substring): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
551
  {
552 10
    $stringy = static::create($this->str, $this->encoding);
553
554 10
    if (!$stringy->endsWith($substring)) {
555 4
      $stringy->str .= $substring;
556
    }
557
558 10
    return $stringy;
559
  }
560
561
  /**
562
   * Returns true if the string ends with $substring, false otherwise. By
563
   * default, the comparison is case-sensitive, but can be made insensitive
564
   * by setting $caseSensitive to false.
565
   *
566
   * @param string $substring     <p>The substring to look for.</p>
567
   * @param bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
568
   *
569
   * @return bool   <p>Whether or not $str ends with $substring.</p>
570
   */
571 44
  public function endsWith(string $substring, bool $caseSensitive = true): bool
572
  {
573 44
    $substringLength = UTF8::strlen($substring, $this->encoding);
574 44
    $strLength = $this->length();
575
576 44
    $endOfStr = UTF8::substr(
577 44
        $this->str,
578 44
        $strLength - $substringLength,
579 44
        $substringLength,
580 44
        $this->encoding
581
    );
582
583 44 View Code Duplication
    if (!$caseSensitive) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
584 8
      $substring = UTF8::strtolower($substring, $this->encoding);
585 8
      $endOfStr = UTF8::strtolower($endOfStr, $this->encoding);
0 ignored issues
show
Security Bug introduced by
It seems like $endOfStr can also be of type false; however, voku\helper\UTF8::strtolower() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
586
    }
587
588 44
    return $substring === $endOfStr;
589
  }
590
591
  /**
592
   * Returns true if the string ends with any of $substrings, false otherwise.
593
   * By default, the comparison is case-sensitive, but can be made insensitive
594
   * by setting $caseSensitive to false.
595
   *
596
   * @param string[] $substrings    <p>Substrings to look for.</p>
597
   * @param bool     $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
598
   *
599
   * @return bool     <p>Whether or not $str ends with $substring.</p>
600
   */
601 11 View Code Duplication
  public function endsWithAny(array $substrings, bool $caseSensitive = true): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
602
  {
603 11
    if (empty($substrings)) {
604
      return false;
605
    }
606
607 11
    foreach ($substrings as $substring) {
608 11
      if ($this->endsWith($substring, $caseSensitive)) {
609 11
        return true;
610
      }
611
    }
612
613 6
    return false;
614
  }
615
616
  /**
617
   * Returns the first $n characters of the string.
618
   *
619
   * @param int $n <p>Number of characters to retrieve from the start.</p>
620
   *
621
   * @return static <p>Object with its $str being the first $n chars.</p>
622
   */
623 13 View Code Duplication
  public function first(int $n): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
624
  {
625 13
    $stringy = static::create($this->str, $this->encoding);
626
627 13
    if ($n < 0) {
628 2
      $stringy->str = '';
629
    } else {
630 11
      return $stringy->substr(0, $n);
631
    }
632
633 2
    return $stringy;
634
  }
635
636
  /**
637
   * Returns the encoding used by the Stringy object.
638
   *
639
   * @return string <p>The current value of the $encoding property.</p>
640
   */
641 3
  public function getEncoding(): string
642
  {
643 3
    return $this->encoding;
644
  }
645
646
  /**
647
   * Returns a new ArrayIterator, thus implementing the IteratorAggregate
648
   * interface. The ArrayIterator's constructor is passed an array of chars
649
   * in the multibyte string. This enables the use of foreach with instances
650
   * of Stringy\Stringy.
651
   *
652
   * @return \ArrayIterator <p>An iterator for the characters in the string.</p>
653
   */
654 1
  public function getIterator(): \ArrayIterator
655
  {
656 1
    return new \ArrayIterator($this->chars());
657
  }
658
659
  /**
660
   * Returns an array consisting of the characters in the string.
661
   *
662
   * @return array <p>An array of string chars.</p>
663
   */
664 4
  public function chars(): array
665
  {
666
    // init
667 4
    $chars = [];
668 4
    $l = $this->length();
669
670 4
    for ($i = 0; $i < $l; $i++) {
671 3
      $chars[] = $this->at($i)->str;
672
    }
673
674 4
    return $chars;
675
  }
676
677
  /**
678
   * Returns the character at $index, with indexes starting at 0.
679
   *
680
   * @param int $index <p>Position of the character.</p>
681
   *
682
   * @return static <p>The character at $index.</p>
683
   */
684 11
  public function at(int $index): Stringy
685
  {
686 11
    return $this->substr($index, 1);
687
  }
688
689
  /**
690
   * Returns true if the string contains a lower case char, false otherwise.
691
   *
692
   * @return bool <p>Whether or not the string contains a lower case character.</p>
693
   */
694 12
  public function hasLowerCase(): bool
695
  {
696 12
    return $this->matchesPattern('.*[[:lower:]]');
697
  }
698
699
  /**
700
   * Returns true if $str matches the supplied pattern, false otherwise.
701
   *
702
   * @param string $pattern <p>Regex pattern to match against.</p>
703
   *
704
   * @return bool <p>Whether or not $str matches the pattern.</p>
705
   */
706 103
  protected function matchesPattern(string $pattern): bool
707
  {
708 103
    if (\preg_match('/' . $pattern . '/u', $this->str)) {
709 64
      return true;
710
    }
711
712 39
    return false;
713
  }
714
715
  /**
716
   * Returns true if the string contains an upper case char, false otherwise.
717
   *
718
   * @return bool <p>Whether or not the string contains an upper case character.</p>
719
   */
720 12
  public function hasUpperCase(): bool
721
  {
722 12
    return $this->matchesPattern('.*[[:upper:]]');
723
  }
724
725
  /**
726
   * Convert all HTML entities to their applicable characters.
727
   *
728
   * @param int $flags       [optional] <p>
729
   *                         A bitmask of one or more of the following flags, which specify how to handle quotes and
730
   *                         which document type to use. The default is ENT_COMPAT.
731
   *                         <table>
732
   *                         Available <i>flags</i> constants
733
   *                         <tr valign="top">
734
   *                         <td>Constant Name</td>
735
   *                         <td>Description</td>
736
   *                         </tr>
737
   *                         <tr valign="top">
738
   *                         <td><b>ENT_COMPAT</b></td>
739
   *                         <td>Will convert double-quotes and leave single-quotes alone.</td>
740
   *                         </tr>
741
   *                         <tr valign="top">
742
   *                         <td><b>ENT_QUOTES</b></td>
743
   *                         <td>Will convert both double and single quotes.</td>
744
   *                         </tr>
745
   *                         <tr valign="top">
746
   *                         <td><b>ENT_NOQUOTES</b></td>
747
   *                         <td>Will leave both double and single quotes unconverted.</td>
748
   *                         </tr>
749
   *                         <tr valign="top">
750
   *                         <td><b>ENT_HTML401</b></td>
751
   *                         <td>
752
   *                         Handle code as HTML 4.01.
753
   *                         </td>
754
   *                         </tr>
755
   *                         <tr valign="top">
756
   *                         <td><b>ENT_XML1</b></td>
757
   *                         <td>
758
   *                         Handle code as XML 1.
759
   *                         </td>
760
   *                         </tr>
761
   *                         <tr valign="top">
762
   *                         <td><b>ENT_XHTML</b></td>
763
   *                         <td>
764
   *                         Handle code as XHTML.
765
   *                         </td>
766
   *                         </tr>
767
   *                         <tr valign="top">
768
   *                         <td><b>ENT_HTML5</b></td>
769
   *                         <td>
770
   *                         Handle code as HTML 5.
771
   *                         </td>
772
   *                         </tr>
773
   *                         </table>
774
   *                         </p>
775
   *
776
   * @return static <p>Object with the resulting $str after being html decoded.</p>
777
   */
778 5
  public function htmlDecode(int $flags = ENT_COMPAT): Stringy
779
  {
780 5
    $str = UTF8::html_entity_decode($this->str, $flags, $this->encoding);
781
782 5
    return static::create($str, $this->encoding);
783
  }
784
785
  /**
786
   * Convert all applicable characters to HTML entities.
787
   *
788
   * @param int $flags       [optional] <p>
789
   *                         A bitmask of one or more of the following flags, which specify how to handle quotes and
790
   *                         which document type to use. The default is ENT_COMPAT.
791
   *                         <table>
792
   *                         Available <i>flags</i> constants
793
   *                         <tr valign="top">
794
   *                         <td>Constant Name</td>
795
   *                         <td>Description</td>
796
   *                         </tr>
797
   *                         <tr valign="top">
798
   *                         <td><b>ENT_COMPAT</b></td>
799
   *                         <td>Will convert double-quotes and leave single-quotes alone.</td>
800
   *                         </tr>
801
   *                         <tr valign="top">
802
   *                         <td><b>ENT_QUOTES</b></td>
803
   *                         <td>Will convert both double and single quotes.</td>
804
   *                         </tr>
805
   *                         <tr valign="top">
806
   *                         <td><b>ENT_NOQUOTES</b></td>
807
   *                         <td>Will leave both double and single quotes unconverted.</td>
808
   *                         </tr>
809
   *                         <tr valign="top">
810
   *                         <td><b>ENT_HTML401</b></td>
811
   *                         <td>
812
   *                         Handle code as HTML 4.01.
813
   *                         </td>
814
   *                         </tr>
815
   *                         <tr valign="top">
816
   *                         <td><b>ENT_XML1</b></td>
817
   *                         <td>
818
   *                         Handle code as XML 1.
819
   *                         </td>
820
   *                         </tr>
821
   *                         <tr valign="top">
822
   *                         <td><b>ENT_XHTML</b></td>
823
   *                         <td>
824
   *                         Handle code as XHTML.
825
   *                         </td>
826
   *                         </tr>
827
   *                         <tr valign="top">
828
   *                         <td><b>ENT_HTML5</b></td>
829
   *                         <td>
830
   *                         Handle code as HTML 5.
831
   *                         </td>
832
   *                         </tr>
833
   *                         </table>
834
   *                         </p>
835
   *
836
   * @return static <p>Object with the resulting $str after being html encoded.</p>
837
   */
838 5
  public function htmlEncode(int $flags = ENT_COMPAT): Stringy
839
  {
840 5
    $str = UTF8::htmlentities($this->str, $flags, $this->encoding);
841
842 5
    return static::create($str, $this->encoding);
843
  }
844
845
  /**
846
   * Capitalizes the first word of the string, replaces underscores with
847
   * spaces, and strips '_id'.
848
   *
849
   * @return static <p>Object with a humanized $str.</p>
850
   */
851 3
  public function humanize(): Stringy
852
  {
853 3
    $str = UTF8::str_replace(['_id', '_'], ['', ' '], $this->str);
854
855 3
    return static::create($str, $this->encoding)->trim()->upperCaseFirst();
856
  }
857
858
  /**
859
   * Converts the first character of the supplied string to upper case.
860
   *
861
   * @return static <p>Object with the first character of $str being upper case.</p>
862
   */
863 27 View Code Duplication
  public function upperCaseFirst(): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
864
  {
865 27
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
866 27
    $rest = UTF8::substr(
867 27
        $this->str,
868 27
        1,
869 27
        $this->length() - 1,
870 27
        $this->encoding
871
    );
872
873 27
    $str = UTF8::strtoupper($first, $this->encoding) . $rest;
0 ignored issues
show
Security Bug introduced by
It seems like $first defined by \voku\helper\UTF8::subst... 0, 1, $this->encoding) on line 865 can also be of type false; however, voku\helper\UTF8::strtoupper() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
874
875 27
    return static::create($str, $this->encoding);
876
  }
877
878
  /**
879
   * Returns the index of the last occurrence of $needle in the string,
880
   * and false if not found. Accepts an optional offset from which to begin
881
   * the search. Offsets may be negative to count from the last character
882
   * in the string.
883
   *
884
   * @param  string $needle <p>Substring to look for.</p>
885
   * @param  int    $offset [optional] <p>Offset from which to search. Default: 0</p>
886
   *
887
   * @return int|false <p>The last occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
888
   */
889 12
  public function indexOfLast(string $needle, int $offset = 0)
890
  {
891 12
    return UTF8::strrpos($this->str, $needle, $offset, $this->encoding);
892
  }
893
894
  /**
895
   * Returns the index of the last occurrence of $needle in the string,
896
   * and false if not found. Accepts an optional offset from which to begin
897
   * the search. Offsets may be negative to count from the last character
898
   * in the string.
899
   *
900
   * @param  string $needle <p>Substring to look for.</p>
901
   * @param  int    $offset [optional] <p>Offset from which to search. Default: 0</p>
902
   *
903
   * @return int|false <p>The last occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
904
   */
905 2
  public function indexOfLastIgnoreCase(string $needle, int $offset = 0)
906
  {
907 2
    return UTF8::strripos($this->str, $needle, $offset, $this->encoding);
908
  }
909
910
  /**
911
   * Inserts $substring into the string at the $index provided.
912
   *
913
   * @param  string $substring <p>String to be inserted.</p>
914
   * @param  int    $index     <p>The index at which to insert the substring.</p>
915
   *
916
   * @return static <p>Object with the resulting $str after the insertion.</p>
917
   */
918 8 View Code Duplication
  public function insert(string $substring, int $index): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
919
  {
920 8
    $stringy = static::create($this->str, $this->encoding);
921 8
    if ($index > $stringy->length()) {
922 1
      return $stringy;
923
    }
924
925 7
    $start = UTF8::substr($stringy->str, 0, $index, $stringy->encoding);
926 7
    $end = UTF8::substr($stringy->str, $index, $stringy->length(), $stringy->encoding);
927
928 7
    $stringy->str = $start . $substring . $end;
929
930 7
    return $stringy;
931
  }
932
933
  /**
934
   * Returns true if the string contains the $pattern, otherwise false.
935
   *
936
   * WARNING: Asterisks ("*") are translated into (".*") zero-or-more regular
937
   * expression wildcards.
938
   *
939
   * @credit Originally from Laravel, thanks Taylor.
940
   *
941
   * @param string $pattern <p>The string or pattern to match against.</p>
942
   *
943
   * @return bool <p>Whether or not we match the provided pattern.</p>
944
   */
945 13
  public function is(string $pattern): bool
946
  {
947 13
    if ($this->toString() === $pattern) {
948 1
      return true;
949
    }
950
951 12
    $quotedPattern = \preg_quote($pattern, '/');
952 12
    $replaceWildCards = \str_replace('\*', '.*', $quotedPattern);
953
954 12
    return $this->matchesPattern('^' . $replaceWildCards . '\z');
955
  }
956
957
  /**
958
   * Returns true if the string contains only alphabetic chars, false otherwise.
959
   *
960
   * @return bool <p>Whether or not $str contains only alphabetic chars.</p>
961
   */
962 10
  public function isAlpha(): bool
963
  {
964 10
    return $this->matchesPattern('^[[:alpha:]]*$');
965
  }
966
967
  /**
968
   * Determine whether the string is considered to be empty.
969
   *
970
   * A variable is considered empty if it does not exist or if its value equals FALSE.
971
   * empty() does not generate a warning if the variable does not exist.
972
   *
973
   * @return bool <p>Whether or not $str is empty().</p>
974
   */
975
  public function isEmpty(): bool
976
  {
977
    return empty($this->str);
978
  }
979
980
  /**
981
   * Returns true if the string contains only alphabetic and numeric chars, false otherwise.
982
   *
983
   * @return bool <p>Whether or not $str contains only alphanumeric chars.</p>
984
   */
985 13
  public function isAlphanumeric(): bool
986
  {
987 13
    return $this->matchesPattern('^[[:alnum:]]*$');
988
  }
989
990
  /**
991
   * Returns true if the string contains only whitespace chars, false otherwise.
992
   *
993
   * @return bool <p>Whether or not $str contains only whitespace characters.</p>
994
   */
995 15
  public function isBlank(): bool
996
  {
997 15
    return $this->matchesPattern('^[[:space:]]*$');
998
  }
999
1000
  /**
1001
   * Returns true if the string contains only hexadecimal chars, false otherwise.
1002
   *
1003
   * @return bool <p>Whether or not $str contains only hexadecimal chars.</p>
1004
   */
1005 13
  public function isHexadecimal(): bool
1006
  {
1007 13
    return $this->matchesPattern('^[[:xdigit:]]*$');
1008
  }
1009
1010
  /**
1011
   * Returns true if the string contains HTML-Tags, false otherwise.
1012
   *
1013
   * @return bool <p>Whether or not $str contains HTML-Tags.</p>
1014
   */
1015 1
  public function isHtml(): bool
1016
  {
1017 1
    return UTF8::is_html($this->str);
1018
  }
1019
1020
  /**
1021
   * Returns true if the string contains a valid E-Mail address, false otherwise.
1022
   *
1023
   * @param bool $useExampleDomainCheck   [optional] <p>Default: false</p>
1024
   * @param bool $useTypoInDomainCheck    [optional] <p>Default: false</p>
1025
   * @param bool $useTemporaryDomainCheck [optional] <p>Default: false</p>
1026
   * @param bool $useDnsCheck             [optional] <p>Default: false</p>
1027
   *
1028
   * @return bool <p>Whether or not $str contains a valid E-Mail address.</p>
1029
   */
1030 1
  public function isEmail(bool $useExampleDomainCheck = false, bool $useTypoInDomainCheck = false, bool $useTemporaryDomainCheck = false, bool $useDnsCheck = false): bool
1031
  {
1032 1
    return EmailCheck::isValid($this->str, $useExampleDomainCheck, $useTypoInDomainCheck, $useTemporaryDomainCheck, $useDnsCheck);
1033
  }
1034
1035
  /**
1036
   * Returns true if the string is JSON, false otherwise. Unlike json_decode
1037
   * in PHP 5.x, this method is consistent with PHP 7 and other JSON parsers,
1038
   * in that an empty string is not considered valid JSON.
1039
   *
1040
   * @return bool <p>Whether or not $str is JSON.</p>
1041
   */
1042 20
  public function isJson(): bool
1043
  {
1044 20
    if (!isset($this->str[0])) {
1045 1
      return false;
1046
    }
1047
1048 19
    \json_decode($this->str);
1049
1050 19
    return \json_last_error() === JSON_ERROR_NONE;
1051
  }
1052
1053
  /**
1054
   * Returns true if the string contains only lower case chars, false otherwise.
1055
   *
1056
   * @return bool <p>Whether or not $str contains only lower case characters.</p>
1057
   */
1058 8
  public function isLowerCase(): bool
1059
  {
1060 8
    if ($this->matchesPattern('^[[:lower:]]*$')) {
1061 3
      return true;
1062
    }
1063
1064 5
    return false;
1065
  }
1066
1067
  /**
1068
   * Returns true if the string is serialized, false otherwise.
1069
   *
1070
   * @return bool <p>Whether or not $str is serialized.</p>
1071
   */
1072 7
  public function isSerialized(): bool
1073
  {
1074 7
    if (!isset($this->str[0])) {
1075 1
      return false;
1076
    }
1077
1078
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1079
    /** @noinspection UnserializeExploitsInspection */
1080 6
    return $this->str === 'b:0;'
1081
           ||
1082 6
           @\unserialize($this->str) !== false;
1083
  }
1084
1085
  /**
1086
   * Returns true if the string contains only lower case chars, false
1087
   * otherwise.
1088
   *
1089
   * @return bool <p>Whether or not $str contains only lower case characters.</p>
1090
   */
1091 8
  public function isUpperCase(): bool
1092
  {
1093 8
    return $this->matchesPattern('^[[:upper:]]*$');
1094
  }
1095
1096
  /**
1097
   * Returns the last $n characters of the string.
1098
   *
1099
   * @param int $n <p>Number of characters to retrieve from the end.</p>
1100
   *
1101
   * @return static <p>Object with its $str being the last $n chars.</p>
1102
   */
1103 12 View Code Duplication
  public function last(int $n): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1104
  {
1105 12
    $stringy = static::create($this->str, $this->encoding);
1106
1107 12
    if ($n <= 0) {
1108 4
      $stringy->str = '';
1109
    } else {
1110 8
      return $stringy->substr(-$n);
1111
    }
1112
1113 4
    return $stringy;
1114
  }
1115
1116
  /**
1117
   * Splits on newlines and carriage returns, returning an array of Stringy
1118
   * objects corresponding to the lines in the string.
1119
   *
1120
   * @return static[] <p>An array of Stringy objects.</p>
1121
   */
1122 15
  public function lines(): array
1123
  {
1124 15
    $array = \preg_split('/[\r\n]{1,2}/u', $this->str);
1125
    /** @noinspection CallableInLoopTerminationConditionInspection */
1126
    /** @noinspection ForeachInvariantsInspection */
1127 15 View Code Duplication
    for ($i = 0; $i < \count($array); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1128 15
      $array[$i] = static::create($array[$i], $this->encoding);
1129
    }
1130
1131 15
    return $array;
1132
  }
1133
1134
  /**
1135
   * Returns the longest common prefix between the string and $otherStr.
1136
   *
1137
   * @param string $otherStr <p>Second string for comparison.</p>
1138
   *
1139
   * @return static <p>Object with its $str being the longest common prefix.</p>
1140
   */
1141 10
  public function longestCommonPrefix(string $otherStr): Stringy
1142
  {
1143 10
    $encoding = $this->encoding;
1144 10
    $maxLength = \min($this->length(), UTF8::strlen($otherStr, $encoding));
1145
1146 10
    $longestCommonPrefix = '';
1147 10
    for ($i = 0; $i < $maxLength; $i++) {
1148 8
      $char = UTF8::substr($this->str, $i, 1, $encoding);
1149
1150 8
      if ($char == UTF8::substr($otherStr, $i, 1, $encoding)) {
1151 6
        $longestCommonPrefix .= $char;
1152
      } else {
1153 6
        break;
1154
      }
1155
    }
1156
1157 10
    return static::create($longestCommonPrefix, $encoding);
1158
  }
1159
1160
  /**
1161
   * Returns the longest common suffix between the string and $otherStr.
1162
   *
1163
   * @param string $otherStr <p>Second string for comparison.</p>
1164
   *
1165
   * @return static <p>Object with its $str being the longest common suffix.</p>
1166
   */
1167 10
  public function longestCommonSuffix(string $otherStr): Stringy
1168
  {
1169 10
    $encoding = $this->encoding;
1170 10
    $maxLength = \min($this->length(), UTF8::strlen($otherStr, $encoding));
1171
1172 10
    $longestCommonSuffix = '';
1173 10
    for ($i = 1; $i <= $maxLength; $i++) {
1174 8
      $char = UTF8::substr($this->str, -$i, 1, $encoding);
1175
1176 8
      if ($char == UTF8::substr($otherStr, -$i, 1, $encoding)) {
1177 6
        $longestCommonSuffix = $char . $longestCommonSuffix;
1178
      } else {
1179 6
        break;
1180
      }
1181
    }
1182
1183 10
    return static::create($longestCommonSuffix, $encoding);
1184
  }
1185
1186
  /**
1187
   * Returns the longest common substring between the string and $otherStr.
1188
   * In the case of ties, it returns that which occurs first.
1189
   *
1190
   * @param string $otherStr <p>Second string for comparison.</p>
1191
   *
1192
   * @return static <p>Object with its $str being the longest common substring.</p>
1193
   */
1194 10
  public function longestCommonSubstring(string $otherStr): Stringy
1195
  {
1196
    // Uses dynamic programming to solve
1197
    // http://en.wikipedia.org/wiki/Longest_common_substring_problem
1198 10
    $encoding = $this->encoding;
1199 10
    $stringy = static::create($this->str, $encoding);
1200 10
    $strLength = $stringy->length();
1201 10
    $otherLength = UTF8::strlen($otherStr, $encoding);
1202
1203
    // Return if either string is empty
1204 10
    if ($strLength == 0 || $otherLength == 0) {
1205 2
      $stringy->str = '';
1206
1207 2
      return $stringy;
1208
    }
1209
1210 8
    $len = 0;
1211 8
    $end = 0;
1212 8
    $table = \array_fill(
1213 8
        0,
1214 8
        $strLength + 1,
1215 8
        \array_fill(0, $otherLength + 1, 0)
1216
    );
1217
1218 8
    for ($i = 1; $i <= $strLength; $i++) {
1219 8
      for ($j = 1; $j <= $otherLength; $j++) {
1220 8
        $strChar = UTF8::substr($stringy->str, $i - 1, 1, $encoding);
1221 8
        $otherChar = UTF8::substr($otherStr, $j - 1, 1, $encoding);
1222
1223 8
        if ($strChar == $otherChar) {
1224 8
          $table[$i][$j] = $table[$i - 1][$j - 1] + 1;
1225 8
          if ($table[$i][$j] > $len) {
1226 8
            $len = $table[$i][$j];
1227 8
            $end = $i;
1228
          }
1229
        } else {
1230 8
          $table[$i][$j] = 0;
1231
        }
1232
      }
1233
    }
1234
1235 8
    $stringy->str = UTF8::substr($stringy->str, $end - $len, $len, $encoding);
0 ignored issues
show
Documentation Bug introduced by
It seems like \voku\helper\UTF8::subst... $len, $len, $encoding) can also be of type false. However, the property $str is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1236
1237 8
    return $stringy;
1238
  }
1239
1240
  /**
1241
   * Returns whether or not a character exists at an index. Offsets may be
1242
   * negative to count from the last character in the string. Implements
1243
   * part of the ArrayAccess interface.
1244
   *
1245
   * @param int $offset <p>The index to check.</p>
1246
   *
1247
   * @return boolean <p>Whether or not the index exists.</p>
1248
   */
1249 6
  public function offsetExists($offset): bool
1250
  {
1251
    // init
1252 6
    $length = $this->length();
1253 6
    $offset = (int)$offset;
1254
1255 6
    if ($offset >= 0) {
1256 3
      return ($length > $offset);
1257
    }
1258
1259 3
    return ($length >= \abs($offset));
1260
  }
1261
1262
  /**
1263
   * Returns the character at the given index. Offsets may be negative to
1264
   * count from the last character in the string. Implements part of the
1265
   * ArrayAccess interface, and throws an OutOfBoundsException if the index
1266
   * does not exist.
1267
   *
1268
   * @param int $offset <p>The <strong>index</strong> from which to retrieve the char.</p>
1269
   *
1270
   * @return string <p>The character at the specified index.</p>
1271
   *
1272
   * @throws \OutOfBoundsException <p>If the positive or negative offset does not exist.</p>
1273
   */
1274 2
  public function offsetGet($offset): string
1275
  {
1276
    // init
1277 2
    $offset = (int)$offset;
1278 2
    $length = $this->length();
1279
1280
    if (
1281 2
        ($offset >= 0 && $length <= $offset)
1282
        ||
1283 2
        $length < \abs($offset)
1284
    ) {
1285 1
      throw new \OutOfBoundsException('No character exists at the index');
1286
    }
1287
1288 1
    return UTF8::substr($this->str, $offset, 1, $this->encoding);
1289
  }
1290
1291
  /**
1292
   * Implements part of the ArrayAccess interface, but throws an exception
1293
   * when called. This maintains the immutability of Stringy objects.
1294
   *
1295
   * @param int   $offset <p>The index of the character.</p>
1296
   * @param mixed $value  <p>Value to set.</p>
1297
   *
1298
   * @throws \Exception <p>When called.</p>
1299
   */
1300 1
  public function offsetSet($offset, $value)
1301
  {
1302
    // Stringy is immutable, cannot directly set char
1303 1
    throw new \Exception('Stringy object is immutable, cannot modify char');
1304
  }
1305
1306
  /**
1307
   * Implements part of the ArrayAccess interface, but throws an exception
1308
   * when called. This maintains the immutability of Stringy objects.
1309
   *
1310
   * @param int $offset <p>The index of the character.</p>
1311
   *
1312
   * @throws \Exception <p>When called.</p>
1313
   */
1314 1
  public function offsetUnset($offset)
1315
  {
1316
    // Don't allow directly modifying the string
1317 1
    throw new \Exception('Stringy object is immutable, cannot unset char');
1318
  }
1319
1320
  /**
1321
   * Pads the string to a given length with $padStr. If length is less than
1322
   * or equal to the length of the string, no padding takes places. The
1323
   * default string used for padding is a space, and the default type (one of
1324
   * 'left', 'right', 'both') is 'right'. Throws an InvalidArgumentException
1325
   * if $padType isn't one of those 3 values.
1326
   *
1327
   * @param int    $length  <p>Desired string length after padding.</p>
1328
   * @param string $padStr  [optional] <p>String used to pad, defaults to space. Default: ' '</p>
1329
   * @param string $padType [optional] <p>One of 'left', 'right', 'both'. Default: 'right'</p>
1330
   *
1331
   * @return static <p>Object with a padded $str.</p>
1332
   *
1333
   * @throws \InvalidArgumentException <p>If $padType isn't one of 'right', 'left' or 'both'.</p>
1334
   */
1335 13
  public function pad(int $length, string $padStr = ' ', string $padType = 'right'): Stringy
1336
  {
1337 13
    if (!\in_array($padType, ['left', 'right', 'both'], true)) {
1338 1
      throw new \InvalidArgumentException(
1339 1
          'Pad expects $padType ' . "to be one of 'left', 'right' or 'both'"
1340
      );
1341
    }
1342
1343
    switch ($padType) {
1344 12
      case 'left':
1345 3
        return $this->padLeft($length, $padStr);
1346 9
      case 'right':
1347 6
        return $this->padRight($length, $padStr);
1348
      default:
1349 3
        return $this->padBoth($length, $padStr);
1350
    }
1351
  }
1352
1353
  /**
1354
   * Returns a new string of a given length such that the beginning of the
1355
   * string is padded. Alias for pad() with a $padType of 'left'.
1356
   *
1357
   * @param int    $length <p>Desired string length after padding.</p>
1358
   * @param string $padStr [optional] <p>String used to pad, defaults to space. Default: ' '</p>
1359
   *
1360
   * @return static <p>String with left padding.</p>
1361
   */
1362 10
  public function padLeft(int $length, string $padStr = ' '): Stringy
1363
  {
1364 10
    return $this->applyPadding($length - $this->length(), 0, $padStr);
1365
  }
1366
1367
  /**
1368
   * Adds the specified amount of left and right padding to the given string.
1369
   * The default character used is a space.
1370
   *
1371
   * @param int    $left   [optional] <p>Length of left padding. Default: 0</p>
1372
   * @param int    $right  [optional] <p>Length of right padding. Default: 0</p>
1373
   * @param string $padStr [optional] <p>String used to pad. Default: ' '</p>
1374
   *
1375
   * @return static <p>String with padding applied.</p>
1376
   */
1377 37
  protected function applyPadding(int $left = 0, int $right = 0, string $padStr = ' '): Stringy
1378
  {
1379 37
    $stringy = static::create($this->str, $this->encoding);
1380
1381 37
    $length = UTF8::strlen($padStr, $stringy->encoding);
1382
1383 37
    $strLength = $stringy->length();
1384 37
    $paddedLength = $strLength + $left + $right;
1385
1386 37
    if (!$length || $paddedLength <= $strLength) {
1387 3
      return $stringy;
1388
    }
1389
1390 34
    $leftPadding = UTF8::substr(
1391 34
        UTF8::str_repeat(
1392 34
            $padStr,
1393 34
            (int)\ceil($left / $length)
1394
        ),
1395 34
        0,
1396 34
        $left,
1397 34
        $stringy->encoding
1398
    );
1399
1400 34
    $rightPadding = UTF8::substr(
1401 34
        UTF8::str_repeat(
1402 34
            $padStr,
1403 34
            (int)\ceil($right / $length)
1404
        ),
1405 34
        0,
1406 34
        $right,
1407 34
        $stringy->encoding
1408
    );
1409
1410 34
    $stringy->str = $leftPadding . $stringy->str . $rightPadding;
1411
1412 34
    return $stringy;
1413
  }
1414
1415
  /**
1416
   * Returns a new string of a given length such that the end of the string
1417
   * is padded. Alias for pad() with a $padType of 'right'.
1418
   *
1419
   * @param int    $length <p>Desired string length after padding.</p>
1420
   * @param string $padStr [optional] <p>String used to pad, defaults to space. Default: ' '</p>
1421
   *
1422
   * @return static <p>String with right padding.</p>
1423
   */
1424 13
  public function padRight(int $length, string $padStr = ' '): Stringy
1425
  {
1426 13
    return $this->applyPadding(0, $length - $this->length(), $padStr);
1427
  }
1428
1429
  /**
1430
   * Returns a new string of a given length such that both sides of the
1431
   * string are padded. Alias for pad() with a $padType of 'both'.
1432
   *
1433
   * @param int    $length <p>Desired string length after padding.</p>
1434
   * @param string $padStr [optional] <p>String used to pad, defaults to space. Default: ' '</p>
1435
   *
1436
   * @return static <p>String with padding applied.</p>
1437
   */
1438 14
  public function padBoth(int $length, string $padStr = ' '): Stringy
1439
  {
1440 14
    $padding = $length - $this->length();
1441
1442 14
    return $this->applyPadding((int)\floor($padding / 2), (int)\ceil($padding / 2), $padStr);
1443
  }
1444
1445
  /**
1446
   * Returns a new string starting with $string.
1447
   *
1448
   * @param string $string <p>The string to append.</p>
1449
   *
1450
   * @return static <p>Object with appended $string.</p>
1451
   */
1452 2
  public function prepend(string $string): Stringy
1453
  {
1454 2
    return static::create($string . $this->str, $this->encoding);
1455
  }
1456
1457
  /**
1458
   * Returns a new string with the prefix $substring removed, if present.
1459
   *
1460
   * @param string $substring <p>The prefix to remove.</p>
1461
   *
1462
   * @return static <p>Object having a $str without the prefix $substring.</p>
1463
   */
1464 12 View Code Duplication
  public function removeLeft(string $substring): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1465
  {
1466 12
    $stringy = static::create($this->str, $this->encoding);
1467
1468 12
    if ($stringy->startsWith($substring)) {
1469 6
      $substringLength = UTF8::strlen($substring, $stringy->encoding);
1470
1471 6
      return $stringy->substr($substringLength);
1472
    }
1473
1474 6
    return $stringy;
1475
  }
1476
1477
  /**
1478
   * Returns a new string with the suffix $substring removed, if present.
1479
   *
1480
   * @param string $substring <p>The suffix to remove.</p>
1481
   *
1482
   * @return static <p>Object having a $str without the suffix $substring.</p>
1483
   */
1484 12 View Code Duplication
  public function removeRight(string $substring): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1485
  {
1486 12
    $stringy = static::create($this->str, $this->encoding);
1487
1488 12
    if ($stringy->endsWith($substring)) {
1489 8
      $substringLength = UTF8::strlen($substring, $stringy->encoding);
1490
1491 8
      return $stringy->substr(0, $stringy->length() - $substringLength);
1492
    }
1493
1494 4
    return $stringy;
1495
  }
1496
1497
  /**
1498
   * Returns a repeated string given a multiplier.
1499
   *
1500
   * @param int $multiplier <p>The number of times to repeat the string.</p>
1501
   *
1502
   * @return static <p>Object with a repeated str.</p>
1503
   */
1504 7
  public function repeat(int $multiplier): Stringy
1505
  {
1506 7
    $repeated = UTF8::str_repeat($this->str, $multiplier);
1507
1508 7
    return static::create($repeated, $this->encoding);
1509
  }
1510
1511
  /**
1512
   * Replaces all occurrences of $search in $str by $replacement.
1513
   *
1514
   * @param string $search        <p>The needle to search for.</p>
1515
   * @param string $replacement   <p>The string to replace with.</p>
1516
   * @param bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
1517
   *
1518
   * @return static <p>Object with the resulting $str after the replacements.</p>
1519
   */
1520 29 View Code Duplication
  public function replace(string $search, string $replacement, bool $caseSensitive = true): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1521
  {
1522 29
    if ($caseSensitive) {
1523 22
      $return = UTF8::str_replace($search, $replacement, $this->str);
1524
    } else {
1525 7
      $return = UTF8::str_ireplace($search, $replacement, $this->str);
1526
    }
1527
1528 29
    return static::create($return);
1529
  }
1530
1531
  /**
1532
   * Replaces all occurrences of $search in $str by $replacement.
1533
   *
1534
   * @param array        $search        <p>The elements to search for.</p>
1535
   * @param string|array $replacement   <p>The string to replace with.</p>
1536
   * @param bool         $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
1537
   *
1538
   * @return static <p>Object with the resulting $str after the replacements.</p>
1539
   */
1540 30 View Code Duplication
  public function replaceAll(array $search, $replacement, bool $caseSensitive = true): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1541
  {
1542 30
    if ($caseSensitive) {
1543 23
      $return = UTF8::str_replace($search, $replacement, $this->str);
1544
    } else {
1545 7
      $return = UTF8::str_ireplace($search, $replacement, $this->str);
1546
    }
1547
1548 30
    return static::create($return);
1549
  }
1550
1551
  /**
1552
   * Replaces all occurrences of $search from the beginning of string with $replacement.
1553
   *
1554
   * @param string $search      <p>The string to search for.</p>
1555
   * @param string $replacement <p>The replacement.</p>
1556
   *
1557
   * @return static <p>Object with the resulting $str after the replacements.</p>
1558
   */
1559 16
  public function replaceBeginning(string $search, string $replacement): Stringy
1560
  {
1561 16
    $str = $this->regexReplace('^' . \preg_quote($search, '/'), UTF8::str_replace('\\', '\\\\', $replacement));
1562
1563 16
    return static::create($str, $this->encoding);
1564
  }
1565
1566
  /**
1567
   * Replaces all occurrences of $search from the ending of string with $replacement.
1568
   *
1569
   * @param string $search      <p>The string to search for.</p>
1570
   * @param string $replacement <p>The replacement.</p>
1571
   *
1572
   * @return static <p>Object with the resulting $str after the replacements.</p>
1573
   */
1574 16
  public function replaceEnding(string $search, string $replacement): Stringy
1575
  {
1576 16
    $str = $this->regexReplace(\preg_quote($search, '/') . '$', UTF8::str_replace('\\', '\\\\', $replacement));
1577
1578 16
    return static::create($str, $this->encoding);
1579
  }
1580
1581
  /**
1582
   * Gets the substring after (or before via "$beforeNeedle") the first occurrence of the "$needle".
1583
   * If no match is found returns new empty Stringy object.
1584
   *
1585
   * @param string $needle       <p>The string to look for.</p>
1586
   * @param bool   $beforeNeedle [optional] <p>Default: false</p>
1587
   *
1588
   * @return static
1589
   */
1590 2 View Code Duplication
  public function substringOf(string $needle, bool $beforeNeedle = false): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1591
  {
1592 2
    if ('' === $needle) {
1593
      return static::create();
1594
    }
1595
1596 2
    if (false === $part = UTF8::strstr($this->str, $needle, $beforeNeedle, $this->encoding)) {
1597 2
      return static::create();
1598
    }
1599
1600 2
    return static::create($part);
1601
  }
1602
1603
  /**
1604
   * Gets the substring after (or before via "$beforeNeedle") the first occurrence of the "$needle".
1605
   * If no match is found returns new empty Stringy object.
1606
   *
1607
   * @param string $needle       <p>The string to look for.</p>
1608
   * @param bool   $beforeNeedle [optional] <p>Default: false</p>
1609
   *
1610
   * @return static
1611
   */
1612 2 View Code Duplication
  public function substringOfIgnoreCase(string $needle, bool $beforeNeedle = false): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1613
  {
1614 2
    if ('' === $needle) {
1615
      return static::create();
1616
    }
1617
1618 2
    if (false === $part = UTF8::stristr($this->str, $needle, $beforeNeedle, $this->encoding)) {
1619 2
      return static::create();
1620
    }
1621
1622 2
    return static::create($part);
1623
  }
1624
1625
  /**
1626
   * Gets the substring after (or before via "$beforeNeedle") the last occurrence of the "$needle".
1627
   * If no match is found returns new empty Stringy object.
1628
   *
1629
   * @param string $needle       <p>The string to look for.</p>
1630
   * @param bool   $beforeNeedle [optional] <p>Default: false</p>
1631
   *
1632
   * @return static
1633
   */
1634 2 View Code Duplication
  public function lastSubstringOf(string $needle, bool $beforeNeedle = false): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1635
  {
1636 2
    if ('' === $needle) {
1637
      return static::create();
1638
    }
1639
1640 2
    if (false === $part = UTF8::strrchr($this->str, $needle, $beforeNeedle, $this->encoding)) {
1641 2
      return static::create();
1642
    }
1643
1644 2
    return static::create($part);
1645
  }
1646
1647
  /**
1648
   * Gets the substring after (or before via "$beforeNeedle") the last occurrence of the "$needle".
1649
   * If no match is found returns new empty Stringy object.
1650
   *
1651
   * @param string $needle       <p>The string to look for.</p>
1652
   * @param bool   $beforeNeedle [optional] <p>Default: false</p>
1653
   *
1654
   * @return static
1655
   */
1656 1 View Code Duplication
  public function lastSubstringOfIgnoreCase(string $needle, bool $beforeNeedle = false): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1657
  {
1658 1
    if ('' === $needle) {
1659
      return static::create();
1660
    }
1661
1662 1
    if (false === $part = UTF8::strrichr($this->str, $needle, $beforeNeedle, $this->encoding)) {
1663 1
      return static::create();
1664
    }
1665
1666 1
    return static::create($part);
1667
  }
1668
1669
  /**
1670
   * Returns a reversed string. A multibyte version of strrev().
1671
   *
1672
   * @return static <p>Object with a reversed $str.</p>
1673
   */
1674 5
  public function reverse(): Stringy
1675
  {
1676 5
    $reversed = UTF8::strrev($this->str);
1677
1678 5
    return static::create($reversed, $this->encoding);
1679
  }
1680
1681
  /**
1682
   * Truncates the string to a given length, while ensuring that it does not
1683
   * split words. If $substring is provided, and truncating occurs, the
1684
   * string is further truncated so that the substring may be appended without
1685
   * exceeding the desired length.
1686
   *
1687
   * @param int    $length    <p>Desired length of the truncated string.</p>
1688
   * @param string $substring [optional] <p>The substring to append if it can fit. Default: ''</p>
1689
   *
1690
   * @return static <p>Object with the resulting $str after truncating.</p>
1691
   */
1692 23
  public function safeTruncate(int $length, string $substring = ''): Stringy
1693
  {
1694 23
    $stringy = static::create($this->str, $this->encoding);
1695 23
    if ($length >= $stringy->length()) {
1696 4
      return $stringy;
1697
    }
1698
1699
    // need to further trim the string so we can append the substring
1700 19
    $encoding = $stringy->encoding;
1701 19
    $substringLength = UTF8::strlen($substring, $encoding);
1702 19
    $length -= $substringLength;
1703
1704 19
    $truncated = UTF8::substr($stringy->str, 0, $length, $encoding);
1705
1706
    // if the last word was truncated
1707 19
    $strPosSpace = UTF8::strpos($stringy->str, ' ', $length - 1, $encoding);
1708 19
    if ($strPosSpace != $length) {
1709
      // find pos of the last occurrence of a space, get up to that
1710 12
      $lastPos = UTF8::strrpos($truncated, ' ', 0, $encoding);
0 ignored issues
show
Security Bug introduced by
It seems like $truncated defined by \voku\helper\UTF8::subst... 0, $length, $encoding) on line 1704 can also be of type false; however, voku\helper\UTF8::strrpos() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1711
1712 12
      if ($lastPos !== false || $strPosSpace !== false) {
1713 11
        $truncated = UTF8::substr($truncated, 0, (int)$lastPos, $encoding);
0 ignored issues
show
Security Bug introduced by
It seems like $truncated defined by \voku\helper\UTF8::subst...t) $lastPos, $encoding) on line 1713 can also be of type false; however, voku\helper\UTF8::substr() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1714
      }
1715
    }
1716
1717 19
    $stringy->str = $truncated . $substring;
1718
1719 19
    return $stringy;
1720
  }
1721
1722
  /**
1723
   * A multibyte string shuffle function. It returns a string with its
1724
   * characters in random order.
1725
   *
1726
   * @return static <p>Object with a shuffled $str.</p>
1727
   */
1728 3
  public function shuffle(): Stringy
1729
  {
1730 3
    $shuffledStr = UTF8::str_shuffle($this->str);
1731
1732 3
    return static::create($shuffledStr, $this->encoding);
1733
  }
1734
1735
  /**
1736
   * Converts the string into an URL slug. This includes replacing non-ASCII
1737
   * characters with their closest ASCII equivalents, removing remaining
1738
   * non-ASCII and non-alphanumeric characters, and replacing whitespace with
1739
   * $replacement. The replacement defaults to a single dash, and the string
1740
   * is also converted to lowercase.
1741
   *
1742
   * @param string $replacement [optional] <p>The string used to replace whitespace. Default: '-'</p>
1743
   * @param string $language    [optional] <p>The language for the url. Default: 'de'</p>
1744
   * @param bool   $strToLower  [optional] <p>string to lower. Default: true</p>
1745
   *
1746
   * @return static <p>Object whose $str has been converted to an URL slug.</p>
1747
   */
1748 15
  public function slugify(string $replacement = '-', string $language = 'de', bool $strToLower = true): Stringy
1749
  {
1750 15
    $slug = URLify::slug($this->str, $language, $replacement, $strToLower);
1751
1752 15
    return static::create($slug, $this->encoding);
1753
  }
1754
1755
  /**
1756
   * Remove css media-queries.
1757
   *
1758
   * @return static
1759
   */
1760 1
  public function stripeCssMediaQueries(): Stringy
1761
  {
1762 1
    $pattern = '#@media\\s+(?:only\\s)?(?:[\\s{\\(]|screen|all)\\s?[^{]+{.*}\\s*}\\s*#misU';
1763
1764 1
    return static::create(\preg_replace($pattern, '', $this->str));
1765
  }
1766
1767
  /**
1768
   * Strip all whitespace characters. This includes tabs and newline characters,
1769
   * as well as multibyte whitespace such as the thin space and ideographic space.
1770
   *
1771
   * @return static
1772
   */
1773 12
  public function stripWhitespace(): Stringy
1774
  {
1775 12
    return static::create(UTF8::strip_whitespace($this->str));
1776
  }
1777
1778
  /**
1779
   * Remove empty html-tag.
1780
   *
1781
   * e.g.: <tag></tag>
1782
   *
1783
   * @return static
1784
   */
1785 1
  public function stripeEmptyHtmlTags(): Stringy
1786
  {
1787 1
    $pattern = "/<[^\/>]*>(([\s]?)*|)<\/[^>]*>/i";
1788
1789 1
    return static::create(\preg_replace($pattern, '', $this->str));
1790
  }
1791
1792
  /**
1793
   * Converts the string into an valid UTF-8 string.
1794
   *
1795
   * @return static
1796
   */
1797 1
  public function utf8ify(): Stringy
1798
  {
1799 1
    return static::create(UTF8::cleanup($this->str));
1800
  }
1801
1802
  /**
1803
   * Create a escape html version of the string via "UTF8::htmlspecialchars()".
1804
   *
1805
   * @return static
1806
   */
1807 6
  public function escape(): Stringy
1808
  {
1809 6
    $str = UTF8::htmlspecialchars(
1810 6
        $this->str,
1811 6
        ENT_QUOTES | ENT_SUBSTITUTE,
1812 6
        $this->encoding
1813
    );
1814
1815 6
    return static::create($str, $this->encoding);
1816
  }
1817
1818
  /**
1819
   * Create an extract from a sentence, so if the search-string was found, it try to centered in the output.
1820
   *
1821
   * @param string   $search
1822
   * @param int|null $length                 [optional] <p>Default: null === text->length / 2</p>
1823
   * @param string   $replacerForSkippedText [optional] <p>Default: …</p>
1824
   *
1825
   * @return static
1826
   */
1827 1
  public function extractText(string $search = '', int $length = null, string $replacerForSkippedText = '…'): Stringy
1828
  {
1829
    // init
1830 1
    $text = $this->str;
1831
1832 1
    if (empty($text)) {
1833 1
      return static::create('', $this->encoding);
1834
    }
1835
1836 1
    $trimChars = "\t\r\n -_()!~?=+/*\\,.:;\"'[]{}`&";
1837
1838 1
    if ($length === null) {
1839 1
      $length = (int)\round($this->length() / 2, 0);
1840
    }
1841
1842 1
    if (empty($search)) {
1843
1844 1
      $stringLength = UTF8::strlen($text, $this->encoding);
1845
1846 1
      if ($length > 0) {
1847 1
        $end = ($length - 1) > $stringLength ? $stringLength : ($length - 1);
1848
      } else {
1849 1
        $end = 0;
1850
      }
1851
1852 1
      $pos = \min(
1853 1
          UTF8::strpos($text, ' ', $end, $this->encoding),
1854 1
          UTF8::strpos($text, '.', $end, $this->encoding)
1855
      );
1856
1857 1
      if ($pos) {
1858 1
        return static::create(
1859 1
            \rtrim(
1860 1
                UTF8::substr($text, 0, $pos, $this->encoding),
1861 1
                $trimChars
1862 1
            ) . $replacerForSkippedText,
1863 1
            $this->encoding
1864
        );
1865
      }
1866
1867
      return static::create($text, $this->encoding);
1868
    }
1869
1870 1
    $wordPos = UTF8::stripos(
1871 1
        $text,
1872 1
        $search,
1873 1
        0,
1874 1
        $this->encoding
1875
    );
1876 1
    $halfSide = (int)($wordPos - $length / 2 + UTF8::strlen($search, $this->encoding) / 2);
1877
1878 1
    if ($halfSide > 0) {
1879
1880 1
      $halfText = UTF8::substr($text, 0, $halfSide, $this->encoding);
1881 1
      $pos_start = \max(UTF8::strrpos($halfText, ' ', 0), UTF8::strrpos($halfText, '.', 0));
0 ignored issues
show
Security Bug introduced by
It seems like $halfText defined by \voku\helper\UTF8::subst...fSide, $this->encoding) on line 1880 can also be of type false; however, voku\helper\UTF8::strrpos() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1882
1883 1
      if (!$pos_start) {
1884 1
        $pos_start = 0;
1885
      }
1886
1887
    } else {
1888 1
      $pos_start = 0;
1889
    }
1890
1891 1
    if ($wordPos && $halfSide > 0) {
1892 1
      $l = $pos_start + $length - 1;
1893 1
      $realLength = UTF8::strlen($text, $this->encoding);
1894
1895 1
      if ($l > $realLength) {
1896
        $l = $realLength;
1897
      }
1898
1899 1
      $pos_end = \min(
1900 1
                     UTF8::strpos($text, ' ', $l, $this->encoding),
1901 1
                     UTF8::strpos($text, '.', $l, $this->encoding)
1902 1
                 ) - $pos_start;
1903
1904 1
      if (!$pos_end || $pos_end <= 0) {
1905 1
        $extract = $replacerForSkippedText . \ltrim(
1906 1
                UTF8::substr(
1907 1
                    $text,
1908 1
                    $pos_start,
1909 1
                    UTF8::strlen($text),
1910 1
                    $this->encoding
1911
                ),
1912 1
                $trimChars
1913
            );
1914 View Code Duplication
      } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1915 1
        $extract = $replacerForSkippedText . \trim(
1916 1
                UTF8::substr(
1917 1
                    $text,
1918 1
                    $pos_start,
1919 1
                    $pos_end,
1920 1
                    $this->encoding
1921
                ),
1922 1
                $trimChars
1923 1
            ) . $replacerForSkippedText;
1924
      }
1925
1926
    } else {
1927
1928 1
      $l = $length - 1;
1929 1
      $trueLength = UTF8::strlen($text, $this->encoding);
1930
1931 1
      if ($l > $trueLength) {
1932
        $l = $trueLength;
1933
      }
1934
1935 1
      $pos_end = \min(
1936 1
          UTF8::strpos($text, ' ', $l, $this->encoding),
1937 1
          UTF8::strpos($text, '.', $l, $this->encoding)
1938
      );
1939
1940 1 View Code Duplication
      if ($pos_end) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1941 1
        $extract = \rtrim(
1942 1
                       UTF8::substr($text, 0, $pos_end, $this->encoding),
1943 1
                       $trimChars
1944 1
                   ) . $replacerForSkippedText;
1945
      } else {
1946 1
        $extract = $text;
1947
      }
1948
    }
1949
1950 1
    return static::create($extract, $this->encoding);
1951
  }
1952
1953
1954
  /**
1955
   * Try to remove all XSS-attacks from the string.
1956
   *
1957
   * @return static
1958
   */
1959 6
  public function removeXss(): Stringy
1960
  {
1961 6
    static $antiXss = null;
1962
1963 6
    if ($antiXss === null) {
1964 1
      $antiXss = new AntiXSS();
1965
    }
1966
1967 6
    $str = $antiXss->xss_clean($this->str);
1968
1969 6
    return static::create($str, $this->encoding);
1970
  }
1971
1972
  /**
1973
   * Remove all breaks [<br> | \r\n | \r | \n | ...] from the string.
1974
   *
1975
   * @param string $replacement [optional] <p>Default is a empty string.</p>
1976
   *
1977
   * @return static
1978
   */
1979 6
  public function removeHtmlBreak(string $replacement = ''): Stringy
1980
  {
1981 6
    $str = (string)\preg_replace('#/\r\n|\r|\n|<br.*/?>#isU', $replacement, $this->str);
1982
1983 6
    return static::create($str, $this->encoding);
1984
  }
1985
1986
  /**
1987
   * Remove html via "strip_tags()" from the string.
1988
   *
1989
   * @param string $allowableTags [optional] <p>You can use the optional second parameter to specify tags which should
1990
   *                              not be stripped. Default: null
1991
   *                              </p>
1992
   *
1993
   * @return static
1994
   */
1995 6
  public function removeHtml(string $allowableTags = null): Stringy
1996
  {
1997 6
    $str = \strip_tags($this->str, $allowableTags);
1998
1999 6
    return static::create($str, $this->encoding);
2000
  }
2001
2002
  /**
2003
   * Returns the substring beginning at $start, and up to, but not including
2004
   * the index specified by $end. If $end is omitted, the function extracts
2005
   * the remaining string. If $end is negative, it is computed from the end
2006
   * of the string.
2007
   *
2008
   * @param int $start <p>Initial index from which to begin extraction.</p>
2009
   * @param int $end   [optional] <p>Index at which to end extraction. Default: null</p>
2010
   *
2011
   * @return static <p>Object with its $str being the extracted substring.</p>
2012
   */
2013 18
  public function slice(int $start, int $end = null): Stringy
2014
  {
2015 18
    if ($end === null) {
2016 4
      $length = $this->length();
2017 14
    } elseif ($end >= 0 && $end <= $start) {
2018 4
      return static::create('', $this->encoding);
2019 10
    } elseif ($end < 0) {
2020 2
      $length = $this->length() + $end - $start;
2021
    } else {
2022 8
      $length = $end - $start;
2023
    }
2024
2025 14
    $str = UTF8::substr($this->str, $start, $length, $this->encoding);
2026
2027 14
    return static::create($str, $this->encoding);
2028
  }
2029
2030
  /**
2031
   * Splits the string with the provided regular expression, returning an
2032
   * array of Stringy objects. An optional integer $limit will truncate the
2033
   * results.
2034
   *
2035
   * @param string $pattern <p>The regex with which to split the string.</p>
2036
   * @param int    $limit   [optional] <p>Maximum number of results to return. Default: -1 === no limit</p>
2037
   *
2038
   * @return static[] <p>An array of Stringy objects.</p>
2039
   */
2040 16
  public function split(string $pattern, int $limit = -1): array
2041
  {
2042 16
    if ($limit === 0) {
2043 2
      return [];
2044
    }
2045
2046
    // this->split errors when supplied an empty pattern in < PHP 5.4.13
2047
    // and current versions of HHVM (3.8 and below)
2048 14
    if ($pattern === '') {
2049 1
      return [static::create($this->str, $this->encoding)];
2050
    }
2051
2052
    // this->split returns the remaining unsplit string in the last index when
2053
    // supplying a limit
2054 13
    if ($limit > 0) {
2055 8
      $limit += 1;
2056
    } else {
2057 5
      $limit = -1;
2058
    }
2059
2060 13
    $array = \preg_split('/' . \preg_quote($pattern, '/') . '/u', $this->str, $limit);
2061
2062 13
    if ($limit > 0 && \count($array) === $limit) {
2063 4
      \array_pop($array);
2064
    }
2065
2066
    /** @noinspection CallableInLoopTerminationConditionInspection */
2067
    /** @noinspection ForeachInvariantsInspection */
2068 13 View Code Duplication
    for ($i = 0; $i < \count($array); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2069 13
      $array[$i] = static::create($array[$i], $this->encoding);
2070
    }
2071
2072 13
    return $array;
2073
  }
2074
2075
  /**
2076
   * Surrounds $str with the given substring.
2077
   *
2078
   * @param string $substring <p>The substring to add to both sides.</P>
2079
   *
2080
   * @return static <p>Object whose $str had the substring both prepended and appended.</p>
2081
   */
2082 5
  public function surround(string $substring): Stringy
2083
  {
2084 5
    $str = \implode('', [$substring, $this->str, $substring]);
2085
2086 5
    return static::create($str, $this->encoding);
2087
  }
2088
2089
  /**
2090
   * Returns a case swapped version of the string.
2091
   *
2092
   * @return static <p>Object whose $str has each character's case swapped.</P>
2093
   */
2094 5
  public function swapCase(): Stringy
2095
  {
2096 5
    $stringy = static::create($this->str, $this->encoding);
2097
2098 5
    $stringy->str = UTF8::swapCase($stringy->str, $stringy->encoding);
2099
2100 5
    return $stringy;
2101
  }
2102
2103
  /**
2104
   * Returns a string with smart quotes, ellipsis characters, and dashes from
2105
   * Windows-1252 (commonly used in Word documents) replaced by their ASCII
2106
   * equivalents.
2107
   *
2108
   * @return static <p>Object whose $str has those characters removed.</p>
2109
   */
2110 4
  public function tidy(): Stringy
2111
  {
2112 4
    $str = UTF8::normalize_msword($this->str);
2113
2114 4
    return static::create($str, $this->encoding);
2115
  }
2116
2117
  /**
2118
   * Returns a trimmed string with the first letter of each word capitalized.
2119
   * Also accepts an array, $ignore, allowing you to list words not to be
2120
   * capitalized.
2121
   *
2122
   * @param array|null $ignore [optional] <p>An array of words not to capitalize or null. Default: null</p>
2123
   *
2124
   * @return static <p>Object with a titleized $str.</p>
2125
   */
2126 5
  public function titleize(array $ignore = null): Stringy
2127
  {
2128 5
    $stringy = static::create($this->trim(), $this->encoding);
2129 5
    $encoding = $this->encoding;
2130
2131 5
    $stringy->str = (string)\preg_replace_callback(
2132 5
        '/([\S]+)/u',
2133 5
        function ($match) use ($encoding, $ignore) {
2134 5
          if ($ignore && \in_array($match[0], $ignore, true)) {
2135 2
            return $match[0];
2136
          }
2137
2138 5
          $stringy = new static($match[0], $encoding);
2139
2140 5
          return (string)$stringy->toLowerCase()->upperCaseFirst();
2141 5
        },
2142 5
        $stringy->str
2143
    );
2144
2145 5
    return $stringy;
2146
  }
2147
2148
  /**
2149
   * Converts all characters in the string to lowercase.
2150
   *
2151
   * @return static <p>Object with all characters of $str being lowercase.</p>
2152
   */
2153 27
  public function toLowerCase(): Stringy
2154
  {
2155 27
    $str = UTF8::strtolower($this->str, $this->encoding);
2156
2157 27
    return static::create($str, $this->encoding);
2158
  }
2159
2160
  /**
2161
   * Returns true if the string is base64 encoded, false otherwise.
2162
   *
2163
   * @return bool <p>Whether or not $str is base64 encoded.</p>
2164
   */
2165 7
  public function isBase64(): bool
2166
  {
2167 7
    return UTF8::is_base64($this->str);
2168
  }
2169
2170
  /**
2171
   * Returns an ASCII version of the string. A set of non-ASCII characters are
2172
   * replaced with their closest ASCII counterparts, and the rest are removed
2173
   * unless instructed otherwise.
2174
   *
2175
   * @param bool $strict [optional] <p>Use "transliterator_transliterate()" from PHP-Intl | WARNING: bad performance |
2176
   *                     Default: false</p>
2177
   *
2178
   * @return static <p>Object whose $str contains only ASCII characters.</p>
2179
   */
2180 16
  public function toAscii(bool $strict = false): Stringy
2181
  {
2182 16
    $str = UTF8::to_ascii($this->str, '?', $strict);
2183
2184 16
    return static::create($str, $this->encoding);
2185
  }
2186
2187
  /**
2188
   * Returns a boolean representation of the given logical string value.
2189
   * For example, 'true', '1', 'on' and 'yes' will return true. 'false', '0',
2190
   * 'off', and 'no' will return false. In all instances, case is ignored.
2191
   * For other numeric strings, their sign will determine the return value.
2192
   * In addition, blank strings consisting of only whitespace will return
2193
   * false. For all other strings, the return value is a result of a
2194
   * boolean cast.
2195
   *
2196
   * @return bool <p>A boolean value for the string.</p>
2197
   */
2198 15
  public function toBoolean(): bool
2199
  {
2200 15
    $key = $this->toLowerCase()->str;
2201
    $map = [
2202 15
        'true'  => true,
2203
        '1'     => true,
2204
        'on'    => true,
2205
        'yes'   => true,
2206
        'false' => false,
2207
        '0'     => false,
2208
        'off'   => false,
2209
        'no'    => false,
2210
    ];
2211
2212 15
    if (\array_key_exists($key, $map)) {
2213 10
      return $map[$key];
2214
    }
2215
2216 5
    if (\is_numeric($this->str)) {
2217 2
      return ((int)$this->str > 0);
2218
    }
2219
2220 3
    return (bool)$this->regexReplace('[[:space:]]', '')->str;
2221
  }
2222
2223
  /**
2224
   * Return Stringy object as string, but you can also use (string) for automatically casting the object into a string.
2225
   *
2226
   * @return string
2227
   */
2228 1041
  public function toString(): string
2229
  {
2230 1041
    return (string)$this->str;
2231
  }
2232
2233
  /**
2234
   * Converts each tab in the string to some number of spaces, as defined by
2235
   * $tabLength. By default, each tab is converted to 4 consecutive spaces.
2236
   *
2237
   * @param int $tabLength [optional] <p>Number of spaces to replace each tab with. Default: 4</p>
2238
   *
2239
   * @return static <p>Object whose $str has had tabs switched to spaces.</p>
2240
   */
2241 6 View Code Duplication
  public function toSpaces(int $tabLength = 4): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2242
  {
2243 6
    $spaces = UTF8::str_repeat(' ', $tabLength);
2244 6
    $str = UTF8::str_replace("\t", $spaces, $this->str);
2245
2246 6
    return static::create($str, $this->encoding);
2247
  }
2248
2249
  /**
2250
   * Converts each occurrence of some consecutive number of spaces, as
2251
   * defined by $tabLength, to a tab. By default, each 4 consecutive spaces
2252
   * are converted to a tab.
2253
   *
2254
   * @param int $tabLength [optional] <p>Number of spaces to replace with a tab. Default: 4</p>
2255
   *
2256
   * @return static <p>Object whose $str has had spaces switched to tabs.</p>
2257
   */
2258 5 View Code Duplication
  public function toTabs(int $tabLength = 4): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2259
  {
2260 5
    $spaces = UTF8::str_repeat(' ', $tabLength);
2261 5
    $str = UTF8::str_replace($spaces, "\t", $this->str);
2262
2263 5
    return static::create($str, $this->encoding);
2264
  }
2265
2266
  /**
2267
   * Converts the first character of each word in the string to uppercase.
2268
   *
2269
   * @return static  Object with all characters of $str being title-cased
2270
   */
2271 5
  public function toTitleCase(): Stringy
2272
  {
2273
    // "mb_convert_case()" used a polyfill from the "UTF8"-Class
2274 5
    $str = \mb_convert_case($this->str, MB_CASE_TITLE, $this->encoding);
2275
2276 5
    return static::create($str, $this->encoding);
2277
  }
2278
2279
  /**
2280
   * Converts all characters in the string to uppercase.
2281
   *
2282
   * @return static  Object with all characters of $str being uppercase
2283
   */
2284 5
  public function toUpperCase(): Stringy
2285
  {
2286 5
    $str = UTF8::strtoupper($this->str, $this->encoding);
2287
2288 5
    return static::create($str, $this->encoding);
2289
  }
2290
2291
  /**
2292
   * Returns a string with whitespace removed from the start of the string.
2293
   * Supports the removal of unicode whitespace. Accepts an optional
2294
   * string of characters to strip instead of the defaults.
2295
   *
2296
   * @param string $chars [optional] <p>Optional string of characters to strip. Default: null</p>
2297
   *
2298
   * @return static <p>Object with a trimmed $str.</p>
2299
   */
2300 13 View Code Duplication
  public function trimLeft(string $chars = null): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2301
  {
2302 13
    if (!$chars) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $chars of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2303 11
      $chars = '[:space:]';
2304
    } else {
2305 2
      $chars = \preg_quote($chars, '/');
2306
    }
2307
2308 13
    return $this->regexReplace("^[$chars]+", '');
2309
  }
2310
2311
  /**
2312
   * Returns a string with whitespace removed from the end of the string.
2313
   * Supports the removal of unicode whitespace. Accepts an optional
2314
   * string of characters to strip instead of the defaults.
2315
   *
2316
   * @param string $chars [optional] <p>Optional string of characters to strip. Default: null</p>
2317
   *
2318
   * @return static <p>Object with a trimmed $str.</p>
2319
   */
2320 13 View Code Duplication
  public function trimRight(string $chars = null): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2321
  {
2322 13
    if (!$chars) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $chars of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2323 11
      $chars = '[:space:]';
2324
    } else {
2325 2
      $chars = \preg_quote($chars, '/');
2326
    }
2327
2328 13
    return $this->regexReplace("[$chars]+\$", '');
2329
  }
2330
2331
  /**
2332
   * Truncates the string to a given length. If $substring is provided, and
2333
   * truncating occurs, the string is further truncated so that the substring
2334
   * may be appended without exceeding the desired length.
2335
   *
2336
   * @param int    $length    <p>Desired length of the truncated string.</p>
2337
   * @param string $substring [optional] <p>The substring to append if it can fit. Default: ''</p>
2338
   *
2339
   * @return static <p>Object with the resulting $str after truncating.</p>
2340
   */
2341 22 View Code Duplication
  public function truncate(int $length, string $substring = ''): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2342
  {
2343 22
    $stringy = static::create($this->str, $this->encoding);
2344 22
    if ($length >= $stringy->length()) {
2345 4
      return $stringy;
2346
    }
2347
2348
    // Need to further trim the string so we can append the substring
2349 18
    $substringLength = UTF8::strlen($substring, $stringy->encoding);
2350 18
    $length -= $substringLength;
2351
2352 18
    $truncated = UTF8::substr($stringy->str, 0, $length, $stringy->encoding);
2353 18
    $stringy->str = $truncated . $substring;
2354
2355 18
    return $stringy;
2356
  }
2357
2358
  /**
2359
   * Returns a lowercase and trimmed string separated by underscores.
2360
   * Underscores are inserted before uppercase characters (with the exception
2361
   * of the first character of the string), and in place of spaces as well as
2362
   * dashes.
2363
   *
2364
   * @return static <p>Object with an underscored $str.</p>
2365
   */
2366 16
  public function underscored(): Stringy
2367
  {
2368 16
    return $this->delimit('_');
2369
  }
2370
2371
  /**
2372
   * Returns an UpperCamelCase version of the supplied string. It trims
2373
   * surrounding spaces, capitalizes letters following digits, spaces, dashes
2374
   * and underscores, and removes spaces, dashes, underscores.
2375
   *
2376
   * @return static  <p>Object with $str in UpperCamelCase.</p>
2377
   */
2378 13
  public function upperCamelize(): Stringy
2379
  {
2380 13
    return $this->camelize()->upperCaseFirst();
2381
  }
2382
2383
  /**
2384
   * Returns a camelCase version of the string. Trims surrounding spaces,
2385
   * capitalizes letters following digits, spaces, dashes and underscores,
2386
   * and removes spaces, dashes, as well as underscores.
2387
   *
2388
   * @return static <p>Object with $str in camelCase.</p>
2389
   */
2390 32
  public function camelize(): Stringy
2391
  {
2392 32
    $encoding = $this->encoding;
2393 32
    $stringy = $this->trim()->lowerCaseFirst();
2394 32
    $stringy->str = (string)\preg_replace('/^[-_]+/', '', $stringy->str);
2395
2396 32
    $stringy->str = (string)\preg_replace_callback(
2397 32
        '/[-_\s]+(.)?/u',
2398 32
        function ($match) use ($encoding) {
2399 27
          if (isset($match[1])) {
2400 27
            return UTF8::strtoupper($match[1], $encoding);
2401
          }
2402
2403 1
          return '';
2404 32
        },
2405 32
        $stringy->str
2406
    );
2407
2408 32
    $stringy->str = (string)\preg_replace_callback(
2409 32
        '/[\d]+(.)?/u',
2410 32
        function ($match) use ($encoding) {
2411 6
          return UTF8::strtoupper($match[0], $encoding);
2412 32
        },
2413 32
        $stringy->str
2414
    );
2415
2416 32
    return $stringy;
2417
  }
2418
2419
  /**
2420
   * Convert a string to e.g.: "snake_case"
2421
   *
2422
   * @return static <p>Object with $str in snake_case.</p>
2423
   */
2424 20
  public function snakeize(): Stringy
2425
  {
2426 20
    $str = $this->str;
2427
2428 20
    $encoding = $this->encoding;
2429 20
    $str = UTF8::normalize_whitespace($str);
2430 20
    $str = \str_replace('-', '_', $str);
2431
2432 20
    $str = (string)\preg_replace_callback(
2433 20
        '/([\d|A-Z])/u',
2434 20
        function ($matches) use ($encoding) {
2435 8
          $match = $matches[1];
2436 8
          $matchInt = (int)$match;
2437
2438 8
          if ("$matchInt" == $match) {
2439 4
            return '_' . $match . '_';
2440
          }
2441
2442 4
          return '_' . UTF8::strtolower($match, $encoding);
2443 20
        },
2444 20
        $str
2445
    );
2446
2447 20
    $str = (string)\preg_replace(
2448
        [
2449
2450 20
            '/\s+/',      // convert spaces to "_"
2451
            '/^\s+|\s+$/',  // trim leading & trailing spaces
2452
            '/_+/',         // remove double "_"
2453
        ],
2454
        [
2455 20
            '_',
2456
            '',
2457
            '_',
2458
        ],
2459 20
        $str
2460
    );
2461
2462 20
    $str = UTF8::trim($str, '_'); // trim leading & trailing "_"
2463 20
    $str = UTF8::trim($str); // trim leading & trailing whitespace
2464
2465 20
    return static::create($str, $this->encoding);
2466
  }
2467
2468
  /**
2469
   * Converts the first character of the string to lower case.
2470
   *
2471
   * @return static <p>Object with the first character of $str being lower case.</p>
2472
   */
2473 37 View Code Duplication
  public function lowerCaseFirst(): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2474
  {
2475 37
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
2476 37
    $rest = UTF8::substr($this->str, 1, $this->length() - 1, $this->encoding);
2477
2478 37
    $str = UTF8::strtolower($first, $this->encoding) . $rest;
0 ignored issues
show
Security Bug introduced by
It seems like $first defined by \voku\helper\UTF8::subst... 0, 1, $this->encoding) on line 2475 can also be of type false; however, voku\helper\UTF8::strtolower() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
2479
2480 37
    return static::create($str, $this->encoding);
2481
  }
2482
2483
  /**
2484
   * Shorten the string after $length, but also after the next word.
2485
   *
2486
   * @param int    $length
2487
   * @param string $strAddOn [optional] <p>Default: '…'</p>
2488
   *
2489
   * @return static
2490
   */
2491 4
  public function shortenAfterWord(int $length, string $strAddOn = '…'): Stringy
2492
  {
2493 4
    $string = UTF8::str_limit_after_word($this->str, $length, $strAddOn);
2494
2495 4
    return static::create($string);
2496
  }
2497
2498
  /**
2499
   * Line-Wrap the string after $limit, but also after the next word.
2500
   *
2501
   * @param int $limit
2502
   *
2503
   * @return static
2504
   */
2505 1
  public function lineWrapAfterWord(int $limit): Stringy
2506
  {
2507 1
    $strings = (array)\preg_split('/\\r\\n|\\r|\\n/', $this->str);
2508
2509 1
    $string = '';
2510 1
    foreach ($strings as $value) {
2511 1
      $string .= wordwrap($value, $limit);
2512 1
      $string .= "\n";
2513
    }
2514
2515 1
    return static::create($string);
2516
  }
2517
2518
  /**
2519
   * Gets the substring after the first occurrence of a separator.
2520
   * If no match is found returns new empty Stringy object.
2521
   *
2522
   * @param string $separator
2523
   *
2524
   * @return static
2525
   */
2526 2 View Code Duplication
  public function afterFirst(string $separator): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2527
  {
2528 2
    if ($separator === '') {
2529
      return static::create();
2530
    }
2531
2532 2
    if ($this->str === '') {
2533 1
      return static::create();
2534
    }
2535
2536 2
    if (($offset = $this->indexOf($separator)) === false) {
2537 2
      return static::create();
2538
    }
2539
2540 2
    return static::create(
2541 2
        UTF8::substr(
2542 2
            $this->str,
2543 2
            $offset + UTF8::strlen($separator, $this->encoding),
2544 2
            null,
2545 2
            $this->encoding
2546
        ),
2547 2
        $this->encoding
2548
    );
2549
  }
2550
2551
  /**
2552
   * Gets the substring after the first occurrence of a separator.
2553
   * If no match is found returns new empty Stringy object.
2554
   *
2555
   * @param string $separator
2556
   *
2557
   * @return static
2558
   */
2559 1 View Code Duplication
  public function afterFirstIgnoreCase(string $separator): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2560
  {
2561 1
    if ($separator === '') {
2562
      return static::create();
2563
    }
2564
2565 1
    if ($this->str === '') {
2566 1
      return static::create();
2567
    }
2568
2569 1
    if (($offset = $this->indexOfIgnoreCase($separator)) === false) {
2570 1
      return static::create();
2571
    }
2572
2573 1
    return static::create(
2574 1
        UTF8::substr(
2575 1
            $this->str,
2576 1
            $offset + UTF8::strlen($separator, $this->encoding),
2577 1
            null,
2578 1
            $this->encoding
2579
        ),
2580 1
        $this->encoding
2581
    );
2582
  }
2583
2584
  /**
2585
   * Gets the substring after the last occurrence of a separator.
2586
   * If no match is found returns new empty Stringy object.
2587
   *
2588
   * @param string $separator
2589
   *
2590
   * @return static
2591
   */
2592 1 View Code Duplication
  public function afterLastIgnoreCase(string $separator): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2593
  {
2594 1
    if ($separator === '') {
2595
      return static::create();
2596
    }
2597
2598 1
    if ($this->str === '') {
2599 1
      return static::create();
2600
    }
2601
2602 1
    $offset = $this->indexOfLastIgnoreCase($separator);
2603 1
    if ($offset === false) {
2604 1
      return static::create('', $this->encoding);
2605
    }
2606
2607 1
    return static::create(
2608 1
        UTF8::substr(
2609 1
            $this->str,
2610 1
            $offset + UTF8::strlen($separator, $this->encoding),
2611 1
            null,
2612 1
            $this->encoding
2613
        ),
2614 1
        $this->encoding
2615
    );
2616
  }
2617
2618
  /**
2619
   * Gets the substring after the last occurrence of a separator.
2620
   * If no match is found returns new empty Stringy object.
2621
   *
2622
   * @param string $separator
2623
   *
2624
   * @return static
2625
   */
2626 1 View Code Duplication
  public function afterLast(string $separator): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2627
  {
2628 1
    if ($separator === '') {
2629
      return static::create();
2630
    }
2631
2632 1
    if ($this->str === '') {
2633 1
      return static::create();
2634
    }
2635
2636 1
    $offset = $this->indexOfLast($separator);
2637 1
    if ($offset === false) {
2638 1
      return static::create('', $this->encoding);
2639
    }
2640
2641 1
    return static::create(
2642 1
        UTF8::substr(
2643 1
            $this->str,
2644 1
            $offset + UTF8::strlen($separator, $this->encoding),
2645 1
            null,
2646 1
            $this->encoding
2647
        ),
2648 1
        $this->encoding
2649
    );
2650
  }
2651
2652
  /**
2653
   * Gets the substring before the first occurrence of a separator.
2654
   * If no match is found returns new empty Stringy object.
2655
   *
2656
   * @param string $separator
2657
   *
2658
   * @return static
2659
   */
2660 1 View Code Duplication
  public function beforeFirst(string $separator): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2661
  {
2662 1
    if ($separator === '') {
2663
      return static::create();
2664
    }
2665
2666 1
    if ($this->str === '') {
2667 1
      return static::create();
2668
    }
2669
2670 1
    $offset = $this->indexOf($separator);
2671 1
    if ($offset === false) {
2672 1
      return static::create('', $this->encoding);
2673
    }
2674
2675 1
    return static::create(
2676 1
        UTF8::substr(
2677 1
            $this->str,
2678 1
            0,
2679 1
            $offset,
2680 1
            $this->encoding
2681
        ),
2682 1
        $this->encoding
2683
    );
2684
  }
2685
2686
  /**
2687
   * Gets the substring before the first occurrence of a separator.
2688
   * If no match is found returns new empty Stringy object.
2689
   *
2690
   * @param string $separator
2691
   *
2692
   * @return static
2693
   */
2694 1 View Code Duplication
  public function beforeFirstIgnoreCase(string $separator): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2695
  {
2696 1
    if ($separator === '') {
2697
      return static::create();
2698
    }
2699
2700 1
    if ($this->str === '') {
2701 1
      return static::create();
2702
    }
2703
2704 1
    $offset = $this->indexOfIgnoreCase($separator);
2705 1
    if ($offset === false) {
2706 1
      return static::create('', $this->encoding);
2707
    }
2708
2709 1
    return static::create(
2710 1
        UTF8::substr(
2711 1
            $this->str,
2712 1
            0,
2713 1
            $offset,
2714 1
            $this->encoding
2715
        ),
2716 1
        $this->encoding
2717
    );
2718
  }
2719
2720
  /**
2721
   * Gets the substring before the last occurrence of a separator.
2722
   * If no match is found returns new empty Stringy object.
2723
   *
2724
   * @param string $separator
2725
   *
2726
   * @return static
2727
   */
2728 1 View Code Duplication
  public function beforeLast(string $separator): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2729
  {
2730 1
    if ($separator === '') {
2731
      return static::create();
2732
    }
2733
2734 1
    if ($this->str === '') {
2735 1
      return static::create();
2736
    }
2737
2738 1
    $offset = $this->indexOfLast($separator);
2739 1
    if ($offset === false) {
2740 1
      return static::create('', $this->encoding);
2741
    }
2742
2743 1
    return static::create(
2744 1
        UTF8::substr(
2745 1
            $this->str,
2746 1
            0,
2747 1
            $offset,
2748 1
            $this->encoding
2749
        ),
2750 1
        $this->encoding
2751
    );
2752
  }
2753
2754
  /**
2755
   * Gets the substring before the last occurrence of a separator.
2756
   * If no match is found returns new empty Stringy object.
2757
   *
2758
   * @param string $separator
2759
   *
2760
   * @return static
2761
   */
2762 1 View Code Duplication
  public function beforeLastIgnoreCase(string $separator): Stringy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2763
  {
2764 1
    if ($separator === '') {
2765
      return static::create();
2766
    }
2767
2768 1
    if ($this->str === '') {
2769 1
      return static::create();
2770
    }
2771
2772 1
    $offset = $this->indexOfLastIgnoreCase($separator);
2773 1
    if ($offset === false) {
2774 1
      return static::create('', $this->encoding);
2775
    }
2776
2777 1
    return static::create(
2778 1
        UTF8::substr(
2779 1
            $this->str,
2780 1
            0,
2781 1
            $offset,
2782 1
            $this->encoding
2783
        ),
2784 1
        $this->encoding
2785
    );
2786
  }
2787
2788
  /**
2789
   * Returns the string with the first letter of each word capitalized,
2790
   * except for when the word is a name which shouldn't be capitalized.
2791
   *
2792
   * @return static <p>Object with $str capitalized.</p>
2793
   */
2794 39
  public function capitalizePersonalName(): Stringy
2795
  {
2796 39
    $stringy = $this->collapseWhitespace();
2797 39
    $stringy->str = $this->capitalizePersonalNameByDelimiter($stringy->str, ' ')->toString();
2798 39
    $stringy->str = $this->capitalizePersonalNameByDelimiter($stringy->str, '-')->toString();
2799
2800 39
    return static::create($stringy, $this->encoding);
2801
  }
2802
2803
  /**
2804
   * @param string $word
2805
   *
2806
   * @return static <p>Object with $str capitalized.</p>
2807
   */
2808 7
  protected function capitalizeWord(string $word): Stringy
2809
  {
2810 7
    $encoding = $this->encoding;
2811
2812 7
    $firstCharacter = UTF8::substr($word, 0, 1, $encoding);
2813 7
    $restOfWord = UTF8::substr($word, 1, null, $encoding);
2814 7
    $firstCharacterUppercased = UTF8::strtoupper($firstCharacter, $encoding);
0 ignored issues
show
Security Bug introduced by
It seems like $firstCharacter defined by \voku\helper\UTF8::substr($word, 0, 1, $encoding) on line 2812 can also be of type false; however, voku\helper\UTF8::strtoupper() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
2815
2816 7
    return static::create($firstCharacterUppercased . $restOfWord, $encoding);
2817
  }
2818
2819
  /**
2820
   * Personal names such as "Marcus Aurelius" are sometimes typed incorrectly using lowercase ("marcus aurelius").
2821
   *
2822
   * @param string $names
2823
   * @param string $delimiter
2824
   *
2825
   * @return static
2826
   */
2827 39
  protected function capitalizePersonalNameByDelimiter(string $names, string $delimiter): Stringy
2828
  {
2829
    // init
2830 39
    $namesArray = \explode($delimiter, $names);
2831 39
    $encoding = $this->encoding;
2832
2833
    $specialCases = [
2834 39
        'names'    => [
2835
            'ab',
2836
            'af',
2837
            'al',
2838
            'and',
2839
            'ap',
2840
            'bint',
2841
            'binte',
2842
            'da',
2843
            'de',
2844
            'del',
2845
            'den',
2846
            'der',
2847
            'di',
2848
            'dit',
2849
            'ibn',
2850
            'la',
2851
            'mac',
2852
            'nic',
2853
            'of',
2854
            'ter',
2855
            'the',
2856
            'und',
2857
            'van',
2858
            'von',
2859
            'y',
2860
            'zu',
2861
        ],
2862
        'prefixes' => [
2863
            'al-',
2864
            "d'",
2865
            'ff',
2866
            "l'",
2867
            'mac',
2868
            'mc',
2869
            'nic',
2870
        ],
2871
    ];
2872
2873 39
    foreach ($namesArray as &$name) {
2874 39
      if (\in_array($name, $specialCases['names'], true)) {
2875 27
        continue;
2876
      }
2877
2878 13
      $continue = false;
2879
2880 13
      if ($delimiter == '-') {
2881 13 View Code Duplication
        foreach ($specialCases['names'] as $beginning) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2882 13
          if (UTF8::strpos($name, $beginning, 0, $encoding) === 0) {
2883 13
            $continue = true;
2884
          }
2885
        }
2886
      }
2887
2888 13 View Code Duplication
      foreach ($specialCases['prefixes'] as $beginning) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2889 13
        if (UTF8::strpos($name, $beginning, 0, $encoding) === 0) {
2890 13
          $continue = true;
2891
        }
2892
      }
2893
2894 13
      if ($continue) {
2895 7
        continue;
2896
      }
2897
2898 7
      $name = $this->capitalizeWord($name);
2899
    }
2900
2901 39
    return static::create(\implode($delimiter, $namesArray), $encoding);
2902
  }
2903
}
2904