Completed
Pull Request — master (#6)
by Lars
04:49 queued 13s
created

Stringy   D

Complexity

Total Complexity 190

Size/Duplication

Total Lines 2037
Duplicated Lines 11.54 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 99.54%

Importance

Changes 65
Bugs 7 Features 33
Metric Value
c 65
b 7
f 33
dl 235
loc 2037
ccs 646
cts 649
cp 0.9954
rs 4.4102
wmc 190
lcom 1
cbo 3

105 Methods

Rating   Name   Duplication   Size   Complexity  
A prepend() 0 4 1
A padLeft() 0 4 1
A padRight() 0 4 1
A pad() 0 17 4
A longestCommonPrefix() 0 18 3
A longestCommonSuffix() 0 18 3
B applyPadding() 0 37 3
A padBoth() 0 6 1
A removeLeft() 12 12 2
A removeRight() 12 12 2
A repeat() 0 6 1
A __toString() 0 4 1
A append() 0 4 1
A create() 0 4 1
A between() 0 15 3
A indexOf() 0 4 1
A length() 0 4 1
A collapseWhitespace() 0 4 1
A contains() 0 10 2
A count() 0 4 1
A dasherize() 0 4 1
A delimit() 0 12 1
A getEncoding() 0 4 1
A getIterator() 0 4 1
A at() 0 4 1
A hasLowerCase() 0 4 1
A matchesPattern() 0 8 2
A hasUpperCase() 0 4 1
A htmlDecode() 0 6 1
A htmlEncode() 0 6 1
A indexOfLast() 0 4 1
A isAlpha() 0 4 1
A isAlphanumeric() 0 4 1
A isBlank() 0 4 1
A isHexadecimal() 0 4 1
A isJson() 0 14 3
A isLowerCase() 0 8 2
A isUpperCase() 0 4 1
C __construct() 0 34 7
A trim() 10 10 2
A regexReplace() 0 14 2
A ensureLeft() 10 10 2
A startsWith() 4 15 2
A ensureRight() 10 10 2
A endsWith() 4 19 2
A first() 12 12 2
A chars() 0 12 2
A upperCaseFirst() 14 14 1
A insert() 17 17 2
A isSerialized() 0 17 4
A last() 12 12 2
A lines() 3 11 2
A replace() 10 10 2
A replaceAll() 10 10 2
A replaceBeginning() 0 6 1
A replaceEnding() 0 6 1
A reverse() 0 6 1
B safeTruncate() 0 25 3
A shuffle() 0 6 1
A offsetExists() 0 12 2
A offsetSet() 0 5 1
A offsetUnset() 0 5 1
A countSubstr() 0 11 2
A offsetGet() 0 16 4
C longestCommonSubstring() 0 44 7
A humanize() 0 6 1
A appendPassword() 0 6 1
A appendUniqueIdentifier() 0 10 3
A appendRandomString() 0 20 3
A slugify() 0 6 1
A stripeCssMediaQueries() 0 5 1
A stripeEmptyHtmlTags() 0 6 1
A utf8ify() 0 4 1
A escape() 0 10 1
A removeXss() 0 12 2
A removeHtmlBreak() 0 6 1
A removeHtml() 0 6 1
B slice() 0 16 5
C split() 3 34 7
A surround() 0 6 1
A swapCase() 0 8 1
A tidy() 0 6 1
A titleize() 0 21 3
A toLowerCase() 0 6 1
A isBase64() 0 4 1
A toAscii() 0 6 1
A toBoolean() 0 22 3
A toString() 0 4 1
A toSpaces() 7 7 1
A toTabs() 7 7 1
A toTitleCase() 0 7 1
A toUpperCase() 0 6 1
A trimLeft() 10 10 2
A trimRight() 10 10 2
A truncate() 16 16 2
A underscored() 0 4 1
A upperCamelize() 0 4 1
B camelize() 0 28 2
B snakeize() 0 42 2
A lowerCaseFirst() 12 12 1
A shortenAfterWord() 0 6 1
A lineWrapAfterWord() 0 12 2
A substr() 0 10 2
A containsAll() 15 15 4
A containsAny() 15 15 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Stringy often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Stringy, and based on these observations, apply Extract Interface, too.

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 1014
  public function __construct($str = '', $encoding = null)
45
  {
46 1014
    if (is_array($str)) {
47 1
      throw new \InvalidArgumentException(
48
          'Passed value cannot be an array'
49 1
      );
50 1013
    } 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 1012
    if (!defined('ENT_SUBSTITUTE')) {
58
      define('ENT_SUBSTITUTE', 8);
59
    }
60
61
    // init
62 1012
    UTF8::checkForSupport();
63 1012
    $this->str = (string)$str;
64
65 1012
    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 798
      $this->encoding = $encoding;
67 798
    } else {
68 644
      UTF8::mbstring_loaded();
69 644
      $this->encoding = mb_internal_encoding();
70
    }
71
72 1012
    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 798
      $this->encoding = $encoding;
74 798
    } else {
75 644
      $this->encoding = mb_internal_encoding();
76
    }
