Completed
Push — master ( 95ff15...f3b22f )
by Lars
03:19 queued 30s
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
namespace Stringy;
4
5
use voku\helper\AntiXSS;
6
use voku\helper\EmailCheck;
7
use voku\helper\URLify;
8
use voku\helper\UTF8;
9
10
/**
11
 * Class Stringy
12
 *
13
 * @package Stringy
14
 */
15
class Stringy implements \Countable, \IteratorAggregate, \ArrayAccess
16
{
17
  /**
18
   * An instance's string.
19
   *
20
   * @var string
21
   */
22
  protected $str;
23
24
  /**
25
   * The string's encoding, which should be one of the mbstring module's
26
   * supported encodings.
27
   *
28
   * @var string
29
   */
30
  protected $encoding;
31
32
  /**
33
   * Initializes a Stringy object and assigns both str and encoding properties
34
   * the supplied values. $str is cast to a string prior to assignment, and if
35
   * $encoding is not specified, it defaults to mb_internal_encoding(). Throws
36
   * an InvalidArgumentException if the first argument is an array or object
37
   * without a __toString method.
38
   *
39
   * @param mixed  $str      [optional] <p>Value to modify, after being cast to string. Default: ''</p>
40
   * @param string $encoding [optional] <p>The character encoding.</p>
41
   *
42
   * @throws \InvalidArgumentException <p>if an array or object without a
43
   *         __toString method is passed as the first argument</p>
44
   */
45 1118
  public function __construct($str = '', $encoding = null)
46
  {
47 1118
    if (is_array($str)) {
48 1
      throw new \InvalidArgumentException(
49
          'Passed value cannot be an array'
50 1
      );
51
    }
52
53
    if (
54 1117
        is_object($str)
55 1117
        &&
56 77
        !method_exists($str, '__toString')
57 1117
    ) {
58 1
      throw new \InvalidArgumentException(
59
          'Passed object must have a __toString method'
60 1
      );
61
    }
62
63
    // don't throw a notice on PHP 5.3
64 1116
    if (!defined('ENT_SUBSTITUTE')) {
65
      define('ENT_SUBSTITUTE', 8);
66
    }
67
68
    // init
69 1116
    UTF8::checkForSupport();
70
71 1116
    $this->str = (string)$str;
72
73 1116
    if ($encoding) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $encoding of type string|null 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...
74 865
      $this->encoding = $encoding;
75 865
    } else {
76 740
      $this->encoding = \mb_internal_encoding();
77
    }
78 1116
  }
79
80
  /**
81
   * Returns the value in $str.
82
   *
83
   * @return string <p>The current value of the $str property.</p>
84
   */
85 186
  public function __toString()
86
  {
87 186
    return (string)$this->str;
88
  }
89
90
  /**
91
   * Returns a new string with $string appended.
92
   *
93
   * @param string $string <p>The string to append.</p>
94
   *
95
   * @return static <p>Object with appended $string.</p>
96
   */
97 5
  public function append($string)
98
  {
99 5
    return static::create($this->str . $string, $this->encoding);
100
  }
101
102
  /**
103
   * Append an password (limited to chars that are good readable).
104
   *
105
   * @param int $length <p>Length of the random string.</p>
106
   *
107
   * @return static <p>Object with appended password.</p>
108
   */
109 1
  public function appendPassword($length)
110
  {
111 1
    $possibleChars = '2346789bcdfghjkmnpqrtvwxyzBCDFGHJKLMNPQRTVWXYZ';
112
113 1
    return $this->appendRandomString($length, $possibleChars);
114
  }
115
116
  /**
117
   * Append an unique identifier.
118
   *
119
   * @param string|int $entropyExtra [optional] <p>Extra entropy via a string or int value.</p>
120
   * @param bool       $md5          [optional] <p>Return the unique identifier as md5-hash? Default: true</p>
121
   *
122
   * @return static <p>Object with appended unique identifier as md5-hash.</p>
123
   */
124 1
  public function appendUniqueIdentifier($entropyExtra = '', $md5 = true)
0 ignored issues
show
Coding Style introduced by
appendUniqueIdentifier uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
125
  {
126 1
    $uniqueHelper = mt_rand() .
127 1
                    session_id() .
128 1
                    (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '') .
129 1
                    (isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '') .
130 1
                    $entropyExtra;
131
132 1
    $uniqueString = uniqid($uniqueHelper, true);
133 1
    if ($md5) {
134 1
      $uniqueString = md5($uniqueString . $uniqueHelper);
135 1
    }
136
137 1
    return $this->append($uniqueString);
138
  }
139
140
  /**
141
   * Append an random string.
142
   *
143
   * @param int    $length        <p>Length of the random string.</p>
144
   * @param string $possibleChars [optional] <p>Characters string for the random selection.</p>
145
   *
146
   * @return static <p>Object with appended random string.</p>
147
   */
148 2
  public function appendRandomString($length, $possibleChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')
149
  {
150
    // init
151 2
    $i = 0;
152 2
    $length = (int)$length;
153 2
    $str = $this->str;
154 2
    $maxlength = UTF8::strlen($possibleChars, $this->encoding);
155
156 2
    if ($maxlength === 0) {
157 1
      return $this;
158
    }
159
160
    // add random chars
161 2
    while ($i < $length) {
162 2
      $char = UTF8::substr($possibleChars, mt_rand(0, $maxlength - 1), 1, $this->encoding);
163 2
      $str .= $char;
164 2
      $i++;
165 2
    }
166
167 2
    return $this->append($str);
168
  }
169
170
  /**
171
   * Creates a Stringy object and assigns both str and encoding properties
172
   * the supplied values. $str is cast to a string prior to assignment, and if
173
   * $encoding is not specified, it defaults to mb_internal_encoding(). It
174
   * then returns the initialized object. Throws an InvalidArgumentException
175
   * if the first argument is an array or object without a __toString method.
176
   *
177
   * @param  mixed  $str      [optional] <p>Value to modify, after being cast to string. Default: ''</p>
178
   * @param  string $encoding [optional] <p>The character encoding.</p>
179
   *
180
   * @return static <p>A Stringy object.</p>
181
   * @throws \InvalidArgumentException <p>if an array or object without a
182
   *         __toString method is passed as the first argument</p>
183
   */
184 1108
  public static function create($str = '', $encoding = null)
185
  {
186 1108
    return new static($str, $encoding);
187
  }
188
189
  /**
190
   * Returns the substring between $start and $end, if found, or an empty
191
   * string. An optional offset may be supplied from which to begin the
192
   * search for the start string.
193
   *
194
   * @param  string $start  <p>Delimiter marking the start of the substring.</p>
195
   * @param  string $end    <p>Delimiter marking the end of the substring.</p>
196
   * @param  int    $offset [optional] <p>Index from which to begin the search. Default: 0</p>
197
   *
198
   * @return static <p>Object whose $str is a substring between $start and $end.</p>
199
   */
200 16
  public function between($start, $end, $offset = 0)
201
  {
202 16
    $startIndex = $this->indexOf($start, $offset);
203 16
    if ($startIndex === false) {
204 2
      return static::create('', $this->encoding);
205
    }
206
207 14
    $substrIndex = $startIndex + UTF8::strlen($start, $this->encoding);
208 14
    $endIndex = $this->indexOf($end, $substrIndex);
209 14
    if ($endIndex === false) {
210 2
      return static::create('', $this->encoding);
211
    }
212
213 12
    return $this->substr($substrIndex, $endIndex - $substrIndex);
214
  }
215
216
  /**
217
   * Returns the index of the first occurrence of $needle in the string,
218
   * and false if not found. Accepts an optional offset from which to begin
219
   * the search.
220
   *
221
   * @param  string $needle <p>Substring to look for.</p>
222
   * @param  int    $offset [optional] <p>Offset from which to search. Default: 0</p>
223
   *
224
   * @return int|false <p>The occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
225
   */
226 28
  public function indexOf($needle, $offset = 0)
227
  {
228 28
    return UTF8::strpos($this->str, (string)$needle, (int)$offset, $this->encoding);
229
  }
230
231
  /**
232
   * Returns the index of the first occurrence of $needle in the string,
233
   * and false if not found. Accepts an optional offset from which to begin
234
   * the search.
235
   *
236
   * @param  string $needle <p>Substring to look for.</p>
237
   * @param  int    $offset [optional] <p>Offset from which to search. Default: 0</p>
238
   *
239
   * @return int|false <p>The occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
240
   */
241 2
  public function indexOfIgnoreCase($needle, $offset = 0)
242
  {
243 2
    return UTF8::stripos($this->str, (string)$needle, (int)$offset, $this->encoding);
244
  }
245
246
  /**
247
   * Returns the substring beginning at $start with the specified $length.
248
   * It differs from the UTF8::substr() function in that providing a $length of
249
   * null will return the rest of the string, rather than an empty string.
250
   *
251
   * @param int $start  <p>Position of the first character to use.</p>
252
   * @param int $length [optional] <p>Maximum number of characters used. Default: null</p>
253
   *
254
   * @return static <p>Object with its $str being the substring.</p>
255
   */
256 64
  public function substr($start, $length = null)
257
  {
258 64
    if ($length === null) {
259 19
      $length = $this->length();
260 19
    }
261
262 64
    $str = UTF8::substr($this->str, $start, $length, $this->encoding);
263
264 64
    return static::create($str, $this->encoding);
265
  }
266
267
  /**
268
   * Returns the length of the string.
269
   *
270
   * @return int <p>The number of characters in $str given the encoding.</p>
271
   */
272 259
  public function length()
273
  {
274 259
    return UTF8::strlen($this->str, $this->encoding);
275
  }
276
277
  /**
278
   * Trims the string and replaces consecutive whitespace characters with a
279
   * single space. This includes tabs and newline characters, as well as
280
   * multibyte whitespace such as the thin space and ideographic space.
281
   *
282
   * @return static <p>Object with a trimmed $str and condensed whitespace.</p>
283
   */
284 52
  public function collapseWhitespace()
285
  {
286 52
    return $this->regexReplace('[[:space:]]+', ' ')->trim();
287
  }
288
289
  /**
290
   * Returns a string with whitespace removed from the start and end of the
291
   * string. Supports the removal of unicode whitespace. Accepts an optional
292
   * string of characters to strip instead of the defaults.
293
   *
294
   * @param string $chars [optional] <p>String of characters to strip. Default: null</p>
295
   *
296
   * @return static <p>Object with a trimmed $str.</p>
297
   */
298 153 View Code Duplication
  public function trim($chars = null)
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...
299
  {
300 153
    if (!$chars) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $chars of type string|null 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...
301 152
      $chars = '[:space:]';
302 152
    } else {
303 1
      $chars = preg_quote($chars, '/');
304
    }
305
306 153
    return $this->regexReplace("^[$chars]+|[$chars]+\$", '');
307
  }
308
309
  /**
310
   * Replaces all occurrences of $pattern in $str by $replacement.
311
   *
312
   * @param  string $pattern     <p>The regular expression pattern.</p>
313
   * @param  string $replacement <p>The string to replace with.</p>
314
   * @param  string $options     [optional] <p>Matching conditions to be used.</p>
315
   * @param  string $delimiter   [optional] <p>Delimiter the the regex. Default: '/'</p>
316
   *
317
   * @return static <p>Object with the result2ing $str after the replacements.</p>
318
   */
319 224
  public function regexReplace($pattern, $replacement, $options = '', $delimiter = '/')
320
  {
321 224
    if ($options === 'msr') {
322 9
      $options = 'ms';
323 9
    }
324
325
    // fallback
326 224
    if (!$delimiter) {
327
      $delimiter = '/';
328
    }
329
330 224
    $str = preg_replace(
331 224
        $delimiter . $pattern . $delimiter . 'u' . $options,
332 224
        $replacement,
333 224
        $this->str
334 224
    );
335
336 224
    return static::create($str, $this->encoding);
337
  }
338
339
  /**
340
   * Returns true if the string contains all $needles, false otherwise. By
341
   * default the comparison is case-sensitive, but can be made insensitive by
342
   * setting $caseSensitive to false.
343
   *
344
   * @param  array $needles       <p>SubStrings to look for.</p>
345
   * @param  bool  $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
346
   *
347
   * @return bool  <p>Whether or not $str contains $needle.</p>
348
   */
