Completed
Push — master ( ea710a...d360ed )
by Lars
03:20
created

Stringy::offsetGet()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

Changes 5
Bugs 0 Features 0
Metric Value
c 5
b 0
f 0
dl 0
loc 16
ccs 7
cts 7
cp 1
rs 9.2
cc 4
eloc 9
nc 2
nop 1
crap 4
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 936
  public function __construct($str = '', $encoding = null)
45
  {
46 936
    if (is_array($str)) {
47 1
      throw new \InvalidArgumentException(
48 1
          'Passed value cannot be an array'
49
      );
50 935
    } elseif (is_object($str) && !method_exists($str, '__toString')) {
51 1
      throw new \InvalidArgumentException(
52 1
          'Passed object must have a __toString method'
53
      );
54
    }
55
56
57
    // don't throw a notice on PHP 5.3
58 934
    if (!defined('ENT_SUBSTITUTE')) {
59
      define('ENT_SUBSTITUTE', 8);
60
    }
61
62
    // init
63 934
    UTF8::checkForSupport();
64 934
    $this->str = (string) $str;
65
66 934
    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...
67 762
      $this->encoding = $encoding;
68
    } else {
69 563
      UTF8::mbstring_loaded();
70 563
      $this->encoding = mb_internal_encoding();
71
    }
72
73 934
    if ($encoding) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $encoding of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
74 762
      $this->encoding = $encoding;
75
    } else {
76 563
      $this->encoding = mb_internal_encoding();
77
    }
78 934
  }
79
80
  /**
81
   * Returns the value in $str.
82
   *
83
   * @return string The current value of the $str property
84
   */
85 895
  public function __toString()
86
  {
87 895
    return $this->str;
88
  }
89
90
  /**
91
   * Returns a new string with $string appended.
92
   *
93
   * @param  string $string The string to append
94
   *
95
   * @return Stringy Object with appended $string
96
   */
97 2
  public function append($string)
98
  {
99 2
    return static::create($this->str . $string, $this->encoding);
100
  }
101
102
  /**
103
   * Creates a Stringy object and assigns both str and encoding properties
104
   * the supplied values. $str is cast to a string prior to assignment, and if
105
   * $encoding is not specified, it defaults to mb_internal_encoding(). It
106
   * then returns the initialized object. Throws an InvalidArgumentException
107
   * if the first argument is an array or object without a __toString method.
108
   *
109
   * @param  mixed  $str      Value to modify, after being cast to string
110
   * @param  string $encoding The character encoding
111
   *
112
   * @return Stringy A Stringy object
113
   * @throws \InvalidArgumentException if an array or object without a
114
   *         __toString method is passed as the first argument
115
   */
116 926
  public static function create($str = '', $encoding = null)
117
  {
118 926
    return new static($str, $encoding);
119
  }
120
121
  /**
122
   * Returns the substring between $start and $end, if found, or an empty
123
   * string. An optional offset may be supplied from which to begin the
124
   * search for the start string.
125
   *
126
   * @param  string $start  Delimiter marking the start of the substring
127
   * @param  string $end    Delimiter marketing the end of the substring
128
   * @param  int    $offset Index from which to begin the search
129
   *
130
   * @return Stringy Object whose $str has been converted to an URL slug
131
   */
132 16
  public function between($start, $end, $offset = 0)
133
  {
134 16
    $startIndex = $this->indexOf($start, $offset);
135 16
    if ($startIndex === false) {
136 2
      return static::create('', $this->encoding);
137
    }
138
139 14
    $substrIndex = $startIndex + UTF8::strlen($start, $this->encoding);
140 14
    $endIndex = $this->indexOf($end, $substrIndex);
141 14
    if ($endIndex === false) {
142 2
      return static::create('', $this->encoding);
143
    }
144
145 12
    return $this->substr($substrIndex, $endIndex - $substrIndex);
146
  }
147
148
  /**
149
   * Returns the index of the first occurrence of $needle in the string,
150
   * and false if not found. Accepts an optional offset from which to begin
151
   * the search.
152
   *
153
   * @param  string $needle Substring to look for
154
   * @param  int    $offset Offset from which to search
155
   *
156
   * @return int|bool The occurrence's index if found, otherwise false
157
   */
158 26
  public function indexOf($needle, $offset = 0)
159
  {
160 26
    return UTF8::strpos($this->str, (string) $needle, (int) $offset, $this->encoding);
161
  }
162
163
  /**
164
   * Returns the substring beginning at $start with the specified $length.
165
   * It differs from the UTF8::substr() function in that providing a $length of
166
   * null will return the rest of the string, rather than an empty string.
167
   *
168
   * @param  int $start  Position of the first character to use
169
   * @param  int $length Maximum number of characters used
170
   *
171
   * @return Stringy Object with its $str being the substring
172
   */
173 66
  public function substr($start, $length = null)
174
  {
175 66
    if ($length === null) {
176 21
      $length = $this->length();
177
    }
178
179 66
    $str = UTF8::substr($this->str, $start, $length, $this->encoding);
180
181 66
    return static::create($str, $this->encoding);
182
  }
183
184
  /**
185
   * Returns the length of the string.
186
   *
187
   * @return int The number of characters in $str given the encoding
188
   */
189 247
  public function length()
190
  {
191 247
    return UTF8::strlen($this->str, $this->encoding);
192
  }
193
194
  /**
195
   * Trims the string and replaces consecutive whitespace characters with a
196
   * single space. This includes tabs and newline characters, as well as
197
   * multibyte whitespace such as the thin space and ideographic space.
198
   *
199
   * @return Stringy Object with a trimmed $str and condensed whitespace
200
   */
201 13
  public function collapseWhitespace()
202
  {
203 13
    return $this->regexReplace('[[:space:]]+', ' ')->trim();
204
  }
205
206
  /**
207
   * Returns a string with whitespace removed from the start and end of the
208
   * string. Supports the removal of unicode whitespace. Accepts an optional
209
   * string of characters to strip instead of the defaults.
210
   *
211
   * @param  string $chars Optional string of characters to strip
212
   *
213
   * @return Stringy Object with a trimmed $str
214
   */
