Completed
Push — master ( d60692...4c4f45 )
by Lars
03:02
created

Stringy::pad()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 4
nop 3
dl 0
loc 17
ccs 11
cts 11
cp 1
crap 4
rs 9.2
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      Value to modify, after being cast to string
40
   * @param  string $encoding The character encoding
41
   *
42
   * @throws \InvalidArgumentException if an array or object without a
43
   *         __toString method is passed as the first argument
44
   */
45 1060
  public function __construct($str = '', $encoding = null)
46
  {
47 1060
    if (is_array($str)) {
48 1
      throw new \InvalidArgumentException(
49
          'Passed value cannot be an array'
50 1
      );
51 1059
    } elseif (is_object($str) && !method_exists($str, '__toString')) {
52 1
      throw new \InvalidArgumentException(
53
          'Passed object must have a __toString method'
54 1
      );
55
    }
56
57
    // don't throw a notice on PHP 5.3
58 1058
    if (!defined('ENT_SUBSTITUTE')) {
59
      define('ENT_SUBSTITUTE', 8);
60
    }
61
62
    // init
63 1058
    UTF8::checkForSupport();
64
65 1058
    $this->str = (string)$str;
66
67 1058
    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...
68 845
      $this->encoding = $encoding;
69 845
    } else {
70 690
      UTF8::mbstring_loaded();
71 690
      $this->encoding = mb_internal_encoding();
72
    }
73
74 1058
    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...
75 845
      $this->encoding = $encoding;
76 845
    } else {
77 690
      $this->encoding = mb_internal_encoding();
78
    }
79 1058
  }
80
81
  /**
82
   * Returns the value in $str.
83
   *
84
   * @return string The current value of the $str property
85
   */
86 150
  public function __toString()
87
  {
88 150
    return (string)$this->str;
89
  }
90
91
  /**
92
   * Returns a new string with $string appended.
93
   *
94
   * @param  string $string The string to append
95
   *
96
   * @return Stringy Object with appended $string
97
   */
98 5
  public function append($string)
99
  {
100 5
    return static::create($this->str . $string, $this->encoding);
101
  }
102
103
  /**
104
   * Append an password (limited to chars that are good readable).
105
   *
106
   * @param int $length length of the random string
107
   *
108
   * @return Stringy Object with appended password
109
   */
110 1
  public function appendPassword($length)
111
  {
112 1
    $possibleChars = '2346789bcdfghjkmnpqrtvwxyzBCDFGHJKLMNPQRTVWXYZ';
113
114 1
    return $this->appendRandomString($length, $possibleChars);
115
  }
116
117
  /**
118
   * Append an unique identifier.
119
   *
120
   * @param string|int $extraPrefix
121
   *
122
   * @return Stringy Object with appended unique identifier as md5-hash
123
   */
124 1
  public function appendUniqueIdentifier($extraPrefix = '')
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
    $prefix = 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
              $extraPrefix;
131
132 1
    return $this->append(md5(uniqid($prefix, true) . $prefix));
133
  }
134
135
  /**
136
   * Append an random string.
137
   *
138
   * @param int    $length        length of the random string
139
   * @param string $possibleChars characters string for the random selection
140
   *
141
   * @return Stringy Object with appended random string
142
   */
143 2
  public function appendRandomString($length, $possibleChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')
144
  {
145
    // init
146 2
    $i = 0;
147 2
    $length = (int)$length;
148 2
    $str = $this->str;
149 2
    $maxlength = UTF8::strlen($possibleChars, $this->encoding);
150
151 2
    if ($maxlength === 0) {
152 1
      return $this;
153
    }
154
155
    // add random chars
156 2
    while ($i < $length) {
157 2
      $char = UTF8::substr($possibleChars, mt_rand(0, $maxlength - 1), 1, $this->encoding);
158 2
      $str .= $char;
159 2
      $i++;
160 2
    }
161
162 2
    return $this->append($str);
163
  }
164
165
  /**
166
   * Creates a Stringy object and assigns both str and encoding properties
167
   * the supplied values. $str is cast to a string prior to assignment, and if
168
   * $encoding is not specified, it defaults to mb_internal_encoding(). It
169
   * then returns the initialized object. Throws an InvalidArgumentException
170
   * if the first argument is an array or object without a __toString method.
171
   *
172
   * @param  mixed  $str      Value to modify, after being cast to string
173
   * @param  string $encoding The character encoding
174
   *
175
   * @return Stringy A Stringy object
176
   * @throws \InvalidArgumentException if an array or object without a
177
   *         __toString method is passed as the first argument
178
   */
179 1050
  public static function create($str = '', $encoding = null)
180
  {
181 1050
    return new static($str, $encoding);
182
  }
183
184
  /**
185
   * Returns the substring between $start and $end, if found, or an empty
186
   * string. An optional offset may be supplied from which to begin the
187
   * search for the start string.
188
   *
189
   * @param  string $start  Delimiter marking the start of the substring
190
   * @param  string $end    Delimiter marking the end of the substring
191
   * @param  int    $offset Index from which to begin the search
192
   *
193
   * @return Stringy Object whose $str is a substring between $start and $end
194
   */
195 16
  public function between($start, $end, $offset = 0)
196
  {
197 16
    $startIndex = $this->indexOf($start, $offset);
198 16
    if ($startIndex === false) {
199 2
      return static::create('', $this->encoding);
200
    }
201
202 14
    $substrIndex = $startIndex + UTF8::strlen($start, $this->encoding);
203 14
    $endIndex = $this->indexOf($end, $substrIndex);
204 14
    if ($endIndex === false) {
205 2
      return static::create('', $this->encoding);
206
    }
207
208 12
    return $this->substr($substrIndex, $endIndex - $substrIndex);
209
  }
210
211
  /**
212
   * Returns the index of the first occurrence of $needle in the string,
213
   * and false if not found. Accepts an optional offset from which to begin
214
   * the search.
215
   *
216
   * @param  string $needle Substring to look for
217
   * @param  int    $offset Offset from which to search
218
   *
219
   * @return int|bool The occurrence's index if found, otherwise false
220
   */
221 28
  public function indexOf($needle, $offset = 0)
222
  {
223 28
    return UTF8::strpos($this->str, (string)$needle, (int)$offset, $this->encoding);
224
  }
225
226
  /**
227
   * Returns the substring beginning at $start with the specified $length.
228
   * It differs from the UTF8::substr() function in that providing a $length of
229
   * null will return the rest of the string, rather than an empty string.
230
   *
231
   * @param  int $start  Position of the first character to use
232
   * @param  int $length Maximum number of characters used
233
   *
234
   * @return Stringy Object with its $str being the substring
235
   */
236 64
  public function substr($start, $length = null)
237
  {
238 64
    if ($length === null) {
239 19
      $length = $this->length();
240 19
    }
241
242 64
    $str = UTF8::substr($this->str, $start, $length, $this->encoding);
243
244 64
    return static::create($str, $this->encoding);
245
  }
246
247
  /**
248
   * Returns the length of the string.
249
   *
250
   * @return int The number of characters in $str given the encoding
251
   */
252 247
  public function length()
253
  {
254 247
    return UTF8::strlen($this->str, $this->encoding);
255
  }
256
257
  /**
258
   * Trims the string and replaces consecutive whitespace characters with a
259
   * single space. This includes tabs and newline characters, as well as
260
   * multibyte whitespace such as the thin space and ideographic space.
261
   *
262
   * @return Stringy Object with a trimmed $str and condensed whitespace
263
   */
264 52
  public function collapseWhitespace()
265
  {
266 52
    return $this->regexReplace('[[:space:]]+', ' ')->trim();
267
  }
268
269
  /**
270
   * Returns a string with whitespace removed from the start and end of the
271
   * string. Supports the removal of unicode whitespace. Accepts an optional
272
   * string of characters to strip instead of the defaults.
273
   *
274
   * @param  string $chars Optional string of characters to strip
275
   *
276
   * @return Stringy Object with a trimmed $str
277
   */
278 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...
279
  {
280 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...
281 152
      $chars = '[:space:]';
282 152
    } else {
283 1
      $chars = preg_quote($chars, '/');
284
    }
285
286 153
    return $this->regexReplace("^[$chars]+|[$chars]+\$", '');
287
  }
288
289
  /**
290
   * Replaces all occurrences of $pattern in $str by $replacement.
291
   *
292
   * @param  string $pattern     The regular expression pattern
293
   * @param  string $replacement The string to replace with
294
   * @param  string $options     Matching conditions to be used
295
   *
296
   * @return Stringy Object with the result2ing $str after the replacements
297
   */
298 223
  public function regexReplace($pattern, $replacement, $options = '')
299
  {
300 223
    if ($options === 'msr') {
301 8
      $options = 'ms';
302 8
    }
303
304 223
    $str = preg_replace(
305 223
        '/' . $pattern . '/u' . $options,
306 223
        $replacement,
307 223
        $this->str
308 223
    );
309
310 223
    return static::create($str, $this->encoding);
311
  }
312
313
  /**
314
   * Returns true if the string contains all $needles, false otherwise. By
315
   * default the comparison is case-sensitive, but can be made insensitive by
316
   * setting $caseSensitive to false.
317
   *
318
   * @param  array $needles       SubStrings to look for
319
   * @param  bool  $caseSensitive Whether or not to enforce case-sensitivity
320
   *
321
   * @return bool   Whether or not $str contains $needle
322
   */
323 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...
324
  {
325
    /** @noinspection IsEmptyFunctionUsageInspection */
326 43
    if (empty($needles)) {
327 1
      return false;
328
    }
329
330 42
    foreach ($needles as $needle) {
331 42
      if (!$this->contains($needle, $caseSensitive)) {
332 18
        return false;
333
      }
334 24
    }
335
336 24
    return true;
337
  }
