Completed
Push — master ( ec3ed7...a29976 )
by Lars
04:23
created

Stringy::applyPadding()   B

Complexity

Conditions 3
Paths 2

Size

Total Lines 37
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 3

Importance

Changes 6
Bugs 1 Features 1
Metric Value
c 6
b 1
f 1
dl 0
loc 37
ccs 27
cts 27
cp 1
rs 8.8571
cc 3
eloc 23
nc 2
nop 3
crap 3
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 1005
  public function __construct($str = '', $encoding = null)
45
  {
46 1005
    if (is_array($str)) {
47 1
      throw new \InvalidArgumentException(
48
          'Passed value cannot be an array'
49 1
      );
50 1004
    } 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 1003
    if (!defined('ENT_SUBSTITUTE')) {
58
      define('ENT_SUBSTITUTE', 8);
59
    }
60
61
    // init
62 1003
    UTF8::checkForSupport();
63 1003
    $this->str = (string)$str;
64
65 1003
    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 797
      $this->encoding = $encoding;
67 797
    } else {
68 636
      UTF8::mbstring_loaded();
69 636
      $this->encoding = mb_internal_encoding();
70
    }
71
72 1003
    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 797
      $this->encoding = $encoding;
74 797
    } else {
75 636
      $this->encoding = mb_internal_encoding();
76
    }
77 1003
  }
78
79
  /**
80
   * Returns the value in $str.
81
   *
82
   * @return string The current value of the $str property
83
   */
84 964
  public function __toString()
85
  {
86 964
    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 2
  public function append($string)
97
  {
98 2
    return static::create($this->str . $string, $this->encoding);
99
  }
100
101
  /**
102
   * Creates a Stringy object and assigns both str and encoding properties
103
   * the supplied values. $str is cast to a string prior to assignment, and if
104
   * $encoding is not specified, it defaults to mb_internal_encoding(). It
105
   * then returns the initialized object. Throws an InvalidArgumentException
106
   * if the first argument is an array or object without a __toString method.
107
   *
108
   * @param  mixed  $str      Value to modify, after being cast to string
109
   * @param  string $encoding The character encoding
110
   *
111
   * @return Stringy A Stringy object
112
   * @throws \InvalidArgumentException if an array or object without a
113
   *         __toString method is passed as the first argument
114
   */
115 995
  public static function create($str = '', $encoding = null)
116
  {
117 995
    return new static($str, $encoding);
118
  }
119
120
  /**
121
   * Returns the substring between $start and $end, if found, or an empty
122
   * string. An optional offset may be supplied from which to begin the
123
   * search for the start string.
124
   *
125
   * @param  string $start  Delimiter marking the start of the substring
126
   * @param  string $end    Delimiter marketing the end of the substring
127
   * @param  int    $offset Index from which to begin the search
128
   *
129
   * @return Stringy Object whose $str has been converted to an URL slug
130
   */
131 16
  public function between($start, $end, $offset = 0)
132
  {
133 16
    $startIndex = $this->indexOf($start, $offset);
134 16
    if ($startIndex === false) {
135 2
      return static::create('', $this->encoding);
136
    }
137
138 14
    $substrIndex = $startIndex + UTF8::strlen($start, $this->encoding);
139 14
    $endIndex = $this->indexOf($end, $substrIndex);
140 14
    if ($endIndex === false) {
141 2
      return static::create('', $this->encoding);
142
    }
143
144 12
    return $this->substr($substrIndex, $endIndex - $substrIndex);
145
  }
146
147
  /**
148
   * Returns the index of the first occurrence of $needle in the string,
149
   * and false if not found. Accepts an optional offset from which to begin
150
   * the search.
151
   *
152
   * @param  string $needle Substring to look for
153
   * @param  int    $offset Offset from which to search
154
   *
155
   * @return int|bool The occurrence's index if found, otherwise false
156
   */
157 26
  public function indexOf($needle, $offset = 0)
158
  {
159 26
    return UTF8::strpos($this->str, (string)$needle, (int)$offset, $this->encoding);
160
  }
161
162
  /**
163
   * Returns the substring beginning at $start with the specified $length.
164
   * It differs from the UTF8::substr() function in that providing a $length of
165
   * null will return the rest of the string, rather than an empty string.
166
   *
167
   * @param  int $start  Position of the first character to use
168
   * @param  int $length Maximum number of characters used
169
   *
170
   * @return Stringy Object with its $str being the substring
171
   */
172 66
  public function substr($start, $length = null)
173
  {
174 66
    if ($length === null) {
175 21
      $length = $this->length();
176 21
    }
177
178 66
    $str = UTF8::substr($this->str, $start, $length, $this->encoding);
179
180 66
    return static::create($str, $this->encoding);
181
  }
182
183
  /**
184
   * Returns the length of the string.
185
   *
186
   * @return int The number of characters in $str given the encoding
187
   */
188 247
  public function length()
189
  {
190 247
    return UTF8::strlen($this->str, $this->encoding);
191
  }
192
193
  /**
194
   * Trims the string and replaces consecutive whitespace characters with a
195
   * single space. This includes tabs and newline characters, as well as
196
   * multibyte whitespace such as the thin space and ideographic space.
197
   *
198
   * @return Stringy Object with a trimmed $str and condensed whitespace
199
   */
200 13
  public function collapseWhitespace()
201
  {
202 13
    return $this->regexReplace('[[:space:]]+', ' ')->trim();
203
  }
204
205
  /**
206
   * Returns a string with whitespace removed from the start and end of the
207
   * string. Supports the removal of unicode whitespace. Accepts an optional
208
   * string of characters to strip instead of the defaults.
209
   *
210
   * @param  string $chars Optional string of characters to strip
211
   *
212
   * @return Stringy Object with a trimmed $str
213
   */
214 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...
215
  {
216 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...
217 113
      $chars = '[:space:]';
218 113
    } else {
219 1
      $chars = preg_quote($chars, '/');
220
    }
221
222 114
    return $this->regexReplace("^[$chars]+|[$chars]+\$", '');
223
  }
224
225
  /**
226
   * Replaces all occurrences of $pattern in $str by $replacement.
227
   *
228
   * @param  string $pattern     The regular expression pattern
229
   * @param  string $replacement The string to replace with
230
   * @param  string $options     Matching conditions to be used
231
   *
232
   * @return Stringy Object with the result2ing $str after the replacements
233
   */
234 183
  public function regexReplace($pattern, $replacement, $options = '')
235
  {
236 183
    if ($options === 'msr') {
237 7
      $options = 'ms';
238 7
    }
239
240 183
    $str = preg_replace(
241 183
        '/' . $pattern . '/u' . $options,
242 183
        $replacement,
243 183
        $this->str
244 183
    );
245
246 183
    return static::create($str, $this->encoding);
247
  }
248
249
  /**
250
   * Returns true if the string contains all $needles, false otherwise. By
251
   * default the comparison is case-sensitive, but can be made insensitive by
252
   * setting $caseSensitive to false.
253
   *
254
   * @param  array $needles       SubStrings to look for
255
   * @param  bool  $caseSensitive Whether or not to enforce case-sensitivity
256
   *
257
   * @return bool   Whether or not $str contains $needle
258
   */
259 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...
260
  {
261
    /** @noinspection IsEmptyFunctionUsageInspection */
262 43
    if (empty($needles)) {
263 1
      return false;
264
    }
265
266 42
    foreach ($needles as $needle) {
267 42
      if (!$this->contains($needle, $caseSensitive)) {
268 18
        return false;
269
      }
270 24
    }
271
272 24
    return true;
273
  }
274
275
  /**
276
   * Returns true if the string contains $needle, false otherwise. By default
277
   * the comparison is case-sensitive, but can be made insensitive by setting
278
   * $caseSensitive to false.
279
   *
280
   * @param  string $needle        Substring to look for
281
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
282
   *
283
   * @return bool   Whether or not $str contains $needle
284
   */
285 105
  public function contains($needle, $caseSensitive = true)