215 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...
216
  {
217 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...
218 113
      $chars = '[:space:]';
219
    } else {
220 1
      $chars = preg_quote($chars, '/');
221
    }
222
223 114
    return $this->regexReplace("^[$chars]+|[$chars]+\$", '');
224
  }
225
226
  /**
227
   * Replaces all occurrences of $pattern in $str by $replacement.
228
   *
229
   * @param  string $pattern     The regular expression pattern
230
   * @param  string $replacement The string to replace with
231
   * @param  string $options     Matching conditions to be used
232
   *
233
   * @return Stringy Object with the result2ing $str after the replacements
234
   */
235 202
  public function regexReplace($pattern, $replacement, $options = '')
236
  {
237 202
    if ($options === 'msr') {
238 7
      $options = 'ms';
239
    }
240
241 202
    $str = preg_replace(
242 202
        '/' . $pattern . '/u' . $options,
243
        $replacement,
244 202
        $this->str
245
    );
246
247 202
    return static::create($str, $this->encoding);
248
  }
249
250
  /**
251
   * Returns true if the string contains all $needles, false otherwise. By
252
   * default the comparison is case-sensitive, but can be made insensitive by
253
   * setting $caseSensitive to false.
254
   *
255
   * @param  array $needles       SubStrings to look for
256
   * @param  bool  $caseSensitive Whether or not to enforce case-sensitivity
257
   *
258
   * @return bool   Whether or not $str contains $needle
259
   */
260 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...
261
  {
262
    /** @noinspection IsEmptyFunctionUsageInspection */
263 43
    if (empty($needles)) {
264 1
      return false;
265
    }
266
267 42
    foreach ($needles as $needle) {
268 42
      if (!$this->contains($needle, $caseSensitive)) {
269 42
        return false;
270
      }
271
    }
272
273 24
    return true;
274
  }
275
276
  /**
277
   * Returns true if the string contains $needle, false otherwise. By default
278
   * the comparison is case-sensitive, but can be made insensitive by setting
279
   * $caseSensitive to false.
280
   *
281
   * @param  string $needle        Substring to look for
282
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
283
   *
284
   * @return bool   Whether or not $str contains $needle
285
   */
286 105
  public function contains($needle, $caseSensitive = true)
287
  {
288 105
    $encoding = $this->encoding;
289
290 105
    if ($caseSensitive) {
291 55
      return (UTF8::strpos($this->str, $needle, 0, $encoding) !== false);
292
    } else {
293 50
      return (UTF8::stripos($this->str, $needle, 0, $encoding) !== false);
294
    }
295
  }
296
297
  /**
298
   * Returns true if the string contains any $needles, false otherwise. By
299
   * default the comparison is case-sensitive, but can be made insensitive by
300
   * setting $caseSensitive to false.
301
   *
302
   * @param  array $needles       SubStrings to look for
303
   * @param  bool  $caseSensitive Whether or not to enforce case-sensitivity
304
   *
305
   * @return bool   Whether or not $str contains $needle
306
   */
307 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...
308
  {
309
    /** @noinspection IsEmptyFunctionUsageInspection */
310 43
    if (empty($needles)) {
311 1
      return false;
312
    }
313
314 42
    foreach ($needles as $needle) {
315 42
      if ($this->contains($needle, $caseSensitive)) {
316 42
        return true;
317
      }
318
    }
319
320 18
    return false;
321
  }
322
323
  /**
324
   * Returns the length of the string, implementing the countable interface.
325
   *
326
   * @return int The number of characters in the string, given the encoding
327
   */
328 1
  public function count()
329
  {
330 1
    return $this->length();
331
  }
332
333
  /**
334
   * Returns the number of occurrences of $substring in the given string.
335
   * By default, the comparison is case-sensitive, but can be made insensitive
336
   * by setting $caseSensitive to false.
337
   *
338
   * @param  string $substring     The substring to search for
339
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
340
   *
341
   * @return int    The number of $substring occurrences
342
   */
343 15
  public function countSubstr($substring, $caseSensitive = true)
344
  {
345 15
    if ($caseSensitive) {
346 9
      return UTF8::substr_count($this->str, $substring, $this->encoding);
347
    }
348
349 6
    $str = UTF8::strtoupper($this->str, $this->encoding);
350 6
    $substring = UTF8::strtoupper($substring, $this->encoding);
351
352 6
    return UTF8::substr_count($str, $substring, $this->encoding);
353
  }
354
355
  /**
356
   * Returns a lowercase and trimmed string separated by dashes. Dashes are
357
   * inserted before uppercase characters (with the exception of the first
358
   * character of the string), and in place of spaces as well as underscores.
359
   *
360
   * @return Stringy Object with a dasherized $str
361
   */
362 19
  public function dasherize()
363
  {
364 19
    return $this->delimit('-');
365
  }
366
367
  /**
368
   * Returns a lowercase and trimmed string separated by the given delimiter.
369
   * Delimiters are inserted before uppercase characters (with the exception
370
   * of the first character of the string), and in place of spaces, dashes,
371
   * and underscores. Alpha delimiters are not converted to lowercase.
372
   *
373
   * @param  string $delimiter Sequence used to separate parts of the string
374
   *
375
   * @return Stringy Object with a delimited $str
376
   */
377 49
  public function delimit($delimiter)
378
  {
379 49
    $str = $this->trim();
380
381 49
    $str = preg_replace('/\B([A-Z])/u', '-\1', $str);
382
383 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...
384
385 49
    $str = preg_replace('/[-_\s]+/u', $delimiter, $str);
386
387 49
    return static::create($str, $this->encoding);
388
  }
389
390
  /**
391
   * Ensures that the string begins with $substring. If it doesn't, it's
392
   * prepended.
393
   *
394
   * @param  string $substring The substring to add if not present
395
   *
396
   * @return Stringy Object with its $str prefixed by the $substring
397
   */