338
339
  /**
340
   * Returns true if the string contains $needle, false otherwise. By default
341
   * the comparison is case-sensitive, but can be made insensitive by setting
342
   * $caseSensitive to false.
343
   *
344
   * @param  string $needle        Substring to look for
345
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
346
   *
347
   * @return bool   Whether or not $str contains $needle
348
   */
349 105
  public function contains($needle, $caseSensitive = true)
350
  {
351 105
    $encoding = $this->encoding;
352
353 105
    if ($caseSensitive) {
354 55
      return (UTF8::strpos($this->str, $needle, 0, $encoding) !== false);
355
    } else {
356 50
      return (UTF8::stripos($this->str, $needle, 0, $encoding) !== false);
357
    }
358
  }
359
360
  /**
361
   * Returns true if the string contains any $needles, false otherwise. By
362
   * default the comparison is case-sensitive, but can be made insensitive by
363
   * setting $caseSensitive to false.
364
   *
365
   * @param  array $needles       SubStrings to look for
366
   * @param  bool  $caseSensitive Whether or not to enforce case-sensitivity
367
   *
368
   * @return bool   Whether or not $str contains $needle
369
   */
370 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...
371
  {
372
    /** @noinspection IsEmptyFunctionUsageInspection */
373 43
    if (empty($needles)) {
374 1
      return false;
375
    }
376
377 42
    foreach ($needles as $needle) {
378 42
      if ($this->contains($needle, $caseSensitive)) {
379 24
        return true;
380
      }
381 18
    }
382
383 18
    return false;
384
  }
385
386
  /**
387
   * Returns the length of the string, implementing the countable interface.
388
   *
389
   * @return int The number of characters in the string, given the encoding
390
   */
391 1
  public function count()
392
  {
393 1
    return $this->length();
394
  }
395
396
  /**
397
   * Returns the number of occurrences of $substring in the given string.
398
   * By default, the comparison is case-sensitive, but can be made insensitive
399
   * by setting $caseSensitive to false.
400
   *
401
   * @param  string $substring     The substring to search for
402
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
403
   *
404
   * @return int    The number of $substring occurrences
405
   */
406 15
  public function countSubstr($substring, $caseSensitive = true)
407
  {
408 15
    if ($caseSensitive) {
409 9
      return UTF8::substr_count($this->str, $substring, 0, null, $this->encoding);
410
    }
411
412 6
    $str = UTF8::strtoupper($this->str, $this->encoding);
413 6
    $substring = UTF8::strtoupper($substring, $this->encoding);
414
415 6
    return UTF8::substr_count($str, $substring, 0, null, $this->encoding);
416
  }
417
418
  /**
419
   * Returns a lowercase and trimmed string separated by dashes. Dashes are
420
   * inserted before uppercase characters (with the exception of the first
421
   * character of the string), and in place of spaces as well as underscores.
422
   *
423
   * @return Stringy Object with a dasherized $str
424
   */
425 19
  public function dasherize()
426
  {
427 19
    return $this->delimit('-');
428
  }
429
430
  /**
431
   * Returns a lowercase and trimmed string separated by the given delimiter.
432
   * Delimiters are inserted before uppercase characters (with the exception
433
   * of the first character of the string), and in place of spaces, dashes,
434
   * and underscores. Alpha delimiters are not converted to lowercase.
435
   *
436
   * @param  string $delimiter Sequence used to separate parts of the string
437
   *
438
   * @return Stringy Object with a delimited $str
439
   */
440 49
  public function delimit($delimiter)
441
  {
442 49
    $str = $this->trim();
443
444 49
    $str = preg_replace('/\B([A-Z])/u', '-\1', $str);
445
446 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...
447
448 49
    $str = preg_replace('/[-_\s]+/u', $delimiter, $str);
449
450 49
    return static::create($str, $this->encoding);
451
  }
452
453
  /**
454
   * Ensures that the string begins with $substring. If it doesn't, it's
455
   * prepended.
456
   *
457
   * @param  string $substring The substring to add if not present
458
   *
459
   * @return Stringy Object with its $str prefixed by the $substring
460
   */
461 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...
462
  {
463 10
    $stringy = static::create($this->str, $this->encoding);
464
465 10
    if (!$stringy->startsWith($substring)) {
466 4
      $stringy->str = $substring . $stringy->str;
467 4
    }
468
469 10
    return $stringy;
470
  }
471
472
  /**
473
   * Returns true if the string begins with $substring, false otherwise. By
474
   * default, the comparison is case-sensitive, but can be made insensitive
475
   * by setting $caseSensitive to false.
476
   *
477
   * @param  string $substring     The substring to look for
478
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
479
   *
480
   * @return bool   Whether or not $str starts with $substring
481
   */
482 33
  public function startsWith($substring, $caseSensitive = true)
483
  {
484 33
    $str = $this->str;
485
486 33 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...
487 4
      $substring = UTF8::strtolower($substring, $this->encoding);
488 4
      $str = UTF8::strtolower($this->str, $this->encoding);
489 4
    }
490
491 33
    return UTF8::strpos($str, $substring, $this->encoding) === 0;
492
  }
493
494
  /**
495
   * Ensures that the string ends with $substring. If it doesn't, it's
496
   * appended.
497
   *
498
   * @param  string $substring The substring to add if not present
499
   *
500
   * @return Stringy Object with its $str suffixed by the $substring
501
   */
502 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...
503
  {
504 10
    $stringy = static::create($this->str, $this->encoding);
505
506 10
    if (!$stringy->endsWith($substring)) {
507 4
      $stringy->str .= $substring;
508 4
    }
509
510 10
    return $stringy;
511
  }
512
513
  /**
514
   * Returns true if the string ends with $substring, false otherwise. By
515
   * default, the comparison is case-sensitive, but can be made insensitive
516
   * by setting $caseSensitive to false.
517
   *
518
   * @param  string $substring     The substring to look for
519
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
520
   *
521
   * @return bool   Whether or not $str ends with $substring
522
   */
523 33
  public function endsWith($substring, $caseSensitive = true)
524
  {
525 33
    $substringLength = UTF8::strlen($substring, $this->encoding);
526 33
    $strLength = $this->length();
527
528 33
    $endOfStr = UTF8::substr(
529 33
        $this->str,
530 33
        $strLength - $substringLength,
531 33
        $substringLength,
532 33
        $this->encoding
533 33
    );
534
535 33 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...
536 4
      $substring = UTF8::strtolower($substring, $this->encoding);
537 4
      $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...
538 4
    }
539
540 33
    return (string)$substring === $endOfStr;
541
  }
542
543
  /**
544
   * Returns the first $n characters of the string.
545
   *
546
   * @param  int $n Number of characters to retrieve from the start
547
   *
548
   * @return Stringy Object with its $str being the first $n chars
549
   */
550 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...
551
  {
552 12
    $stringy = static::create($this->str, $this->encoding);
553
554 12
    if ($n < 0) {
555 2
      $stringy->str = '';
556 2
    } else {
557 10
      return $stringy->substr(0, $n);
558
    }
559
560 2
    return $stringy;
561
  }
562
563
  /**
564
   * Returns the encoding used by the Stringy object.
565
   *
566
   * @return string The current value of the $encoding property
567
   */
568 3
  public function getEncoding()
569
  {
570 3
    return $this->encoding;
571
  }
572
573
  /**
574
   * Returns a new ArrayIterator, thus implementing the IteratorAggregate
575
   * interface. The ArrayIterator's constructor is passed an array of chars
576
   * in the multibyte string. This enables the use of foreach with instances
577
   * of Stringy\Stringy.
578
   *
579
   * @return \ArrayIterator An iterator for the characters in the string
580
   */
581 1
  public function getIterator()
582
  {
583 1
    return new \ArrayIterator($this->chars());
584
  }
585
586
  /**
587
   * Returns an array consisting of the characters in the string.
588
   *
589
   * @return array An array of string chars
590
   */
591 4
  public function chars()
592
  {
593
    // init
594 4
    $chars = array();
595 4
    $l = $this->length();
596
597 4
    for ($i = 0; $i < $l; $i++) {
598 3
      $chars[] = $this->at($i)->str;
599 3
    }
600
601 4
    return $chars;
602
  }
603
604
  /**
605
   * Returns the character at $index, with indexes starting at 0.
606
   *
607
   * @param  int $index Position of the character
608
   *
609
   * @return Stringy The character at $index
610
   */
611 11
  public function at($index)
612
  {
613 11
    return $this->substr($index, 1);
614
  }
615
616
  /**
617
   * Returns true if the string contains a lower case char, false
618
   * otherwise.
619
   *
620
   * @return bool Whether or not the string contains a lower case character.
621
   */
622 12
  public function hasLowerCase()
623
  {
624 12
    return $this->matchesPattern('.*[[:lower:]]');
625
  }
626
627
  /**
628
   * Returns true if $str matches the supplied pattern, false otherwise.
629
   *
630
   * @param  string $pattern Regex pattern to match against
631
   *
632
   * @return bool   Whether or not $str matches the pattern
633
   */
634 91
  private function matchesPattern($pattern)
635
  {
636 91
    if (preg_match('/' . $pattern . '/u', $this->str)) {
637 54
      return true;
638
    } else {
639 37
      return false;
640
    }
641
  }
642
643
  /**
644
   * Returns true if the string contains an upper case char, false
645
   * otherwise.
646
   *
647
   * @return bool Whether or not the string contains an upper case character.
648
   */
649 12
  public function hasUpperCase()
650
  {
651 12
    return $this->matchesPattern('.*[[:upper:]]');
652
  }
653
654
  /**
655
   * Convert all HTML entities to their applicable characters.
656
   *
657
   * @param  int|null $flags Optional flags
658
   *
659
   * @return Stringy  Object with the resulting $str after being html decoded.
660
   */
661 5
  public function htmlDecode($flags = ENT_COMPAT)
662
  {
663 5
    $str = UTF8::html_entity_decode($this->str, $flags, $this->encoding);
664
665 5
    return static::create($str, $this->encoding);
666
  }