286
  {
287 105
    $encoding = $this->encoding;
288
289 105
    if ($caseSensitive) {
290 55
      return (UTF8::strpos($this->str, $needle, 0, $encoding) !== false);
291
    } else {
292 50
      return (UTF8::stripos($this->str, $needle, 0, $encoding) !== false);
293
    }
294
  }
295
296
  /**
297
   * Returns true if the string contains any $needles, false otherwise. By
298
   * default the comparison is case-sensitive, but can be made insensitive by
299
   * setting $caseSensitive to false.
300
   *
301
   * @param  array $needles       SubStrings to look for
302
   * @param  bool  $caseSensitive Whether or not to enforce case-sensitivity
303
   *
304
   * @return bool   Whether or not $str contains $needle
305
   */
306 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...
307
  {
308
    /** @noinspection IsEmptyFunctionUsageInspection */
309 43
    if (empty($needles)) {
310 1
      return false;
311
    }
312
313 42
    foreach ($needles as $needle) {
314 42
      if ($this->contains($needle, $caseSensitive)) {
315 24
        return true;
316
      }
317 18
    }
318
319 18
    return false;
320
  }
321
322
  /**
323
   * Returns the length of the string, implementing the countable interface.
324
   *
325
   * @return int The number of characters in the string, given the encoding
326
   */
327 1
  public function count()
328
  {
329 1
    return $this->length();
330
  }
331
332
  /**
333
   * Returns the number of occurrences of $substring in the given string.
334
   * By default, the comparison is case-sensitive, but can be made insensitive
335
   * by setting $caseSensitive to false.
336
   *
337
   * @param  string $substring     The substring to search for
338
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
339
   *
340
   * @return int    The number of $substring occurrences
341
   */
342 15
  public function countSubstr($substring, $caseSensitive = true)
343
  {
344 15
    if ($caseSensitive) {
345 9
      return UTF8::substr_count($this->str, $substring, $this->encoding);
346
    }
347
348 6
    $str = UTF8::strtoupper($this->str, $this->encoding);
349 6
    $substring = UTF8::strtoupper($substring, $this->encoding);
350
351 6
    return UTF8::substr_count($str, $substring, $this->encoding);
352
  }
353
354
  /**
355
   * Returns a lowercase and trimmed string separated by dashes. Dashes are
356
   * inserted before uppercase characters (with the exception of the first
357
   * character of the string), and in place of spaces as well as underscores.
358
   *
359
   * @return Stringy Object with a dasherized $str
360
   */
361 19
  public function dasherize()
362
  {
363 19
    return $this->delimit('-');
364
  }
365
366
  /**
367
   * Returns a lowercase and trimmed string separated by the given delimiter.
368
   * Delimiters are inserted before uppercase characters (with the exception
369
   * of the first character of the string), and in place of spaces, dashes,
370
   * and underscores. Alpha delimiters are not converted to lowercase.
371
   *
372
   * @param  string $delimiter Sequence used to separate parts of the string
373
   *
374
   * @return Stringy Object with a delimited $str
375
   */
376 49
  public function delimit($delimiter)
377
  {
378 49
    $str = $this->trim();
379
380 49
    $str = preg_replace('/\B([A-Z])/u', '-\1', $str);
381
382 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...
383
384 49
    $str = preg_replace('/[-_\s]+/u', $delimiter, $str);
385
386 49
    return static::create($str, $this->encoding);
387
  }
388
389
  /**
390
   * Ensures that the string begins with $substring. If it doesn't, it's
391
   * prepended.
392
   *
393
   * @param  string $substring The substring to add if not present
394
   *
395
   * @return Stringy Object with its $str prefixed by the $substring
396
   */
397 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...
398
  {
399 10
    $stringy = static::create($this->str, $this->encoding);
400
401 10
    if (!$stringy->startsWith($substring)) {
402 4
      $stringy->str = $substring . $stringy->str;
403 4
    }
404
405 10
    return $stringy;
406
  }
407
408
  /**
409
   * Returns true if the string begins with $substring, false otherwise. By
410
   * default, the comparison is case-sensitive, but can be made insensitive
411
   * by setting $caseSensitive to false.
412
   *
413
   * @param  string $substring     The substring to look for
414
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
415
   *
416
   * @return bool   Whether or not $str starts with $substring
417
   */
418 33
  public function startsWith($substring, $caseSensitive = true)
419
  {
420 33
    $substringLength = UTF8::strlen($substring, $this->encoding);
421 33
    $startOfStr = UTF8::substr(
422 33
        $this->str, 0, $substringLength,
423 33
        $this->encoding
424 33
    );
425
426 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...
427 4
      $substring = UTF8::strtolower($substring, $this->encoding);
428 4
      $startOfStr = UTF8::strtolower($startOfStr, $this->encoding);
429 4
    }
430
431 33
    return (string)$substring === $startOfStr;
432
  }
433
434
  /**
435
   * Ensures that the string ends with $substring. If it doesn't, it's
436
   * appended.
437
   *
438
   * @param  string $substring The substring to add if not present
439
   *
440
   * @return Stringy Object with its $str suffixed by the $substring
441
   */
442 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...
443
  {
444 10
    $stringy = static::create($this->str, $this->encoding);
445
446 10
    if (!$stringy->endsWith($substring)) {
447 4
      $stringy->str .= $substring;
448 4
    }
449
450 10
    return $stringy;
451
  }
452
453
  /**
454
   * Returns true if the string ends with $substring, false otherwise. By
455
   * default, the comparison is case-sensitive, but can be made insensitive
456
   * by setting $caseSensitive to false.
457
   *
458
   * @param  string $substring     The substring to look for
459
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
460
   *
461
   * @return bool   Whether or not $str ends with $substring
462
   */
463 33
  public function endsWith($substring, $caseSensitive = true)
464
  {
465 33
    $substringLength = UTF8::strlen($substring, $this->encoding);
466 33
    $strLength = $this->length();
467
468 33
    $endOfStr = UTF8::substr(
469 33
        $this->str,
470 33
        $strLength - $substringLength,
471 33
        $substringLength,
472 33
        $this->encoding
473 33
    );
474
475 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...
476 4
      $substring = UTF8::strtolower($substring, $this->encoding);
477 4
      $endOfStr = UTF8::strtolower($endOfStr, $this->encoding);
478 4
    }
479
480 33
    return (string)$substring === $endOfStr;
481
  }
482
483
  /**
484
   * Returns the first $n characters of the string.
485
   *
486
   * @param  int $n Number of characters to retrieve from the start
487
   *
488
   * @return Stringy Object with its $str being the first $n chars
489
   */
490 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...
491
  {
492 12
    $stringy = static::create($this->str, $this->encoding);
493
494 12
    if ($n < 0) {
495 2
      $stringy->str = '';
496 2
    } else {
497 10
      return $stringy->substr(0, $n);
498
    }
499
500 2
    return $stringy;
501
  }
502
503
  /**
504
   * Returns the encoding used by the Stringy object.
505
   *
506
   * @return string The current value of the $encoding property
507
   */
508 3
  public function getEncoding()
509
  {
510 3
    return $this->encoding;
511
  }
512
513
  /**
514
   * Returns a new ArrayIterator, thus implementing the IteratorAggregate
515
   * interface. The ArrayIterator's constructor is passed an array of chars
516
   * in the multibyte string. This enables the use of foreach with instances
517
   * of Stringy\Stringy.
518
   *
519
   * @return \ArrayIterator An iterator for the characters in the string
520
   */
521 1
  public function getIterator()
522
  {
523 1
    return new \ArrayIterator($this->chars());
524
  }
525
526
  /**
527
   * Returns an array consisting of the characters in the string.
528
   *
529
   * @return array An array of string chars
530
   */
531 4
  public function chars()
532
  {
533
    // init
534 4
    $chars = array();
535 4
    $l = $this->length();
536
537 4
    for ($i = 0; $i < $l; $i++) {
538 3
      $chars[] = $this->at($i)->str;
539 3
    }
540
541 4
    return $chars;
542
  }
543
544
  /**
545
   * Returns the character at $index, with indexes starting at 0.
546
   *
547
   * @param  int $index Position of the character
548
   *
549
   * @return Stringy The character at $index
550
   */
551 11
  public function at($index)