77 1012
  }
78
79
  /**
80
   * Returns the value in $str.
81
   *
82
   * @return string The current value of the $str property
83
   */
84 971
  public function __toString()
85
  {
86 971
    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 1004
  public static function create($str = '', $encoding = null)
116
  {
117 1004
    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 248
  public function length()
189
  {
190 248
    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 184
  public function regexReplace($pattern, $replacement, $options = '')
235
  {
236 184
    if ($options === 'msr') {
237 8
      $options = 'ms';
238 8
    }
239
240 184
    $str = preg_replace(
241 184
        '/' . $pattern . '/u' . $options,
242 184
        $replacement,
243 184
        $this->str
244 184
    );
245
246 184
    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);
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);
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);
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
   * Append an password (limited to chars that are good readable).
1359
   *
1360
   * @param int    $length        length of the random string
1361
   *
1362
   * @return $this
1363
   */
1364 1
  public function appendPassword($length)
1365
  {
1366 1
    $possibleChars = '2346789bcdfghjkmnpqrtvwxyzBCDFGHJKLMNPQRTVWXYZ';
1367
1368 1
    return $this->appendRandomString($length, $possibleChars);
1369
  }
1370
1371
  /**
1372
   * Append an unique identifier.
1373
   *
1374
   * @param string|int $extraPrefix
1375
   *
1376
   * @return string md5-hash
1377
   */
1378 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...
1379
  {
1380 1
    $prefix = mt_rand() .
1381 1
              session_id() .
1382 1
              (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '') .
1383 1
              (isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '') .
1384 1
              $extraPrefix;
1385
1386 1
    return $this->str .= md5(uniqid($prefix, true) . $prefix);
1387
  }
1388
1389
  /**
1390
   * Append an random string.
1391
   *
1392
   * @param int    $length        length of the random string
1393
   * @param string $possibleChars characters string for the random selection
1394
   *
1395
   * @return $this
1396
   */
1397 2
  public function appendRandomString($length, $possibleChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')
1398
  {
1399
    // init
1400 2
    $i = 0;
1401 2
    $length = (int)$length;
1402 2
    $maxlength = UTF8::strlen($possibleChars);
1403
1404 2
    if ($maxlength === 0) {
1405 1
      return $this;
1406
    }
1407
1408
    // add random chars
1409 2
    while ($i < $length) {
1410 2
      $char = UTF8::substr($possibleChars, mt_rand(0, $maxlength - 1), 1);
1411 2
      $this->str .= $char;
1412 2
      $i++;
1413 2
    }
1414
1415 2
    return $this;
1416
  }
1417
1418
  /**
1419
   * Converts the string into an URL slug. This includes replacing non-ASCII
1420
   * characters with their closest ASCII equivalents, removing remaining
1421
   * non-ASCII and non-alphanumeric characters, and replacing whitespace with
1422
   * $replacement. The replacement defaults to a single dash, and the string
1423
   * is also converted to lowercase.
1424
   *
1425
   * @param string $replacement The string used to replace whitespace
1426
   * @param string $language    The language for the url
1427
   * @param bool   $strToLower  string to lower
1428
   *
1429
   * @return Stringy Object whose $str has been converted to an URL slug
1430
   */
1431 15
  public function slugify($replacement = '-', $language = 'de', $strToLower = true)
1432
  {
1433 15
    $slug = URLify::slug($this->str, $language, $replacement, $strToLower);
1434
1435 15
    return static::create($slug, $this->encoding);
1436
  }
1437
1438
  /**
1439
   * Remove css media-queries.
1440
   *
1441
   * @return Stringy
1442
   */
1443 1
  public function stripeCssMediaQueries()
1444
  {
1445 1
    $pattern = '#@media\\s+(?:only\\s)?(?:[\\s{\\(]|screen|all)\\s?[^{]+{.*}\\s*}\\s*#misU';
1446 1
    return static::create(preg_replace($pattern, '', $this->str));
1447
  }
1448
1449
  /**
1450
   * Remove empty html-tag.
1451
   *
1452
   * e.g.: <tag></tag>
1453
   *
1454
   * @return Stringy
1455
   */
1456 1
  public function stripeEmptyHtmlTags()
1457
  {
1458 1
    $pattern = "/<[^\/>]*>(([\s]?)*|)<\/[^>]*>/i";
1459
1460 1
    return static::create(preg_replace($pattern, '', $this->str));
1461
  }
1462
1463
  /**
1464
   * Converts the string into an valid UTF-8 string.
1465
   *
1466
   * @return Stringy
1467
   */
1468 1
  public function utf8ify()
1469
  {
1470 1
    return static::create(UTF8::cleanup($this->str));
1471
  }
1472
1473
  /**
1474
   * escape html
1475
   *
1476
   * @return Stringy
1477
   */
1478 6
  public function escape()
1479
  {
1480 6
    $str = UTF8::htmlspecialchars(
1481 6
        $this->str,
1482 6
        ENT_QUOTES | ENT_SUBSTITUTE,
1483 6
        $this->encoding
1484 6
    );
1485
1486 6
    return static::create($str, $this->encoding);
1487
  }
1488
1489
  /**
1490
   * remove xss from html
1491
   *
1492
   * @return Stringy
1493
   */
1494 6
  public function removeXss()
1495
  {
1496 6
    static $antiXss = null;
1497
1498 6
    if ($antiXss === null) {
1499 1
      $antiXss = new AntiXSS();
1500 1
    }
1501
1502 6
    $str = $antiXss->xss_clean($this->str);
1503
1504 6
    return static::create($str, $this->encoding);
1505
  }
1506
1507
  /**
1508
   * remove html-break [br | \r\n | \r | \n | ...]
1509
   *
1510
   * @param string $replacement
1511
   *
1512
   * @return Stringy
1513
   */
1514 6
  public function removeHtmlBreak($replacement = '')
1515
  {
1516 6
    $str = preg_replace('#/\r\n|\r|\n|<br.*/?>#isU', $replacement, $this->str);
1517
1518 6
    return static::create($str, $this->encoding);
1519
  }
1520
1521
  /**
1522
   * remove html
1523
   *
1524
   * @param $allowableTags
1525
   *
1526
   * @return Stringy
1527
   */
1528 6
  public function removeHtml($allowableTags = null)
1529
  {
1530 6
    $str = strip_tags($this->str, $allowableTags);
1531
1532 6
    return static::create($str, $this->encoding);
1533
  }
1534
1535
  /**
1536
   * Returns the substring beginning at $start, and up to, but not including
1537
   * the index specified by $end. If $end is omitted, the function extracts
1538
   * the remaining string. If $end is negative, it is computed from the end
1539
   * of the string.
1540
   *
1541
   * @param  int $start Initial index from which to begin extraction
1542
   * @param  int $end   Optional index at which to end extraction
1543
   *
1544
   * @return Stringy Object with its $str being the extracted substring
1545
   */
1546 18
  public function slice($start, $end = null)
1547
  {
1548 18
    if ($end === null) {
1549 4
      $length = $this->length();
1550 18
    } elseif ($end >= 0 && $end <= $start) {
1551 4
      return static::create('', $this->encoding);
1552 10
    } elseif ($end < 0) {
1553 2
      $length = $this->length() + $end - $start;
1554 2
    } else {
1555 8
      $length = $end - $start;
1556
    }
1557
1558 14
    $str = UTF8::substr($this->str, $start, $length, $this->encoding);
1559
1560 14
    return static::create($str, $this->encoding);
1561
  }
1562
1563
  /**
1564
   * Splits the string with the provided regular expression, returning an
1565
   * array of Stringy objects. An optional integer $limit will truncate the
1566
   * results.
1567
   *
1568
   * @param  string $pattern The regex with which to split the string
1569
   * @param  int    $limit   Optional maximum number of results to return
1570
   *
1571
   * @return Stringy[] An array of Stringy objects
1572
   */
1573 19
  public function split($pattern, $limit = null)
1574
  {
1575 19
    if ($limit === 0) {
1576 2
      return array();
1577
    }
1578
1579
    // UTF8::split errors when supplied an empty pattern in < PHP 5.4.13
1580
    // and current versions of HHVM (3.8 and below)
1581 17
    if ($pattern === '') {
1582 1
      return array(static::create($this->str, $this->encoding));
1583
    }
1584
1585
    // UTF8::split returns the remaining unsplit string in the last index when
1586
    // supplying a limit
1587 16
    if ($limit > 0) {
1588 8
      $limit += 1;
1589 8
    } else {
1590 8
      $limit = -1;
1591
    }
1592
1593 16
    $array = preg_split('/' . preg_quote($pattern, '/') . '/u', $this->str, $limit);
1594
1595 16
    if ($limit > 0 && count($array) === $limit) {
1596 4
      array_pop($array);
1597 4
    }
1598
1599
    /** @noinspection CallableInLoopTerminationConditionInspection */
1600
    /** @noinspection ForeachInvariantsInspection */
1601 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...
1602 16
      $array[$i] = static::create($array[$i], $this->encoding);
1603 16
    }
1604
1605 16
    return $array;
1606
  }
1607
1608
  /**
1609
   * Surrounds $str with the given substring.
1610
   *
1611
   * @param  string $substring The substring to add to both sides
1612
   *
1613
   * @return Stringy Object whose $str had the substring both prepended and
1614
   *                 appended
1615
   */
1616 5
  public function surround($substring)
1617
  {
1618 5
    $str = implode('', array($substring, $this->str, $substring));
1619
1620 5
    return static::create($str, $this->encoding);
1621
  }
1622
1623
  /**
1624
   * Returns a case swapped version of the string.
1625
   *
1626
   * @return Stringy Object whose $str has each character's case swapped
1627
   */
1628 5
  public function swapCase()
1629
  {
1630 5
    $stringy = static::create($this->str, $this->encoding);
1631
1632 5
    $stringy->str = UTF8::swapCase($stringy->str, $stringy->encoding);
1633
1634 5
    return $stringy;
1635
  }
1636
1637
  /**
1638
   * Returns a string with smart quotes, ellipsis characters, and dashes from
1639
   * Windows-1252 (commonly used in Word documents) replaced by their ASCII
1640
   * equivalents.
1641
   *
1642
   * @return Stringy Object whose $str has those characters removed
1643
   */
1644 4
  public function tidy()
1645
  {
1646 4
    $str = UTF8::normalize_msword($this->str);
1647
1648 4
    return static::create($str, $this->encoding);
1649
  }
1650
1651
  /**
1652
   * Returns a trimmed string with the first letter of each word capitalized.
1653
   * Also accepts an array, $ignore, allowing you to list words not to be
1654
   * capitalized.
1655
   *
1656
   * @param  array $ignore An array of words not to capitalize
1657
   *
1658
   * @return Stringy Object with a titleized $str
1659
   */
1660 5
  public function titleize($ignore = null)
1661
  {
1662 5
    $stringy = static::create($this->trim(), $this->encoding);
1663 5
    $encoding = $this->encoding;
1664
1665 5
    $stringy->str = preg_replace_callback(
1666 5
        '/([\S]+)/u',
1667
        function ($match) use ($encoding, $ignore) {
1668 5
          if ($ignore && in_array($match[0], $ignore, true)) {
1669 2
            return $match[0];
1670
          } else {
1671 5
            $stringy = new Stringy($match[0], $encoding);
1672
1673 5
            return (string)$stringy->toLowerCase()->upperCaseFirst();
1674
          }
1675 5
        },
1676 5
        $stringy->str
1677 5
    );
1678
1679 5
    return $stringy;
1680
  }
1681
1682
  /**
1683
   * Converts all characters in the string to lowercase. An alias for PHP's
1684
   * UTF8::strtolower().
1685
   *
1686
   * @return Stringy Object with all characters of $str being lowercase
1687
   */
1688 27
  public function toLowerCase()
1689
  {
1690 27
    $str = UTF8::strtolower($this->str, $this->encoding);
1691
1692 27
    return static::create($str, $this->encoding);
1693
  }
1694
1695
  /**
1696
   * Returns true if the string is base64 encoded, false otherwise.
1697
   *
1698
   * @return bool Whether or not $str is base64 encoded
1699
   */
1700 7
  public function isBase64()
1701
  {
1702 7
    return UTF8::is_base64($this->str);
1703
  }
1704
1705
  /**
1706
   * Returns an ASCII version of the string. A set of non-ASCII characters are
1707
   * replaced with their closest ASCII counterparts, and the rest are removed
1708
   * unless instructed otherwise.
1709
   *
1710
   * @return Stringy Object whose $str contains only ASCII characters
1711
   */
1712 16
  public function toAscii()
1713
  {
1714 16
    $str = UTF8::toAscii($this->str);
1715
1716 16
    return static::create($str, $this->encoding);
1717
  }
1718
1719
  /**
1720
   * Returns a boolean representation of the given logical string value.
1721
   * For example, 'true', '1', 'on' and 'yes' will return true. 'false', '0',
1722
   * 'off', and 'no' will return false. In all instances, case is ignored.
1723
   * For other numeric strings, their sign will determine the return value.
1724
   * In addition, blank strings consisting of only whitespace will return
1725
   * false. For all other strings, the return value is a result of a
1726
   * boolean cast.
1727
   *
1728
   * @return bool A boolean value for the string
1729
   */
1730 15
  public function toBoolean()
1731
  {
1732 15
    $key = $this->toLowerCase()->str;
1733
    $map = array(
1734 15
        'true'  => true,
1735 15
        '1'     => true,
1736 15
        'on'    => true,
1737 15
        'yes'   => true,
1738 15
        'false' => false,
1739 15
        '0'     => false,
1740 15
        'off'   => false,
1741 15
        'no'    => false,
1742 15
    );
1743
1744 15
    if (array_key_exists($key, $map)) {
1745 10
      return $map[$key];
1746 5
    } elseif (is_numeric($this->str)) {
1747 2
      return ((int)$this->str > 0);
1748
    } else {
1749 3
      return (bool)$this->regexReplace('[[:space:]]', '')->str;
1750
    }
1751
  }
1752
1753
  /**
1754
   * Return Stringy object as string, but you can also use (string) for automatically casting the object into a string.
1755
   *
1756
   * @return string
1757
   */
1758 8
  public function toString()
1759
  {
1760 8
    return (string)$this->str;
1761
  }
1762
1763
  /**
1764
   * Converts each tab in the string to some number of spaces, as defined by
1765
   * $tabLength. By default, each tab is converted to 4 consecutive spaces.
1766
   *
1767
   * @param  int $tabLength Number of spaces to replace each tab with
1768
   *
1769
   * @return Stringy Object whose $str has had tabs switched to spaces
1770
   */
1771 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...
1772
  {
1773 6
    $spaces = UTF8::str_repeat(' ', $tabLength);
1774 6
    $str = UTF8::str_replace("\t", $spaces, $this->str);
1775
1776 6
    return static::create($str, $this->encoding);
1777
  }
1778
1779
  /**
1780
   * Converts each occurrence of some consecutive number of spaces, as
1781
   * defined by $tabLength, to a tab. By default, each 4 consecutive spaces
1782
   * are converted to a tab.
1783
   *
1784
   * @param  int $tabLength Number of spaces to replace with a tab
1785
   *
1786
   * @return Stringy Object whose $str has had spaces switched to tabs
1787
   */
1788 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...
1789
  {
1790 5
    $spaces = UTF8::str_repeat(' ', $tabLength);
1791 5
    $str = UTF8::str_replace($spaces, "\t", $this->str);
1792
1793 5
    return static::create($str, $this->encoding);
1794
  }
1795
1796
  /**
1797
   * Converts the first character of each word in the string to uppercase.
1798
   *
1799
   * @return Stringy Object with all characters of $str being title-cased
1800
   */
1801 5
  public function toTitleCase()
1802
  {
1803
    // "mb_convert_case()" used a polyfill from the "UTF8"-Class
1804 5
    $str = mb_convert_case($this->str, MB_CASE_TITLE, $this->encoding);
1805
1806 5
    return static::create($str, $this->encoding);
1807
  }
1808
1809
  /**
1810
   * Converts all characters in the string to uppercase. An alias for PHP's
1811
   * UTF8::strtoupper().
1812
   *
1813
   * @return Stringy Object with all characters of $str being uppercase
1814
   */
1815 5
  public function toUpperCase()
1816
  {
1817 5
    $str = UTF8::strtoupper($this->str, $this->encoding);
1818
1819 5
    return static::create($str, $this->encoding);
1820
  }
1821
1822
  /**
1823
   * Returns a string with whitespace removed from the start of the string.
1824
   * Supports the removal of unicode whitespace. Accepts an optional
1825
   * string of characters to strip instead of the defaults.
1826
   *
1827
   * @param  string $chars Optional string of characters to strip
1828
   *
1829
   * @return Stringy Object with a trimmed $str
1830
   */
1831 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...
1832
  {
1833 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...
1834 11
      $chars = '[:space:]';
1835 11
    } else {
1836 2
      $chars = preg_quote($chars, '/');
1837
    }
1838
1839 13
    return $this->regexReplace("^[$chars]+", '');
1840
  }