667
668
  /**
669
   * Convert all applicable characters to HTML entities.
670
   *
671
   * @param  int|null $flags Optional flags
672
   *
673
   * @return Stringy  Object with the resulting $str after being html encoded.
674
   */
675 5
  public function htmlEncode($flags = ENT_COMPAT)
676
  {
677 5
    $str = UTF8::htmlentities($this->str, $flags, $this->encoding);
678
679 5
    return static::create($str, $this->encoding);
680
  }
681
682
  /**
683
   * Capitalizes the first word of the string, replaces underscores with
684
   * spaces, and strips '_id'.
685
   *
686
   * @return Stringy Object with a humanized $str
687
   */
688 3
  public function humanize()
689
  {
690 3
    $str = UTF8::str_replace(array('_id', '_'), array('', ' '), $this->str);
691
692 3
    return static::create($str, $this->encoding)->trim()->upperCaseFirst();
693
  }
694
695
  /**
696
   * Converts the first character of the supplied string to upper case.
697
   *
698
   * @return Stringy Object with the first character of $str being upper case
699
   */
700 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...
701
  {
702 27
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
703 27
    $rest = UTF8::substr(
704 27
        $this->str,
705 27
        1,
706 27
        $this->length() - 1,
707 27
        $this->encoding
708 27
    );
709
710 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 702 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...
711
712 27
    return static::create($str, $this->encoding);
713
  }
714
715
  /**
716
   * Returns the index of the last occurrence of $needle in the string,
717
   * and false if not found. Accepts an optional offset from which to begin
718
   * the search. Offsets may be negative to count from the last character
719
   * in the string.
720
   *
721
   * @param  string $needle Substring to look for
722
   * @param  int    $offset Offset from which to search
723
   *
724
   * @return int|bool The last occurrence's index if found, otherwise false
725
   */
726 12
  public function indexOfLast($needle, $offset = 0)
727
  {
728 12
    return UTF8::strrpos($this->str, (string)$needle, (int)$offset, $this->encoding);
729
  }
730
731
  /**
732
   * Inserts $substring into the string at the $index provided.
733
   *
734
   * @param  string $substring String to be inserted
735
   * @param  int    $index     The index at which to insert the substring
736
   *
737
   * @return Stringy Object with the resulting $str after the insertion
738
   */
739 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...
740
  {
741 8
    $stringy = static::create($this->str, $this->encoding);
742 8
    if ($index > $stringy->length()) {
743 1
      return $stringy;
744
    }
745
746 7
    $start = UTF8::substr($stringy->str, 0, $index, $stringy->encoding);
747 7
    $end = UTF8::substr($stringy->str, $index, $stringy->length(), $stringy->encoding);
748
749 7
    $stringy->str = $start . $substring . $end;
750
751 7
    return $stringy;
752
  }
753
754
  /**
755
   * Returns true if the string contains only alphabetic chars, false
756
   * otherwise.
757
   *
758
   * @return bool Whether or not $str contains only alphabetic chars
759
   */
760 10
  public function isAlpha()
761
  {
762 10
    return $this->matchesPattern('^[[:alpha:]]*$');
763
  }
764
765
  /**
766
   * Determine whether the string is considered to be empty.
767
   *
768
   * A variable is considered empty if it does not exist or if its value equals FALSE.
769
   * empty() does not generate a warning if the variable does not exist.
770
   *
771
   * @return bool
772
   */
773
  public function isEmpty()
774
  {
775
    return empty($this->str);
776
  }
777
778
  /**
779
   * Returns true if the string contains only alphabetic and numeric chars,
780
   * false otherwise.
781
   *
782
   * @return bool Whether or not $str contains only alphanumeric chars
783
   */
784 13
  public function isAlphanumeric()
785
  {
786 13
    return $this->matchesPattern('^[[:alnum:]]*$');
787
  }
788
789
  /**
790
   * Returns true if the string contains only whitespace chars, false
791
   * otherwise.
792
   *
793
   * @return bool Whether or not $str contains only whitespace characters
794
   */
795 15
  public function isBlank()
796
  {
797 15
    return $this->matchesPattern('^[[:space:]]*$');
798
  }
799
800
  /**
801
   * Returns true if the string contains only hexadecimal chars, false
802
   * otherwise.
803
   *
804
   * @return bool Whether or not $str contains only hexadecimal chars
805
   */
806 13
  public function isHexadecimal()
807
  {
808 13
    return $this->matchesPattern('^[[:xdigit:]]*$');
809
  }
810
811
  /**
812
   * Returns true if the string contains HTML-Tags, false otherwise.
813
   *
814
   * @return bool Whether or not $str contains HTML-Tags
815
   */
816 1
  public function isHtml()
817
  {
818 1
    return UTF8::isHtml($this->str);
819
  }
820
821
  /**
822
   * Returns true if the string contains a valid E-Mail address, false otherwise.
823
   *
824
   * @param bool $useExampleDomainCheck
825
   * @param bool $useTypoInDomainCheck
826
   * @param bool $useTemporaryDomainCheck
827
   * @param bool $useDnsCheck
828
   *
829
   * @return bool Whether or not $str contains a valid E-Mail address
830
   */
831 1
  public function isEmail($useExampleDomainCheck = false, $useTypoInDomainCheck = false, $useTemporaryDomainCheck = false, $useDnsCheck = false)
832
  {
833 1
    return EmailCheck::isValid($this->str, $useExampleDomainCheck, $useTypoInDomainCheck, $useTemporaryDomainCheck, $useDnsCheck);
834
  }
835
836
  /**
837
   * Returns true if the string is JSON, false otherwise. Unlike json_decode
838
   * in PHP 5.x, this method is consistent with PHP 7 and other JSON parsers,
839
   * in that an empty string is not considered valid JSON.
840
   *
841
   * @return bool Whether or not $str is JSON
842
   */
843 20
  public function isJson()
844
  {
845 20
    if (!isset($this->str[0])) {
846 1
      return false;
847
    }
848
849 19
    json_decode($this->str);
850
851 19
    if (json_last_error() === JSON_ERROR_NONE) {
852 11
      return true;
853
    } else {
854 8
      return false;
855
    }
856
  }
857
858
  /**
859
   * Returns true if the string contains only lower case chars, false
860
   * otherwise.
861
   *
862
   * @return bool Whether or not $str contains only lower case characters
863
   */
864 8
  public function isLowerCase()
865
  {
866 8
    if ($this->matchesPattern('^[[:lower:]]*$')) {
867 3
      return true;
868
    } else {
869 5
      return false;
870
    }
871
  }
872
873
  /**
874
   * Returns true if the string is serialized, false otherwise.
875
   *
876
   * @return bool Whether or not $str is serialized
877
   */
878 7
  public function isSerialized()
879
  {
880 7
    if (!isset($this->str[0])) {
881 1
      return false;
882
    }
883
884
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
885
    if (
886 6
        $this->str === 'b:0;'
887 6
        ||
888 6
        @unserialize($this->str) !== false
889 6
    ) {
890 4
      return true;
891
    } else {
892 2
      return false;
893
    }
894
  }
895
896
  /**
897
   * Returns true if the string contains only lower case chars, false
898
   * otherwise.
899
   *
900
   * @return bool Whether or not $str contains only lower case characters
901
   */
902 8
  public function isUpperCase()
903
  {
904 8
    return $this->matchesPattern('^[[:upper:]]*$');
905
  }
906
907
  /**
908
   * Returns the last $n characters of the string.
909
   *
910
   * @param  int $n Number of characters to retrieve from the end
911
   *
912
   * @return Stringy Object with its $str being the last $n chars
913
   */
914 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...
915
  {
916 12
    $stringy = static::create($this->str, $this->encoding);
917
918 12
    if ($n <= 0) {
919 4
      $stringy->str = '';
920 4
    } else {
921 8
      return $stringy->substr(-$n);
922
    }
923
924 4
    return $stringy;
925
  }
926
927
  /**
928
   * Splits on newlines and carriage returns, returning an array of Stringy
929
   * objects corresponding to the lines in the string.
930
   *
931
   * @return Stringy[] An array of Stringy objects
932
   */
933 15
  public function lines()
934
  {
935 15
    $array = preg_split('/[\r\n]{1,2}/u', $this->str);
936
    /** @noinspection CallableInLoopTerminationConditionInspection */
937
    /** @noinspection ForeachInvariantsInspection */
938 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...
939 15
      $array[$i] = static::create($array[$i], $this->encoding);
940 15
    }
941
942 15
    return $array;
943
  }
944
945
  /**
946
   * Returns the longest common prefix between the string and $otherStr.
947
   *
948
   * @param  string $otherStr Second string for comparison
949
   *
950
   * @return Stringy Object with its $str being the longest common prefix
951
   */
952 10
  public function longestCommonPrefix($otherStr)
953
  {
954 10
    $encoding = $this->encoding;
955 10
    $maxLength = min($this->length(), UTF8::strlen($otherStr, $encoding));
956
957 10
    $longestCommonPrefix = '';
958 10
    for ($i = 0; $i < $maxLength; $i++) {
959 8
      $char = UTF8::substr($this->str, $i, 1, $encoding);
960
961 8
      if ($char == UTF8::substr($otherStr, $i, 1, $encoding)) {
962 6
        $longestCommonPrefix .= $char;
963 6
      } else {
964 6
        break;
965
      }
966 6
    }
967
968 10
    return static::create($longestCommonPrefix, $encoding);
969
  }
970
971
  /**
972
   * Returns the longest common suffix between the string and $otherStr.
973
   *
974
   * @param  string $otherStr Second string for comparison
975
   *
976
   * @return Stringy Object with its $str being the longest common suffix
977
   */
978 10
  public function longestCommonSuffix($otherStr)