398 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...
399
  {
400 10
    $stringy = static::create($this->str, $this->encoding);
401
402 10
    if (!$stringy->startsWith($substring)) {
403 4
      $stringy->str = $substring . $stringy->str;
404
    }
405
406 10
    return $stringy;
407
  }
408
409
  /**
410
   * Returns true if the string begins with $substring, false otherwise. By
411
   * default, the comparison is case-sensitive, but can be made insensitive
412
   * by setting $caseSensitive to false.
413
   *
414
   * @param  string $substring     The substring to look for
415
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
416
   *
417
   * @return bool   Whether or not $str starts with $substring
418
   */
419 33
  public function startsWith($substring, $caseSensitive = true)
420
  {
421 33
    $substringLength = UTF8::strlen($substring, $this->encoding);
422 33
    $startOfStr = UTF8::substr(
423 33
        $this->str, 0, $substringLength,
424 33
        $this->encoding
425
    );
426
427 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...
428 4
      $substring = UTF8::strtolower($substring, $this->encoding);
429 4
      $startOfStr = UTF8::strtolower($startOfStr, $this->encoding);
430
    }
431
432 33
    return (string) $substring === $startOfStr;
433
  }
434
435
  /**
436
   * Ensures that the string ends with $substring. If it doesn't, it's
437
   * appended.
438
   *
439
   * @param  string $substring The substring to add if not present
440
   *
441
   * @return Stringy Object with its $str suffixed by the $substring
442
   */
443 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...
444
  {
445 10
    $stringy = static::create($this->str, $this->encoding);
446
447 10
    if (!$stringy->endsWith($substring)) {
448 4
      $stringy->str .= $substring;
449
    }
450
451 10
    return $stringy;
452
  }
453
454
  /**
455
   * Returns true if the string ends with $substring, false otherwise. By
456
   * default, the comparison is case-sensitive, but can be made insensitive
457
   * by setting $caseSensitive to false.
458
   *
459
   * @param  string $substring     The substring to look for
460
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
461
   *
462
   * @return bool   Whether or not $str ends with $substring
463
   */
464 33
  public function endsWith($substring, $caseSensitive = true)
465
  {
466 33
    $substringLength = UTF8::strlen($substring, $this->encoding);
467 33
    $strLength = $this->length();
468
469 33
    $endOfStr = UTF8::substr(
470 33
        $this->str,
471 33
        $strLength - $substringLength,
472
        $substringLength,
473 33
        $this->encoding
474
    );
475
476 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...
477 4
      $substring = UTF8::strtolower($substring, $this->encoding);
478 4
      $endOfStr = UTF8::strtolower($endOfStr, $this->encoding);
479
    }
480
481 33
    return (string) $substring === $endOfStr;
482
  }
483
484
  /**
485
   * Returns the first $n characters of the string.
486
   *
487
   * @param  int $n Number of characters to retrieve from the start
488
   *
489
   * @return Stringy Object with its $str being the first $n chars
490
   */
491 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...
492
  {
493 12
    $stringy = static::create($this->str, $this->encoding);
494
495 12
    if ($n < 0) {
496 2
      $stringy->str = '';
497
    } else {
498 10
      return $stringy->substr(0, $n);
499
    }
500
501 2
    return $stringy;
502
  }
503
504
  /**
505
   * Returns the encoding used by the Stringy object.
506
   *
507
   * @return string The current value of the $encoding property
508
   */
509 3
  public function getEncoding()
510
  {
511 3
    return $this->encoding;
512
  }
513
514
  /**
515
   * Returns a new ArrayIterator, thus implementing the IteratorAggregate
516
   * interface. The ArrayIterator's constructor is passed an array of chars
517
   * in the multibyte string. This enables the use of foreach with instances
518
   * of Stringy\Stringy.
519
   *
520
   * @return \ArrayIterator An iterator for the characters in the string
521
   */
522 1
  public function getIterator()
523
  {
524 1
    return new \ArrayIterator($this->chars());
525
  }
526
527
  /**
528
   * Returns an array consisting of the characters in the string.
529
   *
530
   * @return array An array of string chars
531
   */
532 4
  public function chars()
533
  {
534
    // init
535 4
    $chars = array();
536 4
    $l = $this->length();
537
538 4
    for ($i = 0; $i < $l; $i++) {
539 3
      $chars[] = $this->at($i)->str;
540
    }
541
542 4
    return $chars;
543
  }
544
545
  /**
546
   * Returns the character at $index, with indexes starting at 0.
547
   *
548
   * @param  int $index Position of the character
549
   *
550
   * @return Stringy The character at $index
551
   */
552 11
  public function at($index)
553
  {
554 11
    return $this->substr($index, 1);
555
  }
556
557
  /**
558
   * Returns true if the string contains a lower case char, false
559
   * otherwise.
560
   *
561
   * @return bool Whether or not the string contains a lower case character.
562
   */
563 12
  public function hasLowerCase()
564
  {
565 12
    return $this->matchesPattern('.*[[:lower:]]');
566
  }
567
568
  /**
569
   * Returns true if $str matches the supplied pattern, false otherwise.
570
   *
571
   * @param  string $pattern Regex pattern to match against
572
   *
573
   * @return bool   Whether or not $str matches the pattern
574
   */
575 91
  private function matchesPattern($pattern)
576
  {
577 91
    if (preg_match('/' . $pattern . '/u', $this->str)) {
578 54
      return true;
579
    } else {
580 37
      return false;
581
    }
582
  }
583
584
  /**
585
   * Returns true if the string contains an upper case char, false
586
   * otherwise.
587
   *
588
   * @return bool Whether or not the string contains an upper case character.
589
   */
590 12
  public function hasUpperCase()
591
  {
592 12
    return $this->matchesPattern('.*[[:upper:]]');
593
  }
594
595
  /**
596
   * Convert all HTML entities to their applicable characters.
597
   *
598
   * @param  int|null $flags Optional flags
599
   *
600
   * @return Stringy  Object with the resulting $str after being html decoded.
601
   */