552
  {
553 11
    return $this->substr($index, 1);
554
  }
555
556
  /**
557
   * Returns true if the string contains a lower case char, false
558
   * otherwise.
559
   *
560
   * @return bool Whether or not the string contains a lower case character.
561
   */
562 12
  public function hasLowerCase()
563
  {
564 12
    return $this->matchesPattern('.*[[:lower:]]');
565
  }
566
567
  /**
568
   * Returns true if $str matches the supplied pattern, false otherwise.
569
   *
570
   * @param  string $pattern Regex pattern to match against
571
   *
572
   * @return bool   Whether or not $str matches the pattern
573
   */
574 91
  private function matchesPattern($pattern)
575
  {
576 91
    if (preg_match('/' . $pattern . '/u', $this->str)) {
577 54
      return true;
578
    } else {
579 37
      return false;
580
    }
581
  }
582
583
  /**
584
   * Returns true if the string contains an upper case char, false
585
   * otherwise.
586
   *
587
   * @return bool Whether or not the string contains an upper case character.
588
   */
589 12
  public function hasUpperCase()
590
  {
591 12
    return $this->matchesPattern('.*[[:upper:]]');
592
  }
593
594
  /**
595
   * Convert all HTML entities to their applicable characters.
596
   *
597
   * @param  int|null $flags Optional flags
598
   *
599
   * @return Stringy  Object with the resulting $str after being html decoded.
600
   */
601 5
  public function htmlDecode($flags = ENT_COMPAT)
602
  {
603 5
    $str = UTF8::html_entity_decode($this->str, $flags, $this->encoding);
604
605 5
    return static::create($str, $this->encoding);
606
  }
607
608
  /**
609
   * Convert all applicable characters to HTML entities.
610
   *
611
   * @param  int|null $flags Optional flags
612
   *
613
   * @return Stringy  Object with the resulting $str after being html encoded.
614
   */
615 5
  public function htmlEncode($flags = ENT_COMPAT)
616
  {
617 5
    $str = UTF8::htmlentities($this->str, $flags, $this->encoding);
618
619 5
    return static::create($str, $this->encoding);
620
  }
621
622
  /**
623
   * Capitalizes the first word of the string, replaces underscores with
624
   * spaces, and strips '_id'.
625
   *
626
   * @return Stringy Object with a humanized $str
627
   */
628 3
  public function humanize()
629
  {
630 3
    $str = UTF8::str_replace(array('_id', '_'), array('', ' '), $this->str);
631
632 3
    return static::create($str, $this->encoding)->trim()->upperCaseFirst();
633
  }
634
635
  /**
636
   * Converts the first character of the supplied string to upper case.
637
   *
638
   * @return Stringy Object with the first character of $str being upper case
639
   */
640 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...
641
  {
642 27
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
643 27
    $rest = UTF8::substr(
644 27
        $this->str,
645 27
        1,
646 27
        $this->length() - 1,
647 27
        $this->encoding
648 27
    );
649
650 27
    $str = UTF8::strtoupper($first, $this->encoding) . $rest;
651
652 27
    return static::create($str, $this->encoding);
653
  }
654
655
  /**
656
   * Returns the index of the last occurrence of $needle in the string,
657
   * and false if not found. Accepts an optional offset from which to begin
658
   * the search. Offsets may be negative to count from the last character
659
   * in the string.
660
   *
661
   * @param  string $needle Substring to look for
662
   * @param  int    $offset Offset from which to search
663
   *
664
   * @return int|bool The last occurrence's index if found, otherwise false
665
   */
666 10
  public function indexOfLast($needle, $offset = 0)
667
  {
668 10
    return UTF8::strrpos($this->str, (string)$needle, (int)$offset, $this->encoding);
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...
669
  }
670
671
  /**
672
   * Inserts $substring into the string at the $index provided.
673
   *
674
   * @param  string $substring String to be inserted
675
   * @param  int    $index     The index at which to insert the substring
676
   *
677
   * @return Stringy Object with the resulting $str after the insertion
678
   */
679 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...
680
  {
681 8
    $stringy = static::create($this->str, $this->encoding);
682 8
    if ($index > $stringy->length()) {
683 1
      return $stringy;
684
    }
685
686 7
    $start = UTF8::substr($stringy->str, 0, $index, $stringy->encoding);
687 7
    $end = UTF8::substr(
688 7
        $stringy->str, $index, $stringy->length(),
689 7
        $stringy->encoding
690 7
    );
691
692 7
    $stringy->str = $start . $substring . $end;
693
694 7
    return $stringy;
695
  }
696
697
  /**
698
   * Returns true if the string contains only alphabetic chars, false
699
   * otherwise.
700
   *
701
   * @return bool Whether or not $str contains only alphabetic chars
702
   */
703 10
  public function isAlpha()
704
  {
705 10
    return $this->matchesPattern('^[[:alpha:]]*$');
706
  }
707
708
  /**
709
   * Returns true if the string contains only alphabetic and numeric chars,
710
   * false otherwise.
711
   *
712
   * @return bool Whether or not $str contains only alphanumeric chars
713
   */
714 13
  public function isAlphanumeric()
715
  {
716 13
    return $this->matchesPattern('^[[:alnum:]]*$');
717
  }
718
719
  /**
720
   * Returns true if the string contains only whitespace chars, false
721
   * otherwise.
722
   *
723
   * @return bool Whether or not $str contains only whitespace characters
724
   */
725 15
  public function isBlank()
726
  {
727 15
    return $this->matchesPattern('^[[:space:]]*$');
728
  }
729
730
  /**
731
   * Returns true if the string contains only hexadecimal chars, false
732
   * otherwise.
733
   *
734
   * @return bool Whether or not $str contains only hexadecimal chars
735
   */
736 13
  public function isHexadecimal()
737
  {
738 13
    return $this->matchesPattern('^[[:xdigit:]]*$');
739
  }
740
741
  /**
742
   * Returns true if the string is JSON, false otherwise. Unlike json_decode
743
   * in PHP 5.x, this method is consistent with PHP 7 and other JSON parsers,
744
   * in that an empty string is not considered valid JSON.
745
   *
746
   * @return bool Whether or not $str is JSON
747
   */
748 20
  public function isJson()
749
  {
750 20
    if (!isset($this->str[0])) {
751 1
      return false;
752
    }
753
754 19
    json_decode($this->str);
755
756 19
    if (json_last_error() === JSON_ERROR_NONE) {
757 11
      return true;
758
    } else {
759 8
      return false;
760
    }
761
  }
762
763
  /**
764
   * Returns true if the string contains only lower case chars, false
765
   * otherwise.
766
   *
767
   * @return bool Whether or not $str contains only lower case characters
768
   */
769 8
  public function isLowerCase()
770
  {
771 8
    if ($this->matchesPattern('^[[:lower:]]*$')) {
772 3
      return true;
773
    } else {
774 5
      return false;
775
    }
776
  }
777
778
  /**
779
   * Returns true if the string is serialized, false otherwise.
780
   *
781
   * @return bool Whether or not $str is serialized
782
   */
783 7
  public function isSerialized()
784
  {
785 7
    if (!isset($this->str[0])) {
786 1
      return false;
787
    }
788
789
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
790
    if (
791 6
        $this->str === 'b:0;'
792 6
        ||
793 6
        @unserialize($this->str) !== false
794 6
    ) {
795 4
      return true;
796
    } else {
797 2
      return false;
798
    }
799
  }
800
801
  /**
802
   * Returns true if the string contains only lower case chars, false
803
   * otherwise.
804
   *
805
   * @return bool Whether or not $str contains only lower case characters
806
   */
807 8
  public function isUpperCase()
808
  {
809 8
    return $this->matchesPattern('^[[:upper:]]*$');
810
  }
811
812
  /**
813
   * Returns the last $n characters of the string.
814
   *
815
   * @param  int $n Number of characters to retrieve from the end
816
   *
817
   * @return Stringy Object with its $str being the last $n chars
818
   */
819 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...
820
  {
821 12
    $stringy = static::create($this->str, $this->encoding);
822
823 12
    if ($n <= 0) {
824 4
      $stringy->str = '';
825 4
    } else {
826 8
      return $stringy->substr(-$n);
827
    }
828
829 4
    return $stringy;
830
  }