1841
1842
  /**
1843
   * Returns a string with whitespace removed from the end of the string.
1844
   * Supports the removal of unicode whitespace. Accepts an optional
1845
   * string of characters to strip instead of the defaults.
1846
   *
1847
   * @param  string $chars Optional string of characters to strip
1848
   *
1849
   * @return Stringy Object with a trimmed $str
1850
   */
1851 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...
1852
  {
1853 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...
1854 11
      $chars = '[:space:]';
1855 11
    } else {
1856 2
      $chars = preg_quote($chars, '/');
1857
    }
1858
1859 13
    return $this->regexReplace("[$chars]+\$", '');
1860
  }
1861
1862
  /**
1863
   * Truncates the string to a given length. If $substring is provided, and
1864
   * truncating occurs, the string is further truncated so that the substring
1865
   * may be appended without exceeding the desired length.
1866
   *
1867
   * @param  int    $length    Desired length of the truncated string
1868
   * @param  string $substring The substring to append if it can fit
1869
   *
1870
   * @return Stringy Object with the resulting $str after truncating
1871
   */
1872 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...
1873
  {
1874 22
    $stringy = static::create($this->str, $this->encoding);
1875 22
    if ($length >= $stringy->length()) {
1876 4
      return $stringy;
1877
    }
1878
1879
    // Need to further trim the string so we can append the substring
1880 18
    $substringLength = UTF8::strlen($substring, $stringy->encoding);
1881 18
    $length -= $substringLength;
1882
1883 18
    $truncated = UTF8::substr($stringy->str, 0, $length, $stringy->encoding);
1884 18
    $stringy->str = $truncated . $substring;
1885
1886 18
    return $stringy;
1887
  }