349 43 View Code Duplication
  public function containsAll($needles, $caseSensitive = true)
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...
350
  {
351
    /** @noinspection IsEmptyFunctionUsageInspection */
352 43
    if (empty($needles)) {
353 1
      return false;
354
    }
355
356 42
    foreach ($needles as $needle) {
357 42
      if (!$this->contains($needle, $caseSensitive)) {
358 18
        return false;
359
      }
360 24
    }
361
362 24
    return true;
363
  }
364
365
  /**
366
   * Returns true if the string contains $needle, false otherwise. By default
367
   * the comparison is case-sensitive, but can be made insensitive by setting
368
   * $caseSensitive to false.
369
   *
370
   * @param  string $needle        <p>Substring to look for.</p>
371
   * @param  bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
372
   *
373
   * @return bool   <p>Whether or not $str contains $needle.</p>
374
   */
375 105
  public function contains($needle, $caseSensitive = true)
376
  {
377 105
    $encoding = $this->encoding;
378
379 105
    if ($caseSensitive) {
380 55
      return (UTF8::strpos($this->str, $needle, 0, $encoding) !== false);
381
    }
382
383 50
    return (UTF8::stripos($this->str, $needle, 0, $encoding) !== false);
384
  }
385
386
  /**
387
   * Returns true if the string contains any $needles, false otherwise. By
388
   * default the comparison is case-sensitive, but can be made insensitive by
389
   * setting $caseSensitive to false.
390
   *
391
   * @param  array $needles       <p>SubStrings to look for.</p>
392
   * @param  bool  $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
393
   *
394
   * @return bool <p>Whether or not $str contains $needle.</p>
395
   */
396 43 View Code Duplication
  public function containsAny($needles, $caseSensitive = true)
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...
397
  {
398
    /** @noinspection IsEmptyFunctionUsageInspection */
399 43
    if (empty($needles)) {
400 1
      return false;
401
    }
402
403 42
    foreach ($needles as $needle) {
404 42
      if ($this->contains($needle, $caseSensitive)) {
405 24
        return true;
406
      }
407 18
    }
408
409 18
    return false;
410
  }
411
412
  /**
413
   * Returns the length of the string, implementing the countable interface.
414
   *
415
   * @return int <p>The number of characters in the string, given the encoding.</p>
416
   */
417 1
  public function count()
418
  {
419 1
    return $this->length();
420
  }
421
422
  /**
423
   * Returns the number of occurrences of $substring in the given string.
424
   * By default, the comparison is case-sensitive, but can be made insensitive
425
   * by setting $caseSensitive to false.
426
   *
427
   * @param  string $substring     <p>The substring to search for.</p>
428
   * @param  bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
429
   *
430
   * @return int <p>The number of $substring occurrences.</p>
431
   */
432 15
  public function countSubstr($substring, $caseSensitive = true)
433
  {
434 15
    if ($caseSensitive) {
435 9
      return UTF8::substr_count($this->str, $substring, 0, null, $this->encoding);
436
    }
437
438 6
    $str = UTF8::strtoupper($this->str, $this->encoding);
439 6
    $substring = UTF8::strtoupper($substring, $this->encoding);
440
441 6
    return UTF8::substr_count($str, $substring, 0, null, $this->encoding);
442
  }
443
444
  /**
445
   * Returns a lowercase and trimmed string separated by dashes. Dashes are
446
   * inserted before uppercase characters (with the exception of the first
447
   * character of the string), and in place of spaces as well as underscores.
448
   *
449
   * @return static <p>Object with a dasherized $str</p>
450
   */
451 19
  public function dasherize()
452
  {
453 19
    return $this->delimit('-');
454
  }
455
456
  /**
457
   * Returns a lowercase and trimmed string separated by the given delimiter.
458
   * Delimiters are inserted before uppercase characters (with the exception
459
   * of the first character of the string), and in place of spaces, dashes,
460
   * and underscores. Alpha delimiters are not converted to lowercase.
461
   *
462
   * @param string $delimiter <p>Sequence used to separate parts of the string.</p>
463
   *
464
   * @return static <p>Object with a delimited $str.</p>
465
   */
466 49
  public function delimit($delimiter)
467
  {
468 49
    $str = $this->trim();
469
470 49
    $str = preg_replace('/\B([A-Z])/u', '-\1', $str);
471
472 49
    $str = UTF8::strtolower($str, $this->encoding);
0 ignored issues
show
Bug introduced by
It seems like $str can also be of type array<integer,string>; however, voku\helper\UTF8::strtolower() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
473
474 49
    $str = preg_replace('/[-_\s]+/u', $delimiter, $str);
475
476 49
    return static::create($str, $this->encoding);
477
  }
478
479
  /**
480
   * Ensures that the string begins with $substring. If it doesn't, it's
481
   * prepended.
482
   *
483
   * @param string $substring <p>The substring to add if not present.</p>
484
   *
485
   * @return static <p>Object with its $str prefixed by the $substring.</p>
486
   */
487 10 View Code Duplication
  public function ensureLeft($substring)
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...
488
  {
489 10
    $stringy = static::create($this->str, $this->encoding);
490
491 10
    if (!$stringy->startsWith($substring)) {
492 4
      $stringy->str = $substring . $stringy->str;
493 4
    }
494
495 10
    return $stringy;
496
  }
497
498
  /**
499
   * Returns true if the string begins with $substring, false otherwise. By
500
   * default, the comparison is case-sensitive, but can be made insensitive
501
   * by setting $caseSensitive to false.
502
   *
503
   * @param  string $substring     <p>The substring to look for.</p>
504
   * @param  bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
505
   *
506
   * @return bool   <p>Whether or not $str starts with $substring.</p>
507
   */
508 45
  public function startsWith($substring, $caseSensitive = true)
509
  {
510 45
    $str = $this->str;
511
512 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...
513 8
      $substring = UTF8::strtolower($substring, $this->encoding);
514 8
      $str = UTF8::strtolower($this->str, $this->encoding);
515 8
    }
516
517 45
    return UTF8::strpos($str, $substring, $this->encoding) === 0;
518
  }
519
520
  /**
521
   * Returns true if the string begins with any of $substrings, false otherwise.
522
   * By default the comparison is case-sensitive, but can be made insensitive by
523
   * setting $caseSensitive to false.
524
   *
525
   * @param  array $substrings    <p>Substrings to look for.</p>
526
   * @param  bool  $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
527
   *
528
   * @return bool  <p>Whether or not $str starts with $substring.</p>
529
   */
530 12 View Code Duplication
  public function startsWithAny(array $substrings, $caseSensitive = true)
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...
531
  {
532 12
    if (empty($substrings)) {
533
      return false;
534
    }
535
536 12
    foreach ($substrings as $substring) {
537 12
      if ($this->startsWith($substring, $caseSensitive)) {
538 6
        return true;
539
      }
540 6
    }
541
542 6
    return false;
543
  }
544
545
  /**
546
   * Ensures that the string ends with $substring. If it doesn't, it's appended.
547
   *
548
   * @param string $substring <p>The substring to add if not present.</p>
549
   *
550
   * @return static <p>Object with its $str suffixed by the $substring.</p>
551
   */
552 10 View Code Duplication
  public function ensureRight($substring)
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...
553
  {
554 10
    $stringy = static::create($this->str, $this->encoding);
555
556 10
    if (!$stringy->endsWith($substring)) {
557 4
      $stringy->str .= $substring;
558 4
    }
559
560 10
    return $stringy;
561
  }
562
563
  /**
564
   * Returns true if the string ends with $substring, false otherwise. By
565
   * default, the comparison is case-sensitive, but can be made insensitive
566
   * by setting $caseSensitive to false.
567
   *
568
   * @param  string $substring     <p>The substring to look for.</p>
569
   * @param  bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
570
   *
571
   * @return bool   <p>Whether or not $str ends with $substring.</p>
572
   */
573 44
  public function endsWith($substring, $caseSensitive = true)
574
  {
575 44
    $substringLength = UTF8::strlen($substring, $this->encoding);
576 44
    $strLength = $this->length();
577
578 44
    $endOfStr = UTF8::substr(
579 44
        $this->str,
580 44
        $strLength - $substringLength,
581 44
        $substringLength,
582 44
        $this->encoding
583 44
    );
584
585 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...
586 8
      $substring = UTF8::strtolower($substring, $this->encoding);
587 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...
588 8
    }
589
590 44
    return (string)$substring === $endOfStr;
591
  }
592
593
  /**
594
   * Returns true if the string ends with any of $substrings, false otherwise.
595
   * By default, the comparison is case-sensitive, but can be made insensitive
596
   * by setting $caseSensitive to false.
597
   *
598
   * @param  string[] $substrings    <p>Substrings to look for.</p>
599
   * @param  bool     $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
600
   *
601
   * @return bool     <p>Whether or not $str ends with $substring.</p>
602
   */
603 11 View Code Duplication
  public function endsWithAny($substrings, $caseSensitive = true)
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...
604
  {
605 11
    if (empty($substrings)) {
606
      return false;
607
    }
608
609 11
    foreach ($substrings as $substring) {
610 11
      if ($this->endsWith($substring, $caseSensitive)) {
611 5
        return true;
612
      }
613 11
    }
614
615 6
    return false;
616
  }
617
618
  /**
619
   * Returns the first $n characters of the string.
620
   *
621
   * @param int $n <p>Number of characters to retrieve from the start.</p>
622
   *
623
   * @return static <p>Object with its $str being the first $n chars.</p>
624
   */
625 12 View Code Duplication
  public function first($n)
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...
626
  {
627 12
    $stringy = static::create($this->str, $this->encoding);
628
629 12
    if ($n < 0) {
630 2
      $stringy->str = '';
631 2
    } else {
632 10
      return $stringy->substr(0, $n);
633
    }
634
635 2
    return $stringy;
636
  }
637
638
  /**
639
   * Returns the encoding used by the Stringy object.
640
   *
641
   * @return string <p>The current value of the $encoding property.</p>
642
   */
643 3
  public function getEncoding()
644
  {
645 3
    return $this->encoding;
646
  }
647
648
  /**
649
   * Returns a new ArrayIterator, thus implementing the IteratorAggregate
650
   * interface. The ArrayIterator's constructor is passed an array of chars
651
   * in the multibyte string. This enables the use of foreach with instances
652
   * of Stringy\Stringy.
653
   *
654
   * @return \ArrayIterator <p>An iterator for the characters in the string.</p>
655
   */
656 1
  public function getIterator()
657
  {
658 1
    return new \ArrayIterator($this->chars());
659
  }
660
661
  /**
662
   * Returns an array consisting of the characters in the string.
663
   *
664
   * @return array <p>An array of string chars.</p>
665
   */
666 4
  public function chars()
667
  {
668
    // init
669 4
    $chars = array();
670 4
    $l = $this->length();
671
672 4
    for ($i = 0; $i < $l; $i++) {
673 3
      $chars[] = $this->at($i)->str;
674 3
    }
675
676 4
    return $chars;
677
  }
678
679
  /**
680
   * Returns the character at $index, with indexes starting at 0.
681
   *
682
   * @param int $index <p>Position of the character.</p>
683
   *
684
   * @return static <p>The character at $index.</p>
685
   */
686 11
  public function at($index)
687
  {
688 11
    return $this->substr($index, 1);
689
  }
690
691
  /**
692
   * Returns true if the string contains a lower case char, false otherwise.
693
   *
694
   * @return bool <p>Whether or not the string contains a lower case character.</p>
695
   */
696 12
  public function hasLowerCase()
697
  {
698 12
    return $this->matchesPattern('.*[[:lower:]]');
699
  }
700
701
  /**
702
   * Returns true if $str matches the supplied pattern, false otherwise.
703
   *
704
   * @param  string $pattern <p>Regex pattern to match against.</p>
705
   *
706
   * @return bool <p>Whether or not $str matches the pattern.</p>
707
   */
708 103
  protected function matchesPattern($pattern)
709
  {
710 103
    if (preg_match('/' . $pattern . '/u', $this->str)) {
711 64
      return true;
712
    }
713
714 39
    return false;
715
  }
716
717
  /**
718
   * Returns true if the string contains an upper case char, false otherwise.
719
   *
720
   * @return bool <p>Whether or not the string contains an upper case character.</p>
721
   */