979
  {
980 10
    $encoding = $this->encoding;
981 10
    $maxLength = min($this->length(), UTF8::strlen($otherStr, $encoding));
982
983 10
    $longestCommonSuffix = '';
984 10
    for ($i = 1; $i <= $maxLength; $i++) {
985 8
      $char = UTF8::substr($this->str, -$i, 1, $encoding);
986
987 8
      if ($char == UTF8::substr($otherStr, -$i, 1, $encoding)) {
988 6
        $longestCommonSuffix = $char . $longestCommonSuffix;
989 6
      } else {
990 6
        break;
991
      }
992 6
    }
993
994 10
    return static::create($longestCommonSuffix, $encoding);
995
  }
996
997
  /**
998
   * Returns the longest common substring between the string and $otherStr.
999
   * In the case of ties, it returns that which occurs first.
1000
   *
1001
   * @param  string $otherStr Second string for comparison
1002
   *
1003
   * @return Stringy Object with its $str being the longest common substring
1004
   */
1005 10
  public function longestCommonSubstring($otherStr)
1006
  {
1007
    // Uses dynamic programming to solve
1008
    // http://en.wikipedia.org/wiki/Longest_common_substring_problem
1009 10
    $encoding = $this->encoding;
1010 10
    $stringy = static::create($this->str, $encoding);
1011 10
    $strLength = $stringy->length();
1012 10
    $otherLength = UTF8::strlen($otherStr, $encoding);
1013
1014
    // Return if either string is empty
1015 10
    if ($strLength == 0 || $otherLength == 0) {
1016 2
      $stringy->str = '';
1017
1018 2
      return $stringy;
1019
    }
1020
1021 8
    $len = 0;
1022 8
    $end = 0;
1023 8
    $table = array_fill(
1024 8
        0, $strLength + 1,
1025 8
        array_fill(0, $otherLength + 1, 0)
1026 8
    );
1027
1028 8
    for ($i = 1; $i <= $strLength; $i++) {
1029 8
      for ($j = 1; $j <= $otherLength; $j++) {
1030 8
        $strChar = UTF8::substr($stringy->str, $i - 1, 1, $encoding);
1031 8
        $otherChar = UTF8::substr($otherStr, $j - 1, 1, $encoding);
1032
1033 8
        if ($strChar == $otherChar) {
1034 8
          $table[$i][$j] = $table[$i - 1][$j - 1] + 1;
1035 8
          if ($table[$i][$j] > $len) {
1036 8
            $len = $table[$i][$j];
1037 8
            $end = $i;
1038 8
          }
1039 8
        } else {
1040 8
          $table[$i][$j] = 0;
1041
        }
1042 8
      }
1043 8
    }
1044
1045 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...
1046
1047 8
    return $stringy;
1048
  }
1049
1050
  /**
1051
   * Returns whether or not a character exists at an index. Offsets may be
1052
   * negative to count from the last character in the string. Implements
1053
   * part of the ArrayAccess interface.
1054
   *
1055
   * @param  mixed $offset The index to check
1056
   *
1057
   * @return boolean Whether or not the index exists
1058
   */
1059 6
  public function offsetExists($offset)
1060
  {
1061
    // init
1062 6
    $length = $this->length();
1063 6
    $offset = (int)$offset;
1064
1065 6
    if ($offset >= 0) {
1066 3
      return ($length > $offset);
1067
    }
1068
1069 3
    return ($length >= abs($offset));
1070
  }
1071
1072
  /**
1073
   * Returns the character at the given index. Offsets may be negative to
1074
   * count from the last character in the string. Implements part of the
1075
   * ArrayAccess interface, and throws an OutOfBoundsException if the index
1076
   * does not exist.
1077
   *
1078
   * @param  mixed $offset The index from which to retrieve the char
1079
   *
1080
   * @return string                 The character at the specified index
1081
   * @throws \OutOfBoundsException If the positive or negative offset does
1082
   *                               not exist
1083
   */
1084 2
  public function offsetGet($offset)
1085
  {
1086
    // init
1087 2
    $offset = (int)$offset;
1088 2
    $length = $this->length();
1089
1090
    if (
1091 2
        ($offset >= 0 && $length <= $offset)
1092
        ||
1093 1
        $length < abs($offset)
1094 2
    ) {
1095 1
      throw new \OutOfBoundsException('No character exists at the index');
1096
    }
1097
1098 1
    return UTF8::substr($this->str, $offset, 1, $this->encoding);
1099
  }
1100
1101
  /**
1102
   * Implements part of the ArrayAccess interface, but throws an exception
1103
   * when called. This maintains the immutability of Stringy objects.
1104
   *
1105
   * @param  mixed $offset The index of the character
1106
   * @param  mixed $value  Value to set
1107
   *
1108
   * @throws \Exception When called
1109
   */
1110 1
  public function offsetSet($offset, $value)
1111
  {
1112
    // Stringy is immutable, cannot directly set char
1113 1
    throw new \Exception('Stringy object is immutable, cannot modify char');
1114
  }
1115
1116
  /**
1117
   * Implements part of the ArrayAccess interface, but throws an exception
1118
   * when called. This maintains the immutability of Stringy objects.
1119
   *
1120
   * @param  mixed $offset The index of the character
1121
   *
1122
   * @throws \Exception When called
1123
   */
1124 1
  public function offsetUnset($offset)
1125
  {
1126
    // Don't allow directly modifying the string
1127 1
    throw new \Exception('Stringy object is immutable, cannot unset char');
1128
  }
1129
1130
  /**
1131
   * Pads the string to a given length with $padStr. If length is less than
1132
   * or equal to the length of the string, no padding takes places. The
1133
   * default string used for padding is a space, and the default type (one of
1134
   * 'left', 'right', 'both') is 'right'. Throws an InvalidArgumentException
1135
   * if $padType isn't one of those 3 values.
1136
   *
1137
   * @param  int    $length  Desired string length after padding
1138
   * @param  string $padStr  String used to pad, defaults to space
1139
   * @param  string $padType One of 'left', 'right', 'both'
1140
   *
1141
   * @return Stringy Object with a padded $str
1142
   * @throws \InvalidArgumentException If $padType isn't one of 'right', 'left' or 'both'
1143
   */
1144 13
  public function pad($length, $padStr = ' ', $padType = 'right')
1145
  {
1146 13
    if (!in_array($padType, array('left', 'right', 'both'), true)) {
1147 1
      throw new \InvalidArgumentException(
1148
          'Pad expects $padType ' . "to be one of 'left', 'right' or 'both'"
1149 1
      );
1150
    }
1151
1152
    switch ($padType) {
1153 12
      case 'left':
1154 3
        return $this->padLeft($length, $padStr);
1155 9
      case 'right':
1156 6
        return $this->padRight($length, $padStr);
1157 3
      default:
1158 3
        return $this->padBoth($length, $padStr);
1159 3
    }
1160
  }
1161
1162
  /**
1163
   * Returns a new string of a given length such that the beginning of the
1164
   * string is padded. Alias for pad() with a $padType of 'left'.
1165
   *
1166
   * @param  int    $length Desired string length after padding
1167
   * @param  string $padStr String used to pad, defaults to space
1168
   *
1169
   * @return Stringy String with left padding
1170
   */
1171 10
  public function padLeft($length, $padStr = ' ')
1172
  {
1173 10
    return $this->applyPadding($length - $this->length(), 0, $padStr);
1174
  }
1175
1176
  /**
1177
   * Adds the specified amount of left and right padding to the given string.
1178
   * The default character used is a space.
1179
   *
1180
   * @param  int    $left   Length of left padding
1181
   * @param  int    $right  Length of right padding
1182
   * @param  string $padStr String used to pad
1183
   *
1184
   * @return Stringy String with padding applied
1185
   */
1186 37
  private function applyPadding($left = 0, $right = 0, $padStr = ' ')
1187
  {
1188 37
    $stringy = static::create($this->str, $this->encoding);
1189
1190 37
    $length = UTF8::strlen($padStr, $stringy->encoding);
1191
1192 37
    $strLength = $stringy->length();
1193 37
    $paddedLength = $strLength + $left + $right;
1194
1195 37
    if (!$length || $paddedLength <= $strLength) {
1196 3
      return $stringy;
1197
    }
1198
1199 34
    $leftPadding = UTF8::substr(
1200 34
        UTF8::str_repeat(
1201 34
            $padStr,
1202 34
            ceil($left / $length)
1203 34
        ),
1204 34
        0,
1205 34
        $left,
1206 34
        $stringy->encoding
1207 34
    );
1208
1209 34
    $rightPadding = UTF8::substr(
1210 34
        UTF8::str_repeat(
1211 34
            $padStr,
1212 34
            ceil($right / $length)
1213 34
        ),
1214 34
        0,
1215 34
        $right,
1216 34
        $stringy->encoding
1217 34
    );
1218
1219 34
    $stringy->str = $leftPadding . $stringy->str . $rightPadding;
1220
1221 34
    return $stringy;
1222
  }
1223
1224
  /**
1225
   * Returns a new string of a given length such that the end of the string
1226
   * is padded. Alias for pad() with a $padType of 'right'.
1227
   *
1228
   * @param  int    $length Desired string length after padding
1229
   * @param  string $padStr String used to pad, defaults to space
1230
   *
1231
   * @return Stringy String with right padding
1232
   */
1233 13
  public function padRight($length, $padStr = ' ')
1234
  {
1235 13
    return $this->applyPadding(0, $length - $this->length(), $padStr);
1236
  }
1237
1238
  /**
1239
   * Returns a new string of a given length such that both sides of the
1240
   * string are padded. Alias for pad() with a $padType of 'both'.
1241
   *
1242
   * @param  int    $length Desired string length after padding
1243
   * @param  string $padStr String used to pad, defaults to space
1244
   *
1245
   * @return Stringy String with padding applied
1246
   */
1247 14
  public function padBoth($length, $padStr = ' ')
1248
  {
1249 14
    $padding = $length - $this->length();
1250
1251 14
    return $this->applyPadding(floor($padding / 2), ceil($padding / 2), $padStr);
1252
  }