602 5
  public function htmlDecode($flags = ENT_COMPAT)
603
  {
604 5
    $str = UTF8::html_entity_decode($this->str, $flags, $this->encoding);
605
606 5
    return static::create($str, $this->encoding);
607
  }
608
609
  /**
610
   * Convert all applicable characters to HTML entities.
611
   *
612
   * @param  int|null $flags Optional flags
613
   *
614
   * @return Stringy  Object with the resulting $str after being html encoded.
615
   */
616 5
  public function htmlEncode($flags = ENT_COMPAT)
617
  {
618 5
    $str = UTF8::htmlentities($this->str, $flags, $this->encoding);
619
620 5
    return static::create($str, $this->encoding);
621
  }
622
623
  /**
624
   * Capitalizes the first word of the string, replaces underscores with
625
   * spaces, and strips '_id'.
626
   *
627
   * @return Stringy Object with a humanized $str
628
   */
629 3
  public function humanize()
630
  {
631 3
    $str = UTF8::str_replace(array('_id', '_'), array('', ' '), $this->str);
632
633 3
    return static::create($str, $this->encoding)->trim()->upperCaseFirst();
634
  }
635
636
  /**
637
   * Converts the first character of the supplied string to upper case.
638
   *
639
   * @return Stringy Object with the first character of $str being upper case
640
   */
641 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...
642
  {
643 27
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
644 27
    $rest = UTF8::substr(
645 27
        $this->str,
646 27
        1,
647 27
        $this->length() - 1,
648 27
        $this->encoding
649
    );
650
651 27
    $str = UTF8::strtoupper($first, $this->encoding) . $rest;
652
653 27
    return static::create($str, $this->encoding);
654
  }
655
656
  /**
657
   * Returns the index of the last occurrence of $needle in the string,
658
   * and false if not found. Accepts an optional offset from which to begin
659
   * the search. Offsets may be negative to count from the last character
660
   * in the string.
661
   *
662
   * @param  string $needle Substring to look for
663
   * @param  int    $offset Offset from which to search
664
   *
665
   * @return int|bool The last occurrence's index if found, otherwise false
666
   */
667 10
  public function indexOfLast($needle, $offset = 0)
668
  {
669 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...
670
  }
671
672
  /**
673
   * Inserts $substring into the string at the $index provided.
674
   *
675
   * @param  string $substring String to be inserted
676
   * @param  int    $index     The index at which to insert the substring
677
   *
678
   * @return Stringy Object with the resulting $str after the insertion
679
   */
680 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...
681
  {
682 8
    $stringy = static::create($this->str, $this->encoding);
683 8
    if ($index > $stringy->length()) {
684 1
      return $stringy;
685
    }
686
687 7
    $start = UTF8::substr($stringy->str, 0, $index, $stringy->encoding);
688 7
    $end = UTF8::substr(
689 7
        $stringy->str, $index, $stringy->length(),
690 7
        $stringy->encoding
691
    );
692
693 7
    $stringy->str = $start . $substring . $end;
694
695 7
    return $stringy;
696
  }
697
698
  /**
699
   * Returns true if the string contains only alphabetic chars, false
700
   * otherwise.
701
   *
702
   * @return bool Whether or not $str contains only alphabetic chars
703
   */
704 10
  public function isAlpha()
705
  {
706 10
    return $this->matchesPattern('^[[:alpha:]]*$');
707
  }
708
709
  /**
710
   * Returns true if the string contains only alphabetic and numeric chars,
711
   * false otherwise.
712
   *
713
   * @return bool Whether or not $str contains only alphanumeric chars
714
   */
715 13
  public function isAlphanumeric()
716
  {
717 13
    return $this->matchesPattern('^[[:alnum:]]*$');
718
  }
719
720
  /**
721
   * Returns true if the string contains only whitespace chars, false
722
   * otherwise.
723
   *
724
   * @return bool Whether or not $str contains only whitespace characters
725
   */
726 15
  public function isBlank()
727
  {
728 15
    return $this->matchesPattern('^[[:space:]]*$');
729
  }
730
731
  /**
732
   * Returns true if the string contains only hexadecimal chars, false
733
   * otherwise.
734
   *
735
   * @return bool Whether or not $str contains only hexadecimal chars
736
   */
737 13
  public function isHexadecimal()
738
  {
739 13
    return $this->matchesPattern('^[[:xdigit:]]*$');
740
  }
741
742
  /**
743
   * Returns true if the string is JSON, false otherwise. Unlike json_decode
744
   * in PHP 5.x, this method is consistent with PHP 7 and other JSON parsers,
745
   * in that an empty string is not considered valid JSON.
746
   *
747
   * @return bool Whether or not $str is JSON
748
   */
749 20
  public function isJson()
750
  {
751 20
    if (!isset($this->str[0])) {
752 1
      return false;
753
    }
754
755 19
    json_decode($this->str);
756
757 19
    if (json_last_error() === JSON_ERROR_NONE) {
758 11
      return true;
759
    } else {
760 8
      return false;
761
    }
762
  }
763
764
  /**
765
   * Returns true if the string contains only lower case chars, false
766
   * otherwise.
767
   *
768
   * @return bool Whether or not $str contains only lower case characters
769
   */
770 8
  public function isLowerCase()
771
  {
772 8
    if ($this->matchesPattern('^[[:lower:]]*$')) {
773 3
      return true;
774
    } else {
775 5
      return false;
776
    }
777
  }
778
779
  /**
780
   * Returns true if the string is serialized, false otherwise.
781
   *
782
   * @return bool Whether or not $str is serialized
783
   */
784 7
  public function isSerialized()
785
  {
786 7
    if (!isset($this->str[0])) {
787 1
      return false;
788
    }
789
790
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
791
    if (
792 6
        $this->str === 'b:0;'
793
        ||
794 6
        @unserialize($this->str) !== false
795
    ) {
796 4
      return true;
797
    } else {
798 2
      return false;
799
    }
800
  }