831
832
  /**
833
   * Splits on newlines and carriage returns, returning an array of Stringy
834
   * objects corresponding to the lines in the string.
835
   *
836
   * @return Stringy[] An array of Stringy objects
837
   */
838 15
  public function lines()
839
  {
840 15
    $array = preg_split('/[\r\n]{1,2}/u', $this->str);
841
    /** @noinspection CallableInLoopTerminationConditionInspection */
842
    /** @noinspection ForeachInvariantsInspection */
843 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...
844 15
      $array[$i] = static::create($array[$i], $this->encoding);
845 15
    }
846
847 15
    return $array;
848
  }
849
850
  /**
851
   * Returns the longest common prefix between the string and $otherStr.
852
   *
853
   * @param  string $otherStr Second string for comparison
854
   *
855
   * @return Stringy Object with its $str being the longest common prefix
856
   */
857 10
  public function longestCommonPrefix($otherStr)
858
  {
859 10
    $encoding = $this->encoding;
860 10
    $maxLength = min($this->length(), UTF8::strlen($otherStr, $encoding));
861
862 10
    $longestCommonPrefix = '';
863 10
    for ($i = 0; $i < $maxLength; $i++) {
864 8
      $char = UTF8::substr($this->str, $i, 1, $encoding);
865
866 8
      if ($char == UTF8::substr($otherStr, $i, 1, $encoding)) {
867 6
        $longestCommonPrefix .= $char;
868 6
      } else {
869 6
        break;
870
      }
871 6
    }
872
873 10
    return static::create($longestCommonPrefix, $encoding);
874
  }
875
876
  /**
877
   * Returns the longest common suffix between the string and $otherStr.
878
   *
879
   * @param  string $otherStr Second string for comparison
880
   *
881
   * @return Stringy Object with its $str being the longest common suffix
882
   */
883 10
  public function longestCommonSuffix($otherStr)
884
  {
885 10
    $encoding = $this->encoding;
886 10
    $maxLength = min($this->length(), UTF8::strlen($otherStr, $encoding));
887
888 10
    $longestCommonSuffix = '';
889 10
    for ($i = 1; $i <= $maxLength; $i++) {
890 8
      $char = UTF8::substr($this->str, -$i, 1, $encoding);
891
892 8
      if ($char == UTF8::substr($otherStr, -$i, 1, $encoding)) {
893 6
        $longestCommonSuffix = $char . $longestCommonSuffix;
894 6
      } else {
895 6
        break;
896
      }
897 6
    }
898
899 10
    return static::create($longestCommonSuffix, $encoding);
900
  }
901
902
  /**
903
   * Returns the longest common substring between the string and $otherStr.
904
   * In the case of ties, it returns that which occurs first.
905
   *
906
   * @param  string $otherStr Second string for comparison
907
   *
908
   * @return Stringy Object with its $str being the longest common substring
909
   */
910 10
  public function longestCommonSubstring($otherStr)
911
  {
912
    // Uses dynamic programming to solve
913
    // http://en.wikipedia.org/wiki/Longest_common_substring_problem
914 10
    $encoding = $this->encoding;
915 10
    $stringy = static::create($this->str, $encoding);
916 10
    $strLength = $stringy->length();
917 10
    $otherLength = UTF8::strlen($otherStr, $encoding);
918
919
    // Return if either string is empty
920 10
    if ($strLength == 0 || $otherLength == 0) {
921 2
      $stringy->str = '';
922
923 2
      return $stringy;
924
    }
925
926 8
    $len = 0;
927 8
    $end = 0;
928 8
    $table = array_fill(
929 8
        0, $strLength + 1,
930 8
        array_fill(0, $otherLength + 1, 0)
931 8
    );
932
933 8
    for ($i = 1; $i <= $strLength; $i++) {
934 8
      for ($j = 1; $j <= $otherLength; $j++) {
935 8
        $strChar = UTF8::substr($stringy->str, $i - 1, 1, $encoding);
936 8
        $otherChar = UTF8::substr($otherStr, $j - 1, 1, $encoding);
937
938 8
        if ($strChar == $otherChar) {
939 8
          $table[$i][$j] = $table[$i - 1][$j - 1] + 1;
940 8
          if ($table[$i][$j] > $len) {
941 8
            $len = $table[$i][$j];
942 8
            $end = $i;
943 8
          }
944 8
        } else {
945 8
          $table[$i][$j] = 0;
946
        }
947 8
      }
948 8
    }
949
950 8
    $stringy->str = UTF8::substr($stringy->str, $end - $len, $len, $encoding);
951
952 8
    return $stringy;
953
  }
954
955
  /**
956
   * Returns whether or not a character exists at an index. Offsets may be
957
   * negative to count from the last character in the string. Implements
958
   * part of the ArrayAccess interface.
959
   *
960
   * @param  mixed $offset The index to check
961
   *
962
   * @return boolean Whether or not the index exists
963
   */
964 6
  public function offsetExists($offset)
965
  {
966
    // init
967 6
    $length = $this->length();
968 6
    $offset = (int)$offset;
969
970 6
    if ($offset >= 0) {
971 3
      return ($length > $offset);
972
    }
973
974 3
    return ($length >= abs($offset));
975
  }
976
977
  /**
978
   * Returns the character at the given index. Offsets may be negative to
979
   * count from the last character in the string. Implements part of the
980
   * ArrayAccess interface, and throws an OutOfBoundsException if the index
981
   * does not exist.
982
   *
983
   * @param  mixed $offset The index from which to retrieve the char
984
   *
985
   * @return string                 The character at the specified index
986
   * @throws \OutOfBoundsException If the positive or negative offset does
987
   *                               not exist
988
   */
989 2
  public function offsetGet($offset)
990
  {
991
    // init
992 2
    $offset = (int)$offset;
993 2
    $length = $this->length();
994
995
    if (
996 2
        ($offset >= 0 && $length <= $offset)
997
        ||
998 1
        $length < abs($offset)
999 2
    ) {
1000 1
      throw new \OutOfBoundsException('No character exists at the index');
1001
    }
1002
1003 1
    return UTF8::substr($this->str, $offset, 1, $this->encoding);
1004
  }
1005
1006
  /**
1007
   * Implements part of the ArrayAccess interface, but throws an exception
1008
   * when called. This maintains the immutability of Stringy objects.
1009
   *
1010
   * @param  mixed $offset The index of the character
1011
   * @param  mixed $value  Value to set
1012
   *
1013
   * @throws \Exception When called
1014
   */
1015 1
  public function offsetSet($offset, $value)
1016
  {
1017
    // Stringy is immutable, cannot directly set char
1018 1
    throw new \Exception('Stringy object is immutable, cannot modify char');
1019
  }
1020
1021
  /**
1022
   * Implements part of the ArrayAccess interface, but throws an exception
1023
   * when called. This maintains the immutability of Stringy objects.
1024
   *
1025
   * @param  mixed $offset The index of the character
1026
   *
1027
   * @throws \Exception When called
1028
   */
1029 1
  public function offsetUnset($offset)
1030
  {
1031
    // Don't allow directly modifying the string
1032 1
    throw new \Exception('Stringy object is immutable, cannot unset char');
1033
  }
1034
1035
  /**
1036
   * Pads the string to a given length with $padStr. If length is less than
1037
   * or equal to the length of the string, no padding takes places. The
1038
   * default string used for padding is a space, and the default type (one of
1039
   * 'left', 'right', 'both') is 'right'. Throws an InvalidArgumentException
1040
   * if $padType isn't one of those 3 values.
1041
   *
1042
   * @param  int    $length  Desired string length after padding
1043
   * @param  string $padStr  String used to pad, defaults to space
1044
   * @param  string $padType One of 'left', 'right', 'both'
1045
   *
1046
   * @return Stringy Object with a padded $str
1047
   * @throws \InvalidArgumentException If $padType isn't one of 'right', 'left' or 'both'
1048
   */
1049 13
  public function pad($length, $padStr = ' ', $padType = 'right')