1253
1254
  /**
1255
   * Returns a new string starting with $string.
1256
   *
1257
   * @param  string $string The string to append
1258
   *
1259
   * @return Stringy Object with appended $string
1260
   */
1261 2
  public function prepend($string)
1262
  {
1263 2
    return static::create($string . $this->str, $this->encoding);
1264
  }
1265
1266
  /**
1267
   * Returns a new string with the prefix $substring removed, if present.
1268
   *
1269
   * @param  string $substring The prefix to remove
1270
   *
1271
   * @return Stringy Object having a $str without the prefix $substring
1272
   */
1273 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...
1274
  {
1275 12
    $stringy = static::create($this->str, $this->encoding);
1276
1277 12
    if ($stringy->startsWith($substring)) {
1278 6
      $substringLength = UTF8::strlen($substring, $stringy->encoding);
1279
1280 6
      return $stringy->substr($substringLength);
1281
    }
1282
1283 6
    return $stringy;
1284
  }
1285
1286
  /**
1287
   * Returns a new string with the suffix $substring removed, if present.
1288
   *
1289
   * @param  string $substring The suffix to remove
1290
   *
1291
   * @return Stringy Object having a $str without the suffix $substring
1292
   */
1293 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...
1294
  {
1295 12
    $stringy = static::create($this->str, $this->encoding);
1296
1297 12
    if ($stringy->endsWith($substring)) {
1298 8
      $substringLength = UTF8::strlen($substring, $stringy->encoding);
1299
1300 8
      return $stringy->substr(0, $stringy->length() - $substringLength);
1301
    }
1302
1303 4
    return $stringy;
1304
  }
1305
1306
  /**
1307
   * Returns a repeated string given a multiplier.
1308
   *
1309
   * @param  int $multiplier The number of times to repeat the string
1310
   *
1311
   * @return Stringy Object with a repeated str
1312
   */
1313 7
  public function repeat($multiplier)
1314
  {
1315 7
    $repeated = UTF8::str_repeat($this->str, $multiplier);
1316
1317 7
    return static::create($repeated, $this->encoding);
1318
  }
1319
1320
  /**
1321
   * Replaces all occurrences of $search in $str by $replacement.
1322
   *
1323
   * @param string $search        The needle to search for
1324
   * @param string $replacement   The string to replace with
1325
   * @param bool   $caseSensitive Whether or not to enforce case-sensitivity
1326
   *
1327
   * @return Stringy Object with the resulting $str after the replacements
1328
   */
1329 28 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...
1330
  {
1331 28
    if ($caseSensitive) {
1332 21
      $return = UTF8::str_replace($search, $replacement, $this->str);
1333 21
    } else {
1334 7
      $return = UTF8::str_ireplace($search, $replacement, $this->str);
1335
    }
1336
1337 28
    return static::create($return);
1338
  }
1339
1340
  /**
1341
   * Replaces all occurrences of $search in $str by $replacement.
1342
   *
1343
   * @param array        $search        The elements to search for
1344
   * @param string|array $replacement   The string to replace with
1345
   * @param bool         $caseSensitive Whether or not to enforce case-sensitivity
1346
   *
1347
   * @return Stringy Object with the resulting $str after the replacements
1348
   */
1349 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...
1350
  {
1351 30
    if ($caseSensitive) {
1352 23
      $return = UTF8::str_replace($search, $replacement, $this->str);
1353 23
    } else {
1354 7
      $return = UTF8::str_ireplace($search, $replacement, $this->str);
1355
    }
1356
1357 30
    return static::create($return);
1358
  }
1359
1360
  /**
1361
   * Replaces all occurrences of $search from the beginning of string with $replacement
1362
   *
1363
   * @param string $search
1364
   * @param string $replacement
1365
   *
1366
   * @return Stringy Object with the resulting $str after the replacements
1367
   */
1368 16
  public function replaceBeginning($search, $replacement)
1369
  {
1370 16
    $str = $this->regexReplace('^' . preg_quote($search, '/'), UTF8::str_replace('\\', '\\\\', $replacement));
1371
1372 16
    return static::create($str, $this->encoding);
1373
  }
1374
1375
  /**
1376
   * Replaces all occurrences of $search from the ending of string with $replacement
1377
   *
1378
   * @param string $search
1379
   * @param string $replacement
1380
   *
1381
   * @return Stringy Object with the resulting $str after the replacements
1382
   */
1383 16
  public function replaceEnding($search, $replacement)
1384
  {
1385 16
    $str = $this->regexReplace(preg_quote($search, '/') . '$', UTF8::str_replace('\\', '\\\\', $replacement));
1386
1387 16
    return static::create($str, $this->encoding);
1388
  }
1389
1390
  /**
1391
   * Returns a reversed string. A multibyte version of strrev().
1392
   *
1393
   * @return Stringy Object with a reversed $str
1394
   */
1395 5
  public function reverse()
1396
  {
1397 5
    $reversed = UTF8::strrev($this->str);
1398
1399 5
    return static::create($reversed, $this->encoding);
1400
  }
1401
1402
  /**
1403
   * Truncates the string to a given length, while ensuring that it does not
1404
   * split words. If $substring is provided, and truncating occurs, the
1405
   * string is further truncated so that the substring may be appended without
1406
   * exceeding the desired length.
1407
   *
1408
   * @param  int    $length    Desired length of the truncated string
1409
   * @param  string $substring The substring to append if it can fit
1410
   *
1411
   * @return Stringy Object with the resulting $str after truncating
1412
   */
1413 22
  public function safeTruncate($length, $substring = '')
1414
  {
1415 22
    $stringy = static::create($this->str, $this->encoding);
1416 22
    if ($length >= $stringy->length()) {
1417 4
      return $stringy;
1418
    }
1419
1420
    // Need to further trim the string so we can append the substring
1421 18
    $encoding = $stringy->encoding;
1422 18
    $substringLength = UTF8::strlen($substring, $encoding);
1423 18
    $length -= $substringLength;
1424
1425 18
    $truncated = UTF8::substr($stringy->str, 0, $length, $encoding);
1426
1427
    // If the last word was truncated
1428 18
    if (UTF8::strpos($stringy->str, ' ', $length - 1, $encoding) != $length) {
1429
      // Find pos of the last occurrence of a space, get up to that
1430 11
      $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 1425 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...
1431 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 1431 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 1430 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...
1432 11
    }
1433
1434 18
    $stringy->str = $truncated . $substring;
1435
1436 18
    return $stringy;
1437
  }
1438
1439
  /**
1440
   * A multibyte string shuffle function. It returns a string with its
1441
   * characters in random order.
1442
   *
1443
   * @return Stringy Object with a shuffled $str
1444
   */
1445 3
  public function shuffle()
1446
  {
1447 3
    $shuffledStr = UTF8::str_shuffle($this->str);
1448
1449 3
    return static::create($shuffledStr, $this->encoding);
1450
  }
1451
1452
  /**
1453
   * Converts the string into an URL slug. This includes replacing non-ASCII
1454
   * characters with their closest ASCII equivalents, removing remaining
1455
   * non-ASCII and non-alphanumeric characters, and replacing whitespace with
1456
   * $replacement. The replacement defaults to a single dash, and the string
1457
   * is also converted to lowercase.
1458
   *
1459
   * @param string $replacement The string used to replace whitespace
1460
   * @param string $language    The language for the url
1461
   * @param bool   $strToLower  string to lower
1462
   *
1463
   * @return Stringy Object whose $str has been converted to an URL slug
1464
   */
1465 15
  public function slugify($replacement = '-', $language = 'de', $strToLower = true)
1466
  {
1467 15
    $slug = URLify::slug($this->str, $language, $replacement, $strToLower);
1468
1469 15
    return static::create($slug, $this->encoding);
1470
  }
1471
1472
  /**
1473
   * Remove css media-queries.
1474
   *
1475
   * @return Stringy
1476
   */
1477 1
  public function stripeCssMediaQueries()
1478
  {
1479 1
    $pattern = '#@media\\s+(?:only\\s)?(?:[\\s{\\(]|screen|all)\\s?[^{]+{.*}\\s*}\\s*#misU';
1480
1481 1
    return static::create(preg_replace($pattern, '', $this->str));
1482
  }
1483
1484
  /**
1485
   * Remove empty html-tag.
1486
   *
1487
   * e.g.: <tag></tag>
1488
   *
1489
   * @return Stringy
1490
   */
1491 1
  public function stripeEmptyHtmlTags()
1492
  {
1493 1
    $pattern = "/<[^\/>]*>(([\s]?)*|)<\/[^>]*>/i";
1494
1495 1
    return static::create(preg_replace($pattern, '', $this->str));
1496
  }
1497
1498
  /**
1499
   * Converts the string into an valid UTF-8 string.
1500
   *
1501
   * @return Stringy
1502
   */
1503 1
  public function utf8ify()
1504
  {
1505 1
    return static::create(UTF8::cleanup($this->str));
1506
  }
1507
1508
  /**
1509
   * escape html
1510
   *
1511
   * @return Stringy
1512
   */
1513 6
  public function escape()
1514
  {
1515 6
    $str = UTF8::htmlspecialchars(
1516 6
        $this->str,
1517 6
        ENT_QUOTES | ENT_SUBSTITUTE,
1518 6
        $this->encoding
1519 6
    );
1520
1521 6
    return static::create($str, $this->encoding);
1522
  }
1523
1524
  /**
1525
   * Create an extract from a text, so if the search-string was found, it will be centered in the output.
1526
   *
1527
   * @param string   $search
1528
   * @param int|null $length
1529
   * @param string   $ellipsis
1530
   *
1531
   * @return Stringy
1532
   */
1533 1
  public function extractText($search = '', $length = null, $ellipsis = '...')