801
802
  /**
803
   * Returns true if the string contains only lower case chars, false
804
   * otherwise.
805
   *
806
   * @return bool Whether or not $str contains only lower case characters
807
   */
808 8
  public function isUpperCase()
809
  {
810 8
    return $this->matchesPattern('^[[:upper:]]*$');
811
  }
812
813
  /**
814
   * Returns the last $n characters of the string.
815
   *
816
   * @param  int $n Number of characters to retrieve from the end
817
   *
818
   * @return Stringy Object with its $str being the last $n chars
819
   */
820 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...
821
  {
822 12
    $stringy = static::create($this->str, $this->encoding);
823
824 12
    if ($n <= 0) {
825 4
      $stringy->str = '';
826
    } else {
827 8
      return $stringy->substr(-$n);
828
    }
829
830 4
    return $stringy;
831
  }
832
833
  /**
834
   * Splits on newlines and carriage returns, returning an array of Stringy
835
   * objects corresponding to the lines in the string.
836
   *
837
   * @return Stringy[] An array of Stringy objects
838
   */
839 15
  public function lines()
840
  {
841 15
    $array = preg_split('/[\r\n]{1,2}/u', $this->str);
842
    /** @noinspection CallableInLoopTerminationConditionInspection */
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
    }
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
      } else {
869 6
        break;
870
      }
871
    }
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
      } else {
895 6
        break;
896
      }
897
    }
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
    );
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
          }
944
        } else {
945 8
          $table[$i][$j] = 0;
946
        }
947
      }
948
    }
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 2
        $length < abs($offset)
999
    ) {
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 1
          'Pad expects $padType ' . "to be one of 'left', 'right' or 'both'"
1054
      );
1055
    }
1056
1057
    switch ($padType) {
1058 12
      case 'left':
1059 3
        return $this->padLeft($length, $padStr);
1060 3
      case 'right':
1061 6
        return $this->padRight($length, $padStr);
1062
      default:
1063 3
        return $this->padBoth($length, $padStr);
1064
    }
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
            $padStr,
1107 34
            ceil($left / $length)
1108
        ),
1109 34
        0,
1110
        $left,
1111 34
        $stringy->encoding
1112
    );
1113
1114 34
    $rightPadding = UTF8::substr(
1115 34
        UTF8::str_repeat(
1116
            $padStr,
1117 34
            ceil($right / $length)
1118
        ),
1119 34
        0,
1120
        $right,
1121 34
        $stringy->encoding
1122
    );
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
   *
1231
   * @return Stringy Object with the resulting $str after the replacements
1232
   */
1233 19
  public function replace($search, $replacement)
1234
  {
1235 19
    return $this->regexReplace(preg_quote($search, '/'), UTF8::str_replace('\\', '\\\\', $replacement));
1236
  }
1237
1238
  /**
1239
   * Replaces all occurrences of $search from the beginning of string with $replacement
1240
   *
1241
   * @param string $search
1242
   * @param string $replacement
1243
   *
1244
   * @return Stringy Object with the resulting $str after the replacements
1245
   */
1246 16
  public function replaceBeginning($search, $replacement)
1247
  {
1248 16
    $str = $this->regexReplace('^' . preg_quote($search, '/'), UTF8::str_replace('\\', '\\\\', $replacement));
1249
1250 16
    return static::create($str, $this->encoding);
1251
  }
1252
1253
  /**
1254
   * Replaces all occurrences of $search from the ending of string with $replacement
1255
   *
1256
   * @param string $search
1257
   * @param string $replacement
1258
   *
1259
   * @return Stringy Object with the resulting $str after the replacements
1260
   */
1261 16
  public function replaceEnding($search, $replacement)
1262
  {
1263 16
    $str = $this->regexReplace(preg_quote($search, '/') . '$', UTF8::str_replace('\\', '\\\\', $replacement));
1264
1265 16
    return static::create($str, $this->encoding);
1266
  }
1267
1268
  /**
1269
   * Returns a reversed string. A multibyte version of strrev().
1270
   *
1271
   * @return Stringy Object with a reversed $str
1272
   */
1273 5
  public function reverse()
1274
  {
1275 5
    $reversed = UTF8::strrev($this->str);
1276
1277 5
    return static::create($reversed, $this->encoding);
1278
  }
1279
1280
  /**
1281
   * Truncates the string to a given length, while ensuring that it does not
1282
   * split words. If $substring is provided, and truncating occurs, the
1283
   * string is further truncated so that the substring may be appended without
1284
   * exceeding the desired length.
1285
   *
1286
   * @param  int    $length    Desired length of the truncated string
1287
   * @param  string $substring The substring to append if it can fit
1288
   *
1289
   * @return Stringy Object with the resulting $str after truncating
1290
   */
1291 22
  public function safeTruncate($length, $substring = '')
1292
  {
1293 22
    $stringy = static::create($this->str, $this->encoding);
1294 22
    if ($length >= $stringy->length()) {
1295 4
      return $stringy;
1296
    }
1297
1298
    // Need to further trim the string so we can append the substring
1299 18
    $encoding = $stringy->encoding;
1300 18
    $substringLength = UTF8::strlen($substring, $encoding);
1301 18
    $length -= $substringLength;
1302
1303 18
    $truncated = UTF8::substr($stringy->str, 0, $length, $encoding);
1304
1305
    // If the last word was truncated
1306 18
    if (UTF8::strpos($stringy->str, ' ', $length - 1, $encoding) != $length) {
1307
      // Find pos of the last occurrence of a space, get up to that
1308 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...
1309 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 1308 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...
1310
    }
1311
1312 18
    $stringy->str = $truncated . $substring;
1313
1314 18
    return $stringy;
1315
  }
1316
1317
  /**
1318
   * A multibyte string shuffle function. It returns a string with its
1319
   * characters in random order.
1320
   *
1321
   * @return Stringy Object with a shuffled $str
1322
   */
1323 3
  public function shuffle()