722 12
  public function hasUpperCase()
723
  {
724 12
    return $this->matchesPattern('.*[[:upper:]]');
725
  }
726
727
  /**
728
   * Convert all HTML entities to their applicable characters.
729
   *
730
   * @param int $flags       [optional] <p>
731
   *                         A bitmask of one or more of the following flags, which specify how to handle quotes and
732
   *                         which document type to use. The default is ENT_COMPAT.
733
   *                         <table>
734
   *                         Available <i>flags</i> constants
735
   *                         <tr valign="top">
736
   *                         <td>Constant Name</td>
737
   *                         <td>Description</td>
738
   *                         </tr>
739
   *                         <tr valign="top">
740
   *                         <td><b>ENT_COMPAT</b></td>
741
   *                         <td>Will convert double-quotes and leave single-quotes alone.</td>
742
   *                         </tr>
743
   *                         <tr valign="top">
744
   *                         <td><b>ENT_QUOTES</b></td>
745
   *                         <td>Will convert both double and single quotes.</td>
746
   *                         </tr>
747
   *                         <tr valign="top">
748
   *                         <td><b>ENT_NOQUOTES</b></td>
749
   *                         <td>Will leave both double and single quotes unconverted.</td>
750
   *                         </tr>
751
   *                         <tr valign="top">
752
   *                         <td><b>ENT_HTML401</b></td>
753
   *                         <td>
754
   *                         Handle code as HTML 4.01.
755
   *                         </td>
756
   *                         </tr>
757
   *                         <tr valign="top">
758
   *                         <td><b>ENT_XML1</b></td>
759
   *                         <td>
760
   *                         Handle code as XML 1.
761
   *                         </td>
762
   *                         </tr>
763
   *                         <tr valign="top">
764
   *                         <td><b>ENT_XHTML</b></td>
765
   *                         <td>
766
   *                         Handle code as XHTML.
767
   *                         </td>
768
   *                         </tr>
769
   *                         <tr valign="top">
770
   *                         <td><b>ENT_HTML5</b></td>
771
   *                         <td>
772
   *                         Handle code as HTML 5.
773
   *                         </td>
774
   *                         </tr>
775
   *                         </table>
776
   *                         </p>
777
   *
778
   * @return static <p>Object with the resulting $str after being html decoded.</p>
779
   */
780 5
  public function htmlDecode($flags = ENT_COMPAT)
781
  {
782 5
    $str = UTF8::html_entity_decode($this->str, $flags, $this->encoding);
783
784 5
    return static::create($str, $this->encoding);
785
  }
786
787
  /**
788
   * Convert all applicable characters to HTML entities.
789
   *
790
   * @param int $flags       [optional] <p>
791
   *                         A bitmask of one or more of the following flags, which specify how to handle quotes and
792
   *                         which document type to use. The default is ENT_COMPAT.
793
   *                         <table>
794
   *                         Available <i>flags</i> constants
795
   *                         <tr valign="top">
796
   *                         <td>Constant Name</td>
797
   *                         <td>Description</td>
798
   *                         </tr>
799
   *                         <tr valign="top">
800
   *                         <td><b>ENT_COMPAT</b></td>
801
   *                         <td>Will convert double-quotes and leave single-quotes alone.</td>
802
   *                         </tr>
803
   *                         <tr valign="top">
804
   *                         <td><b>ENT_QUOTES</b></td>
805
   *                         <td>Will convert both double and single quotes.</td>
806
   *                         </tr>
807
   *                         <tr valign="top">
808
   *                         <td><b>ENT_NOQUOTES</b></td>
809
   *                         <td>Will leave both double and single quotes unconverted.</td>
810
   *                         </tr>
811
   *                         <tr valign="top">
812
   *                         <td><b>ENT_HTML401</b></td>
813
   *                         <td>
814
   *                         Handle code as HTML 4.01.
815
   *                         </td>
816
   *                         </tr>
817
   *                         <tr valign="top">
818
   *                         <td><b>ENT_XML1</b></td>
819
   *                         <td>
820
   *                         Handle code as XML 1.
821
   *                         </td>
822
   *                         </tr>
823
   *                         <tr valign="top">
824
   *                         <td><b>ENT_XHTML</b></td>
825
   *                         <td>
826
   *                         Handle code as XHTML.
827
   *                         </td>
828
   *                         </tr>
829
   *                         <tr valign="top">
830
   *                         <td><b>ENT_HTML5</b></td>
831
   *                         <td>
832
   *                         Handle code as HTML 5.
833
   *                         </td>
834
   *                         </tr>
835
   *                         </table>
836
   *                         </p>
837
   *
838
   * @return static <p>Object with the resulting $str after being html encoded.</p>
839
   */
840 5
  public function htmlEncode($flags = ENT_COMPAT)
841
  {
842 5
    $str = UTF8::htmlentities($this->str, $flags, $this->encoding);
843
844 5
    return static::create($str, $this->encoding);
845
  }
846
847
  /**
848
   * Capitalizes the first word of the string, replaces underscores with
849
   * spaces, and strips '_id'.
850
   *
851
   * @return static <p>Object with a humanized $str.</p>
852
   */
853 3
  public function humanize()
854
  {
855 3
    $str = UTF8::str_replace(array('_id', '_'), array('', ' '), $this->str);
856
857 3
    return static::create($str, $this->encoding)->trim()->upperCaseFirst();
858
  }
859
860
  /**
861
   * Converts the first character of the supplied string to upper case.
862
   *
863
   * @return static <p>Object with the first character of $str being upper case.</p>
864
   */
865 27 View Code Duplication
  public function upperCaseFirst()
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...
866
  {
867 27
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
868 27
    $rest = UTF8::substr(
869 27
        $this->str,
870 27
        1,
871 27
        $this->length() - 1,
872 27
        $this->encoding
873 27
    );
874
875 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 867 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...
876
877 27
    return static::create($str, $this->encoding);
878
  }
879
880
  /**
881
   * Returns the index of the last occurrence of $needle in the string,
882
   * and false if not found. Accepts an optional offset from which to begin
883
   * the search. Offsets may be negative to count from the last character
884
   * in the string.
885
   *
886
   * @param  string $needle <p>Substring to look for.</p>
887
   * @param  int    $offset [optional] <p>Offset from which to search. Default: 0</p>
888
   *
889
   * @return int|false <p>The last occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
890
   */
891 12
  public function indexOfLast($needle, $offset = 0)
892
  {
893 12
    return UTF8::strrpos($this->str, (string)$needle, (int)$offset, $this->encoding);
894
  }
895
896
  /**
897
   * Returns the index of the last occurrence of $needle in the string,
898
   * and false if not found. Accepts an optional offset from which to begin
899
   * the search. Offsets may be negative to count from the last character
900
   * in the string.
901
   *
902
   * @param  string $needle <p>Substring to look for.</p>
903
   * @param  int    $offset [optional] <p>Offset from which to search. Default: 0</p>
904
   *
905
   * @return int|false <p>The last occurrence's <strong>index</strong> if found, otherwise <strong>false</strong>.</p>
906
   */
907 2
  public function indexOfLastIgnoreCase($needle, $offset = 0)
908
  {
909 2
    return UTF8::strripos($this->str, (string)$needle, (int)$offset, $this->encoding);
910
  }
911
912
  /**
913
   * Inserts $substring into the string at the $index provided.
914
   *
915
   * @param  string $substring <p>String to be inserted.</p>
916
   * @param  int    $index     <p>The index at which to insert the substring.</p>
917
   *
918
   * @return static <p>Object with the resulting $str after the insertion.</p>
919
   */
920 8 View Code Duplication
  public function insert($substring, $index)
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...
921
  {
922 8
    $stringy = static::create($this->str, $this->encoding);
923 8
    if ($index > $stringy->length()) {
924 1
      return $stringy;
925
    }
926
927 7
    $start = UTF8::substr($stringy->str, 0, $index, $stringy->encoding);
928 7
    $end = UTF8::substr($stringy->str, $index, $stringy->length(), $stringy->encoding);
929
930 7
    $stringy->str = $start . $substring . $end;
931
932 7
    return $stringy;
933
  }
934
935
  /**
936
   * Returns true if the string contains the $pattern, otherwise false.
937
   *
938
   * WARNING: Asterisks ("*") are translated into (".*") zero-or-more regular
939
   * expression wildcards.
940
   *
941
   * @credit Originally from Laravel, thanks Taylor.
942
   *
943
   * @param string $pattern <p>The string or pattern to match against.</p>
944
   *
945
   * @return bool <p>Whether or not we match the provided pattern.</p>
946
   */
947 13
  public function is($pattern)
948
  {
949 13
    if ($this->toString() === $pattern) {
950 1
      return true;
951
    }
952
953 12
    $quotedPattern = preg_quote($pattern, '/');
954 12
    $replaceWildCards = str_replace('\*', '.*', $quotedPattern);
955
956 12
    return $this->matchesPattern('^' . $replaceWildCards . '\z');
957
  }
958
959
  /**
960
   * Returns true if the string contains only alphabetic chars, false otherwise.
961
   *
962
   * @return bool <p>Whether or not $str contains only alphabetic chars.</p>
963
   */
964 10
  public function isAlpha()
965
  {
966 10
    return $this->matchesPattern('^[[:alpha:]]*$');
967
  }
968
969
  /**
970
   * Determine whether the string is considered to be empty.
971
   *
972
   * A variable is considered empty if it does not exist or if its value equals FALSE.
973
   * empty() does not generate a warning if the variable does not exist.
974
   *
975
   * @return bool <p>Whether or not $str is empty().</p>
976
   */
977
  public function isEmpty()
978
  {
979
    return empty($this->str);
980
  }
981
982
  /**
983
   * Returns true if the string contains only alphabetic and numeric chars, false otherwise.
984
   *
985
   * @return bool <p>Whether or not $str contains only alphanumeric chars.</p>
986
   */
987 13
  public function isAlphanumeric()
988
  {
989 13
    return $this->matchesPattern('^[[:alnum:]]*$');
990
  }
991
992
  /**
993
   * Returns true if the string contains only whitespace chars, false otherwise.
994
   *
995
   * @return bool <p>Whether or not $str contains only whitespace characters.</p>
996
   */
997 15
  public function isBlank()
998
  {
999 15
    return $this->matchesPattern('^[[:space:]]*$');
1000
  }
1001
1002
  /**
1003
   * Returns true if the string contains only hexadecimal chars, false otherwise.
1004
   *
1005
   * @return bool <p>Whether or not $str contains only hexadecimal chars.</p>
1006
   */
1007 13
  public function isHexadecimal()
1008
  {
1009 13
    return $this->matchesPattern('^[[:xdigit:]]*$');
1010
  }
1011
1012
  /**
1013
   * Returns true if the string contains HTML-Tags, false otherwise.
1014
   *
1015
   * @return bool <p>Whether or not $str contains HTML-Tags.</p>
1016
   */
1017 1
  public function isHtml()
1018
  {
1019 1
    return UTF8::is_html($this->str);
1020
  }
1021
1022
  /**
1023
   * Returns true if the string contains a valid E-Mail address, false otherwise.
1024
   *
1025
   * @param bool $useExampleDomainCheck   [optional] <p>Default: false</p>
1026
   * @param bool $useTypoInDomainCheck    [optional] <p>Default: false</p>
1027
   * @param bool $useTemporaryDomainCheck [optional] <p>Default: false</p>
1028
   * @param bool $useDnsCheck             [optional] <p>Default: false</p>
1029
   *
1030
   * @return bool <p>Whether or not $str contains a valid E-Mail address.</p>
1031
   */
1032 1
  public function isEmail($useExampleDomainCheck = false, $useTypoInDomainCheck = false, $useTemporaryDomainCheck = false, $useDnsCheck = false)
1033
  {
1034 1
    return EmailCheck::isValid($this->str, $useExampleDomainCheck, $useTypoInDomainCheck, $useTemporaryDomainCheck, $useDnsCheck);
1035
  }
1036
1037
  /**
1038
   * Returns true if the string is JSON, false otherwise. Unlike json_decode
1039
   * in PHP 5.x, this method is consistent with PHP 7 and other JSON parsers,
1040
   * in that an empty string is not considered valid JSON.
1041
   *
1042
   * @return bool <p>Whether or not $str is JSON.</p>
1043
   */