1050
  {
1051 13
    if (!in_array($padType, array('left', 'right', 'both'), true)) {
1052 1
      throw new \InvalidArgumentException(
1053
          'Pad expects $padType ' . "to be one of 'left', 'right' or 'both'"
1054 1
      );
1055
    }
1056
1057
    switch ($padType) {
1058 12
      case 'left':
1059 3
        return $this->padLeft($length, $padStr);
1060 9
      case 'right':
1061 6
        return $this->padRight($length, $padStr);
1062 3
      default:
1063 3
        return $this->padBoth($length, $padStr);
1064 3
    }
1065
  }
1066
1067
  /**
1068
   * Returns a new string of a given length such that the beginning of the
1069
   * string is padded. Alias for pad() with a $padType of 'left'.
1070
   *
1071
   * @param  int    $length Desired string length after padding
1072
   * @param  string $padStr String used to pad, defaults to space
1073
   *
1074
   * @return Stringy String with left padding
1075
   */
1076 10
  public function padLeft($length, $padStr = ' ')
1077
  {
1078 10
    return $this->applyPadding($length - $this->length(), 0, $padStr);
1079
  }
1080
1081
  /**
1082
   * Adds the specified amount of left and right padding to the given string.
1083
   * The default character used is a space.
1084
   *
1085
   * @param  int    $left   Length of left padding
1086
   * @param  int    $right  Length of right padding
1087
   * @param  string $padStr String used to pad
1088
   *
1089
   * @return Stringy String with padding applied
1090
   */
1091 37
  private function applyPadding($left = 0, $right = 0, $padStr = ' ')
1092
  {
1093 37
    $stringy = static::create($this->str, $this->encoding);
1094
1095 37
    $length = UTF8::strlen($padStr, $stringy->encoding);
1096
1097 37
    $strLength = $stringy->length();
1098 37
    $paddedLength = $strLength + $left + $right;
1099
1100 37
    if (!$length || $paddedLength <= $strLength) {
1101 3
      return $stringy;
1102
    }
1103
1104 34
    $leftPadding = UTF8::substr(
1105 34
        UTF8::str_repeat(
1106 34
            $padStr,
1107 34
            ceil($left / $length)
1108 34
        ),
1109 34
        0,
1110 34
        $left,
1111 34
        $stringy->encoding
1112 34
    );
1113
1114 34
    $rightPadding = UTF8::substr(
1115 34
        UTF8::str_repeat(
1116 34
            $padStr,
1117 34
            ceil($right / $length)
1118 34
        ),
1119 34
        0,
1120 34
        $right,
1121 34
        $stringy->encoding
1122 34
    );
1123
1124 34
    $stringy->str = $leftPadding . $stringy->str . $rightPadding;
1125
1126 34
    return $stringy;
1127
  }
1128
1129
  /**
1130
   * Returns a new string of a given length such that the end of the string
1131
   * is padded. Alias for pad() with a $padType of 'right'.
1132
   *
1133
   * @param  int    $length Desired string length after padding
1134
   * @param  string $padStr String used to pad, defaults to space
1135
   *
1136
   * @return Stringy String with right padding
1137
   */
1138 13
  public function padRight($length, $padStr = ' ')
1139
  {
1140 13
    return $this->applyPadding(0, $length - $this->length(), $padStr);
1141
  }
1142
1143
  /**
1144
   * Returns a new string of a given length such that both sides of the
1145
   * string are padded. Alias for pad() with a $padType of 'both'.
1146
   *
1147
   * @param  int    $length Desired string length after padding
1148
   * @param  string $padStr String used to pad, defaults to space
1149
   *
1150
   * @return Stringy String with padding applied
1151
   */
1152 14
  public function padBoth($length, $padStr = ' ')
1153
  {
1154 14
    $padding = $length - $this->length();
1155
1156 14
    return $this->applyPadding(floor($padding / 2), ceil($padding / 2), $padStr);
1157
  }
1158
1159
  /**
1160
   * Returns a new string starting with $string.
1161
   *
1162
   * @param  string $string The string to append
1163
   *
1164
   * @return Stringy Object with appended $string
1165
   */
1166 2
  public function prepend($string)
1167
  {
1168 2
    return static::create($string . $this->str, $this->encoding);
1169
  }
1170
1171
  /**
1172
   * Returns a new string with the prefix $substring removed, if present.
1173
   *
1174
   * @param  string $substring The prefix to remove
1175
   *
1176
   * @return Stringy Object having a $str without the prefix $substring
1177
   */
1178 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...
1179
  {
1180 12
    $stringy = static::create($this->str, $this->encoding);
1181
1182 12
    if ($stringy->startsWith($substring)) {
1183 8
      $substringLength = UTF8::strlen($substring, $stringy->encoding);
1184
1185 8
      return $stringy->substr($substringLength);
1186
    }
1187
1188 4
    return $stringy;
1189
  }
1190
1191
  /**
1192
   * Returns a new string with the suffix $substring removed, if present.
1193
   *
1194
   * @param  string $substring The suffix to remove
1195
   *
1196
   * @return Stringy Object having a $str without the suffix $substring
1197
   */
1198 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...
1199
  {
1200 12
    $stringy = static::create($this->str, $this->encoding);
1201
1202 12
    if ($stringy->endsWith($substring)) {
1203 8
      $substringLength = UTF8::strlen($substring, $stringy->encoding);
1204
1205 8
      return $stringy->substr(0, $stringy->length() - $substringLength);
1206
    }
1207
1208 4
    return $stringy;
1209
  }
1210
1211
  /**
1212
   * Returns a repeated string given a multiplier.
1213
   *
1214
   * @param  int $multiplier The number of times to repeat the string
1215
   *
1216
   * @return Stringy Object with a repeated str
1217
   */
1218 7
  public function repeat($multiplier)
1219
  {
1220 7
    $repeated = UTF8::str_repeat($this->str, $multiplier);
1221
1222 7
    return static::create($repeated, $this->encoding);
1223
  }
1224
1225
  /**
1226
   * Replaces all occurrences of $search in $str by $replacement.
1227
   *
1228
   * @param string $search        The needle to search for
1229
   * @param string $replacement   The string to replace with
1230
   * @param bool   $caseSensitive Whether or not to enforce case-sensitivity
1231
   *
1232
   * @return Stringy Object with the resulting $str after the replacements
1233
   */
1234 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...
1235
  {
1236 28
    if ($caseSensitive) {
1237 21
      $return = UTF8::str_replace($search, $replacement, $this->str);
1238 21
    } else {
1239 7
      $return = UTF8::str_ireplace($search, $replacement, $this->str);
1240
    }
1241
1242 28
    return static::create($return);
1243
  }
1244
1245
  /**
1246
   * Replaces all occurrences of $search in $str by $replacement.
1247
   *
1248
   * @param array        $search        The elements to search for
1249
   * @param string|array $replacement   The string to replace with
1250
   * @param bool         $caseSensitive Whether or not to enforce case-sensitivity
1251
   *
1252
   * @return Stringy Object with the resulting $str after the replacements
1253
   */
1254 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...
1255
  {
1256 30
    if ($caseSensitive) {
1257 23
      $return = UTF8::str_replace($search, $replacement, $this->str);
1258 23
    } else {
1259 7
      $return = UTF8::str_ireplace($search, $replacement, $this->str);
0 ignored issues
show
Documentation introduced by
$search is of type array, but the function expects a string.

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...
Bug introduced by
It seems like $replacement defined by parameter $replacement on line 1254 can also be of type array; however, voku\helper\UTF8::str_ireplace() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1260
    }
1261
1262 30
    return static::create($return);
1263
  }
1264
1265
  /**
1266
   * Replaces all occurrences of $search from the beginning of string with $replacement
1267
   *
1268
   * @param string $search
1269
   * @param string $replacement
1270
   *
1271
   * @return Stringy Object with the resulting $str after the replacements
1272
   */
1273 16
  public function replaceBeginning($search, $replacement)
1274
  {
1275 16
    $str = $this->regexReplace('^' . preg_quote($search, '/'), UTF8::str_replace('\\', '\\\\', $replacement));
1276
1277 16
    return static::create($str, $this->encoding);
1278
  }