1888
1889
  /**
1890
   * Returns a lowercase and trimmed string separated by underscores.
1891
   * Underscores are inserted before uppercase characters (with the exception
1892
   * of the first character of the string), and in place of spaces as well as
1893
   * dashes.
1894
   *
1895
   * @return Stringy Object with an underscored $str
1896
   */
1897 16
  public function underscored()
1898
  {
1899 16
    return $this->delimit('_');
1900
  }
1901
1902
  /**
1903
   * Returns an UpperCamelCase version of the supplied string. It trims
1904
   * surrounding spaces, capitalizes letters following digits, spaces, dashes
1905
   * and underscores, and removes spaces, dashes, underscores.
1906
   *
1907
   * @return Stringy Object with $str in UpperCamelCase
1908
   */
1909 13
  public function upperCamelize()
1910
  {
1911 13
    return $this->camelize()->upperCaseFirst();
1912
  }
1913
1914
  /**
1915
   * Returns a camelCase version of the string. Trims surrounding spaces,
1916
   * capitalizes letters following digits, spaces, dashes and underscores,
1917
   * and removes spaces, dashes, as well as underscores.
1918
   *
1919
   * @return Stringy Object with $str in camelCase
1920
   */
1921 32
  public function camelize()
1922
  {
1923 32
    $encoding = $this->encoding;
1924 32
    $stringy = $this->trim()->lowerCaseFirst();
1925 32
    $stringy->str = preg_replace('/^[-_]+/', '', $stringy->str);
1926
1927 32
    $stringy->str = preg_replace_callback(
1928 32
        '/[-_\s]+(.)?/u',
1929
        function ($match) use ($encoding) {
1930 27
          if (isset($match[1])) {
1931 27
            return UTF8::strtoupper($match[1], $encoding);
1932
          } else {
1933 1
            return '';
1934
          }
1935 32
        },
1936 32
        $stringy->str
1937 32
    );
1938
1939 32
    $stringy->str = preg_replace_callback(
1940 32
        '/[\d]+(.)?/u',
1941
        function ($match) use ($encoding) {
1942 6
          return UTF8::strtoupper($match[0], $encoding);
1943 32
        },
1944 32
        $stringy->str
1945 32
    );
1946
1947 32
    return $stringy;
1948
  }