1044 20
  public function isJson()
1045
  {
1046 20
    if (!isset($this->str[0])) {
1047 1
      return false;
1048
    }
1049
1050 19
    json_decode($this->str);
1051
1052 19
    if (json_last_error() === JSON_ERROR_NONE) {
1053 11
      return true;
1054
    }
1055
1056 8
    return false;
1057
  }
1058
1059
  /**
1060
   * Returns true if the string contains only lower case chars, false otherwise.
1061
   *
1062
   * @return bool <p>Whether or not $str contains only lower case characters.</p>
1063
   */
1064 8
  public function isLowerCase()
1065
  {
1066 8
    if ($this->matchesPattern('^[[:lower:]]*$')) {
1067 3
      return true;
1068
    }
1069
1070 5
    return false;
1071
  }
1072
1073
  /**
1074
   * Returns true if the string is serialized, false otherwise.
1075
   *
1076
   * @return bool <p>Whether or not $str is serialized.</p>
1077
   */
1078 7
  public function isSerialized()
1079
  {
1080 7
    if (!isset($this->str[0])) {
1081 1
      return false;
1082
    }
1083
1084
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
1085
    if (
1086 6
        $this->str === 'b:0;'
1087 6
        ||
1088 6
        @unserialize($this->str) !== false
1089 6
    ) {
1090 4
      return true;
1091
    }
1092
1093 2
    return false;
1094
  }
1095
1096
  /**
1097
   * Returns true if the string contains only lower case chars, false
1098
   * otherwise.
1099
   *
1100
   * @return bool <p>Whether or not $str contains only lower case characters.</p>
1101
   */
1102 8
  public function isUpperCase()
1103
  {
1104 8
    return $this->matchesPattern('^[[:upper:]]*$');
1105
  }
1106
1107
  /**
1108
   * Returns the last $n characters of the string.
1109
   *
1110
   * @param int $n <p>Number of characters to retrieve from the end.</p>
1111
   *
1112
   * @return static <p>Object with its $str being the last $n chars.</p>
1113
   */
1114 12 View Code Duplication
  public function last($n)
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...
1115
  {
1116 12
    $stringy = static::create($this->str, $this->encoding);
1117
1118 12
    if ($n <= 0) {
1119 4
      $stringy->str = '';
1120 4
    } else {
1121 8
      return $stringy->substr(-$n);
1122
    }
1123
1124 4
    return $stringy;
1125
  }
1126
1127
  /**
1128
   * Splits on newlines and carriage returns, returning an array of Stringy
1129
   * objects corresponding to the lines in the string.
1130
   *
1131
   * @return static[] <p>An array of Stringy objects.</p>
1132
   */
1133 15
  public function lines()
1134
  {
1135 15
    $array = preg_split('/[\r\n]{1,2}/u', $this->str);
1136
    /** @noinspection CallableInLoopTerminationConditionInspection */
1137
    /** @noinspection ForeachInvariantsInspection */
1138 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...
1139 15
      $array[$i] = static::create($array[$i], $this->encoding);
1140 15
    }
1141
1142 15
    return $array;
1143
  }
1144
1145
  /**
1146
   * Returns the longest common prefix between the string and $otherStr.
1147
   *
1148
   * @param string $otherStr <p>Second string for comparison.</p>
1149
   *
1150
   * @return static <p>Object with its $str being the longest common prefix.</p>
1151
   */
1152 10
  public function longestCommonPrefix($otherStr)
1153
  {
1154 10
    $encoding = $this->encoding;
1155 10
    $maxLength = min($this->length(), UTF8::strlen($otherStr, $encoding));
1156
1157 10
    $longestCommonPrefix = '';
1158 10
    for ($i = 0; $i < $maxLength; $i++) {
1159 8
      $char = UTF8::substr($this->str, $i, 1, $encoding);
1160
1161 8
      if ($char == UTF8::substr($otherStr, $i, 1, $encoding)) {
1162 6
        $longestCommonPrefix .= $char;
1163 6
      } else {
1164 6
        break;
1165
      }
1166 6
    }
1167
1168 10
    return static::create($longestCommonPrefix, $encoding);
1169
  }
1170
1171
  /**
1172
   * Returns the longest common suffix between the string and $otherStr.
1173
   *
1174
   * @param string $otherStr <p>Second string for comparison.</p>
1175
   *
1176
   * @return static <p>Object with its $str being the longest common suffix.</p>
1177
   */
1178 10
  public function longestCommonSuffix($otherStr)
1179
  {
1180 10
    $encoding = $this->encoding;
1181 10
    $maxLength = min($this->length(), UTF8::strlen($otherStr, $encoding));
1182
1183 10
    $longestCommonSuffix = '';
1184 10
    for ($i = 1; $i <= $maxLength; $i++) {
1185 8
      $char = UTF8::substr($this->str, -$i, 1, $encoding);
1186
1187 8
      if ($char == UTF8::substr($otherStr, -$i, 1, $encoding)) {
1188 6
        $longestCommonSuffix = $char . $longestCommonSuffix;
1189 6
      } else {
1190 6
        break;
1191
      }
1192 6
    }
1193
1194 10
    return static::create($longestCommonSuffix, $encoding);
1195
  }
1196
1197
  /**
1198
   * Returns the longest common substring between the string and $otherStr.
1199
   * In the case of ties, it returns that which occurs first.
1200
   *
1201
   * @param string $otherStr <p>Second string for comparison.</p>
1202
   *
1203
   * @return static <p>Object with its $str being the longest common substring.</p>
1204
   */
1205 10
  public function longestCommonSubstring($otherStr)
1206
  {
1207
    // Uses dynamic programming to solve
1208
    // http://en.wikipedia.org/wiki/Longest_common_substring_problem
1209 10
    $encoding = $this->encoding;
1210 10
    $stringy = static::create($this->str, $encoding);
1211 10
    $strLength = $stringy->length();
1212 10
    $otherLength = UTF8::strlen($otherStr, $encoding);
1213
1214
    // Return if either string is empty
1215 10
    if ($strLength == 0 || $otherLength == 0) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $strLength of type integer|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
Bug introduced by
It seems like you are loosely comparing $otherLength of type integer|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
1216 2
      $stringy->str = '';
1217
1218 2
      return $stringy;
1219
    }
1220
1221 8
    $len = 0;
1222 8
    $end = 0;
1223 8
    $table = array_fill(
1224 8
        0, $strLength + 1,
1225 8
        array_fill(0, $otherLength + 1, 0)
1226 8
    );
1227
1228 8
    for ($i = 1; $i <= $strLength; $i++) {
1229 8
      for ($j = 1; $j <= $otherLength; $j++) {
1230 8
        $strChar = UTF8::substr($stringy->str, $i - 1, 1, $encoding);
1231 8
        $otherChar = UTF8::substr($otherStr, $j - 1, 1, $encoding);
1232
1233 8
        if ($strChar == $otherChar) {
1234 8
          $table[$i][$j] = $table[$i - 1][$j - 1] + 1;
1235 8
          if ($table[$i][$j] > $len) {
1236 8
            $len = $table[$i][$j];
1237 8
            $end = $i;
1238 8
          }
1239 8
        } else {
1240 8
          $table[$i][$j] = 0;
1241
        }
1242 8
      }
1243 8
    }
1244
1245 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...
1246
1247 8
    return $stringy;
1248
  }
1249
1250
  /**
1251
   * Returns whether or not a character exists at an index. Offsets may be
1252
   * negative to count from the last character in the string. Implements
1253
   * part of the ArrayAccess interface.
1254
   *
1255
   * @param mixed $offset <p>The index to check.</p>
1256
   *
1257
   * @return boolean <p>Whether or not the index exists.</p>
1258
   */
1259 6
  public function offsetExists($offset)
1260
  {
1261
    // init
1262 6
    $length = $this->length();
1263 6
    $offset = (int)$offset;
1264
1265 6
    if ($offset >= 0) {
1266 3
      return ($length > $offset);
1267
    }
1268
1269 3
    return ($length >= abs($offset));
1270
  }
1271
1272
  /**
1273
   * Returns the character at the given index. Offsets may be negative to
1274
   * count from the last character in the string. Implements part of the
1275
   * ArrayAccess interface, and throws an OutOfBoundsException if the index
1276
   * does not exist.
1277
   *
1278
   * @param mixed $offset <p>The <strong>index</strong> from which to retrieve the char.</p>
1279
   *
1280
   * @return string <p>The character at the specified index.</p>
1281
   *
1282
   * @throws \OutOfBoundsException <p>If the positive or negative offset does not exist.</p>
1283
   */
1284 2
  public function offsetGet($offset)
1285
  {
1286
    // init
1287 2
    $offset = (int)$offset;
1288 2
    $length = $this->length();
1289
1290
    if (
1291 2
        ($offset >= 0 && $length <= $offset)
1292
        ||
1293 1
        $length < abs($offset)
1294 2
    ) {
1295 1
      throw new \OutOfBoundsException('No character exists at the index');
1296
    }
1297
1298 1
    return UTF8::substr($this->str, $offset, 1, $this->encoding);
1299
  }
1300
1301
  /**
1302
   * Implements part of the ArrayAccess interface, but throws an exception
1303
   * when called. This maintains the immutability of Stringy objects.
1304
   *
1305
   * @param  mixed $offset <p>The index of the character.</p>
1306
   * @param  mixed $value  <p>Value to set.</p>
1307
   *
1308
   * @throws \Exception <p>When called.</p>
1309
   */
1310 1
  public function offsetSet($offset, $value)
1311
  {
1312
    // Stringy is immutable, cannot directly set char
1313 1
    throw new \Exception('Stringy object is immutable, cannot modify char');
1314
  }
1315
1316
  /**
1317
   * Implements part of the ArrayAccess interface, but throws an exception
1318
   * when called. This maintains the immutability of Stringy objects.
1319
   *
1320
   * @param mixed $offset <p>The index of the character.</p>
1321
   *
1322
   * @throws \Exception <p>When called.</p>
1323
   */
1324 1
  public function offsetUnset($offset)
1325
  {
1326
    // Don't allow directly modifying the string
1327 1
    throw new \Exception('Stringy object is immutable, cannot unset char');
1328
  }
1329
1330
  /**
1331
   * Pads the string to a given length with $padStr. If length is less than
1332
   * or equal to the length of the string, no padding takes places. The
1333
   * default string used for padding is a space, and the default type (one of
1334
   * 'left', 'right', 'both') is 'right'. Throws an InvalidArgumentException
1335
   * if $padType isn't one of those 3 values.
1336
   *
1337
   * @param  int    $length  <p>Desired string length after padding.</p>
1338
   * @param  string $padStr  [optional] <p>String used to pad, defaults to space. Default: ' '</p>
1339
   * @param  string $padType [optional] <p>One of 'left', 'right', 'both'. Default: 'right'</p>
1340
   *
1341
   * @return static <p>Object with a padded $str.</p>
1342
   *
1343
   * @throws \InvalidArgumentException <p>If $padType isn't one of 'right', 'left' or 'both'.</p>
1344
   */
1345 13
  public function pad($length, $padStr = ' ', $padType = 'right')
1346
  {
1347 13
    if (!in_array($padType, array('left', 'right', 'both'), true)) {
1348 1
      throw new \InvalidArgumentException(
1349
          'Pad expects $padType ' . "to be one of 'left', 'right' or 'both'"
1350 1
      );
1351
    }
1352
1353
    switch ($padType) {
1354 12
      case 'left':
1355 3
        return $this->padLeft($length, $padStr);
1356 9
      case 'right':
1357 6
        return $this->padRight($length, $padStr);
1358 3
      default:
1359 3
        return $this->padBoth($length, $padStr);
1360 3
    }
1361
  }
1362
1363
  /**
1364
   * Returns a new string of a given length such that the beginning of the
1365
   * string is padded. Alias for pad() with a $padType of 'left'.
1366
   *
1367
   * @param  int    $length <p>Desired string length after padding.</p>
1368
   * @param  string $padStr [optional] <p>String used to pad, defaults to space. Default: ' '</p>
1369
   *
1370
   * @return static <p>String with left padding.</p>
1371
   */
1372 10
  public function padLeft($length, $padStr = ' ')