1279
1280
  /**
1281
   * Replaces all occurrences of $search from the ending of string with $replacement
1282
   *
1283
   * @param string $search
1284
   * @param string $replacement
1285
   *
1286
   * @return Stringy Object with the resulting $str after the replacements
1287
   */
1288 16
  public function replaceEnding($search, $replacement)
1289
  {
1290 16
    $str = $this->regexReplace(preg_quote($search, '/') . '$', UTF8::str_replace('\\', '\\\\', $replacement));
1291
1292 16
    return static::create($str, $this->encoding);
1293
  }
1294
1295
  /**
1296
   * Returns a reversed string. A multibyte version of strrev().
1297
   *
1298
   * @return Stringy Object with a reversed $str
1299
   */
1300 5
  public function reverse()
1301
  {
1302 5
    $reversed = UTF8::strrev($this->str);
1303
1304 5
    return static::create($reversed, $this->encoding);
1305
  }
1306
1307
  /**
1308
   * Truncates the string to a given length, while ensuring that it does not
1309
   * split words. If $substring is provided, and truncating occurs, the
1310
   * string is further truncated so that the substring may be appended without
1311
   * exceeding the desired length.
1312
   *
1313
   * @param  int    $length    Desired length of the truncated string
1314
   * @param  string $substring The substring to append if it can fit
1315
   *
1316
   * @return Stringy Object with the resulting $str after truncating
1317
   */
1318 22
  public function safeTruncate($length, $substring = '')
1319
  {
1320 22
    $stringy = static::create($this->str, $this->encoding);
1321 22
    if ($length >= $stringy->length()) {
1322 4
      return $stringy;
1323
    }
1324
1325
    // Need to further trim the string so we can append the substring
1326 18
    $encoding = $stringy->encoding;
1327 18
    $substringLength = UTF8::strlen($substring, $encoding);
1328 18
    $length -= $substringLength;
1329
1330 18
    $truncated = UTF8::substr($stringy->str, 0, $length, $encoding);
1331
1332
    // If the last word was truncated
1333 18
    if (UTF8::strpos($stringy->str, ' ', $length - 1, $encoding) != $length) {
1334
      // Find pos of the last occurrence of a space, get up to that
1335 11
      $lastPos = UTF8::strrpos($truncated, ' ', 0, $encoding);
0 ignored issues
show
Documentation introduced by
$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...
1336 11
      $truncated = UTF8::substr($truncated, 0, $lastPos, $encoding);
0 ignored issues
show
Bug introduced by
It seems like $lastPos defined by \voku\helper\UTF8::strrp...ted, ' ', 0, $encoding) on line 1335 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...
1337 11
    }
1338
1339 18
    $stringy->str = $truncated . $substring;
1340
1341 18
    return $stringy;
1342
  }
1343
1344
  /**
1345
   * A multibyte string shuffle function. It returns a string with its
1346
   * characters in random order.
1347
   *
1348
   * @return Stringy Object with a shuffled $str
1349
   */
1350 3
  public function shuffle()
1351
  {
1352 3
    $shuffledStr = UTF8::str_shuffle($this->str);
1353
1354 3
    return static::create($shuffledStr, $this->encoding);
1355
  }
1356
1357
  /**
1358
   * Converts the string into an URL slug. This includes replacing non-ASCII
1359
   * characters with their closest ASCII equivalents, removing remaining
1360
   * non-ASCII and non-alphanumeric characters, and replacing whitespace with
1361
   * $replacement. The replacement defaults to a single dash, and the string
1362
   * is also converted to lowercase.
1363
   *
1364
   * @param string $replacement The string used to replace whitespace
1365
   * @param string $language    The language for the url
1366
   * @param bool   $strToLower  string to lower
1367
   *
1368
   * @return Stringy Object whose $str has been converted to an URL slug
1369
   */
1370 15
  public function slugify($replacement = '-', $language = 'de', $strToLower = true)
1371
  {
1372 15
    $slug = URLify::slug($this->str, $language, $replacement, $strToLower);
1373
1374 15
    return static::create($slug, $this->encoding);
1375
  }
1376
1377
  /**
1378
   * escape html
1379
   *
1380
   * @return Stringy
1381
   */
1382 6
  public function escape()
1383
  {
1384 6
    $str = UTF8::htmlspecialchars(
1385 6
        $this->str,
1386 6
        ENT_QUOTES | ENT_SUBSTITUTE,
1387 6
        $this->encoding
1388 6
    );
1389
1390 6
    return static::create($str, $this->encoding);
1391
  }
1392
1393
  /**
1394
   * remove xss from html
1395
   *
1396
   * @return Stringy
1397
   */
1398 6
  public function removeXss()
1399
  {
1400 6
    static $antiXss = null;
1401
1402 6
    if ($antiXss === null) {
1403 1
      $antiXss = new AntiXSS();
1404 1
    }
1405
1406 6
    $str = $antiXss->xss_clean($this->str);
1407
1408 6
    return static::create($str, $this->encoding);
1409
  }
1410
1411
  /**
1412
   * remove html-break [br | \r\n | \r | \n | ...]
1413
   *
1414
   * @param string $replacement
1415
   *
1416
   * @return Stringy
1417
   */
1418 6
  public function removeHtmlBreak($replacement = '')
1419
  {
1420 6
    $str = preg_replace('#/\r\n|\r|\n|<br.*/?>#isU', $replacement, $this->str);
1421
1422 6
    return static::create($str, $this->encoding);
1423
  }
1424
1425
  /**
1426
   * remove html
1427
   *
1428
   * @param $allowableTags
1429
   *
1430
   * @return Stringy
1431
   */
1432 6
  public function removeHtml($allowableTags = null)
1433
  {
1434 6
    $str = UTF8::strip_tags($this->str, $allowableTags);
1435
1436 6
    return static::create($str, $this->encoding);
1437
  }
1438
1439
  /**
1440
   * Returns the substring beginning at $start, and up to, but not including
1441
   * the index specified by $end. If $end is omitted, the function extracts
1442
   * the remaining string. If $end is negative, it is computed from the end
1443
   * of the string.
1444
   *
1445
   * @param  int $start Initial index from which to begin extraction
1446
   * @param  int $end   Optional index at which to end extraction
1447
   *
1448
   * @return Stringy Object with its $str being the extracted substring
1449
   */
1450 18
  public function slice($start, $end = null)
1451
  {
1452 18
    if ($end === null) {
1453 4
      $length = $this->length();
1454 18
    } elseif ($end >= 0 && $end <= $start) {
1455 4
      return static::create('', $this->encoding);
1456 10
    } elseif ($end < 0) {
1457 2
      $length = $this->length() + $end - $start;
1458 2
    } else {
1459 8
      $length = $end - $start;
1460
    }
1461
1462 14
    $str = UTF8::substr($this->str, $start, $length, $this->encoding);
1463
1464 14
    return static::create($str, $this->encoding);
1465
  }
1466
1467
  /**
1468
   * Splits the string with the provided regular expression, returning an
1469
   * array of Stringy objects. An optional integer $limit will truncate the
1470
   * results.
1471
   *
1472
   * @param  string $pattern The regex with which to split the string
1473
   * @param  int    $limit   Optional maximum number of results to return
1474
   *
1475
   * @return Stringy[] An array of Stringy objects
1476
   */
1477 19
  public function split($pattern, $limit = null)
1478
  {
1479 19
    if ($limit === 0) {
1480 2
      return array();
1481
    }
1482
1483
    // UTF8::split errors when supplied an empty pattern in < PHP 5.4.13
1484
    // and current versions of HHVM (3.8 and below)
1485 17
    if ($pattern === '') {
1486 1
      return array(static::create($this->str, $this->encoding));
1487
    }
1488
1489
    // UTF8::split returns the remaining unsplit string in the last index when
1490
    // supplying a limit
1491 16
    if ($limit > 0) {
1492 8
      $limit += 1;
1493 8
    } else {
1494 8
      $limit = -1;
1495
    }
1496
1497 16
    $array = preg_split('/' . preg_quote($pattern, '/') . '/u', $this->str, $limit);
1498
1499 16
    if ($limit > 0 && count($array) === $limit) {
1500 4
      array_pop($array);
1501 4
    }
1502
1503
    /** @noinspection CallableInLoopTerminationConditionInspection */
1504
    /** @noinspection ForeachInvariantsInspection */
1505 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...
1506 16
      $array[$i] = static::create($array[$i], $this->encoding);
1507 16
    }