1949
1950
  /**
1951
   * Convert a string to e.g.: "snake_case"
1952
   *
1953
   * @return Stringy Object with $str in snake_case
1954
   */
1955 20
  public function snakeize()
1956
  {
1957 20
    $str = $this->str;
1958
1959 20
    $str = UTF8::normalize_whitespace($str);
1960 20
    $str = str_replace('-', '_', $str);
1961
1962 20
    $str = preg_replace_callback(
1963 20
        '/([\d|A-Z])/u',
1964 20
        function ($matches) {
1965 8
          $match = $matches[1];
1966 8
          $matchInt = (int)$match;
1967
1968 8
          if ("$matchInt" == $match) {
1969 4
            return '_' . $match . '_';
1970
          } else {
1971 4
            return '_' . UTF8::strtolower($match);
1972
          }
1973 20
        },
1974
        $str
1975 20
    );
1976
1977 20
    $str = preg_replace(
1978
        array(
1979
1980 20
            '/\s+/',      // convert spaces to "_"
1981 20
            '/^\s+|\s+$/',  // trim leading & trailing spaces
1982 20
            '/_+/',         // remove double "_"
1983 20
        ),
1984
        array(
1985 20
            '_',
1986 20
            '',
1987 20
            '_',
1988 20
        ),
1989
        $str
1990 20
    );
1991
1992 20
    $str = UTF8::trim($str, '_'); // trim leading & trailing "_"
1993 20
    $str = UTF8::trim($str); // trim leading & trailing whitespace
1994
1995 20
    return static::create($str, $this->encoding);
1996
  }