1373
  {
1374 10
    return $this->applyPadding($length - $this->length(), 0, $padStr);
1375
  }
1376
1377
  /**
1378
   * Adds the specified amount of left and right padding to the given string.
1379
   * The default character used is a space.
1380
   *
1381
   * @param  int    $left   [optional] <p>Length of left padding. Default: 0</p>
1382
   * @param  int    $right  [optional] <p>Length of right padding. Default: 0</p>
1383
   * @param  string $padStr [optional] <p>String used to pad. Default: ' '</p>
1384
   *
1385
   * @return static <p>String with padding applied.</p>
1386
   */
1387 37
  protected function applyPadding($left = 0, $right = 0, $padStr = ' ')
1388
  {
1389 37
    $stringy = static::create($this->str, $this->encoding);
1390
1391 37
    $length = UTF8::strlen($padStr, $stringy->encoding);
1392
1393 37
    $strLength = $stringy->length();
1394 37
    $paddedLength = $strLength + $left + $right;
1395
1396 37
    if (!$length || $paddedLength <= $strLength) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $length of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. 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 integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1397 3
      return $stringy;
1398
    }
1399
1400 34
    $leftPadding = UTF8::substr(
1401 34
        UTF8::str_repeat(
1402 34
            $padStr,
1403 34
            ceil($left / $length)
1404 34
        ),
1405 34
        0,
1406 34
        $left,
1407 34
        $stringy->encoding
1408 34
    );
1409
1410 34
    $rightPadding = UTF8::substr(
1411 34
        UTF8::str_repeat(
1412 34
            $padStr,
1413 34
            ceil($right / $length)
1414 34
        ),
1415 34
        0,
1416 34
        $right,
1417 34
        $stringy->encoding
1418 34
    );
1419
1420 34
    $stringy->str = $leftPadding . $stringy->str . $rightPadding;
1421
1422 34
    return $stringy;
1423
  }
1424
1425
  /**
1426
   * Returns a new string of a given length such that the end of the string
1427
   * is padded. Alias for pad() with a $padType of 'right'.
1428
   *
1429
   * @param  int    $length <p>Desired string length after padding.</p>
1430
   * @param  string $padStr [optional] <p>String used to pad, defaults to space. Default: ' '</p>
1431
   *
1432
   * @return static <p>String with right padding.</p>
1433
   */
1434 13
  public function padRight($length, $padStr = ' ')
1435
  {
1436 13
    return $this->applyPadding(0, $length - $this->length(), $padStr);
1437
  }
1438
1439
  /**
1440
   * Returns a new string of a given length such that both sides of the
1441
   * string are padded. Alias for pad() with a $padType of 'both'.
1442
   *
1443
   * @param  int    $length <p>Desired string length after padding.</p>
1444
   * @param  string $padStr [optional] <p>String used to pad, defaults to space. Default: ' '</p>
1445
   *
1446
   * @return static <p>String with padding applied.</p>
1447
   */
1448 14
  public function padBoth($length, $padStr = ' ')
1449
  {
1450 14
    $padding = $length - $this->length();
1451
1452 14
    return $this->applyPadding(floor($padding / 2), ceil($padding / 2), $padStr);
1453
  }
1454
1455
  /**
1456
   * Returns a new string starting with $string.
1457
   *
1458
   * @param string $string <p>The string to append.</p>
1459
   *
1460
   * @return static <p>Object with appended $string.</p>
1461
   */
1462 2
  public function prepend($string)
1463
  {
1464 2
    return static::create($string . $this->str, $this->encoding);
1465
  }
1466
1467
  /**
1468
   * Returns a new string with the prefix $substring removed, if present.
1469
   *
1470
   * @param string $substring <p>The prefix to remove.</p>
1471
   *
1472
   * @return static <p>Object having a $str without the prefix $substring.</p>
1473
   */
1474 12 View Code Duplication
  public function removeLeft($substring)
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...
1475
  {
1476 12
    $stringy = static::create($this->str, $this->encoding);
1477
1478 12
    if ($stringy->startsWith($substring)) {
1479 6
      $substringLength = UTF8::strlen($substring, $stringy->encoding);
1480
1481 6
      return $stringy->substr($substringLength);
1482
    }
1483
1484 6
    return $stringy;
1485
  }
1486
1487
  /**
1488
   * Returns a new string with the suffix $substring removed, if present.
1489
   *
1490
   * @param string $substring <p>The suffix to remove.</p>
1491
   *
1492
   * @return static <p>Object having a $str without the suffix $substring.</p>
1493
   */
1494 12 View Code Duplication
  public function removeRight($substring)
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...
1495
  {
1496 12
    $stringy = static::create($this->str, $this->encoding);
1497
1498 12
    if ($stringy->endsWith($substring)) {
1499 8
      $substringLength = UTF8::strlen($substring, $stringy->encoding);
1500
1501 8
      return $stringy->substr(0, $stringy->length() - $substringLength);
1502
    }
1503
1504 4
    return $stringy;
1505
  }
1506
1507
  /**
1508
   * Returns a repeated string given a multiplier.
1509
   *
1510
   * @param int $multiplier <p>The number of times to repeat the string.</p>
1511
   *
1512
   * @return static <p>Object with a repeated str.</p>
1513
   */
1514 7
  public function repeat($multiplier)
1515
  {
1516 7
    $repeated = UTF8::str_repeat($this->str, $multiplier);
1517
1518 7
    return static::create($repeated, $this->encoding);
1519
  }
1520
1521
  /**
1522
   * Replaces all occurrences of $search in $str by $replacement.
1523
   *
1524
   * @param string $search        <p>The needle to search for.</p>
1525
   * @param string $replacement   <p>The string to replace with.</p>
1526
   * @param bool   $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
1527
   *
1528
   * @return static <p>Object with the resulting $str after the replacements.</p>
1529
   */
1530 29 View Code Duplication
  public function replace($search, $replacement, $caseSensitive = true)
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...
1531
  {
1532 29
    if ($caseSensitive) {
1533 22
      $return = UTF8::str_replace($search, $replacement, $this->str);
1534 22
    } else {
1535 7
      $return = UTF8::str_ireplace($search, $replacement, $this->str);
1536
    }
1537
1538 29
    return static::create($return);
1539
  }
1540
1541
  /**
1542
   * Replaces all occurrences of $search in $str by $replacement.
1543
   *
1544
   * @param array        $search        <p>The elements to search for.</p>
1545
   * @param string|array $replacement   <p>The string to replace with.</p>
1546
   * @param bool         $caseSensitive [optional] <p>Whether or not to enforce case-sensitivity. Default: true</p>
1547
   *
1548
   * @return static <p>Object with the resulting $str after the replacements.</p>
1549
   */
1550 30 View Code Duplication
  public function replaceAll(array $search, $replacement, $caseSensitive = true)
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...
1551
  {
1552 30
    if ($caseSensitive) {
1553 23
      $return = UTF8::str_replace($search, $replacement, $this->str);
1554 23
    } else {
1555 7
      $return = UTF8::str_ireplace($search, $replacement, $this->str);
1556
    }
1557
1558 30
    return static::create($return);
1559
  }
1560
1561
  /**
1562
   * Replaces all occurrences of $search from the beginning of string with $replacement.
1563
   *
1564
   * @param string $search      <p>The string to search for.</p>
1565
   * @param string $replacement <p>The replacement.</p>
1566
   *
1567
   * @return static <p>Object with the resulting $str after the replacements.</p>
1568
   */
1569 16
  public function replaceBeginning($search, $replacement)
1570
  {
1571 16
    $str = $this->regexReplace('^' . preg_quote($search, '/'), UTF8::str_replace('\\', '\\\\', $replacement));
1572
1573 16
    return static::create($str, $this->encoding);
1574
  }
1575
1576
  /**
1577
   * Replaces all occurrences of $search from the ending of string with $replacement.
1578
   *
1579
   * @param string $search      <p>The string to search for.</p>
1580
   * @param string $replacement <p>The replacement.</p>
1581
   *
1582
   * @return static <p>Object with the resulting $str after the replacements.</p>
1583
   */
1584 16
  public function replaceEnding($search, $replacement)
1585
  {
1586 16
    $str = $this->regexReplace(preg_quote($search, '/') . '$', UTF8::str_replace('\\', '\\\\', $replacement));
1587
1588 16
    return static::create($str, $this->encoding);
1589
  }
1590
1591
  /**
1592
   * Gets the substring after (or before via "$beforeNeedle") the first occurrence of the "$needle".
1593
   * If no match is found returns new empty Stringy object.
1594
   *
1595
   * @param string $needle       <p>The string to look for.</p>
1596
   * @param bool   $beforeNeedle [optional] <p>Default: false</p>
1597
   *
1598
   * @return static
1599
   */
1600 2 View Code Duplication
  public function substringOf($needle, $beforeNeedle = false)
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...
1601
  {
1602 2
    if ('' === $needle) {
1603
      return static::create('');
1604
    }
1605
1606 2
    if (false === $part = UTF8::strstr($this->str, $needle, $beforeNeedle, $this->encoding)) {
1607 2
      return static::create('');
1608
    }
1609
1610 2
    return static::create($part);
1611
  }
1612
1613
  /**
1614
   * Gets the substring after (or before via "$beforeNeedle") the first occurrence of the "$needle".
1615
   * If no match is found returns new empty Stringy object.
1616
   *
1617
   * @param string $needle       <p>The string to look for.</p>
1618
   * @param bool   $beforeNeedle [optional] <p>Default: false</p>
1619
   *
1620
   * @return static
1621
   */
1622 2 View Code Duplication
  public function substringOfIgnoreCase($needle, $beforeNeedle = false)
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...
1623
  {
1624 2
    if ('' === $needle) {
1625
      return static::create('');
1626
    }
1627
1628 2
    if (false === $part = UTF8::stristr($this->str, $needle, $beforeNeedle, $this->encoding)) {
1629 2
      return static::create('');
1630
    }
1631
1632 2
    return static::create($part);
1633
  }
1634
1635
  /**
1636
   * Gets the substring after (or before via "$beforeNeedle") the last occurrence of the "$needle".
1637
   * If no match is found returns new empty Stringy object.
1638
   *
1639
   * @param string $needle       <p>The string to look for.</p>
1640
   * @param bool   $beforeNeedle [optional] <p>Default: false</p>
1641
   *
1642
   * @return static
1643
   */
1644 2 View Code Duplication
  public function lastSubstringOf($needle, $beforeNeedle = false)
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...
1645
  {
1646 2
    if ('' === $needle) {
1647
      return static::create('');
1648
    }
1649
1650 2
    if (false === $part = UTF8::strrchr($this->str, $needle, $beforeNeedle, $this->encoding)) {
1651 2
      return static::create('');
1652
    }
1653
1654 2
    return static::create($part);
1655
  }
1656
1657
  /**
1658
   * Gets the substring after (or before via "$beforeNeedle") the last occurrence of the "$needle".
1659
   * If no match is found returns new empty Stringy object.
1660
   *
1661
   * @param string $needle       <p>The string to look for.</p>
1662
   * @param bool   $beforeNeedle [optional] <p>Default: false</p>
1663
   *
1664
   * @return static
1665
   */
1666 1 View Code Duplication
  public function lastSubstringOfIgnoreCase($needle, $beforeNeedle = false)
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...
1667
  {
1668 1
    if ('' === $needle) {
1669
      return static::create('');
1670
    }
1671
1672 1
    if (false === $part = UTF8::strrichr($this->str, $needle, $beforeNeedle, $this->encoding)) {
1673 1
      return static::create('');
1674
    }
1675
1676 1
    return static::create($part);
1677
  }
1678
1679
  /**
1680
   * Returns a reversed string. A multibyte version of strrev().
1681
   *
1682
   * @return static <p>Object with a reversed $str.</p>
1683
   */
1684 5
  public function reverse()
1685
  {
1686 5
    $reversed = UTF8::strrev($this->str);
1687
1688 5
    return static::create($reversed, $this->encoding);
1689
  }
1690
1691
  /**
1692
   * Truncates the string to a given length, while ensuring that it does not
1693
   * split words. If $substring is provided, and truncating occurs, the
1694
   * string is further truncated so that the substring may be appended without
1695
   * exceeding the desired length.
1696
   *
1697
   * @param  int    $length    <p>Desired length of the truncated string.</p>
1698
   * @param  string $substring [optional] <p>The substring to append if it can fit. Default: ''</p>
1699
   *
1700
   * @return static <p>Object with the resulting $str after truncating.</p>
1701
   */