1324
  {
1325 3
    $shuffledStr = UTF8::str_shuffle($this->str);
1326
1327 3
    return static::create($shuffledStr, $this->encoding);
1328
  }
1329
1330
  /**
1331
   * Converts the string into an URL slug. This includes replacing non-ASCII
1332
   * characters with their closest ASCII equivalents, removing remaining
1333
   * non-ASCII and non-alphanumeric characters, and replacing whitespace with
1334
   * $replacement. The replacement defaults to a single dash, and the string
1335
   * is also converted to lowercase.
1336
   *
1337
   * @param string $replacement The string used to replace whitespace
1338
   * @param string $language    The language for the url
1339
   * @param bool   $strToLower  string to lower
1340
   *
1341
   * @return Stringy Object whose $str has been converted to an URL slug
1342
   */
1343 15
  public function slugify($replacement = '-', $language = 'de', $strToLower = true)
1344
  {
1345 15
    $slug = URLify::slug($this->str, $language, $replacement, $strToLower);
1346
1347 15
    return static::create($slug, $this->encoding);
1348
  }
1349
1350
  /**
1351
   * escape html
1352
   *
1353
   * @return Stringy
1354
   */
1355 6
  public function escape()
1356
  {
1357 6
    $str = UTF8::htmlspecialchars(
1358 6
        $this->str,
1359 6
        ENT_QUOTES | ENT_SUBSTITUTE,
1360 6
        $this->encoding
1361
    );
1362
1363 6
    return static::create($str, $this->encoding);
1364
  }
1365
1366
  /**
1367
   * remove xss from html
1368
   *
1369
   * @return Stringy
1370
   */
1371 6
  public function removeXss()
1372
  {
1373 6
    static $antiXss = null;
1374
1375 6
    if ($antiXss === null) {
1376 1
      $antiXss = new AntiXSS();
1377
    }
1378
1379 6
    $str = $antiXss->xss_clean($this->str);
1380
1381 6
    return static::create($str, $this->encoding);
1382
  }
1383
1384
  /**
1385
   * remove html
1386
   *
1387
   * @param $allowableTags
1388
   *
1389
   * @return Stringy
1390
   */
1391 6
  public function removeHtml($allowableTags = null)
1392
  {
1393 6
    $str = UTF8::strip_tags($this->str, $allowableTags);
1394
1395 6
    return static::create($str, $this->encoding);
1396
  }
1397
1398
  /**
1399
   * Returns the substring beginning at $start, and up to, but not including
1400
   * the index specified by $end. If $end is omitted, the function extracts
1401
   * the remaining string. If $end is negative, it is computed from the end
1402
   * of the string.
1403
   *
1404
   * @param  int $start Initial index from which to begin extraction
1405
   * @param  int $end   Optional index at which to end extraction
1406
   *
1407
   * @return Stringy Object with its $str being the extracted substring
1408
   */
1409 18
  public function slice($start, $end = null)
1410
  {
1411 18
    if ($end === null) {
1412 4
      $length = $this->length();
1413 14
    } elseif ($end >= 0 && $end <= $start) {
1414 4
      return static::create('', $this->encoding);
1415 10
    } elseif ($end < 0) {
1416 2
      $length = $this->length() + $end - $start;
1417
    } else {
1418 8
      $length = $end - $start;
1419
    }
1420
1421 14
    $str = UTF8::substr($this->str, $start, $length, $this->encoding);
1422
1423 14
    return static::create($str, $this->encoding);
1424
  }
1425
1426
  /**
1427
   * Splits the string with the provided regular expression, returning an
1428
   * array of Stringy objects. An optional integer $limit will truncate the
1429
   * results.
1430
   *
1431
   * @param  string $pattern The regex with which to split the string
1432
   * @param  int    $limit   Optional maximum number of results to return
1433
   *
1434
   * @return Stringy[] An array of Stringy objects
1435
   */
1436 19
  public function split($pattern, $limit = null)
1437
  {
1438 19
    if ($limit === 0) {
1439 2
      return array();
1440
    }
1441
1442
    // UTF8::split errors when supplied an empty pattern in < PHP 5.4.13
1443
    // and current versions of HHVM (3.8 and below)
1444 17
    if ($pattern === '') {
1445 1
      return array(static::create($this->str, $this->encoding));
1446
    }
1447
1448
    // UTF8::split returns the remaining unsplit string in the last index when
1449
    // supplying a limit
1450 16
    if ($limit > 0) {
1451 8
      $limit += 1;
1452
    } else {
1453 8
      $limit = -1;
1454
    }
1455
1456 16
    $array = preg_split('/' . preg_quote($pattern, '/') . '/u', $this->str, $limit);
1457
1458 16
    if ($limit > 0 && count($array) === $limit) {
1459 4
      array_pop($array);
1460
    }
1461
1462
    /** @noinspection CallableInLoopTerminationConditionInspection */
1463 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...
1464 16
      $array[$i] = static::create($array[$i], $this->encoding);
1465
    }
1466
1467 16
    return $array;
1468
  }
1469
1470
  /**
1471
   * Surrounds $str with the given substring.
1472
   *
1473
   * @param  string $substring The substring to add to both sides
1474
   *
1475
   * @return Stringy Object whose $str had the substring both prepended and
1476
   *                 appended
1477
   */
1478 5
  public function surround($substring)
1479
  {
1480 5
    $str = implode('', array($substring, $this->str, $substring));
1481
1482 5
    return static::create($str, $this->encoding);
1483
  }
1484
1485
  /**
1486
   * Returns a case swapped version of the string.
1487
   *
1488
   * @return Stringy Object whose $str has each character's case swapped
1489
   */
1490 5
  public function swapCase()
1491
  {
1492 5
    $stringy = static::create($this->str, $this->encoding);
1493 5
    $encoding = $stringy->encoding;
1494
1495 5
    $stringy->str = preg_replace_callback(
1496 5
        '/[\S]/u',
1497
        function($match) use ($encoding) {
1498 5
          $marchToUpper = UTF8::strtoupper($match[0], $encoding);
1499
1500 5
          if ($match[0] == $marchToUpper) {
1501 5
            return UTF8::strtolower($match[0], $encoding);
1502
          } else {
1503 5
            return $marchToUpper;
1504
          }
1505 5
        },
1506 5
        $stringy->str
1507
    );
1508
1509 5
    return $stringy;
1510
  }