1997
1998
  /**
1999
   * Converts the first character of the string to lower case.
2000
   *
2001
   * @return Stringy Object with the first character of $str being lower case
2002
   */
2003 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...
2004
  {
2005 37
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
2006 37
    $rest = UTF8::substr(
2007 37
        $this->str, 1, $this->length() - 1,
2008 37
        $this->encoding
2009 37
    );
2010
2011 37
    $str = UTF8::strtolower($first, $this->encoding) . $rest;
2012
2013 37
    return static::create($str, $this->encoding);
2014
  }
2015
2016
  /**
2017
   * Shorten the string after $length, but also after the next word.
2018
   *
2019
   * @param int    $length
2020
   * @param string $strAddOn
2021
   *
2022
   * @return Stringy
2023
   */
2024 4
  public function shortenAfterWord($length, $strAddOn = '...')
2025
  {
2026 4
    $string = UTF8::str_limit_after_word($this->str, $length, $strAddOn);
2027
2028 4
    return static::create($string);
2029
  }
2030
2031
  /**
2032
   * Line-Wrap the string after $limit, but also after the next word.
2033
   *
2034
   * @param int $limit
2035
   *
2036
   * @return Stringy
2037
   */
2038 1
  public function lineWrapAfterWord($limit)
2039
  {
2040 1
    $strings = preg_split('/\\r\\n|\\r|\\n/', $this->str);
2041
2042 1
    $string = '';
2043 1
    foreach ($strings as $value) {
2044 1
      $string .= wordwrap($value, $limit);
2045 1
      $string .= "\n";
2046 1
    }
2047
2048 1
    return static::create($string);
2049
  }
2050
}
2051