Completed
Push — master ( 04385c...e94879 )
by Lars
04:50
created

Stringy::trim()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 10
Ratio 100 %

Code Coverage

Tests 6
CRAP Score 2

Importance

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

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

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

Loading history...
2232
  {
2233 1
    $offset = $this->indexOfLast($separator);
2234 1
    if ($offset === false) {
2235 1
      return static::create('');
2236
    }
2237
2238 1
    return static::create(
2239 1
        UTF8::substr(
2240 1
            $this->str,
2241 1
            $offset + UTF8::strlen($separator, $this->encoding),
2242 1
            null,
2243 1
            $this->encoding
2244 1
        ),
2245 1
        $this->encoding
2246 1
    );
2247
  }
2248
2249
  /**
2250
   * Gets the substring before the first occurrence of a separator.
2251
   * If no match is found returns new empty Stringy object.
2252
   *
2253
   * @param string $separator
2254
   *
2255
   * @return Stringy
2256
   */
2257 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...
2258
  {
2259 1
    $offset = $this->indexOf($separator);
2260 1
    if ($offset === false) {
2261 1
      return static::create('');
2262
    }
2263
2264 1
    return static::create(
2265 1
        UTF8::substr(
2266 1
            $this->str,
2267 1
            0,
2268 1
            $offset,
2269 1
            $this->encoding
2270 1
        ),
2271 1
        $this->encoding
2272 1
    );
2273
  }
2274
2275
  /**
2276
   * Gets the substring before the last occurrence of a separator.
2277
   * If no match is found returns new empty Stringy object.
2278
   *
2279
   * @param string $separator
2280
   *
2281
   * @return Stringy
2282
   */
2283 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...
2284
  {
2285 1
    $offset = $this->indexOfLast($separator);
2286 1
    if ($offset === false) {
2287 1
      return static::create('');
2288
    }
2289
2290 1
    return static::create(
2291 1
        UTF8::substr(
2292 1
            $this->str,
2293 1
            0,
2294 1
            $offset,
2295 1
            $this->encoding
2296 1
        ),
2297 1
        $this->encoding
2298 1
    );
2299
  }
2300
}
2301