1511
1512
  /**
1513
   * Returns a string with smart quotes, ellipsis characters, and dashes from
1514
   * Windows-1252 (commonly used in Word documents) replaced by their ASCII
1515
   * equivalents.
1516
   *
1517
   * @return Stringy Object whose $str has those characters removed
1518
   */
1519 4
  public function tidy()
1520
  {
1521 4
    $str = UTF8::normalize_msword($this->str);
1522
1523 4
    return static::create($str, $this->encoding);
1524
  }
1525
1526
  /**
1527
   * Returns a trimmed string with the first letter of each word capitalized.
1528
   * Also accepts an array, $ignore, allowing you to list words not to be
1529
   * capitalized.
1530
   *
1531
   * @param  array $ignore An array of words not to capitalize
1532
   *
1533
   * @return Stringy Object with a titleized $str
1534
   */
1535 5
  public function titleize($ignore = null)
1536
  {
1537 5
    $stringy = static::create($this->trim(), $this->encoding);
1538 5
    $encoding = $this->encoding;
1539
1540 5
    $stringy->str = preg_replace_callback(
1541 5
        '/([\S]+)/u',
1542
        function($match) use ($encoding, $ignore) {
1543 5
          if ($ignore && in_array($match[0], $ignore, true)) {
1544 2
            return $match[0];
1545
          } else {
1546 5
            $stringy = new Stringy($match[0], $encoding);
1547
1548 5
            return (string) $stringy->toLowerCase()->upperCaseFirst();
1549
          }
1550 5
        },
1551 5
        $stringy->str
1552
    );
1553
1554 5
    return $stringy;
1555
  }
1556
1557
  /**
1558
   * Converts all characters in the string to lowercase. An alias for PHP's
1559
   * UTF8::strtolower().
1560
   *
1561
   * @return Stringy Object with all characters of $str being lowercase
1562
   */
1563 27
  public function toLowerCase()
1564
  {
1565 27
    $str = UTF8::strtolower($this->str, $this->encoding);
1566
1567 27
    return static::create($str, $this->encoding);
1568
  }
1569
1570
  /**
1571
   * Returns true if the string is base64 encoded, false otherwise.
1572
   *
1573
   * @return bool Whether or not $str is base64 encoded
1574
   */
1575 7
  public function isBase64()
1576
  {
1577
    if (
1578 7
        $this->str !== ''
1579
        &&
1580 7
        base64_encode(base64_decode($this->str, true)) === $this->str
1581
    ) {
1582 4
      return true;
1583
    } else {
1584 3
      return false;
1585
    }
1586
  }
1587
1588
  /**
1589
   * Returns an ASCII version of the string. A set of non-ASCII characters are
1590
   * replaced with their closest ASCII counterparts, and the rest are removed
1591
   * unless instructed otherwise.
1592
   *
1593
   * @return Stringy Object whose $str contains only ASCII characters
1594
   */
1595 16
  public function toAscii()
1596
  {
1597 16
    $str = UTF8::toAscii($this->str);
1598
1599 16
    return static::create($str, $this->encoding);
1600
  }
1601
1602
  /**
1603
   * Returns a boolean representation of the given logical string value.
1604
   * For example, 'true', '1', 'on' and 'yes' will return true. 'false', '0',
1605
   * 'off', and 'no' will return false. In all instances, case is ignored.
1606
   * For other numeric strings, their sign will determine the return value.
1607
   * In addition, blank strings consisting of only whitespace will return
1608
   * false. For all other strings, the return value is a result of a
1609
   * boolean cast.
1610
   *
1611
   * @return bool A boolean value for the string
1612
   */
1613 15
  public function toBoolean()
1614
  {
1615 15
    $key = $this->toLowerCase()->str;
1616
    $map = array(
1617 15
        'true'  => true,
1618
        '1'     => true,
1619
        'on'    => true,
1620
        'yes'   => true,
1621
        'false' => false,
1622
        '0'     => false,
1623
        'off'   => false,
1624
        'no'    => false,
1625
    );
1626
1627 15
    if (array_key_exists($key, $map)) {
1628 10
      return $map[$key];
1629 5
    } elseif (is_numeric($this->str)) {
1630 2
      return ((int) $this->str > 0);
1631
    } else {
1632 3
      return (bool) $this->regexReplace('[[:space:]]', '')->str;
1633
    }
1634
  }
1635
1636
  /**
1637
   * Converts each tab in the string to some number of spaces, as defined by
1638
   * $tabLength. By default, each tab is converted to 4 consecutive spaces.
1639
   *
1640
   * @param  int $tabLength Number of spaces to replace each tab with
1641
   *
1642
   * @return Stringy Object whose $str has had tabs switched to spaces
1643
   */
1644 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...
1645
  {
1646 6
    $spaces = UTF8::str_repeat(' ', $tabLength);
1647 6
    $str = UTF8::str_replace("\t", $spaces, $this->str);
1648
1649 6
    return static::create($str, $this->encoding);
1650
  }
1651
1652
  /**
1653
   * Converts each occurrence of some consecutive number of spaces, as
1654
   * defined by $tabLength, to a tab. By default, each 4 consecutive spaces
1655
   * are converted to a tab.
1656
   *
1657
   * @param  int $tabLength Number of spaces to replace with a tab
1658
   *
1659
   * @return Stringy Object whose $str has had spaces switched to tabs
1660
   */
1661 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...
1662
  {
1663 5
    $spaces = UTF8::str_repeat(' ', $tabLength);
1664 5
    $str = UTF8::str_replace($spaces, "\t", $this->str);
1665
1666 5
    return static::create($str, $this->encoding);
1667
  }