1534
  {
1535
    // init
1536 1
    $text = $this->str;
1537
1538 1
    if (empty($text)) {
1539 1
      return static::create('', $this->encoding);
1540
    }
1541
1542 1
    $trimChars = "\t\r\n -_()!~?=+/*\\,.:;\"'[]{}`&";
1543
1544 1
    if ($length === null) {
1545 1
      $length = $this->length() / 2;
1546 1
    }
1547
1548 1
    if (empty($search)) {
1549
1550
      $stringLength = UTF8::strlen($text, $this->encoding);
1551
      $end = ($length - 1) > $stringLength ? $stringLength : ($length - 1);
1552
      $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...
1553
1554
      if ($pos) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pos of type false|integer is loosely compared to true; 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...
1555
        return static::create(
1556
            rtrim(
1557
                UTF8::substr($text, 0, $pos, $this->encoding),
1558
                $trimChars
1559
            ) . $ellipsis,
1560
            $this->encoding
1561
        );
1562
      } else {
1563
        return static::create($text, $this->encoding);
1564
      }
1565
1566
    }
1567
1568 1
    $wordPos = UTF8::strpos(
1569 1
        UTF8::strtolower($text),
1570 1
        UTF8::strtolower($search, $this->encoding),
1571 1
        null,
1572 1
        $this->encoding
1573 1
    );
1574 1
    $halfSide = (int)($wordPos - $length / 2 + UTF8::strlen($search, $this->encoding) / 2);
1575
1576 1
    if ($halfSide > 0) {
1577
1578 1
      $halfText = UTF8::substr($text, 0, $halfSide, $this->encoding);
1579 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 1578 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...
1580
1581 1
      if (!$pos_start) {
1582 1
        $pos_start = 0;
1583 1
      }
1584
1585 1
    } else {
1586 1
      $pos_start = 0;
1587
    }
1588
1589 1
    if ($wordPos && $halfSide > 0) {
1590 1
      $l = $pos_start + $length - 1;
1591 1
      $realLength = UTF8::strlen($text, $this->encoding);
1592
1593 1
      if ($l > $realLength) {
1594
        $l = $realLength;
1595
      }
1596
1597 1
      $pos_end = min(
1598 1
                     UTF8::strpos($text, ' ', $l, $this->encoding),
1599 1
                     UTF8::strpos($text, '.', $l, $this->encoding)
1600 1
                 ) - $pos_start;
1601
1602 1
      if (!$pos_end || $pos_end <= 0) {
1603 1
        $extract = $ellipsis . ltrim(
1604 1
                UTF8::substr(
1605 1
                    $text,
1606 1
                    $pos_start,
1607 1
                    UTF8::strlen($text),
1608 1
                    $this->encoding
1609 1
                ),
1610
                $trimChars
1611 1
            );
1612 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...
1613 1
        $extract = $ellipsis . trim(
1614 1
                UTF8::substr(
1615 1
                    $text,
1616 1
                    $pos_start,
1617 1
                    $pos_end,
1618 1
                    $this->encoding
1619 1
                ),
1620
                $trimChars
1621 1
            ) . $ellipsis;
1622
      }
1623
1624 1
    } else {
1625
1626 1
      $l = $length - 1;
1627 1
      $trueLength = UTF8::strlen($text, $this->encoding);
1628
1629 1
      if ($l > $trueLength) {
1630
        $l = $trueLength;
1631
      }
1632
1633 1
      $pos_end = min(
1634 1
          UTF8::strpos($text, ' ', $l, $this->encoding),
1635 1
          UTF8::strpos($text, '.', $l, $this->encoding)
1636 1
      );
1637
1638 1 View Code Duplication
      if ($pos_end) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pos_end of type false|integer is loosely compared to true; 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...
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...
1639 1
        $extract = rtrim(
1640 1
                       UTF8::substr($text, 0, $pos_end, $this->encoding),
1641
                       $trimChars
1642 1
                   ) . $ellipsis;
1643 1
      } else {
1644 1
        $extract = $text;
1645
      }
1646
    }
1647
1648 1
    return static::create($extract, $this->encoding);
1649
  }
1650
1651
1652
  /**
1653
   * remove xss from html
1654
   *
1655
   * @return Stringy
1656
   */
1657 6
  public function removeXss()
1658
  {
1659 6
    static $antiXss = null;
1660
1661 6
    if ($antiXss === null) {
1662 1
      $antiXss = new AntiXSS();
1663 1
    }
1664
1665 6
    $str = $antiXss->xss_clean($this->str);
1666
1667 6
    return static::create($str, $this->encoding);
1668
  }
1669
1670
  /**
1671
   * remove html-break [br | \r\n | \r | \n | ...]
1672
   *
1673
   * @param string $replacement
1674
   *
1675
   * @return Stringy
1676
   */
1677 6
  public function removeHtmlBreak($replacement = '')
1678
  {
1679 6
    $str = preg_replace('#/\r\n|\r|\n|<br.*/?>#isU', $replacement, $this->str);
1680
1681 6
    return static::create($str, $this->encoding);
1682
  }
1683
1684
  /**
1685
   * remove html
1686
   *
1687
   * @param $allowableTags
1688
   *
1689
   * @return Stringy
1690
   */
1691 6
  public function removeHtml($allowableTags = null)
1692
  {
1693 6
    $str = strip_tags($this->str, $allowableTags);
1694
1695 6
    return static::create($str, $this->encoding);
1696
  }
1697
1698
  /**
1699
   * Returns the substring beginning at $start, and up to, but not including
1700
   * the index specified by $end. If $end is omitted, the function extracts
1701
   * the remaining string. If $end is negative, it is computed from the end
1702
   * of the string.
1703
   *
1704
   * @param  int $start Initial index from which to begin extraction
1705
   * @param  int $end   Optional index at which to end extraction
1706
   *
1707
   * @return Stringy Object with its $str being the extracted substring
1708
   */
1709 18
  public function slice($start, $end = null)
1710
  {
1711 18
    if ($end === null) {
1712 4
      $length = $this->length();
1713 18
    } elseif ($end >= 0 && $end <= $start) {
1714 4
      return static::create('', $this->encoding);
1715 10
    } elseif ($end < 0) {
1716 2
      $length = $this->length() + $end - $start;
1717 2
    } else {
1718 8
      $length = $end - $start;
1719
    }
1720
1721 14
    $str = UTF8::substr($this->str, $start, $length, $this->encoding);
1722
1723 14
    return static::create($str, $this->encoding);
1724
  }
1725
1726
  /**
1727
   * Splits the string with the provided regular expression, returning an
1728
   * array of Stringy objects. An optional integer $limit will truncate the
1729
   * results.
1730
   *
1731
   * @param  string $pattern The regex with which to split the string
1732
   * @param  int    $limit   Optional maximum number of results to return
1733
   *
1734
   * @return Stringy[] An array of Stringy objects
1735
   */
1736 19
  public function split($pattern, $limit = null)
1737
  {
1738 19
    if ($limit === 0) {
1739 2
      return array();
1740
    }
1741
1742
    // UTF8::split errors when supplied an empty pattern in < PHP 5.4.13
1743
    // and current versions of HHVM (3.8 and below)
1744 17
    if ($pattern === '') {
1745 1
      return array(static::create($this->str, $this->encoding));
1746
    }
1747
1748
    // UTF8::split returns the remaining unsplit string in the last index when
1749
    // supplying a limit
1750 16
    if ($limit > 0) {
1751 8
      $limit += 1;
1752 8
    } else {
1753 8
      $limit = -1;
1754
    }
1755
1756 16
    $array = preg_split('/' . preg_quote($pattern, '/') . '/u', $this->str, $limit);
1757
1758 16
    if ($limit > 0 && count($array) === $limit) {
1759 4
      array_pop($array);
1760 4
    }
1761
1762
    /** @noinspection CallableInLoopTerminationConditionInspection */
1763
    /** @noinspection ForeachInvariantsInspection */
1764 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...
1765 16
      $array[$i] = static::create($array[$i], $this->encoding);
1766 16
    }
1767
1768 16
    return $array;
1769
  }
1770
1771
  /**
1772
   * Surrounds $str with the given substring.
1773
   *
1774
   * @param  string $substring The substring to add to both sides
1775
   *
1776
   * @return Stringy Object whose $str had the substring both prepended and
1777
   *                 appended
1778
   */
1779 5
  public function surround($substring)
1780
  {
1781 5
    $str = implode('', array($substring, $this->str, $substring));
1782
1783 5
    return static::create($str, $this->encoding);
1784
  }
1785
1786
  /**
1787
   * Returns a case swapped version of the string.
1788
   *
1789
   * @return Stringy Object whose $str has each character's case swapped
1790
   */
1791 5
  public function swapCase()
1792
  {
1793 5
    $stringy = static::create($this->str, $this->encoding);
1794
1795 5
    $stringy->str = UTF8::swapCase($stringy->str, $stringy->encoding);
1796
1797 5
    return $stringy;
1798
  }
1799
1800
  /**
1801
   * Returns a string with smart quotes, ellipsis characters, and dashes from
1802
   * Windows-1252 (commonly used in Word documents) replaced by their ASCII
1803
   * equivalents.
1804
   *
1805
   * @return Stringy Object whose $str has those characters removed
1806
   */
1807 4
  public function tidy()
1808
  {
1809 4
    $str = UTF8::normalize_msword($this->str);
1810
1811 4
    return static::create($str, $this->encoding);
1812
  }
1813
1814
  /**
1815
   * Returns a trimmed string with the first letter of each word capitalized.
1816
   * Also accepts an array, $ignore, allowing you to list words not to be
1817
   * capitalized.
1818
   *
1819
   * @param  array $ignore An array of words not to capitalize
1820
   *
1821
   * @return Stringy Object with a titleized $str
1822
   */
1823 5
  public function titleize($ignore = null)