1702 23
  public function safeTruncate($length, $substring = '')
1703
  {
1704 23
    $stringy = static::create($this->str, $this->encoding);
1705 23
    if ($length >= $stringy->length()) {
1706 4
      return $stringy;
1707
    }
1708
1709
    // need to further trim the string so we can append the substring
1710 19
    $encoding = $stringy->encoding;
1711 19
    $substringLength = UTF8::strlen($substring, $encoding);
1712 19
    $length -= $substringLength;
1713
1714 19
    $truncated = UTF8::substr($stringy->str, 0, $length, $encoding);
1715
1716
    // if the last word was truncated
1717 19
    $strPosSpace = UTF8::strpos($stringy->str, ' ', $length - 1, $encoding);
1718 19
    if ($strPosSpace != $length) {
1719
      // find pos of the last occurrence of a space, get up to that
1720 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 1714 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...
1721
1722 12
      if ($lastPos !== false || $strPosSpace !== false) {
1723 11
        $truncated = UTF8::substr($truncated, 0, $lastPos, $encoding);
0 ignored issues
show
Security Bug introduced by
It seems like $truncated defined by \voku\helper\UTF8::subst...0, $lastPos, $encoding) on line 1723 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...
Bug introduced by
It seems like $lastPos defined by \voku\helper\UTF8::strrp...ted, ' ', 0, $encoding) on line 1720 can also be of type double or false; however, voku\helper\UTF8::substr() does only seem to accept integer|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

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

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1863
1864 1
      if ($pos) {
1865 1
        return static::create(
1866 1
            rtrim(
1867 1
                UTF8::substr($text, 0, $pos, $this->encoding),
1868
                $trimChars
1869 1
            ) . $replacerForSkippedText,
1870 1
            $this->encoding
1871 1
        );
1872
      }
1873
1874
      return static::create($text, $this->encoding);
1875
    }
1876
1877 1
    $wordPos = UTF8::stripos(
1878 1
        $text,
1879 1
        $search,
1880 1
        $this->encoding
1881 1
    );
1882 1
    $halfSide = (int)($wordPos - $length / 2 + UTF8::strlen($search, $this->encoding) / 2);
1883
1884 1
    if ($halfSide > 0) {
1885
1886 1
      $halfText = UTF8::substr($text, 0, $halfSide, $this->encoding);
1887 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 1886 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...
1888
1889 1
      if (!$pos_start) {
1890 1
        $pos_start = 0;
1891 1
      }
1892
1893 1
    } else {
1894 1
      $pos_start = 0;
1895
    }
1896
1897 1
    if ($wordPos && $halfSide > 0) {
1898 1
      $l = $pos_start + $length - 1;
1899 1
      $realLength = UTF8::strlen($text, $this->encoding);
1900
1901 1
      if ($l > $realLength) {
1902
        $l = $realLength;
1903
      }
1904
1905 1
      $pos_end = min(
1906 1
                     UTF8::strpos($text, ' ', $l, $this->encoding),
1907 1
                     UTF8::strpos($text, '.', $l, $this->encoding)
1908 1
                 ) - $pos_start;
1909
1910 1
      if (!$pos_end || $pos_end <= 0) {
1911 1
        $extract = $replacerForSkippedText . ltrim(
1912 1
                UTF8::substr(
1913 1
                    $text,
1914 1
                    $pos_start,
1915 1
                    UTF8::strlen($text),
1916 1
                    $this->encoding
1917 1
                ),
1918
                $trimChars
1919 1
            );
1920 1 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...
1921 1
        $extract = $replacerForSkippedText . trim(
1922 1
                UTF8::substr(
1923 1
                    $text,
1924 1
                    $pos_start,
1925 1
                    $pos_end,
1926 1
                    $this->encoding
1927 1
                ),
1928
                $trimChars
1929 1
            ) . $replacerForSkippedText;
1930
      }
1931
1932 1
    } else {
1933
1934 1
      $l = $length - 1;
1935 1
      $trueLength = UTF8::strlen($text, $this->encoding);
1936
1937 1
      if ($l > $trueLength) {
1938
        $l = $trueLength;
1939
      }
1940
1941 1
      $pos_end = min(
1942 1
          UTF8::strpos($text, ' ', $l, $this->encoding),
1943 1
          UTF8::strpos($text, '.', $l, $this->encoding)
1944 1
      );
1945
1946 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...
1947 1
        $extract = rtrim(
1948 1
                       UTF8::substr($text, 0, $pos_end, $this->encoding),
1949
                       $trimChars
1950 1
                   ) . $replacerForSkippedText;
1951 1
      } else {
1952 1
        $extract = $text;
1953
      }
1954
    }
1955
1956 1
    return static::create($extract, $this->encoding);
1957
  }
1958
1959
1960
  /**
1961
   * Try to remove all XSS-attacks from the string.
1962
   *
1963
   * @return static
1964
   */
1965 6
  public function removeXss()
1966
  {
1967 6
    static $antiXss = null;
1968
1969 6
    if ($antiXss === null) {
1970 1
      $antiXss = new AntiXSS();
1971 1
    }
1972
1973 6
    $str = $antiXss->xss_clean($this->str);
1974
1975 6
    return static::create($str, $this->encoding);
1976
  }
1977
1978
  /**
1979
   * Remove all breaks [<br> | \r\n | \r | \n | ...] from the string.
1980
   *
1981
   * @param string $replacement [optional] <p>Default is a empty string.</p>
1982
   *
1983
   * @return static
1984
   */
1985 6
  public function removeHtmlBreak($replacement = '')
1986
  {
1987 6
    $str = preg_replace('#/\r\n|\r|\n|<br.*/?>#isU', $replacement, $this->str);
1988
1989 6
    return static::create($str, $this->encoding);
1990
  }
1991
1992
  /**
1993
   * Remove html via "strip_tags()" from the string.
1994
   *
1995
   * @param $allowableTags [optional] <p>You can use the optional second parameter to specify tags which should
1996
   *                       not be stripped. Default: null
1997
   *                       </p>
1998
   *
1999
   * @return static
2000
   */
2001 6
  public function removeHtml($allowableTags = null)
2002
  {
2003 6
    $str = strip_tags($this->str, $allowableTags);
2004
2005 6
    return static::create($str, $this->encoding);
2006
  }
2007
2008
  /**
2009
   * Returns the substring beginning at $start, and up to, but not including
2010
   * the index specified by $end. If $end is omitted, the function extracts
2011
   * the remaining string. If $end is negative, it is computed from the end
2012
   * of the string.
2013
   *
2014
   * @param  int $start <p>Initial index from which to begin extraction.</p>
2015
   * @param  int $end   [optional] <p>Index at which to end extraction. Default: null</p>
2016
   *
2017
   * @return static <p>Object with its $str being the extracted substring.</p>
2018
   */
2019 18
  public function slice($start, $end = null)
2020
  {
2021 18
    if ($end === null) {
2022 4
      $length = $this->length();
2023 18
    } elseif ($end >= 0 && $end <= $start) {
2024 4
      return static::create('', $this->encoding);
2025 10
    } elseif ($end < 0) {
2026 2
      $length = $this->length() + $end - $start;
2027 2
    } else {
2028 8
      $length = $end - $start;
2029
    }
2030
2031 14
    $str = UTF8::substr($this->str, $start, $length, $this->encoding);
2032
2033 14
    return static::create($str, $this->encoding);
2034
  }
2035
2036
  /**
2037
   * Splits the string with the provided regular expression, returning an
2038
   * array of Stringy objects. An optional integer $limit will truncate the
2039
   * results.
2040
   *
2041
   * @param  string $pattern <p>The regex with which to split the string.</p>
2042
   * @param  int    $limit   [optional] <p>Maximum number of results to return. Default: -1 === no limit</p>
2043
   *
2044
   * @return static[] <p>An array of Stringy objects.</p>
2045
   */
2046 19
  public function split($pattern, $limit = -1)
2047
  {
2048 19
    if ($limit === 0) {
2049 2
      return array();
2050
    }
2051
2052
    // this->split errors when supplied an empty pattern in < PHP 5.4.13
2053
    // and current versions of HHVM (3.8 and below)
2054 17
    if ($pattern === '') {
2055 1
      return array(static::create($this->str, $this->encoding));
2056
    }
2057
2058
    // this->split returns the remaining unsplit string in the last index when
2059
    // supplying a limit
2060 16
    if ($limit > 0) {
2061 8
      $limit += 1;
2062 8
    } else {
2063 8
      $limit = -1;
2064
    }
2065
2066 16
    $array = preg_split('/' . preg_quote($pattern, '/') . '/u', $this->str, $limit);
2067
2068 16
    if ($limit > 0 && count($array) === $limit) {
2069 4
      array_pop($array);
2070 4
    }
2071
2072
    /** @noinspection CallableInLoopTerminationConditionInspection */
2073
    /** @noinspection ForeachInvariantsInspection */
2074 16 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...
2075 16
      $array[$i] = static::create($array[$i], $this->encoding);
2076 16
    }
2077
2078 16
    return $array;
2079
  }
2080
2081
  /**
2082
   * Surrounds $str with the given substring.
2083
   *
2084
   * @param string $substring <p>The substring to add to both sides.</P>
2085
   *
2086
   * @return static <p>Object whose $str had the substring both prepended and appended.</p>
2087
   */
2088 5
  public function surround($substring)
2089
  {
2090 5
    $str = implode('', array($substring, $this->str, $substring));
2091
2092 5
    return static::create($str, $this->encoding);
2093
  }
2094
2095
  /**
2096
   * Returns a case swapped version of the string.
2097
   *
2098
   * @return static <p>Object whose $str has each character's case swapped.</P>
2099
   */
2100 5
  public function swapCase()
2101
  {
2102 5
    $stringy = static::create($this->str, $this->encoding);
2103
2104 5
    $stringy->str = UTF8::swapCase($stringy->str, $stringy->encoding);
2105
2106 5
    return $stringy;
2107
  }
2108
2109
  /**
2110
   * Returns a string with smart quotes, ellipsis characters, and dashes from
2111
   * Windows-1252 (commonly used in Word documents) replaced by their ASCII
2112
   * equivalents.
2113
   *
2114
   * @return static <p>Object whose $str has those characters removed.</p>
2115
   */
2116 4
  public function tidy()
2117
  {
2118 4
    $str = UTF8::normalize_msword($this->str);
2119
2120 4
    return static::create($str, $this->encoding);
2121
  }
2122
2123
  /**
2124
   * Returns a trimmed string with the first letter of each word capitalized.
2125
   * Also accepts an array, $ignore, allowing you to list words not to be
2126
   * capitalized.
2127
   *
2128
   * @param array|null $ignore [optional] <p>An array of words not to capitalize or null. Default: null</p>
2129
   *
2130
   * @return static <p>Object with a titleized $str.</p>
2131
   */
2132 5
  public function titleize($ignore = null)
2133
  {
2134 5
    $stringy = static::create($this->trim(), $this->encoding);
2135 5
    $encoding = $this->encoding;
2136
2137 5
    $stringy->str = preg_replace_callback(
2138 5
        '/([\S]+)/u',
2139
        function ($match) use ($encoding, $ignore) {
2140 5
          if ($ignore && in_array($match[0], $ignore, true)) {
2141 2
            return $match[0];
2142
          }
2143
2144 5
          $stringy = new Stringy($match[0], $encoding);
2145
2146 5
          return (string)$stringy->toLowerCase()->upperCaseFirst();
2147 5
        },
2148 5
        $stringy->str
2149 5
    );
2150
2151 5
    return $stringy;
2152
  }
2153
2154
  /**
2155
   * Converts all characters in the string to lowercase.
2156
   *
2157
   * @return static <p>Object with all characters of $str being lowercase.</p>
2158
   */
2159 27
  public function toLowerCase()
2160
  {
2161 27
    $str = UTF8::strtolower($this->str, $this->encoding);
2162
2163 27
    return static::create($str, $this->encoding);
2164
  }
2165
2166
  /**
2167
   * Returns true if the string is base64 encoded, false otherwise.
2168
   *
2169
   * @return bool <p>Whether or not $str is base64 encoded.</p>
2170
   */