1668
1669
  /**
1670
   * Converts the first character of each word in the string to uppercase.
1671
   *
1672
   * @return Stringy Object with all characters of $str being title-cased
1673
   */
1674 5
  public function toTitleCase()
1675
  {
1676
    // "mb_convert_case()" used a polyfill from the "UTF8"-Class
1677 5
    $str = mb_convert_case($this->str, MB_CASE_TITLE, $this->encoding);
1678
1679 5
    return static::create($str, $this->encoding);
1680
  }
1681
1682
  /**
1683
   * Converts all characters in the string to uppercase. An alias for PHP's
1684
   * UTF8::strtoupper().
1685
   *
1686
   * @return Stringy Object with all characters of $str being uppercase
1687
   */
1688 5
  public function toUpperCase()
1689
  {
1690 5
    $str = UTF8::strtoupper($this->str, $this->encoding);
1691
1692 5
    return static::create($str, $this->encoding);
1693
  }
1694
1695
  /**
1696
   * Returns a string with whitespace removed from the start of the string.
1697
   * Supports the removal of unicode whitespace. Accepts an optional
1698
   * string of characters to strip instead of the defaults.
1699
   *
1700
   * @param  string $chars Optional string of characters to strip
1701
   *
1702
   * @return Stringy Object with a trimmed $str
1703
   */
1704 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...
1705
  {
1706 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...
1707 11
      $chars = '[:space:]';
1708
    } else {
1709 2
      $chars = preg_quote($chars, '/');
1710
    }
1711
1712 13
    return $this->regexReplace("^[$chars]+", '');
1713
  }
1714
1715
  /**
1716
   * Returns a string with whitespace removed from the end of the string.
1717
   * Supports the removal of unicode whitespace. Accepts an optional
1718
   * string of characters to strip instead of the defaults.
1719
   *
1720
   * @param  string $chars Optional string of characters to strip
1721
   *
1722
   * @return Stringy Object with a trimmed $str
1723
   */
1724 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...
1725
  {
1726 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...
1727 11
      $chars = '[:space:]';
1728
    } else {
1729 2
      $chars = preg_quote($chars, '/');
1730
    }
1731
1732 13
    return $this->regexReplace("[$chars]+\$", '');
1733
  }
1734
1735
  /**
1736
   * Truncates the string to a given length. If $substring is provided, and
1737
   * truncating occurs, the string is further truncated so that the substring
1738
   * may be appended without exceeding the desired length.
1739
   *
1740
   * @param  int    $length    Desired length of the truncated string
1741
   * @param  string $substring The substring to append if it can fit
1742
   *
1743
   * @return Stringy Object with the resulting $str after truncating
1744
   */
1745 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...
1746
  {
1747 22
    $stringy = static::create($this->str, $this->encoding);
1748 22
    if ($length >= $stringy->length()) {
1749 4
      return $stringy;
1750
    }
1751
1752
    // Need to further trim the string so we can append the substring
1753 18
    $substringLength = UTF8::strlen($substring, $stringy->encoding);
1754 18
    $length -= $substringLength;
1755
1756 18
    $truncated = UTF8::substr($stringy->str, 0, $length, $stringy->encoding);
1757 18
    $stringy->str = $truncated . $substring;
1758
1759 18
    return $stringy;
1760
  }
1761
1762
  /**
1763
   * Returns a lowercase and trimmed string separated by underscores.
1764
   * Underscores are inserted before uppercase characters (with the exception
1765
   * of the first character of the string), and in place of spaces as well as
1766
   * dashes.
1767
   *
1768
   * @return Stringy Object with an underscored $str
1769
   */
1770 16
  public function underscored()
1771
  {
1772 16
    return $this->delimit('_');
1773
  }
1774
1775
  /**
1776
   * Returns an UpperCamelCase version of the supplied string. It trims
1777
   * surrounding spaces, capitalizes letters following digits, spaces, dashes
1778
   * and underscores, and removes spaces, dashes, underscores.
1779
   *
1780
   * @return Stringy Object with $str in UpperCamelCase
1781
   */
1782 13
  public function upperCamelize()
1783
  {
1784 13
    return $this->camelize()->upperCaseFirst();
1785
  }
1786
1787
  /**
1788
   * Returns a camelCase version of the string. Trims surrounding spaces,
1789
   * capitalizes letters following digits, spaces, dashes and underscores,
1790
   * and removes spaces, dashes, as well as underscores.
1791
   *
1792
   * @return Stringy Object with $str in camelCase
1793
   */
1794 32
  public function camelize()
1795
  {
1796 32
    $encoding = $this->encoding;
1797 32
    $stringy = $this->trim()->lowerCaseFirst();
1798 32
    $stringy->str = preg_replace('/^[-_]+/', '', $stringy->str);
1799
1800 32
    $stringy->str = preg_replace_callback(
1801 32
        '/[-_\s]+(.)?/u',
1802
        function($match) use ($encoding) {
1803 27
          if (isset($match[1])) {
1804 27
            return UTF8::strtoupper($match[1], $encoding);
1805
          } else {
1806 1
            return '';
1807
          }
1808 32
        },
1809 32
        $stringy->str
1810
    );
1811
1812 32
    $stringy->str = preg_replace_callback(
1813 32
        '/[\d]+(.)?/u',
1814 32
        function($match) use ($encoding) {
1815 6
          return UTF8::strtoupper($match[0], $encoding);
1816 32
        },
1817 32
        $stringy->str
1818
    );
1819
1820 32
    return $stringy;
1821
  }
1822
1823
  /**
1824
   * Converts the first character of the string to lower case.
1825
   *
1826
   * @return Stringy Object with the first character of $str being lower case
1827
   */
1828 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...
1829
  {
1830 37
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
1831 37
    $rest = UTF8::substr(
1832 37
        $this->str, 1, $this->length() - 1,
1833 37
        $this->encoding
1834
    );
1835
1836 37
    $str = UTF8::strtolower($first, $this->encoding) . $rest;
1837
1838 37
    return static::create($str, $this->encoding);
1839
  }
1840
}
1841