1824
  {
1825 5
    $stringy = static::create($this->trim(), $this->encoding);
1826 5
    $encoding = $this->encoding;
1827
1828 5
    $stringy->str = preg_replace_callback(
1829 5
        '/([\S]+)/u',
1830
        function ($match) use ($encoding, $ignore) {
1831 5
          if ($ignore && in_array($match[0], $ignore, true)) {
1832 2
            return $match[0];
1833
          } else {
1834 5
            $stringy = new Stringy($match[0], $encoding);
1835
1836 5
            return (string)$stringy->toLowerCase()->upperCaseFirst();
1837
          }
1838 5
        },
1839 5
        $stringy->str
1840 5
    );
1841
1842 5
    return $stringy;
1843
  }
1844
1845
  /**
1846
   * Converts all characters in the string to lowercase. An alias for PHP's
1847
   * UTF8::strtolower().
1848
   *
1849
   * @return Stringy Object with all characters of $str being lowercase
1850
   */
1851 27
  public function toLowerCase()
1852
  {
1853 27
    $str = UTF8::strtolower($this->str, $this->encoding);
1854
1855 27
    return static::create($str, $this->encoding);
1856
  }
1857
1858
  /**
1859
   * Returns true if the string is base64 encoded, false otherwise.
1860
   *
1861
   * @return bool Whether or not $str is base64 encoded
1862
   */
1863 7
  public function isBase64()
1864
  {
1865 7
    return UTF8::is_base64($this->str);
1866
  }
1867
1868
  /**
1869
   * Returns an ASCII version of the string. A set of non-ASCII characters are
1870
   * replaced with their closest ASCII counterparts, and the rest are removed
1871
   * unless instructed otherwise.
1872
   *
1873
   * @param $strict [optional] <p>Use "transliterator_transliterate()" from PHP-Intl | WARNING: bad performance</p>
1874
   *
1875
   * @return Stringy Object whose $str contains only ASCII characters
1876
   */
1877 16
  public function toAscii($strict = false)
1878
  {
1879 16
    $str = UTF8::to_ascii($this->str, '?', $strict);
1880
1881 16
    return static::create($str, $this->encoding);
1882
  }
1883
1884
  /**
1885
   * Returns a boolean representation of the given logical string value.
1886
   * For example, 'true', '1', 'on' and 'yes' will return true. 'false', '0',
1887
   * 'off', and 'no' will return false. In all instances, case is ignored.
1888
   * For other numeric strings, their sign will determine the return value.
1889
   * In addition, blank strings consisting of only whitespace will return
1890
   * false. For all other strings, the return value is a result of a
1891
   * boolean cast.
1892
   *
1893
   * @return bool A boolean value for the string
1894
   */
1895 15
  public function toBoolean()
1896
  {
1897 15
    $key = $this->toLowerCase()->str;
1898
    $map = array(
1899 15
        'true'  => true,
1900 15
        '1'     => true,
1901 15
        'on'    => true,
1902 15
        'yes'   => true,
1903 15
        'false' => false,
1904 15
        '0'     => false,
1905 15
        'off'   => false,
1906 15
        'no'    => false,
1907 15
    );
1908
1909 15
    if (array_key_exists($key, $map)) {
1910 10
      return $map[$key];
1911 5
    } elseif (is_numeric($this->str)) {
1912 2
      return ((int)$this->str > 0);
1913
    } else {
1914 3
      return (bool)$this->regexReplace('[[:space:]]', '')->str;
1915
    }
1916
  }
1917
1918
  /**
1919
   * Return Stringy object as string, but you can also use (string) for automatically casting the object into a string.
1920
   *
1921
   * @return string
1922
   */
1923 967
  public function toString()
1924
  {
1925 967
    return (string)$this->str;
1926
  }
1927
1928
  /**
1929
   * Converts each tab in the string to some number of spaces, as defined by
1930
   * $tabLength. By default, each tab is converted to 4 consecutive spaces.
1931
   *
1932
   * @param  int $tabLength Number of spaces to replace each tab with
1933
   *
1934
   * @return Stringy Object whose $str has had tabs switched to spaces
1935
   */
1936 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...
1937
  {
1938 6
    $spaces = UTF8::str_repeat(' ', $tabLength);
1939 6
    $str = UTF8::str_replace("\t", $spaces, $this->str);
1940
1941 6
    return static::create($str, $this->encoding);
1942
  }
1943
1944
  /**
1945
   * Converts each occurrence of some consecutive number of spaces, as
1946
   * defined by $tabLength, to a tab. By default, each 4 consecutive spaces
1947
   * are converted to a tab.
1948
   *
1949
   * @param  int $tabLength Number of spaces to replace with a tab
1950
   *
1951
   * @return Stringy Object whose $str has had spaces switched to tabs
1952
   */
1953 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...
1954
  {
1955 5
    $spaces = UTF8::str_repeat(' ', $tabLength);
1956 5
    $str = UTF8::str_replace($spaces, "\t", $this->str);
1957
1958 5
    return static::create($str, $this->encoding);
1959
  }
1960
1961
  /**
1962
   * Converts the first character of each word in the string to uppercase.
1963
   *
1964
   * @return Stringy Object with all characters of $str being title-cased
1965
   */
1966 5
  public function toTitleCase()
1967
  {
1968
    // "mb_convert_case()" used a polyfill from the "UTF8"-Class
1969 5
    $str = mb_convert_case($this->str, MB_CASE_TITLE, $this->encoding);
1970
1971 5
    return static::create($str, $this->encoding);
1972
  }
1973
1974
  /**
1975
   * Converts all characters in the string to uppercase. An alias for PHP's
1976
   * UTF8::strtoupper().
1977
   *
1978
   * @return Stringy Object with all characters of $str being uppercase
1979
   */
1980 5
  public function toUpperCase()
1981
  {
1982 5
    $str = UTF8::strtoupper($this->str, $this->encoding);
1983
1984 5
    return static::create($str, $this->encoding);
1985
  }
1986
1987
  /**
1988
   * Returns a string with whitespace removed from the start of the string.
1989
   * Supports the removal of unicode whitespace. Accepts an optional
1990
   * string of characters to strip instead of the defaults.
1991
   *
1992
   * @param  string $chars Optional string of characters to strip
1993
   *
1994
   * @return Stringy Object with a trimmed $str
1995
   */
1996 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...
1997
  {
1998 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...
1999 11
      $chars = '[:space:]';
2000 11
    } else {
2001 2
      $chars = preg_quote($chars, '/');
2002
    }
2003
2004 13
    return $this->regexReplace("^[$chars]+", '');
2005
  }
2006
2007
  /**
2008
   * Returns a string with whitespace removed from the end of the string.
2009
   * Supports the removal of unicode whitespace. Accepts an optional
2010
   * string of characters to strip instead of the defaults.
2011
   *
2012
   * @param  string $chars Optional string of characters to strip
2013
   *
2014
   * @return Stringy Object with a trimmed $str
2015
   */
2016 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...
2017
  {
2018 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...
2019 11
      $chars = '[:space:]';
2020 11
    } else {
2021 2
      $chars = preg_quote($chars, '/');
2022
    }
2023
2024 13
    return $this->regexReplace("[$chars]+\$", '');
2025
  }
2026
2027
  /**
2028
   * Truncates the string to a given length. If $substring is provided, and
2029
   * truncating occurs, the string is further truncated so that the substring
2030
   * may be appended without exceeding the desired length.
2031
   *
2032
   * @param  int    $length    Desired length of the truncated string
2033
   * @param  string $substring The substring to append if it can fit
2034
   *
2035
   * @return Stringy Object with the resulting $str after truncating
2036
   */
2037 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...
2038
  {
2039 22
    $stringy = static::create($this->str, $this->encoding);
2040 22
    if ($length >= $stringy->length()) {
2041 4
      return $stringy;
2042
    }
2043
2044
    // Need to further trim the string so we can append the substring
2045 18
    $substringLength = UTF8::strlen($substring, $stringy->encoding);
2046 18
    $length -= $substringLength;
2047
2048 18
    $truncated = UTF8::substr($stringy->str, 0, $length, $stringy->encoding);
2049 18
    $stringy->str = $truncated . $substring;
2050
2051 18
    return $stringy;
2052
  }
2053
2054
  /**
2055
   * Returns a lowercase and trimmed string separated by underscores.
2056
   * Underscores are inserted before uppercase characters (with the exception
2057
   * of the first character of the string), and in place of spaces as well as
2058
   * dashes.
2059
   *
2060
   * @return Stringy Object with an underscored $str
2061
   */
2062 16
  public function underscored()
2063
  {
2064 16
    return $this->delimit('_');
2065
  }
2066
2067
  /**
2068
   * Returns an UpperCamelCase version of the supplied string. It trims
2069
   * surrounding spaces, capitalizes letters following digits, spaces, dashes
2070
   * and underscores, and removes spaces, dashes, underscores.
2071
   *
2072
   * @return Stringy Object with $str in UpperCamelCase
2073
   */
2074 13
  public function upperCamelize()
2075
  {
2076 13
    return $this->camelize()->upperCaseFirst();
2077
  }
2078
2079
  /**
2080
   * Returns a camelCase version of the string. Trims surrounding spaces,
2081
   * capitalizes letters following digits, spaces, dashes and underscores,
2082
   * and removes spaces, dashes, as well as underscores.
2083
   *
2084
   * @return Stringy Object with $str in camelCase
2085
   */
2086 32
  public function camelize()
2087
  {
2088 32
    $encoding = $this->encoding;
2089 32
    $stringy = $this->trim()->lowerCaseFirst();
2090 32
    $stringy->str = preg_replace('/^[-_]+/', '', $stringy->str);
2091
2092 32
    $stringy->str = preg_replace_callback(
2093 32
        '/[-_\s]+(.)?/u',
2094
        function ($match) use ($encoding) {
2095 27
          if (isset($match[1])) {
2096 27
            return UTF8::strtoupper($match[1], $encoding);
2097
          } else {
2098 1
            return '';
2099
          }
2100 32
        },
2101 32
        $stringy->str
2102 32
    );
2103
2104 32
    $stringy->str = preg_replace_callback(
2105 32
        '/[\d]+(.)?/u',
2106
        function ($match) use ($encoding) {
2107 6
          return UTF8::strtoupper($match[0], $encoding);
2108 32
        },
2109 32
        $stringy->str
2110 32
    );
2111
2112 32
    return $stringy;
2113
  }