2171 7
  public function isBase64()
2172
  {
2173 7
    return UTF8::is_base64($this->str);
2174
  }
2175
2176
  /**
2177
   * Returns an ASCII version of the string. A set of non-ASCII characters are
2178
   * replaced with their closest ASCII counterparts, and the rest are removed
2179
   * unless instructed otherwise.
2180
   *
2181
   * @param $strict [optional] <p>Use "transliterator_transliterate()" from PHP-Intl | WARNING: bad performance |
2182
   *                Default: false</p>
2183
   *
2184
   * @return static <p>Object whose $str contains only ASCII characters.</p>
2185
   */
2186 16
  public function toAscii($strict = false)
2187
  {
2188 16
    $str = UTF8::to_ascii($this->str, '?', $strict);
2189
2190 16
    return static::create($str, $this->encoding);
2191
  }
2192
2193
  /**
2194
   * Returns a boolean representation of the given logical string value.
2195
   * For example, 'true', '1', 'on' and 'yes' will return true. 'false', '0',
2196
   * 'off', and 'no' will return false. In all instances, case is ignored.
2197
   * For other numeric strings, their sign will determine the return value.
2198
   * In addition, blank strings consisting of only whitespace will return
2199
   * false. For all other strings, the return value is a result of a
2200
   * boolean cast.
2201
   *
2202
   * @return bool A boolean value for the string
2203
   */
2204 15
  public function toBoolean()
2205
  {
2206 15
    $key = $this->toLowerCase()->str;
2207
    $map = array(
2208 15
        'true'  => true,
2209 15
        '1'     => true,
2210 15
        'on'    => true,
2211 15
        'yes'   => true,
2212 15
        'false' => false,
2213 15
        '0'     => false,
2214 15
        'off'   => false,
2215 15
        'no'    => false,
2216 15
    );
2217
2218 15
    if (array_key_exists($key, $map)) {
2219 10
      return $map[$key];
2220
    }
2221
2222 5
    if (is_numeric($this->str)) {
2223 2
      return ((int)$this->str > 0);
2224
    }
2225
2226 3
    return (bool)$this->regexReplace('[[:space:]]', '')->str;
2227
  }
2228
2229
  /**
2230
   * Return Stringy object as string, but you can also use (string) for automatically casting the object into a string.
2231
   *
2232
   * @return string
2233
   */
2234 1003
  public function toString()
2235
  {
2236 1003
    return (string)$this->str;
2237
  }
2238
2239
  /**
2240
   * Converts each tab in the string to some number of spaces, as defined by
2241
   * $tabLength. By default, each tab is converted to 4 consecutive spaces.
2242
   *
2243
   * @param int $tabLength [optional] <p>Number of spaces to replace each tab with. Default: 4</p>
2244
   *
2245
   * @return static <p>Object whose $str has had tabs switched to spaces.</p>
2246
   */
2247 6 View Code Duplication
  public function toSpaces($tabLength = 4)
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...
2248
  {
2249 6
    $spaces = UTF8::str_repeat(' ', $tabLength);
2250 6
    $str = UTF8::str_replace("\t", $spaces, $this->str);
2251
2252 6
    return static::create($str, $this->encoding);
2253
  }
2254
2255
  /**
2256
   * Converts each occurrence of some consecutive number of spaces, as
2257
   * defined by $tabLength, to a tab. By default, each 4 consecutive spaces
2258
   * are converted to a tab.
2259
   *
2260
   * @param int $tabLength [optional] <p>Number of spaces to replace with a tab. Default: 4</p>
2261
   *
2262
   * @return static <p>Object whose $str has had spaces switched to tabs.</p>
2263
   */
2264 5 View Code Duplication
  public function toTabs($tabLength = 4)
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...
2265
  {
2266 5
    $spaces = UTF8::str_repeat(' ', $tabLength);
2267 5
    $str = UTF8::str_replace($spaces, "\t", $this->str);
2268
2269 5
    return static::create($str, $this->encoding);
2270
  }
2271
2272
  /**
2273
   * Converts the first character of each word in the string to uppercase.
2274
   *
2275
   * @return static  Object with all characters of $str being title-cased
2276
   */
2277 5
  public function toTitleCase()
2278
  {
2279
    // "mb_convert_case()" used a polyfill from the "UTF8"-Class
2280 5
    $str = \mb_convert_case($this->str, MB_CASE_TITLE, $this->encoding);
2281
2282 5
    return static::create($str, $this->encoding);
2283
  }
2284
2285
  /**
2286
   * Converts all characters in the string to uppercase.
2287
   *
2288
   * @return static  Object with all characters of $str being uppercase
2289
   */
2290 5
  public function toUpperCase()
2291
  {
2292 5
    $str = UTF8::strtoupper($this->str, $this->encoding);
2293
2294 5
    return static::create($str, $this->encoding);
2295
  }
2296
2297
  /**
2298
   * Returns a string with whitespace removed from the start of the string.
2299
   * Supports the removal of unicode whitespace. Accepts an optional
2300
   * string of characters to strip instead of the defaults.
2301
   *
2302
   * @param string $chars [optional] <p>Optional string of characters to strip. Default: null</p>
2303
   *
2304
   * @return static <p>Object with a trimmed $str.</p>
2305
   */
2306 13 View Code Duplication
  public function trimLeft($chars = null)
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...
2307
  {
2308 13
    if (!$chars) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $chars of type string|null 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...
2309 11
      $chars = '[:space:]';
2310 11
    } else {
2311 2
      $chars = preg_quote($chars, '/');
2312
    }
2313
2314 13
    return $this->regexReplace("^[$chars]+", '');
2315
  }
2316
2317
  /**
2318
   * Returns a string with whitespace removed from the end of the string.
2319
   * Supports the removal of unicode whitespace. Accepts an optional
2320
   * string of characters to strip instead of the defaults.
2321
   *
2322
   * @param string $chars [optional] <p>Optional string of characters to strip. Default: null</p>
2323
   *
2324
   * @return static <p>Object with a trimmed $str.</p>
2325
   */
2326 13 View Code Duplication
  public function trimRight($chars = null)
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...
2327
  {
2328 13
    if (!$chars) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $chars of type string|null 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...
2329 11
      $chars = '[:space:]';
2330 11
    } else {
2331 2
      $chars = preg_quote($chars, '/');
2332
    }
2333
2334 13
    return $this->regexReplace("[$chars]+\$", '');
2335
  }
2336
2337
  /**
2338
   * Truncates the string to a given length. If $substring is provided, and
2339
   * truncating occurs, the string is further truncated so that the substring
2340
   * may be appended without exceeding the desired length.
2341
   *
2342
   * @param int    $length    <p>Desired length of the truncated string.</p>
2343
   * @param string $substring [optional] <p>The substring to append if it can fit. Default: ''</p>
2344
   *
2345
   * @return static <p>Object with the resulting $str after truncating.</p>
2346
   */
2347 22 View Code Duplication
  public function truncate($length, $substring = '')
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...
2348
  {
2349 22
    $stringy = static::create($this->str, $this->encoding);
2350 22
    if ($length >= $stringy->length()) {
2351 4
      return $stringy;
2352
    }
2353
2354
    // Need to further trim the string so we can append the substring
2355 18
    $substringLength = UTF8::strlen($substring, $stringy->encoding);
2356 18
    $length -= $substringLength;
2357
2358 18
    $truncated = UTF8::substr($stringy->str, 0, $length, $stringy->encoding);
2359 18
    $stringy->str = $truncated . $substring;
2360
2361 18
    return $stringy;
2362
  }
2363
2364
  /**
2365
   * Returns a lowercase and trimmed string separated by underscores.
2366
   * Underscores are inserted before uppercase characters (with the exception
2367
   * of the first character of the string), and in place of spaces as well as
2368
   * dashes.
2369
   *
2370
   * @return static  Object with an underscored $str
2371
   */
2372 16
  public function underscored()
2373
  {
2374 16
    return $this->delimit('_');
2375
  }
2376
2377
  /**
2378
   * Returns an UpperCamelCase version of the supplied string. It trims
2379
   * surrounding spaces, capitalizes letters following digits, spaces, dashes
2380
   * and underscores, and removes spaces, dashes, underscores.
2381
   *
2382
   * @return static  Object with $str in UpperCamelCase
2383
   */
2384 13
  public function upperCamelize()
2385
  {
2386 13
    return $this->camelize()->upperCaseFirst();
2387
  }
2388
2389
  /**
2390
   * Returns a camelCase version of the string. Trims surrounding spaces,
2391
   * capitalizes letters following digits, spaces, dashes and underscores,
2392
   * and removes spaces, dashes, as well as underscores.
2393
   *
2394
   * @return static  Object with $str in camelCase
2395
   */
2396 32
  public function camelize()
2397
  {
2398 32
    $encoding = $this->encoding;
2399 32
    $stringy = $this->trim()->lowerCaseFirst();
2400 32
    $stringy->str = preg_replace('/^[-_]+/', '', $stringy->str);
2401
2402 32
    $stringy->str = preg_replace_callback(
2403 32
        '/[-_\s]+(.)?/u',
2404
        function ($match) use ($encoding) {
2405 27
          if (isset($match[1])) {
2406 27
            return UTF8::strtoupper($match[1], $encoding);
2407
          }
2408
2409 1
          return '';
2410 32
        },
2411 32
        $stringy->str
2412 32
    );
2413
2414 32
    $stringy->str = preg_replace_callback(
2415 32
        '/[\d]+(.)?/u',
2416
        function ($match) use ($encoding) {
2417 6
          return UTF8::strtoupper($match[0], $encoding);
2418 32
        },
2419 32
        $stringy->str
2420 32
    );
2421
2422 32
    return $stringy;
2423
  }
2424
2425
  /**
2426
   * Convert a string to e.g.: "snake_case"
2427
   *
2428
   * @return static  Object with $str in snake_case
2429
   */
2430 20
  public function snakeize()
2431
  {
2432 20
    $str = $this->str;
2433
2434 20
    $encoding = $this->encoding;
2435 20
    $str = UTF8::normalize_whitespace($str);
2436 20
    $str = str_replace('-', '_', $str);
2437
2438 20
    $str = preg_replace_callback(
2439 20
        '/([\d|A-Z])/u',
2440 20
        function ($matches) use ($encoding) {
2441 8
          $match = $matches[1];
2442 8
          $matchInt = (int)$match;
2443
2444 8
          if ("$matchInt" == $match) {
2445 4
            return '_' . $match . '_';
2446
          }
2447
2448 4
          return '_' . UTF8::strtolower($match, $encoding);
2449 20
        },
2450
        $str
2451 20
    );
2452
2453 20
    $str = preg_replace(
2454
        array(
2455
2456 20
            '/\s+/',      // convert spaces to "_"
2457 20
            '/^\s+|\s+$/',  // trim leading & trailing spaces
2458 20
            '/_+/',         // remove double "_"
2459 20
        ),
2460
        array(
2461 20
            '_',
2462 20
            '',
2463 20
            '_',
2464 20
        ),
2465
        $str
2466 20
    );
2467
2468 20
    $str = UTF8::trim($str, '_'); // trim leading & trailing "_"
2469 20
    $str = UTF8::trim($str); // trim leading & trailing whitespace
2470
2471 20
    return static::create($str, $this->encoding);
2472
  }
2473
2474
  /**
2475
   * Converts the first character of the string to lower case.
2476
   *
2477
   * @return static  Object with the first character of $str being lower case
2478
   */
2479 37 View Code Duplication
  public function lowerCaseFirst()
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...
2480
  {
2481 37
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
2482 37
    $rest = UTF8::substr($this->str, 1, $this->length() - 1, $this->encoding);
2483
2484 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 2481 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...
2485
2486 37
    return static::create($str, $this->encoding);
2487
  }
2488
2489
  /**
2490
   * Shorten the string after $length, but also after the next word.
2491
   *
2492
   * @param int    $length
2493
   * @param string $strAddOn [optional] <p>Default: '...'</p>
2494
   *
2495
   * @return static
2496
   */
2497 4
  public function shortenAfterWord($length, $strAddOn = '...')
2498
  {
2499 4
    $string = UTF8::str_limit_after_word($this->str, $length, $strAddOn);
2500
2501 4
    return static::create($string);
2502
  }