1508
1509 16
    return $array;
1510
  }
1511
1512
  /**
1513
   * Surrounds $str with the given substring.
1514
   *
1515
   * @param  string $substring The substring to add to both sides
1516
   *
1517
   * @return Stringy Object whose $str had the substring both prepended and
1518
   *                 appended
1519
   */
1520 5
  public function surround($substring)
1521
  {
1522 5
    $str = implode('', array($substring, $this->str, $substring));
1523
1524 5
    return static::create($str, $this->encoding);
1525
  }
1526
1527
  /**
1528
   * Returns a case swapped version of the string.
1529
   *
1530
   * @return Stringy Object whose $str has each character's case swapped
1531
   */
1532 5
  public function swapCase()
1533
  {
1534 5
    $stringy = static::create($this->str, $this->encoding);
1535 5
    $encoding = $stringy->encoding;
1536
1537 5
    $stringy->str = preg_replace_callback(
1538 5
        '/[\S]/u',
1539
        function ($match) use ($encoding) {
1540 5
          $marchToUpper = UTF8::strtoupper($match[0], $encoding);
1541
1542 5
          if ($match[0] == $marchToUpper) {
1543 5
            return UTF8::strtolower($match[0], $encoding);
1544
          } else {
1545 5
            return $marchToUpper;
1546
          }
1547 5
        },
1548 5
        $stringy->str
1549 5
    );
1550
1551 5
    return $stringy;
1552
  }
1553
1554
  /**
1555
   * Returns a string with smart quotes, ellipsis characters, and dashes from
1556
   * Windows-1252 (commonly used in Word documents) replaced by their ASCII
1557
   * equivalents.
1558
   *
1559
   * @return Stringy Object whose $str has those characters removed
1560
   */
1561 4
  public function tidy()
1562
  {
1563 4
    $str = UTF8::normalize_msword($this->str);
1564
1565 4
    return static::create($str, $this->encoding);
1566
  }
1567
1568
  /**
1569
   * Returns a trimmed string with the first letter of each word capitalized.
1570
   * Also accepts an array, $ignore, allowing you to list words not to be
1571
   * capitalized.
1572
   *
1573
   * @param  array $ignore An array of words not to capitalize
1574
   *
1575
   * @return Stringy Object with a titleized $str
1576
   */
1577 5
  public function titleize($ignore = null)
1578
  {
1579 5
    $stringy = static::create($this->trim(), $this->encoding);
1580 5
    $encoding = $this->encoding;
1581
1582 5
    $stringy->str = preg_replace_callback(
1583 5
        '/([\S]+)/u',
1584
        function ($match) use ($encoding, $ignore) {
1585 5
          if ($ignore && in_array($match[0], $ignore, true)) {
1586 2
            return $match[0];
1587
          } else {
1588 5
            $stringy = new Stringy($match[0], $encoding);
1589
1590 5
            return (string)$stringy->toLowerCase()->upperCaseFirst();
1591
          }
1592 5
        },
1593 5
        $stringy->str
1594 5
    );
1595
1596 5
    return $stringy;
1597
  }
1598
1599
  /**
1600
   * Converts all characters in the string to lowercase. An alias for PHP's
1601
   * UTF8::strtolower().
1602
   *
1603
   * @return Stringy Object with all characters of $str being lowercase
1604
   */
1605 27
  public function toLowerCase()
1606
  {
1607 27
    $str = UTF8::strtolower($this->str, $this->encoding);
1608
1609 27
    return static::create($str, $this->encoding);
1610
  }
1611
1612
  /**
1613
   * Returns true if the string is base64 encoded, false otherwise.
1614
   *
1615
   * @return bool Whether or not $str is base64 encoded
1616
   */
1617 7
  public function isBase64()
1618
  {
1619
    if (
1620 7
        $this->str !== ''
1621 7
        &&
1622 6
        base64_encode(base64_decode($this->str, true)) === $this->str
1623 7
    ) {
1624 4
      return true;
1625
    } else {
1626 3
      return false;
1627
    }
1628
  }
1629
1630
  /**
1631
   * Returns an ASCII version of the string. A set of non-ASCII characters are
1632
   * replaced with their closest ASCII counterparts, and the rest are removed
1633
   * unless instructed otherwise.
1634
   *
1635
   * @return Stringy Object whose $str contains only ASCII characters
1636
   */
1637 16
  public function toAscii()
1638
  {
1639 16
    $str = UTF8::toAscii($this->str);
1640
1641 16
    return static::create($str, $this->encoding);
1642
  }
1643
1644
  /**
1645
   * Returns a boolean representation of the given logical string value.
1646
   * For example, 'true', '1', 'on' and 'yes' will return true. 'false', '0',
1647
   * 'off', and 'no' will return false. In all instances, case is ignored.
1648
   * For other numeric strings, their sign will determine the return value.
1649
   * In addition, blank strings consisting of only whitespace will return
1650
   * false. For all other strings, the return value is a result of a
1651
   * boolean cast.
1652
   *
1653
   * @return bool A boolean value for the string
1654
   */
1655 15
  public function toBoolean()
1656
  {
1657 15
    $key = $this->toLowerCase()->str;
1658
    $map = array(
1659 15
        'true'  => true,
1660 15
        '1'     => true,
1661 15
        'on'    => true,
1662 15
        'yes'   => true,
1663 15
        'false' => false,
1664 15
        '0'     => false,
1665 15
        'off'   => false,
1666 15
        'no'    => false,
1667 15
    );
1668
1669 15
    if (array_key_exists($key, $map)) {
1670 10
      return $map[$key];
1671 5
    } elseif (is_numeric($this->str)) {
1672 2
      return ((int)$this->str > 0);
1673
    } else {
1674 3
      return (bool)$this->regexReplace('[[:space:]]', '')->str;
1675
    }
1676
  }
1677
1678
  /**
1679
   * Converts each tab in the string to some number of spaces, as defined by
1680
   * $tabLength. By default, each tab is converted to 4 consecutive spaces.
1681
   *
1682
   * @param  int $tabLength Number of spaces to replace each tab with
1683
   *
1684
   * @return Stringy Object whose $str has had tabs switched to spaces
1685
   */
1686 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...
1687
  {
1688 6
    $spaces = UTF8::str_repeat(' ', $tabLength);
1689 6
    $str = UTF8::str_replace("\t", $spaces, $this->str);
1690
1691 6
    return static::create($str, $this->encoding);
1692
  }
1693
1694
  /**
1695
   * Converts each occurrence of some consecutive number of spaces, as
1696
   * defined by $tabLength, to a tab. By default, each 4 consecutive spaces
1697
   * are converted to a tab.
1698
   *
1699
   * @param  int $tabLength Number of spaces to replace with a tab
1700
   *
1701
   * @return Stringy Object whose $str has had spaces switched to tabs
1702
   */
1703 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...
1704
  {
1705 5
    $spaces = UTF8::str_repeat(' ', $tabLength);
1706 5
    $str = UTF8::str_replace($spaces, "\t", $this->str);
1707
1708 5
    return static::create($str, $this->encoding);
1709
  }
1710
1711
  /**
1712
   * Converts the first character of each word in the string to uppercase.
1713
   *
1714
   * @return Stringy Object with all characters of $str being title-cased
1715
   */
1716 5
  public function toTitleCase()
1717
  {
1718
    // "mb_convert_case()" used a polyfill from the "UTF8"-Class
1719 5
    $str = mb_convert_case($this->str, MB_CASE_TITLE, $this->encoding);
1720
1721 5
    return static::create($str, $this->encoding);
1722
  }
1723
1724
  /**
1725
   * Converts all characters in the string to uppercase. An alias for PHP's
1726
   * UTF8::strtoupper().
1727
   *
1728
   * @return Stringy Object with all characters of $str being uppercase
1729
   */