2114
2115
  /**
2116
   * Convert a string to e.g.: "snake_case"
2117
   *
2118
   * @return Stringy Object with $str in snake_case
2119
   */
2120 20
  public function snakeize()
2121
  {
2122 20
    $str = $this->str;
2123
2124 20
    $encoding = $this->encoding;
2125 20
    $str = UTF8::normalize_whitespace($str);
2126 20
    $str = str_replace('-', '_', $str);
2127
2128 20
    $str = preg_replace_callback(
2129 20
        '/([\d|A-Z])/u',
2130 20
        function ($matches) use ($encoding) {
2131 8
          $match = $matches[1];
2132 8
          $matchInt = (int)$match;
2133
2134 8
          if ("$matchInt" == $match) {
2135 4
            return '_' . $match . '_';
2136
          } else {
2137 4
            return '_' . UTF8::strtolower($match, $encoding);
2138
          }
2139 20
        },
2140
        $str
2141 20
    );
2142
2143 20
    $str = preg_replace(
2144
        array(
2145
2146 20
            '/\s+/',      // convert spaces to "_"
2147 20
            '/^\s+|\s+$/',  // trim leading & trailing spaces
2148 20
            '/_+/',         // remove double "_"
2149 20
        ),
2150
        array(
2151 20
            '_',
2152 20
            '',
2153 20
            '_',
2154 20
        ),
2155
        $str
2156 20
    );
2157
2158 20
    $str = UTF8::trim($str, '_'); // trim leading & trailing "_"
2159 20
    $str = UTF8::trim($str); // trim leading & trailing whitespace
2160
2161 20
    return static::create($str, $this->encoding);
2162
  }
2163
2164
  /**
2165
   * Converts the first character of the string to lower case.
2166
   *
2167
   * @return Stringy Object with the first character of $str being lower case
2168
   */
2169 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...
2170
  {
2171 37
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
2172 37
    $rest = UTF8::substr($this->str, 1, $this->length() - 1, $this->encoding);
2173
2174 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 2171 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...
2175
2176 37
    return static::create($str, $this->encoding);
2177
  }
2178
2179
  /**
2180
   * Shorten the string after $length, but also after the next word.
2181
   *
2182
   * @param int    $length
2183
   * @param string $strAddOn
2184
   *
2185
   * @return Stringy
2186
   */
2187 4
  public function shortenAfterWord($length, $strAddOn = '...')
2188
  {
2189 4
    $string = UTF8::str_limit_after_word($this->str, $length, $strAddOn);
2190
2191 4
    return static::create($string);
2192
  }
2193
2194
  /**
2195
   * Line-Wrap the string after $limit, but also after the next word.
2196
   *
2197
   * @param int $limit
2198
   *
2199
   * @return Stringy
2200
   */
2201 1
  public function lineWrapAfterWord($limit)
2202
  {
2203 1
    $strings = preg_split('/\\r\\n|\\r|\\n/', $this->str);
2204
2205 1
    $string = '';
2206 1
    foreach ($strings as $value) {
2207 1
      $string .= wordwrap($value, $limit);
2208 1
      $string .= "\n";
2209 1
    }
2210
2211 1
    return static::create($string);
2212
  }
2213
2214
  /**
2215
   * Gets the substring after the first occurrence of a separator.
2216
   * If no match is found returns new empty Stringy object.
2217
   *
2218
   * @param string $separator
2219
   *
2220
   * @return Stringy
2221
   */
2222 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...
2223
  {
2224 1
    if (($offset = $this->indexOf($separator)) === false) {
2225 1
      return static::create('');
2226
    }
2227
2228 1
    return static::create(
2229 1
        UTF8::substr(
2230 1
            $this->str,
2231 1
            $offset + UTF8::strlen($separator, $this->encoding),
2232 1
            null,
2233 1
            $this->encoding
2234 1
        ),
2235 1
        $this->encoding
2236 1
    );
2237
  }
2238
2239
  /**
2240
   * Gets the substring after the last occurrence of a separator.
2241
   * If no match is found returns new empty Stringy object.
2242
   *
2243
   * @param string $separator
2244
   *
2245
   * @return Stringy
2246
   */
2247 1
  public function afterLast($separator)
2248
  {
2249 1
    $offset = $this->indexOfLast($separator);
2250 1
    if ($offset === false) {
2251 1
      return static::create('', $this->encoding);
2252
    }
2253
2254 1
    return static::create(
2255 1
        UTF8::substr(
2256 1
            $this->str,
2257 1
            $offset + UTF8::strlen($separator, $this->encoding),
2258 1
            null,
2259 1
            $this->encoding
2260 1
        ),
2261 1
        $this->encoding
2262 1
    );
2263
  }
2264
2265
  /**
2266
   * Gets the substring before the first occurrence of a separator.
2267
   * If no match is found returns new empty Stringy object.
2268
   *
2269
   * @param string $separator
2270
   *
2271
   * @return Stringy
2272
   */
2273 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...
2274
  {
2275 1
    $offset = $this->indexOf($separator);
2276 1
    if ($offset === false) {
2277 1
      return static::create('', $this->encoding);
2278
    }
2279
2280 1
    return static::create(
2281 1
        UTF8::substr(
2282 1
            $this->str,
2283 1
            0,
2284 1
            $offset,
2285 1
            $this->encoding
2286 1
        ),
2287 1
        $this->encoding
2288 1
    );
2289
  }
2290
2291
  /**
2292
   * Gets the substring before the last occurrence of a separator.
2293
   * If no match is found returns new empty Stringy object.
2294
   *
2295
   * @param string $separator
2296
   *
2297
   * @return Stringy
2298
   */
2299 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...
2300
  {
2301 1
    $offset = $this->indexOfLast($separator);
2302 1
    if ($offset === false) {
2303 1
      return static::create('', $this->encoding);
2304
    }
2305
2306 1
    return static::create(
2307 1
        UTF8::substr(
2308 1
            $this->str,
2309 1
            0,
2310 1
            $offset,
2311 1
            $this->encoding
2312 1
        ),
2313 1
        $this->encoding
2314 1
    );
2315
  }
2316
2317
  /**
2318
   * Returns the string with the first letter of each word capitalized,
2319
   * except for when the word is a name which shouldn't be capitalized.
2320
   *
2321
   * @return Stringy Object with $str capitalized
2322
   */
2323 39
  public function capitalizePersonalName()
2324
  {
2325 39
    $stringy = $this->collapseWhitespace();
2326 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...
2327 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...
2328
2329 39
    return static::create($stringy, $this->encoding);
2330
  }
2331
2332
  /**
2333
   * @param string $word
2334
   *
2335
   * @return string
2336
   */
2337 7
  private function capitalizeWord($word)
2338
  {
2339 7
    $encoding = $this->encoding;
2340
2341 7
    $firstCharacter = UTF8::substr($word, 0, 1, $encoding);
2342 7
    $restOfWord = UTF8::substr($word, 1, null, $encoding);
2343 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 2341 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...
2344
2345 7
    return new static($firstCharacterUppercased . $restOfWord, $encoding);
2346
  }
2347
2348
  /**
2349
   * Personal names such as "Marcus Aurelius" are sometimes typed incorrectly using lowercase ("marcus aurelius").
2350
   *
2351
   * @param string $names
2352
   * @param string $delimiter
2353
   *
2354
   * @return string
2355
   */
2356 39
  private function capitalizePersonalNameByDelimiter($names, $delimiter)
2357
  {
2358
    // init
2359 39
    $names = explode($delimiter, $names);
2360 39
    $encoding = $this->encoding;
2361
2362
    $specialCases = array(
2363
        'names'    => array(
2364 39
            'ab',
2365 39
            'af',
2366 39
            'al',
2367 39
            'and',
2368 39
            'ap',
2369 39
            'bint',
2370 39
            'binte',
2371 39
            'da',
2372 39
            'de',
2373 39
            'del',
2374 39
            'den',
2375 39
            'der',
2376 39
            'di',
2377 39
            'dit',
2378 39
            'ibn',
2379 39
            'la',
2380 39
            'mac',
2381 39
            'nic',
2382 39
            'of',
2383 39
            'ter',
2384 39
            'the',
2385 39
            'und',
2386 39
            'van',
2387 39
            'von',
2388 39
            'y',
2389 39
            'zu',
2390 39
        ),
2391
        'prefixes' => array(
2392 39
            'al-',
2393 39
            "d'",
2394 39
            'ff',
2395 39
            "l'",
2396 39
            'mac',
2397 39
            'mc',
2398 39
            'nic',
2399 39
        ),
2400 39
    );
2401
2402 39
    foreach ($names as &$name) {
2403 39
      if (in_array($name, $specialCases['names'], true)) {
2404 27
        continue;
2405
      }
2406
2407 13
      $continue = false;
2408
2409 13
      if ($delimiter == '-') {
2410 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...
2411 13
          if (UTF8::strpos($name, $beginning, null, $encoding) === 0) {
2412 2
            $continue = true;
2413 2
          }
2414 13
        }
2415 13
      }
2416
2417 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...
2418 13
        if (UTF8::strpos($name, $beginning, null, $encoding) === 0) {
2419 7
          $continue = true;
2420 7
        }
2421 13
      }
2422
2423 13
      if ($continue) {
2424 7
        continue;
2425
      }
2426
2427 7
      $name = $this->capitalizeWord($name);
2428 39
    }
2429
2430 39
    return new static(implode($delimiter, $names), $encoding);
2431
  }
2432
}
2433