2503
2504
  /**
2505
   * Line-Wrap the string after $limit, but also after the next word.
2506
   *
2507
   * @param int $limit
2508
   *
2509
   * @return static
2510
   */
2511 1
  public function lineWrapAfterWord($limit)
2512
  {
2513 1
    $strings = preg_split('/\\r\\n|\\r|\\n/', $this->str);
2514
2515 1
    $string = '';
2516 1
    foreach ($strings as $value) {
2517 1
      $string .= wordwrap($value, $limit);
2518 1
      $string .= "\n";
2519 1
    }
2520
2521 1
    return static::create($string);
2522
  }
2523
2524
  /**
2525
   * Gets the substring after the first occurrence of a separator.
2526
   * If no match is found returns new empty Stringy object.
2527
   *
2528
   * @param string $separator
2529
   *
2530
   * @return static
2531
   */
2532 1 View Code Duplication
  public function afterFirst($separator)
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...
2533
  {
2534 1
    if ($separator === '') {
2535
      return static::create('');
2536
    }
2537
2538 1
    if ($this->str === '') {
2539 1
      return static::create('');
2540
    }
2541
2542 1
    if (($offset = $this->indexOf($separator)) === false) {
2543 1
      return static::create('');
2544
    }
2545
2546 1
    return static::create(
2547 1
        UTF8::substr(
2548 1
            $this->str,
2549 1
            $offset + UTF8::strlen($separator, $this->encoding),
2550 1
            null,
2551 1
            $this->encoding
2552 1
        ),
2553 1
        $this->encoding
2554 1
    );
2555
  }
2556
2557
  /**
2558
   * Gets the substring after the first occurrence of a separator.
2559
   * If no match is found returns new empty Stringy object.
2560
   *
2561
   * @param string $separator
2562
   *
2563
   * @return static
2564
   */
2565 1 View Code Duplication
  public function afterFirstIgnoreCase($separator)
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...
2566
  {
2567 1
    if ($separator === '') {
2568
      return static::create('');
2569
    }
2570
2571 1
    if ($this->str === '') {
2572 1
      return static::create('');
2573
    }
2574
2575 1
    if (($offset = $this->indexOfIgnoreCase($separator)) === false) {
2576 1
      return static::create('');
2577
    }
2578
2579 1
    return static::create(
2580 1
        UTF8::substr(
2581 1
            $this->str,
2582 1
            $offset + UTF8::strlen($separator, $this->encoding),
2583 1
            null,
2584 1
            $this->encoding
2585 1
        ),
2586 1
        $this->encoding
2587 1
    );
2588
  }
2589
2590
  /**
2591
   * Gets the substring after the last occurrence of a separator.
2592
   * If no match is found returns new empty Stringy object.
2593
   *
2594
   * @param string $separator
2595
   *
2596
   * @return static
2597
   */
2598 1 View Code Duplication
  public function afterLastIgnoreCase($separator)
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...
2599
  {
2600 1
    if ($separator === '') {
2601
      return static::create('');
2602
    }
2603
2604 1
    if ($this->str === '') {
2605 1
      return static::create('');
2606
    }
2607
2608 1
    $offset = $this->indexOfLastIgnoreCase($separator);
2609 1
    if ($offset === false) {
2610 1
      return static::create('', $this->encoding);
2611
    }
2612
2613 1
    return static::create(
2614 1
        UTF8::substr(
2615 1
            $this->str,
2616 1
            $offset + UTF8::strlen($separator, $this->encoding),
2617 1
            null,
2618 1
            $this->encoding
2619 1
        ),
2620 1
        $this->encoding
2621 1
    );
2622
  }
2623
2624
  /**
2625
   * Gets the substring after the last occurrence of a separator.
2626
   * If no match is found returns new empty Stringy object.
2627
   *
2628
   * @param string $separator
2629
   *
2630
   * @return static
2631
   */
2632 1 View Code Duplication
  public function afterLast($separator)
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...
2633
  {
2634 1
    if ($separator === '') {
2635
      return static::create('');
2636
    }
2637
2638 1
    if ($this->str === '') {
2639 1
      return static::create('');
2640
    }
2641
2642 1
    $offset = $this->indexOfLast($separator);
2643 1
    if ($offset === false) {
2644 1
      return static::create('', $this->encoding);
2645
    }
2646
2647 1
    return static::create(
2648 1
        UTF8::substr(
2649 1
            $this->str,
2650 1
            $offset + UTF8::strlen($separator, $this->encoding),
2651 1
            null,
2652 1
            $this->encoding
2653 1
        ),
2654 1
        $this->encoding
2655 1
    );
2656
  }
2657
2658
  /**
2659
   * Gets the substring before the first occurrence of a separator.
2660
   * If no match is found returns new empty Stringy object.
2661
   *
2662
   * @param string $separator
2663
   *
2664
   * @return static
2665
   */
2666 1 View Code Duplication
  public function beforeFirst($separator)
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...
2667
  {
2668 1
    if ($separator === '') {
2669
      return static::create('');
2670
    }
2671
2672 1
    if ($this->str === '') {
2673 1
      return static::create('');
2674
    }
2675
2676 1
    $offset = $this->indexOf($separator);
2677 1
    if ($offset === false) {
2678 1
      return static::create('', $this->encoding);
2679
    }
2680
2681 1
    return static::create(
2682 1
        UTF8::substr(
2683 1
            $this->str,
2684 1
            0,
2685 1
            $offset,
2686 1
            $this->encoding
2687 1
        ),
2688 1
        $this->encoding
2689 1
    );
2690
  }
2691
2692
  /**
2693
   * Gets the substring before the first occurrence of a separator.
2694
   * If no match is found returns new empty Stringy object.
2695
   *
2696
   * @param string $separator
2697
   *
2698
   * @return static
2699
   */
2700 1 View Code Duplication
  public function beforeFirstIgnoreCase($separator)
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...
2701
  {
2702 1
    if ($separator === '') {
2703
      return static::create('');
2704
    }
2705
2706 1
    if ($this->str === '') {
2707 1
      return static::create('');
2708
    }
2709
2710 1
    $offset = $this->indexOfIgnoreCase($separator);
2711 1
    if ($offset === false) {
2712 1
      return static::create('', $this->encoding);
2713
    }
2714
2715 1
    return static::create(
2716 1
        UTF8::substr(
2717 1
            $this->str,
2718 1
            0,
2719 1
            $offset,
2720 1
            $this->encoding
2721 1
        ),
2722 1
        $this->encoding
2723 1
    );
2724
  }
2725
2726
  /**
2727
   * Gets the substring before the last occurrence of a separator.
2728
   * If no match is found returns new empty Stringy object.
2729
   *
2730
   * @param string $separator
2731
   *
2732
   * @return static
2733
   */
2734 1 View Code Duplication
  public function beforeLast($separator)
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...
2735
  {
2736 1
    if ($separator === '') {
2737
      return static::create('');
2738
    }
2739
2740 1
    if ($this->str === '') {
2741 1
      return static::create('');
2742
    }
2743
2744 1
    $offset = $this->indexOfLast($separator);
2745 1
    if ($offset === false) {
2746 1
      return static::create('', $this->encoding);
2747
    }
2748
2749 1
    return static::create(
2750 1
        UTF8::substr(
2751 1
            $this->str,
2752 1
            0,
2753 1
            $offset,
2754 1
            $this->encoding
2755 1
        ),
2756 1
        $this->encoding
2757 1
    );
2758
  }
2759
2760
  /**
2761
   * Gets the substring before the last occurrence of a separator.
2762
   * If no match is found returns new empty Stringy object.
2763
   *
2764
   * @param string $separator
2765
   *
2766
   * @return static
2767
   */
2768 1 View Code Duplication
  public function beforeLastIgnoreCase($separator)
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...
2769
  {
2770 1
    if ($separator === '') {
2771
      return static::create('');
2772
    }
2773
2774 1
    if ($this->str === '') {
2775 1
      return static::create('');
2776
    }
2777
2778 1
    $offset = $this->indexOfLastIgnoreCase($separator);
2779 1
    if ($offset === false) {
2780 1
      return static::create('', $this->encoding);
2781
    }
2782
2783 1
    return static::create(
2784 1
        UTF8::substr(
2785 1
            $this->str,
2786 1
            0,
2787 1
            $offset,
2788 1
            $this->encoding
2789 1
        ),
2790 1
        $this->encoding
2791 1
    );
2792
  }
2793
2794
  /**
2795
   * Returns the string with the first letter of each word capitalized,
2796
   * except for when the word is a name which shouldn't be capitalized.
2797
   *
2798
   * @return static  Object with $str capitalized
2799
   */
2800 39
  public function capitalizePersonalName()
2801
  {
2802 39
    $stringy = $this->collapseWhitespace();
2803 39
    $stringy->str = $this->capitalizePersonalNameByDelimiter($stringy->str, ' ');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->capitalizePersona...ter($stringy->str, ' ') of type this<Stringy\Stringy> is incompatible with the declared type string of property $str.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
2804 39
    $stringy->str = $this->capitalizePersonalNameByDelimiter($stringy->str, '-');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->capitalizePersona...ter($stringy->str, '-') of type this<Stringy\Stringy> is incompatible with the declared type string of property $str.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
2805
2806 39
    return static::create($stringy, $this->encoding);
2807
  }
2808
2809
  /**
2810
   * @param string $word
2811
   *
2812
   * @return string
2813
   */
2814 7
  protected function capitalizeWord($word)
2815
  {
2816 7
    $encoding = $this->encoding;
2817
2818 7
    $firstCharacter = UTF8::substr($word, 0, 1, $encoding);
2819 7
    $restOfWord = UTF8::substr($word, 1, null, $encoding);
2820 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 2818 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...
2821
2822 7
    return new static($firstCharacterUppercased . $restOfWord, $encoding);
2823
  }
2824
2825
  /**
2826
   * Personal names such as "Marcus Aurelius" are sometimes typed incorrectly using lowercase ("marcus aurelius").
2827
   *
2828
   * @param string $names
2829
   * @param string $delimiter
2830
   *
2831
   * @return string
2832
   */
2833 39
  protected function capitalizePersonalNameByDelimiter($names, $delimiter)
2834
  {
2835
    // init
2836 39
    $names = explode((string)$delimiter, (string)$names);
2837 39
    $encoding = $this->encoding;
2838
2839
    $specialCases = array(
2840
        'names'    => array(
2841 39
            'ab',
2842 39
            'af',
2843 39
            'al',
2844 39
            'and',
2845 39
            'ap',
2846 39
            'bint',
2847 39
            'binte',
2848 39
            'da',
2849 39
            'de',
2850 39
            'del',
2851 39
            'den',
2852 39
            'der',
2853 39
            'di',
2854 39
            'dit',
2855 39
            'ibn',
2856 39
            'la',
2857 39
            'mac',
2858 39
            'nic',
2859 39
            'of',
2860 39
            'ter',
2861 39
            'the',
2862 39
            'und',
2863 39
            'van',
2864 39
            'von',
2865 39
            'y',
2866 39
            'zu',
2867 39
        ),
2868
        'prefixes' => array(
2869 39
            'al-',
2870 39
            "d'",
2871 39
            'ff',
2872 39
            "l'",
2873 39
            'mac',
2874 39
            'mc',
2875 39
            'nic',
2876 39
        ),
2877 39
    );
2878
2879 39
    foreach ($names as &$name) {
2880 39
      if (in_array($name, $specialCases['names'], true)) {
2881 27
        continue;
2882
      }
2883
2884 13
      $continue = false;
2885
2886 13
      if ($delimiter == '-') {
2887 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...
2888 13
          if (UTF8::strpos($name, $beginning, null, $encoding) === 0) {
2889 2
            $continue = true;
2890 2
          }
2891 13
        }
2892 13
      }
2893
2894 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...
2895 13
        if (UTF8::strpos($name, $beginning, null, $encoding) === 0) {
2896 7
          $continue = true;
2897 7
        }
2898 13
      }
2899
2900 13
      if ($continue) {
2901 7
        continue;
2902
      }
2903
2904 7
      $name = $this->capitalizeWord($name);
2905 39
    }
2906
2907 39
    return new static(implode($delimiter, $names), $encoding);
2908
  }
2909
}
2910