1730 5
  public function toUpperCase()
1731
  {
1732 5
    $str = UTF8::strtoupper($this->str, $this->encoding);
1733
1734 5
    return static::create($str, $this->encoding);
1735
  }
1736
1737
  /**
1738
   * Returns a string with whitespace removed from the start of the string.
1739
   * Supports the removal of unicode whitespace. Accepts an optional
1740
   * string of characters to strip instead of the defaults.
1741
   *
1742
   * @param  string $chars Optional string of characters to strip
1743
   *
1744
   * @return Stringy Object with a trimmed $str
1745
   */
1746 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...
1747
  {
1748 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...
1749 11
      $chars = '[:space:]';
1750 11
    } else {
1751 2
      $chars = preg_quote($chars, '/');
1752
    }
1753
1754 13
    return $this->regexReplace("^[$chars]+", '');
1755
  }
1756
1757
  /**
1758
   * Returns a string with whitespace removed from the end of the string.
1759
   * Supports the removal of unicode whitespace. Accepts an optional
1760
   * string of characters to strip instead of the defaults.
1761
   *
1762
   * @param  string $chars Optional string of characters to strip
1763
   *
1764
   * @return Stringy Object with a trimmed $str
1765
   */
1766 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...
1767
  {
1768 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...
1769 11
      $chars = '[:space:]';
1770 11
    } else {
1771 2
      $chars = preg_quote($chars, '/');
1772
    }
1773
1774 13
    return $this->regexReplace("[$chars]+\$", '');
1775
  }
1776
1777
  /**
1778
   * Truncates the string to a given length. If $substring is provided, and
1779
   * truncating occurs, the string is further truncated so that the substring
1780
   * may be appended without exceeding the desired length.
1781
   *
1782
   * @param  int    $length    Desired length of the truncated string
1783
   * @param  string $substring The substring to append if it can fit
1784
   *
1785
   * @return Stringy Object with the resulting $str after truncating
1786
   */
1787 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...
1788
  {
1789 22
    $stringy = static::create($this->str, $this->encoding);
1790 22
    if ($length >= $stringy->length()) {
1791 4
      return $stringy;
1792
    }
1793
1794
    // Need to further trim the string so we can append the substring
1795 18
    $substringLength = UTF8::strlen($substring, $stringy->encoding);
1796 18
    $length -= $substringLength;
1797
1798 18
    $truncated = UTF8::substr($stringy->str, 0, $length, $stringy->encoding);
1799 18
    $stringy->str = $truncated . $substring;
1800
1801 18
    return $stringy;
1802
  }
1803
1804
  /**
1805
   * Returns a lowercase and trimmed string separated by underscores.
1806
   * Underscores are inserted before uppercase characters (with the exception
1807
   * of the first character of the string), and in place of spaces as well as
1808
   * dashes.
1809
   *
1810
   * @return Stringy Object with an underscored $str
1811
   */
1812 16
  public function underscored()
1813
  {
1814 16
    return $this->delimit('_');
1815
  }
1816
1817
  /**
1818
   * Returns an UpperCamelCase version of the supplied string. It trims
1819
   * surrounding spaces, capitalizes letters following digits, spaces, dashes
1820
   * and underscores, and removes spaces, dashes, underscores.
1821
   *
1822
   * @return Stringy Object with $str in UpperCamelCase
1823
   */
1824 13
  public function upperCamelize()
1825
  {
1826 13
    return $this->camelize()->upperCaseFirst();
1827
  }
1828
1829
  /**
1830
   * Returns a camelCase version of the string. Trims surrounding spaces,
1831
   * capitalizes letters following digits, spaces, dashes and underscores,
1832
   * and removes spaces, dashes, as well as underscores.
1833
   *
1834
   * @return Stringy Object with $str in camelCase
1835
   */
1836 32
  public function camelize()
1837
  {
1838 32
    $encoding = $this->encoding;
1839 32
    $stringy = $this->trim()->lowerCaseFirst();
1840 32
    $stringy->str = preg_replace('/^[-_]+/', '', $stringy->str);
1841
1842 32
    $stringy->str = preg_replace_callback(
1843 32
        '/[-_\s]+(.)?/u',
1844
        function ($match) use ($encoding) {
1845 27
          if (isset($match[1])) {
1846 27
            return UTF8::strtoupper($match[1], $encoding);
1847
          } else {
1848 1
            return '';
1849
          }
1850 32
        },
1851 32
        $stringy->str
1852 32
    );
1853
1854 32
    $stringy->str = preg_replace_callback(
1855 32
        '/[\d]+(.)?/u',
1856
        function ($match) use ($encoding) {
1857 6
          return UTF8::strtoupper($match[0], $encoding);
1858 32
        },
1859 32
        $stringy->str
1860 32
    );
1861
1862 32
    return $stringy;
1863
  }
1864
1865
  /**
1866
   * Convert a string to e.g.: "snake_case"
1867
   *
1868
   * @return Stringy Object with $str in snake_case
1869
   */
1870 20
  public function snakeize()
1871
  {
1872 20
    $str = $this->str;
1873
1874 20
    $str = UTF8::normalize_whitespace($str);
1875 20
    $str = str_replace('-', '_', $str);
1876
1877 20
    $str = preg_replace_callback(
1878 20
        '/([\d|A-Z])/u',
1879 20
        function ($matches) {
1880 8
          $match = $matches[1];
1881 8
          $matchInt = (int)$match;
1882
1883 8
          if ("$matchInt" == $match) {
1884 4
            return '_' . $match . '_';
1885
          } else {
1886 4
            return '_' . UTF8::strtolower($match);
1887
          }
1888 20
        },
1889
        $str
1890 20
    );
1891
1892 20
    $str = preg_replace(
1893
        array(
1894
1895 20
            '/\s+/',      // convert spaces to "_"
1896 20
            '/^\s+|\s+$/',  // trim leading & trailing spaces
1897 20
            '/_+/',         // remove double "_"
1898 20
        ),
1899
        array(
1900 20
            '_',
1901 20
            '',
1902 20
            '_',
1903 20
        ),
1904
        $str
1905 20
    );
1906
1907 20
    $str = UTF8::trim($str, '_'); // trim leading & trailing "_"
1908 20
    $str = UTF8::trim($str); // trim leading & trailing whitespace
1909
1910 20
    return static::create($str, $this->encoding);
1911
  }
1912
1913
  /**
1914
   * Converts the first character of the string to lower case.
1915
   *
1916
   * @return Stringy Object with the first character of $str being lower case
1917
   */
1918 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...
1919
  {
1920 37
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
1921 37
    $rest = UTF8::substr(
1922 37
        $this->str, 1, $this->length() - 1,
1923 37
        $this->encoding
1924 37
    );
1925
1926 37
    $str = UTF8::strtolower($first, $this->encoding) . $rest;
1927
1928 37
    return static::create($str, $this->encoding);
1929
  }
1930
1931
  /**
1932
   * shorten the string after $length, but also after the next word
1933
   *
1934
   * @param int    $length
1935
   * @param string $strAddOn
1936
   *
1937
   * @return Stringy
1938
   */
1939 4
  public function shortenAfterWord($length, $strAddOn = '...')
1940
  {
1941 4
    $string = $this->str;
1942
1943 4
    if (UTF8::strlen($string) > $length) {
1944 4
      if (UTF8::substr($string, $length - 1, 1) != ' ') {
1945 1
        $string = UTF8::substr($string, '0', $length);
1946 1
        $array = explode(' ', $string);
1947 1
        array_pop($array);
1948 1
        $new_string = implode(' ', $array);
1949
1950 1
        if ($new_string == '') {
1951
          $string = UTF8::substr($string, '0', $length - 1) . $strAddOn;
1952
        } else {
1953 1
          $string = $new_string . $strAddOn;
1954
        }
1955
1956 1
      } else {
1957 3
        $string = UTF8::substr($string, '0', $length - 1) . $strAddOn;
1958
      }
1959 4
    }
1960
1961 4
    return static::create($string);
1962
  }
1963
}
1964