Completed
Push — master ( 13e604...befdb2 )
by Lars
06:32
created

Stringy::hasLowerCase()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 4
Bugs 0 Features 1
Metric Value
c 4
b 0
f 1
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
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 1016
  public function __construct($str = '', $encoding = null)
45
  {
46 1016
    if (is_array($str)) {
47 1
      throw new \InvalidArgumentException(
48
          'Passed value cannot be an array'
49 1
      );
50 1015
    } 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 1014
    if (!defined('ENT_SUBSTITUTE')) {
58
      define('ENT_SUBSTITUTE', 8);
59
    }
60
61
    // init
62 1014
    UTF8::checkForSupport();
63 1014
    $this->str = (string)$str;
64
65 1014
    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 802
      $this->encoding = $encoding;
67 802
    } else {
68 646
      UTF8::mbstring_loaded();
69 646
      $this->encoding = mb_internal_encoding();
70
    }
71
72 1014
    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 802
      $this->encoding = $encoding;
74 802
    } else {
75 646
      $this->encoding = mb_internal_encoding();
76
    }
77 1014
  }
78
79
  /**
80
   * Returns the value in $str.
81
   *
82
   * @return string The current value of the $str property
83
   */
84 111
  public function __toString()
85
  {
86 111
    return $this->str;
87
  }
88
89
  /**
90
   * Returns a new string with $string appended.
91
   *
92
   * @param  string $string The string to append
93
   *
94
   * @return Stringy Object with appended $string
95
   */
96 5
  public function append($string)
97
  {
98 5
    return static::create($this->str . $string, $this->encoding);
99
  }
100
101
  /**
102
   * Append an password (limited to chars that are good readable).
103
   *
104
   * @param int $length length of the random string
105
   *
106
   * @return Stringy Object with appended password
107
   */
108 1
  public function appendPassword($length)
109
  {
110 1
    $possibleChars = '2346789bcdfghjkmnpqrtvwxyzBCDFGHJKLMNPQRTVWXYZ';
111
112 1
    return $this->appendRandomString($length, $possibleChars);
113
  }
114
115
  /**
116
   * Append an unique identifier.
117
   *
118
   * @param string|int $extraPrefix
119
   *
120
   * @return Stringy Object with appended unique identifier as md5-hash
121
   */
122 1
  public function appendUniqueIdentifier($extraPrefix = '')
0 ignored issues
show
Coding Style introduced by
appendUniqueIdentifier uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
123
  {
124 1
    $prefix = mt_rand() .
125 1
              session_id() .
126 1
              (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '') .
127 1
              (isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '') .
128 1
              $extraPrefix;
129
130 1
    return $this->append(md5(uniqid($prefix, true) . $prefix));
131
  }
132
133
  /**
134
   * Append an random string.
135
   *
136
   * @param int    $length        length of the random string
137
   * @param string $possibleChars characters string for the random selection
138
   *
139
   * @return Stringy Object with appended random string
140
   */
141 2
  public function appendRandomString($length, $possibleChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')
142
  {
143
    // init
144 2
    $i = 0;
145 2
    $length = (int)$length;
146 2
    $str = $this->str;
147 2
    $maxlength = UTF8::strlen($possibleChars);
148
149 2
    if ($maxlength === 0) {
150 1
      return $this;
151
    }
152
153
    // add random chars
154 2
    while ($i < $length) {
155 2
      $char = UTF8::substr($possibleChars, mt_rand(0, $maxlength - 1), 1);
156 2
      $str .= $char;
157 2
      $i++;
158 2
    }
159
160 2
    return $this->append($str);
161
  }
162
163
  /**
164
   * Creates a Stringy object and assigns both str and encoding properties
165
   * the supplied values. $str is cast to a string prior to assignment, and if
166
   * $encoding is not specified, it defaults to mb_internal_encoding(). It
167
   * then returns the initialized object. Throws an InvalidArgumentException
168
   * if the first argument is an array or object without a __toString method.
169
   *
170
   * @param  mixed  $str      Value to modify, after being cast to string
171
   * @param  string $encoding The character encoding
172
   *
173
   * @return Stringy A Stringy object
174
   * @throws \InvalidArgumentException if an array or object without a
175
   *         __toString method is passed as the first argument
176
   */
177 1006
  public static function create($str = '', $encoding = null)
178
  {
179 1006
    return new static($str, $encoding);
180
  }
181
182
  /**
183
   * Returns the substring between $start and $end, if found, or an empty
184
   * string. An optional offset may be supplied from which to begin the
185
   * search for the start string.
186
   *
187
   * @param  string $start  Delimiter marking the start of the substring
188
   * @param  string $end    Delimiter marking the end of the substring
189
   * @param  int    $offset Index from which to begin the search
190
   *
191
   * @return Stringy Object whose $str is a substring between $start and $end
192
   */
193 16
  public function between($start, $end, $offset = 0)
194
  {
195 16
    $startIndex = $this->indexOf($start, $offset);
196 16
    if ($startIndex === false) {
197 2
      return static::create('', $this->encoding);
198
    }
199
200 14
    $substrIndex = $startIndex + UTF8::strlen($start, $this->encoding);
201 14
    $endIndex = $this->indexOf($end, $substrIndex);
202 14
    if ($endIndex === false) {
203 2
      return static::create('', $this->encoding);
204
    }
205
206 12
    return $this->substr($substrIndex, $endIndex - $substrIndex);
207
  }
208
209
  /**
210
   * Returns the index of the first occurrence of $needle in the string,
211
   * and false if not found. Accepts an optional offset from which to begin
212
   * the search.
213
   *
214
   * @param  string $needle Substring to look for
215
   * @param  int    $offset Offset from which to search
216
   *
217
   * @return int|bool The occurrence's index if found, otherwise false
218
   */
219 26
  public function indexOf($needle, $offset = 0)
220
  {
221 26
    return UTF8::strpos($this->str, (string)$needle, (int)$offset, $this->encoding);
222
  }
223
224
  /**
225
   * Returns the substring beginning at $start with the specified $length.
226
   * It differs from the UTF8::substr() function in that providing a $length of
227
   * null will return the rest of the string, rather than an empty string.
228
   *
229
   * @param  int $start  Position of the first character to use
230
   * @param  int $length Maximum number of characters used
231
   *
232
   * @return Stringy Object with its $str being the substring
233
   */
234 66
  public function substr($start, $length = null)
235
  {
236 66
    if ($length === null) {
237 21
      $length = $this->length();
238 21
    }
239
240 66
    $str = UTF8::substr($this->str, $start, $length, $this->encoding);
241
242 66
    return static::create($str, $this->encoding);
243
  }
244
245
  /**
246
   * Returns the length of the string.
247
   *
248
   * @return int The number of characters in $str given the encoding
249
   */
250 249
  public function length()
251
  {
252 249
    return UTF8::strlen($this->str, $this->encoding);
253
  }
254
255
  /**
256
   * Trims the string and replaces consecutive whitespace characters with a
257
   * single space. This includes tabs and newline characters, as well as
258
   * multibyte whitespace such as the thin space and ideographic space.
259
   *
260
   * @return Stringy Object with a trimmed $str and condensed whitespace
261
   */
262 13
  public function collapseWhitespace()
263
  {
264 13
    return $this->regexReplace('[[:space:]]+', ' ')->trim();
265
  }
266
267
  /**
268
   * Returns a string with whitespace removed from the start and end of the
269
   * string. Supports the removal of unicode whitespace. Accepts an optional
270
   * string of characters to strip instead of the defaults.
271
   *
272
   * @param  string $chars Optional string of characters to strip
273
   *
274
   * @return Stringy Object with a trimmed $str
275
   */
276 114 View Code Duplication
  public function trim($chars = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
277
  {
278 114
    if (!$chars) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $chars of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
279 113
      $chars = '[:space:]';
280 113
    } else {
281 1
      $chars = preg_quote($chars, '/');
282
    }
283
284 114
    return $this->regexReplace("^[$chars]+|[$chars]+\$", '');
285
  }
286
287
  /**
288
   * Replaces all occurrences of $pattern in $str by $replacement.
289
   *
290
   * @param  string $pattern     The regular expression pattern
291
   * @param  string $replacement The string to replace with
292
   * @param  string $options     Matching conditions to be used
293
   *
294
   * @return Stringy Object with the result2ing $str after the replacements
295
   */
296 184
  public function regexReplace($pattern, $replacement, $options = '')
297
  {
298 184
    if ($options === 'msr') {
299 8
      $options = 'ms';
300 8
    }
301
302 184
    $str = preg_replace(
303 184
        '/' . $pattern . '/u' . $options,
304 184
        $replacement,
305 184
        $this->str
306 184
    );
307
308 184
    return static::create($str, $this->encoding);
309
  }
310
311
  /**
312
   * Returns true if the string contains all $needles, false otherwise. By
313
   * default the comparison is case-sensitive, but can be made insensitive by
314
   * setting $caseSensitive to false.
315
   *
316
   * @param  array $needles       SubStrings to look for
317
   * @param  bool  $caseSensitive Whether or not to enforce case-sensitivity
318
   *
319
   * @return bool   Whether or not $str contains $needle
320
   */
321 43 View Code Duplication
  public function containsAll($needles, $caseSensitive = true)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
322
  {
323
    /** @noinspection IsEmptyFunctionUsageInspection */
324 43
    if (empty($needles)) {
325 1
      return false;
326
    }
327
328 42
    foreach ($needles as $needle) {
329 42
      if (!$this->contains($needle, $caseSensitive)) {
330 18
        return false;
331
      }
332 24
    }
333
334 24
    return true;
335
  }
336
337
  /**
338
   * Returns true if the string contains $needle, false otherwise. By default
339
   * the comparison is case-sensitive, but can be made insensitive by setting
340
   * $caseSensitive to false.
341
   *
342
   * @param  string $needle        Substring to look for
343
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
344
   *
345
   * @return bool   Whether or not $str contains $needle
346
   */
347 105
  public function contains($needle, $caseSensitive = true)
348
  {
349 105
    $encoding = $this->encoding;
350
351 105
    if ($caseSensitive) {
352 55
      return (UTF8::strpos($this->str, $needle, 0, $encoding) !== false);
353
    } else {
354 50
      return (UTF8::stripos($this->str, $needle, 0, $encoding) !== false);
355
    }
356
  }
357
358
  /**
359
   * Returns true if the string contains any $needles, false otherwise. By
360
   * default the comparison is case-sensitive, but can be made insensitive by
361
   * setting $caseSensitive to false.
362
   *
363
   * @param  array $needles       SubStrings to look for
364
   * @param  bool  $caseSensitive Whether or not to enforce case-sensitivity
365
   *
366
   * @return bool   Whether or not $str contains $needle
367
   */
368 43 View Code Duplication
  public function containsAny($needles, $caseSensitive = true)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
369
  {
370
    /** @noinspection IsEmptyFunctionUsageInspection */
371 43
    if (empty($needles)) {
372 1
      return false;
373
    }
374
375 42
    foreach ($needles as $needle) {
376 42
      if ($this->contains($needle, $caseSensitive)) {
377 24
        return true;
378
      }
379 18
    }
380
381 18
    return false;
382
  }
383
384
  /**
385
   * Returns the length of the string, implementing the countable interface.
386
   *
387
   * @return int The number of characters in the string, given the encoding
388
   */
389 1
  public function count()
390
  {
391 1
    return $this->length();
392
  }
393
394
  /**
395
   * Returns the number of occurrences of $substring in the given string.
396
   * By default, the comparison is case-sensitive, but can be made insensitive
397
   * by setting $caseSensitive to false.
398
   *
399
   * @param  string $substring     The substring to search for
400
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
401
   *
402
   * @return int    The number of $substring occurrences
403
   */
404 15
  public function countSubstr($substring, $caseSensitive = true)
405
  {
406 15
    if ($caseSensitive) {
407 9
      return UTF8::substr_count($this->str, $substring);
408
    }
409
410 6
    $str = UTF8::strtoupper($this->str, $this->encoding);
411 6
    $substring = UTF8::strtoupper($substring, $this->encoding);
412
413 6
    return UTF8::substr_count($str, $substring);
414
  }
415
416
  /**
417
   * Returns a lowercase and trimmed string separated by dashes. Dashes are
418
   * inserted before uppercase characters (with the exception of the first
419
   * character of the string), and in place of spaces as well as underscores.
420
   *
421
   * @return Stringy Object with a dasherized $str
422
   */
423 19
  public function dasherize()
424
  {
425 19
    return $this->delimit('-');
426
  }
427
428
  /**
429
   * Returns a lowercase and trimmed string separated by the given delimiter.
430
   * Delimiters are inserted before uppercase characters (with the exception
431
   * of the first character of the string), and in place of spaces, dashes,
432
   * and underscores. Alpha delimiters are not converted to lowercase.
433
   *
434
   * @param  string $delimiter Sequence used to separate parts of the string
435
   *
436
   * @return Stringy Object with a delimited $str
437
   */
438 49
  public function delimit($delimiter)
439
  {
440 49
    $str = $this->trim();
441
442 49
    $str = preg_replace('/\B([A-Z])/u', '-\1', $str);
443
444 49
    $str = UTF8::strtolower($str, $this->encoding);
0 ignored issues
show
Bug introduced by
It seems like $str can also be of type array<integer,string>; however, voku\helper\UTF8::strtolower() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
445
446 49
    $str = preg_replace('/[-_\s]+/u', $delimiter, $str);
447
448 49
    return static::create($str, $this->encoding);
449
  }
450
451
  /**
452
   * Ensures that the string begins with $substring. If it doesn't, it's
453
   * prepended.
454
   *
455
   * @param  string $substring The substring to add if not present
456
   *
457
   * @return Stringy Object with its $str prefixed by the $substring
458
   */
459 10 View Code Duplication
  public function ensureLeft($substring)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
460
  {
461 10
    $stringy = static::create($this->str, $this->encoding);
462
463 10
    if (!$stringy->startsWith($substring)) {
464 4
      $stringy->str = $substring . $stringy->str;
465 4
    }
466
467 10
    return $stringy;
468
  }
469
470
  /**
471
   * Returns true if the string begins with $substring, false otherwise. By
472
   * default, the comparison is case-sensitive, but can be made insensitive
473
   * by setting $caseSensitive to false.
474
   *
475
   * @param  string $substring     The substring to look for
476
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
477
   *
478
   * @return bool   Whether or not $str starts with $substring
479
   */
480 33
  public function startsWith($substring, $caseSensitive = true)
481
  {
482 33
    $substringLength = UTF8::strlen($substring, $this->encoding);
483 33
    $startOfStr = UTF8::substr(
484 33
        $this->str, 0, $substringLength,
485 33
        $this->encoding
486 33
    );
487
488 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...
489 4
      $substring = UTF8::strtolower($substring, $this->encoding);
490 4
      $startOfStr = UTF8::strtolower($startOfStr, $this->encoding);
0 ignored issues
show
Security Bug introduced by
It seems like $startOfStr can also be of type false; however, voku\helper\UTF8::strtolower() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
491 4
    }
492
493 33
    return (string)$substring === $startOfStr;
494
  }
495
496
  /**
497
   * Ensures that the string ends with $substring. If it doesn't, it's
498
   * appended.
499
   *
500
   * @param  string $substring The substring to add if not present
501
   *
502
   * @return Stringy Object with its $str suffixed by the $substring
503
   */
504 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...
505
  {
506 10
    $stringy = static::create($this->str, $this->encoding);
507
508 10
    if (!$stringy->endsWith($substring)) {
509 4
      $stringy->str .= $substring;
510 4
    }
511
512 10
    return $stringy;
513
  }
514
515
  /**
516
   * Returns true if the string ends with $substring, false otherwise. By
517
   * default, the comparison is case-sensitive, but can be made insensitive
518
   * by setting $caseSensitive to false.
519
   *
520
   * @param  string $substring     The substring to look for
521
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
522
   *
523
   * @return bool   Whether or not $str ends with $substring
524
   */
525 33
  public function endsWith($substring, $caseSensitive = true)
526
  {
527 33
    $substringLength = UTF8::strlen($substring, $this->encoding);
528 33
    $strLength = $this->length();
529
530 33
    $endOfStr = UTF8::substr(
531 33
        $this->str,
532 33
        $strLength - $substringLength,
533 33
        $substringLength,
534 33
        $this->encoding
535 33
    );
536
537 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...
538 4
      $substring = UTF8::strtolower($substring, $this->encoding);
539 4
      $endOfStr = UTF8::strtolower($endOfStr, $this->encoding);
0 ignored issues
show
Security Bug introduced by
It seems like $endOfStr can also be of type false; however, voku\helper\UTF8::strtolower() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
540 4
    }
541
542 33
    return (string)$substring === $endOfStr;
543
  }
544
545
  /**
546
   * Returns the first $n characters of the string.
547
   *
548
   * @param  int $n Number of characters to retrieve from the start
549
   *
550
   * @return Stringy Object with its $str being the first $n chars
551
   */
552 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...
553
  {
554 12
    $stringy = static::create($this->str, $this->encoding);
555
556 12
    if ($n < 0) {
557 2
      $stringy->str = '';
558 2
    } else {
559 10
      return $stringy->substr(0, $n);
560
    }
561
562 2
    return $stringy;
563
  }
564
565
  /**
566
   * Returns the encoding used by the Stringy object.
567
   *
568
   * @return string The current value of the $encoding property
569
   */
570 3
  public function getEncoding()
571
  {
572 3
    return $this->encoding;
573
  }
574
575
  /**
576
   * Returns a new ArrayIterator, thus implementing the IteratorAggregate
577
   * interface. The ArrayIterator's constructor is passed an array of chars
578
   * in the multibyte string. This enables the use of foreach with instances
579
   * of Stringy\Stringy.
580
   *
581
   * @return \ArrayIterator An iterator for the characters in the string
582
   */
583 1
  public function getIterator()
584
  {
585 1
    return new \ArrayIterator($this->chars());
586
  }
587
588
  /**
589
   * Returns an array consisting of the characters in the string.
590
   *
591
   * @return array An array of string chars
592
   */
593 4
  public function chars()
594
  {
595
    // init
596 4
    $chars = array();
597 4
    $l = $this->length();
598
599 4
    for ($i = 0; $i < $l; $i++) {
600 3
      $chars[] = $this->at($i)->str;
601 3
    }
602
603 4
    return $chars;
604
  }
605
606
  /**
607
   * Returns the character at $index, with indexes starting at 0.
608
   *
609
   * @param  int $index Position of the character
610
   *
611
   * @return Stringy The character at $index
612
   */
613 11
  public function at($index)
614
  {
615 11
    return $this->substr($index, 1);
616
  }
617
618
  /**
619
   * Returns true if the string contains a lower case char, false
620
   * otherwise.
621
   *
622
   * @return bool Whether or not the string contains a lower case character.
623
   */
624 12
  public function hasLowerCase()
625
  {
626 12
    return $this->matchesPattern('.*[[:lower:]]');
627
  }
628
629
  /**
630
   * Returns true if $str matches the supplied pattern, false otherwise.
631
   *
632
   * @param  string $pattern Regex pattern to match against
633
   *
634
   * @return bool   Whether or not $str matches the pattern
635
   */
636 91
  private function matchesPattern($pattern)
637
  {
638 91
    if (preg_match('/' . $pattern . '/u', $this->str)) {
639 54
      return true;
640
    } else {
641 37
      return false;
642
    }
643
  }
644
645
  /**
646
   * Returns true if the string contains an upper case char, false
647
   * otherwise.
648
   *
649
   * @return bool Whether or not the string contains an upper case character.
650
   */
651 12
  public function hasUpperCase()
652
  {
653 12
    return $this->matchesPattern('.*[[:upper:]]');
654
  }
655
656
  /**
657
   * Convert all HTML entities to their applicable characters.
658
   *
659
   * @param  int|null $flags Optional flags
660
   *
661
   * @return Stringy  Object with the resulting $str after being html decoded.
662
   */
663 5
  public function htmlDecode($flags = ENT_COMPAT)
664
  {
665 5
    $str = UTF8::html_entity_decode($this->str, $flags, $this->encoding);
666
667 5
    return static::create($str, $this->encoding);
668
  }
669
670
  /**
671
   * Convert all applicable characters to HTML entities.
672
   *
673
   * @param  int|null $flags Optional flags
674
   *
675
   * @return Stringy  Object with the resulting $str after being html encoded.
676
   */
677 5
  public function htmlEncode($flags = ENT_COMPAT)
678
  {
679 5
    $str = UTF8::htmlentities($this->str, $flags, $this->encoding);
680
681 5
    return static::create($str, $this->encoding);
682
  }
683
684
  /**
685
   * Capitalizes the first word of the string, replaces underscores with
686
   * spaces, and strips '_id'.
687
   *
688
   * @return Stringy Object with a humanized $str
689
   */
690 3
  public function humanize()
691
  {
692 3
    $str = UTF8::str_replace(array('_id', '_'), array('', ' '), $this->str);
693
694 3
    return static::create($str, $this->encoding)->trim()->upperCaseFirst();
695
  }
696
697
  /**
698
   * Converts the first character of the supplied string to upper case.
699
   *
700
   * @return Stringy Object with the first character of $str being upper case
701
   */
702 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...
703
  {
704 27
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
705 27
    $rest = UTF8::substr(
706 27
        $this->str,
707 27
        1,
708 27
        $this->length() - 1,
709 27
        $this->encoding
710 27
    );
711
712 27
    $str = UTF8::strtoupper($first, $this->encoding) . $rest;
0 ignored issues
show
Security Bug introduced by
It seems like $first defined by \voku\helper\UTF8::subst... 0, 1, $this->encoding) on line 704 can also be of type false; however, voku\helper\UTF8::strtoupper() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
713
714 27
    return static::create($str, $this->encoding);
715
  }
716
717
  /**
718
   * Returns the index of the last occurrence of $needle in the string,
719
   * and false if not found. Accepts an optional offset from which to begin
720
   * the search. Offsets may be negative to count from the last character
721
   * in the string.
722
   *
723
   * @param  string $needle Substring to look for
724
   * @param  int    $offset Offset from which to search
725
   *
726
   * @return int|bool The last occurrence's index if found, otherwise false
727
   */
728 10
  public function indexOfLast($needle, $offset = 0)
729
  {
730 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...
731
  }
732
733
  /**
734
   * Inserts $substring into the string at the $index provided.
735
   *
736
   * @param  string $substring String to be inserted
737
   * @param  int    $index     The index at which to insert the substring
738
   *
739
   * @return Stringy Object with the resulting $str after the insertion
740
   */
741 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...
742
  {
743 8
    $stringy = static::create($this->str, $this->encoding);
744 8
    if ($index > $stringy->length()) {
745 1
      return $stringy;
746
    }
747
748 7
    $start = UTF8::substr($stringy->str, 0, $index, $stringy->encoding);
749 7
    $end = UTF8::substr(
750 7
        $stringy->str, $index, $stringy->length(),
751 7
        $stringy->encoding
752 7
    );
753
754 7
    $stringy->str = $start . $substring . $end;
755
756 7
    return $stringy;
757
  }
758
759
  /**
760
   * Returns true if the string contains only alphabetic chars, false
761
   * otherwise.
762
   *
763
   * @return bool Whether or not $str contains only alphabetic chars
764
   */
765 10
  public function isAlpha()
766
  {
767 10
    return $this->matchesPattern('^[[:alpha:]]*$');
768
  }
769
770
  /**
771
   * Returns true if the string contains only alphabetic and numeric chars,
772
   * false otherwise.
773
   *
774
   * @return bool Whether or not $str contains only alphanumeric chars
775
   */
776 13
  public function isAlphanumeric()
777
  {
778 13
    return $this->matchesPattern('^[[:alnum:]]*$');
779
  }
780
781
  /**
782
   * Returns true if the string contains only whitespace chars, false
783
   * otherwise.
784
   *
785
   * @return bool Whether or not $str contains only whitespace characters
786
   */
787 15
  public function isBlank()
788
  {
789 15
    return $this->matchesPattern('^[[:space:]]*$');
790
  }
791
792
  /**
793
   * Returns true if the string contains only hexadecimal chars, false
794
   * otherwise.
795
   *
796
   * @return bool Whether or not $str contains only hexadecimal chars
797
   */
798 13
  public function isHexadecimal()
799
  {
800 13
    return $this->matchesPattern('^[[:xdigit:]]*$');
801
  }
802
803
  /**
804
   * Returns true if the string contains HTML-Tags, false otherwise.
805
   *
806
   * @return bool Whether or not $str contains HTML-Tags
807
   */
808 1
  public function isHtml()
809
  {
810 1
    return UTF8::isHtml($this->str);
811
  }
812
813
  /**
814
   * Returns true if the string is JSON, false otherwise. Unlike json_decode
815
   * in PHP 5.x, this method is consistent with PHP 7 and other JSON parsers,
816
   * in that an empty string is not considered valid JSON.
817
   *
818
   * @return bool Whether or not $str is JSON
819
   */
820 20
  public function isJson()
821
  {
822 20
    if (!isset($this->str[0])) {
823 1
      return false;
824
    }
825
826 19
    json_decode($this->str);
827
828 19
    if (json_last_error() === JSON_ERROR_NONE) {
829 11
      return true;
830
    } else {
831 8
      return false;
832
    }
833
  }
834
835
  /**
836
   * Returns true if the string contains only lower case chars, false
837
   * otherwise.
838
   *
839
   * @return bool Whether or not $str contains only lower case characters
840
   */
841 8
  public function isLowerCase()
842
  {
843 8
    if ($this->matchesPattern('^[[:lower:]]*$')) {
844 3
      return true;
845
    } else {
846 5
      return false;
847
    }
848
  }
849
850
  /**
851
   * Returns true if the string is serialized, false otherwise.
852
   *
853
   * @return bool Whether or not $str is serialized
854
   */
855 7
  public function isSerialized()
856
  {
857 7
    if (!isset($this->str[0])) {
858 1
      return false;
859
    }
860
861
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
862
    if (
863 6
        $this->str === 'b:0;'
864 6
        ||
865 6
        @unserialize($this->str) !== false
866 6
    ) {
867 4
      return true;
868
    } else {
869 2
      return false;
870
    }
871
  }
872
873
  /**
874
   * Returns true if the string contains only lower case chars, false
875
   * otherwise.
876
   *
877
   * @return bool Whether or not $str contains only lower case characters
878
   */
879 8
  public function isUpperCase()
880
  {
881 8
    return $this->matchesPattern('^[[:upper:]]*$');
882
  }
883
884
  /**
885
   * Returns the last $n characters of the string.
886
   *
887
   * @param  int $n Number of characters to retrieve from the end
888
   *
889
   * @return Stringy Object with its $str being the last $n chars
890
   */
891 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...
892
  {
893 12
    $stringy = static::create($this->str, $this->encoding);
894
895 12
    if ($n <= 0) {
896 4
      $stringy->str = '';
897 4
    } else {
898 8
      return $stringy->substr(-$n);
899
    }
900
901 4
    return $stringy;
902
  }
903
904
  /**
905
   * Splits on newlines and carriage returns, returning an array of Stringy
906
   * objects corresponding to the lines in the string.
907
   *
908
   * @return Stringy[] An array of Stringy objects
909
   */
910 15
  public function lines()
911
  {
912 15
    $array = preg_split('/[\r\n]{1,2}/u', $this->str);
913
    /** @noinspection CallableInLoopTerminationConditionInspection */
914
    /** @noinspection ForeachInvariantsInspection */
915 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...
916 15
      $array[$i] = static::create($array[$i], $this->encoding);
917 15
    }
918
919 15
    return $array;
920
  }
921
922
  /**
923
   * Returns the longest common prefix between the string and $otherStr.
924
   *
925
   * @param  string $otherStr Second string for comparison
926
   *
927
   * @return Stringy Object with its $str being the longest common prefix
928
   */
929 10
  public function longestCommonPrefix($otherStr)
930
  {
931 10
    $encoding = $this->encoding;
932 10
    $maxLength = min($this->length(), UTF8::strlen($otherStr, $encoding));
933
934 10
    $longestCommonPrefix = '';
935 10
    for ($i = 0; $i < $maxLength; $i++) {
936 8
      $char = UTF8::substr($this->str, $i, 1, $encoding);
937
938 8
      if ($char == UTF8::substr($otherStr, $i, 1, $encoding)) {
939 6
        $longestCommonPrefix .= $char;
940 6
      } else {
941 6
        break;
942
      }
943 6
    }
944
945 10
    return static::create($longestCommonPrefix, $encoding);
946
  }
947
948
  /**
949
   * Returns the longest common suffix between the string and $otherStr.
950
   *
951
   * @param  string $otherStr Second string for comparison
952
   *
953
   * @return Stringy Object with its $str being the longest common suffix
954
   */
955 10
  public function longestCommonSuffix($otherStr)
956
  {
957 10
    $encoding = $this->encoding;
958 10
    $maxLength = min($this->length(), UTF8::strlen($otherStr, $encoding));
959
960 10
    $longestCommonSuffix = '';
961 10
    for ($i = 1; $i <= $maxLength; $i++) {
962 8
      $char = UTF8::substr($this->str, -$i, 1, $encoding);
963
964 8
      if ($char == UTF8::substr($otherStr, -$i, 1, $encoding)) {
965 6
        $longestCommonSuffix = $char . $longestCommonSuffix;
966 6
      } else {
967 6
        break;
968
      }
969 6
    }
970
971 10
    return static::create($longestCommonSuffix, $encoding);
972
  }
973
974
  /**
975
   * Returns the longest common substring between the string and $otherStr.
976
   * In the case of ties, it returns that which occurs first.
977
   *
978
   * @param  string $otherStr Second string for comparison
979
   *
980
   * @return Stringy Object with its $str being the longest common substring
981
   */
982 10
  public function longestCommonSubstring($otherStr)
983
  {
984
    // Uses dynamic programming to solve
985
    // http://en.wikipedia.org/wiki/Longest_common_substring_problem
986 10
    $encoding = $this->encoding;
987 10
    $stringy = static::create($this->str, $encoding);
988 10
    $strLength = $stringy->length();
989 10
    $otherLength = UTF8::strlen($otherStr, $encoding);
990
991
    // Return if either string is empty
992 10
    if ($strLength == 0 || $otherLength == 0) {
993 2
      $stringy->str = '';
994
995 2
      return $stringy;
996
    }
997
998 8
    $len = 0;
999 8
    $end = 0;
1000 8
    $table = array_fill(
1001 8
        0, $strLength + 1,
1002 8
        array_fill(0, $otherLength + 1, 0)
1003 8
    );
1004
1005 8
    for ($i = 1; $i <= $strLength; $i++) {
1006 8
      for ($j = 1; $j <= $otherLength; $j++) {
1007 8
        $strChar = UTF8::substr($stringy->str, $i - 1, 1, $encoding);
1008 8
        $otherChar = UTF8::substr($otherStr, $j - 1, 1, $encoding);
1009
1010 8
        if ($strChar == $otherChar) {
1011 8
          $table[$i][$j] = $table[$i - 1][$j - 1] + 1;
1012 8
          if ($table[$i][$j] > $len) {
1013 8
            $len = $table[$i][$j];
1014 8
            $end = $i;
1015 8
          }
1016 8
        } else {
1017 8
          $table[$i][$j] = 0;
1018
        }
1019 8
      }
1020 8
    }
1021
1022 8
    $stringy->str = UTF8::substr($stringy->str, $end - $len, $len, $encoding);
0 ignored issues
show
Documentation Bug introduced by
It seems like \voku\helper\UTF8::subst... $len, $len, $encoding) can also be of type false. However, the property $str is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1023
1024 8
    return $stringy;
1025
  }
1026
1027
  /**
1028
   * Returns whether or not a character exists at an index. Offsets may be
1029
   * negative to count from the last character in the string. Implements
1030
   * part of the ArrayAccess interface.
1031
   *
1032
   * @param  mixed $offset The index to check
1033
   *
1034
   * @return boolean Whether or not the index exists
1035
   */
1036 6
  public function offsetExists($offset)
1037
  {
1038
    // init
1039 6
    $length = $this->length();
1040 6
    $offset = (int)$offset;
1041
1042 6
    if ($offset >= 0) {
1043 3
      return ($length > $offset);
1044
    }
1045
1046 3
    return ($length >= abs($offset));
1047
  }
1048
1049
  /**
1050
   * Returns the character at the given index. Offsets may be negative to
1051
   * count from the last character in the string. Implements part of the
1052
   * ArrayAccess interface, and throws an OutOfBoundsException if the index
1053
   * does not exist.
1054
   *
1055
   * @param  mixed $offset The index from which to retrieve the char
1056
   *
1057
   * @return string                 The character at the specified index
1058
   * @throws \OutOfBoundsException If the positive or negative offset does
1059
   *                               not exist
1060
   */
1061 2
  public function offsetGet($offset)
1062
  {
1063
    // init
1064 2
    $offset = (int)$offset;
1065 2
    $length = $this->length();
1066
1067
    if (
1068 2
        ($offset >= 0 && $length <= $offset)
1069
        ||
1070 1
        $length < abs($offset)
1071 2
    ) {
1072 1
      throw new \OutOfBoundsException('No character exists at the index');
1073
    }
1074
1075 1
    return UTF8::substr($this->str, $offset, 1, $this->encoding);
1076
  }
1077
1078
  /**
1079
   * Implements part of the ArrayAccess interface, but throws an exception
1080
   * when called. This maintains the immutability of Stringy objects.
1081
   *
1082
   * @param  mixed $offset The index of the character
1083
   * @param  mixed $value  Value to set
1084
   *
1085
   * @throws \Exception When called
1086
   */
1087 1
  public function offsetSet($offset, $value)
1088
  {
1089
    // Stringy is immutable, cannot directly set char
1090 1
    throw new \Exception('Stringy object is immutable, cannot modify char');
1091
  }
1092
1093
  /**
1094
   * Implements part of the ArrayAccess interface, but throws an exception
1095
   * when called. This maintains the immutability of Stringy objects.
1096
   *
1097
   * @param  mixed $offset The index of the character
1098
   *
1099
   * @throws \Exception When called
1100
   */
1101 1
  public function offsetUnset($offset)
1102
  {
1103
    // Don't allow directly modifying the string
1104 1
    throw new \Exception('Stringy object is immutable, cannot unset char');
1105
  }
1106
1107
  /**
1108
   * Pads the string to a given length with $padStr. If length is less than
1109
   * or equal to the length of the string, no padding takes places. The
1110
   * default string used for padding is a space, and the default type (one of
1111
   * 'left', 'right', 'both') is 'right'. Throws an InvalidArgumentException
1112
   * if $padType isn't one of those 3 values.
1113
   *
1114
   * @param  int    $length  Desired string length after padding
1115
   * @param  string $padStr  String used to pad, defaults to space
1116
   * @param  string $padType One of 'left', 'right', 'both'
1117
   *
1118
   * @return Stringy Object with a padded $str
1119
   * @throws \InvalidArgumentException If $padType isn't one of 'right', 'left' or 'both'
1120
   */
1121 13
  public function pad($length, $padStr = ' ', $padType = 'right')
1122
  {
1123 13
    if (!in_array($padType, array('left', 'right', 'both'), true)) {
1124 1
      throw new \InvalidArgumentException(
1125
          'Pad expects $padType ' . "to be one of 'left', 'right' or 'both'"
1126 1
      );
1127
    }
1128
1129
    switch ($padType) {
1130 12
      case 'left':
1131 3
        return $this->padLeft($length, $padStr);
1132 9
      case 'right':
1133 6
        return $this->padRight($length, $padStr);
1134 3
      default:
1135 3
        return $this->padBoth($length, $padStr);
1136 3
    }
1137
  }
1138
1139
  /**
1140
   * Returns a new string of a given length such that the beginning of the
1141
   * string is padded. Alias for pad() with a $padType of 'left'.
1142
   *
1143
   * @param  int    $length Desired string length after padding
1144
   * @param  string $padStr String used to pad, defaults to space
1145
   *
1146
   * @return Stringy String with left padding
1147
   */
1148 10
  public function padLeft($length, $padStr = ' ')
1149
  {
1150 10
    return $this->applyPadding($length - $this->length(), 0, $padStr);
1151
  }
1152
1153
  /**
1154
   * Adds the specified amount of left and right padding to the given string.
1155
   * The default character used is a space.
1156
   *
1157
   * @param  int    $left   Length of left padding
1158
   * @param  int    $right  Length of right padding
1159
   * @param  string $padStr String used to pad
1160
   *
1161
   * @return Stringy String with padding applied
1162
   */
1163 37
  private function applyPadding($left = 0, $right = 0, $padStr = ' ')
1164
  {
1165 37
    $stringy = static::create($this->str, $this->encoding);
1166
1167 37
    $length = UTF8::strlen($padStr, $stringy->encoding);
1168
1169 37
    $strLength = $stringy->length();
1170 37
    $paddedLength = $strLength + $left + $right;
1171
1172 37
    if (!$length || $paddedLength <= $strLength) {
1173 3
      return $stringy;
1174
    }
1175
1176 34
    $leftPadding = UTF8::substr(
1177 34
        UTF8::str_repeat(
1178 34
            $padStr,
1179 34
            ceil($left / $length)
1180 34
        ),
1181 34
        0,
1182 34
        $left,
1183 34
        $stringy->encoding
1184 34
    );
1185
1186 34
    $rightPadding = UTF8::substr(
1187 34
        UTF8::str_repeat(
1188 34
            $padStr,
1189 34
            ceil($right / $length)
1190 34
        ),
1191 34
        0,
1192 34
        $right,
1193 34
        $stringy->encoding
1194 34
    );
1195
1196 34
    $stringy->str = $leftPadding . $stringy->str . $rightPadding;
1197
1198 34
    return $stringy;
1199
  }
1200
1201
  /**
1202
   * Returns a new string of a given length such that the end of the string
1203
   * is padded. Alias for pad() with a $padType of 'right'.
1204
   *
1205
   * @param  int    $length Desired string length after padding
1206
   * @param  string $padStr String used to pad, defaults to space
1207
   *
1208
   * @return Stringy String with right padding
1209
   */
1210 13
  public function padRight($length, $padStr = ' ')
1211
  {
1212 13
    return $this->applyPadding(0, $length - $this->length(), $padStr);
1213
  }
1214
1215
  /**
1216
   * Returns a new string of a given length such that both sides of the
1217
   * string are padded. Alias for pad() with a $padType of 'both'.
1218
   *
1219
   * @param  int    $length Desired string length after padding
1220
   * @param  string $padStr String used to pad, defaults to space
1221
   *
1222
   * @return Stringy String with padding applied
1223
   */
1224 14
  public function padBoth($length, $padStr = ' ')
1225
  {
1226 14
    $padding = $length - $this->length();
1227
1228 14
    return $this->applyPadding(floor($padding / 2), ceil($padding / 2), $padStr);
1229
  }
1230
1231
  /**
1232
   * Returns a new string starting with $string.
1233
   *
1234
   * @param  string $string The string to append
1235
   *
1236
   * @return Stringy Object with appended $string
1237
   */
1238 2
  public function prepend($string)
1239
  {
1240 2
    return static::create($string . $this->str, $this->encoding);
1241
  }
1242
1243
  /**
1244
   * Returns a new string with the prefix $substring removed, if present.
1245
   *
1246
   * @param  string $substring The prefix to remove
1247
   *
1248
   * @return Stringy Object having a $str without the prefix $substring
1249
   */
1250 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...
1251
  {
1252 12
    $stringy = static::create($this->str, $this->encoding);
1253
1254 12
    if ($stringy->startsWith($substring)) {
1255 8
      $substringLength = UTF8::strlen($substring, $stringy->encoding);
1256
1257 8
      return $stringy->substr($substringLength);
1258
    }
1259
1260 4
    return $stringy;
1261
  }
1262
1263
  /**
1264
   * Returns a new string with the suffix $substring removed, if present.
1265
   *
1266
   * @param  string $substring The suffix to remove
1267
   *
1268
   * @return Stringy Object having a $str without the suffix $substring
1269
   */
1270 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...
1271
  {
1272 12
    $stringy = static::create($this->str, $this->encoding);
1273
1274 12
    if ($stringy->endsWith($substring)) {
1275 8
      $substringLength = UTF8::strlen($substring, $stringy->encoding);
1276
1277 8
      return $stringy->substr(0, $stringy->length() - $substringLength);
1278
    }
1279
1280 4
    return $stringy;
1281
  }
1282
1283
  /**
1284
   * Returns a repeated string given a multiplier.
1285
   *
1286
   * @param  int $multiplier The number of times to repeat the string
1287
   *
1288
   * @return Stringy Object with a repeated str
1289
   */
1290 7
  public function repeat($multiplier)
1291
  {
1292 7
    $repeated = UTF8::str_repeat($this->str, $multiplier);
1293
1294 7
    return static::create($repeated, $this->encoding);
1295
  }
1296
1297
  /**
1298
   * Replaces all occurrences of $search in $str by $replacement.
1299
   *
1300
   * @param string $search        The needle to search for
1301
   * @param string $replacement   The string to replace with
1302
   * @param bool   $caseSensitive Whether or not to enforce case-sensitivity
1303
   *
1304
   * @return Stringy Object with the resulting $str after the replacements
1305
   */
1306 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...
1307
  {
1308 28
    if ($caseSensitive) {
1309 21
      $return = UTF8::str_replace($search, $replacement, $this->str);
1310 21
    } else {
1311 7
      $return = UTF8::str_ireplace($search, $replacement, $this->str);
1312
    }
1313
1314 28
    return static::create($return);
1315
  }
1316
1317
  /**
1318
   * Replaces all occurrences of $search in $str by $replacement.
1319
   *
1320
   * @param array        $search        The elements to search for
1321
   * @param string|array $replacement   The string to replace with
1322
   * @param bool         $caseSensitive Whether or not to enforce case-sensitivity
1323
   *
1324
   * @return Stringy Object with the resulting $str after the replacements
1325
   */
1326 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...
1327
  {
1328 30
    if ($caseSensitive) {
1329 23
      $return = UTF8::str_replace($search, $replacement, $this->str);
1330 23
    } else {
1331 7
      $return = UTF8::str_ireplace($search, $replacement, $this->str);
1332
    }
1333
1334 30
    return static::create($return);
1335
  }
1336
1337
  /**
1338
   * Replaces all occurrences of $search from the beginning of string with $replacement
1339
   *
1340
   * @param string $search
1341
   * @param string $replacement
1342
   *
1343
   * @return Stringy Object with the resulting $str after the replacements
1344
   */
1345 16
  public function replaceBeginning($search, $replacement)
1346
  {
1347 16
    $str = $this->regexReplace('^' . preg_quote($search, '/'), UTF8::str_replace('\\', '\\\\', $replacement));
1348
1349 16
    return static::create($str, $this->encoding);
1350
  }
1351
1352
  /**
1353
   * Replaces all occurrences of $search from the ending of string with $replacement
1354
   *
1355
   * @param string $search
1356
   * @param string $replacement
1357
   *
1358
   * @return Stringy Object with the resulting $str after the replacements
1359
   */
1360 16
  public function replaceEnding($search, $replacement)
1361
  {
1362 16
    $str = $this->regexReplace(preg_quote($search, '/') . '$', UTF8::str_replace('\\', '\\\\', $replacement));
1363
1364 16
    return static::create($str, $this->encoding);
1365
  }
1366
1367
  /**
1368
   * Returns a reversed string. A multibyte version of strrev().
1369
   *
1370
   * @return Stringy Object with a reversed $str
1371
   */
1372 5
  public function reverse()
1373
  {
1374 5
    $reversed = UTF8::strrev($this->str);
1375
1376 5
    return static::create($reversed, $this->encoding);
1377
  }
1378
1379
  /**
1380
   * Truncates the string to a given length, while ensuring that it does not
1381
   * split words. If $substring is provided, and truncating occurs, the
1382
   * string is further truncated so that the substring may be appended without
1383
   * exceeding the desired length.
1384
   *
1385
   * @param  int    $length    Desired length of the truncated string
1386
   * @param  string $substring The substring to append if it can fit
1387
   *
1388
   * @return Stringy Object with the resulting $str after truncating
1389
   */
1390 22
  public function safeTruncate($length, $substring = '')
1391
  {
1392 22
    $stringy = static::create($this->str, $this->encoding);
1393 22
    if ($length >= $stringy->length()) {
1394 4
      return $stringy;
1395
    }
1396
1397
    // Need to further trim the string so we can append the substring
1398 18
    $encoding = $stringy->encoding;
1399 18
    $substringLength = UTF8::strlen($substring, $encoding);
1400 18
    $length -= $substringLength;
1401
1402 18
    $truncated = UTF8::substr($stringy->str, 0, $length, $encoding);
1403
1404
    // If the last word was truncated
1405 18
    if (UTF8::strpos($stringy->str, ' ', $length - 1, $encoding) != $length) {
1406
      // Find pos of the last occurrence of a space, get up to that
1407 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...
Security Bug introduced by
It seems like $truncated defined by \voku\helper\UTF8::subst... 0, $length, $encoding) on line 1402 can also be of type false; however, voku\helper\UTF8::strrpos() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1408 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 1407 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...
Security Bug introduced by
It seems like $truncated defined by \voku\helper\UTF8::subst...0, $lastPos, $encoding) on line 1408 can also be of type false; however, voku\helper\UTF8::substr() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1409 11
    }
1410
1411 18
    $stringy->str = $truncated . $substring;
1412
1413 18
    return $stringy;
1414
  }
1415
1416
  /**
1417
   * A multibyte string shuffle function. It returns a string with its
1418
   * characters in random order.
1419
   *
1420
   * @return Stringy Object with a shuffled $str
1421
   */
1422 3
  public function shuffle()
1423
  {
1424 3
    $shuffledStr = UTF8::str_shuffle($this->str);
1425
1426 3
    return static::create($shuffledStr, $this->encoding);
1427
  }
1428
1429
  /**
1430
   * Converts the string into an URL slug. This includes replacing non-ASCII
1431
   * characters with their closest ASCII equivalents, removing remaining
1432
   * non-ASCII and non-alphanumeric characters, and replacing whitespace with
1433
   * $replacement. The replacement defaults to a single dash, and the string
1434
   * is also converted to lowercase.
1435
   *
1436
   * @param string $replacement The string used to replace whitespace
1437
   * @param string $language    The language for the url
1438
   * @param bool   $strToLower  string to lower
1439
   *
1440
   * @return Stringy Object whose $str has been converted to an URL slug
1441
   */
1442 15
  public function slugify($replacement = '-', $language = 'de', $strToLower = true)
1443
  {
1444 15
    $slug = URLify::slug($this->str, $language, $replacement, $strToLower);
1445
1446 15
    return static::create($slug, $this->encoding);
1447
  }
1448
1449
  /**
1450
   * Remove css media-queries.
1451
   *
1452
   * @return Stringy
1453
   */
1454 1
  public function stripeCssMediaQueries()
1455
  {
1456 1
    $pattern = '#@media\\s+(?:only\\s)?(?:[\\s{\\(]|screen|all)\\s?[^{]+{.*}\\s*}\\s*#misU';
1457
1458 1
    return static::create(preg_replace($pattern, '', $this->str));
1459
  }
1460
1461
  /**
1462
   * Remove empty html-tag.
1463
   *
1464
   * e.g.: <tag></tag>
1465
   *
1466
   * @return Stringy
1467
   */
1468 1
  public function stripeEmptyHtmlTags()
1469
  {
1470 1
    $pattern = "/<[^\/>]*>(([\s]?)*|)<\/[^>]*>/i";
1471
1472 1
    return static::create(preg_replace($pattern, '', $this->str));
1473
  }
1474
1475
  /**
1476
   * Converts the string into an valid UTF-8 string.
1477
   *
1478
   * @return Stringy
1479
   */
1480 1
  public function utf8ify()
1481
  {
1482 1
    return static::create(UTF8::cleanup($this->str));
1483
  }
1484
1485
  /**
1486
   * escape html
1487
   *
1488
   * @return Stringy
1489
   */
1490 6
  public function escape()
1491
  {
1492 6
    $str = UTF8::htmlspecialchars(
1493 6
        $this->str,
1494 6
        ENT_QUOTES | ENT_SUBSTITUTE,
1495 6
        $this->encoding
1496 6
    );
1497
1498 6
    return static::create($str, $this->encoding);
1499
  }
1500
1501
  /**
1502
   * Create an extract from a text, so if the search-string was found, it will be centered in the output.
1503
   *
1504
   * @param string   $search
1505
   * @param int|null $length
1506
   * @param string   $ellipsis
1507
   *
1508
   * @return Stringy
1509
   */
1510 1
  public function extractText($search = '', $length = null, $ellipsis = '...')
1511
  {
1512
    // init
1513 1
    $text = $this->str;
1514
1515 1
    if (empty($text)) {
1516 1
      return static::create('', $this->encoding);
1517
    }
1518
1519 1
    $trimChars = "\t\r\n -_()!~?=+/*\\,.:;\"'[]{}`&";
1520
1521 1
    if ($length === null) {
1522 1
      $length = $this->length() / 2;
1523 1
    }
1524
1525 1
    if (empty($search)) {
1526
1527
      $stringLength = UTF8::strlen($text);
1528
      $end = ($length - 1) > $stringLength ? $stringLength : ($length - 1);
1529
      $pos = min(UTF8::strpos($text, ' ', $end), UTF8::strpos($text, '.', $end));
1530
1531
      if ($pos) {
1532
        return static::create(rtrim(UTF8::substr($text, 0, $pos), $trimChars) . $ellipsis, $this->encoding);
1533
      } else {
1534
        return static::create($text, $this->encoding);
1535
      }
1536
1537
    }
1538
1539 1
    $wordPos = UTF8::strpos(UTF8::strtolower($text), UTF8::strtolower($search), null);
1540 1
    $halfSide = (int)($wordPos - $length / 2 + UTF8::strlen($search) / 2);
1541
1542 1
    if ($halfSide > 0) {
1543
1544 1
      $halfText = UTF8::substr($text, 0, $halfSide);
1545 1
      $pos_start = max(UTF8::strrpos($halfText, ' ', 0), UTF8::strrpos($halfText, '.', 0));
0 ignored issues
show
Security Bug introduced by
It seems like $halfText defined by \voku\helper\UTF8::substr($text, 0, $halfSide) on line 1544 can also be of type false; however, voku\helper\UTF8::strrpos() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1546
1547 1
      if (!$pos_start) {
1548 1
        $pos_start = 0;
1549 1
      }
1550
1551 1
    } else {
1552 1
      $pos_start = 0;
1553
    }
1554
1555 1
    if ($wordPos && $halfSide > 0) {
1556 1
      $l = $pos_start + $length - 1;
1557 1
      $realLength = UTF8::strlen($text);
1558
1559 1
      if ($l > $realLength) {
1560
        $l = $realLength;
1561
      }
1562
1563 1
      $pos_end = min(UTF8::strpos($text, ' ', $l), UTF8::strpos($text, '.', $l)) - $pos_start;
1564
1565 1
      if (!$pos_end || $pos_end <= 0) {
1566 1
        $extract = $ellipsis . ltrim(UTF8::substr($text, $pos_start, UTF8::strlen($text)), $trimChars);
1567 1
      } else {
1568 1
        $extract = $ellipsis . trim(UTF8::substr($text, $pos_start, $pos_end), $trimChars) . $ellipsis;
1569
      }
1570
1571 1
    } else {
1572
1573 1
      $l = $length - 1;
1574 1
      $trueLength = UTF8::strlen($text);
1575
1576 1
      if ($l > $trueLength) {
1577
        $l = $trueLength;
1578
      }
1579
1580 1
      $pos_end = min(UTF8::strpos($text, ' ', $l), UTF8::strpos($text, '.', $l));
1581
1582 1
      if ($pos_end) {
1583 1
        $extract = rtrim(UTF8::substr($text, 0, $pos_end), $trimChars) . $ellipsis;
1584 1
      } else {
1585 1
        $extract = $text;
1586
      }
1587
    }
1588
1589 1
    return static::create($extract, $this->encoding);
1590
  }
1591
1592
1593
  /**
1594
   * remove xss from html
1595
   *
1596
   * @return Stringy
1597
   */
1598 6
  public function removeXss()
1599
  {
1600 6
    static $antiXss = null;
1601
1602 6
    if ($antiXss === null) {
1603 1
      $antiXss = new AntiXSS();
1604 1
    }
1605
1606 6
    $str = $antiXss->xss_clean($this->str);
1607
1608 6
    return static::create($str, $this->encoding);
1609
  }
1610
1611
  /**
1612
   * remove html-break [br | \r\n | \r | \n | ...]
1613
   *
1614
   * @param string $replacement
1615
   *
1616
   * @return Stringy
1617
   */
1618 6
  public function removeHtmlBreak($replacement = '')
1619
  {
1620 6
    $str = preg_replace('#/\r\n|\r|\n|<br.*/?>#isU', $replacement, $this->str);
1621
1622 6
    return static::create($str, $this->encoding);
1623
  }
1624
1625
  /**
1626
   * remove html
1627
   *
1628
   * @param $allowableTags
1629
   *
1630
   * @return Stringy
1631
   */
1632 6
  public function removeHtml($allowableTags = null)
1633
  {
1634 6
    $str = strip_tags($this->str, $allowableTags);
1635
1636 6
    return static::create($str, $this->encoding);
1637
  }
1638
1639
  /**
1640
   * Returns the substring beginning at $start, and up to, but not including
1641
   * the index specified by $end. If $end is omitted, the function extracts
1642
   * the remaining string. If $end is negative, it is computed from the end
1643
   * of the string.
1644
   *
1645
   * @param  int $start Initial index from which to begin extraction
1646
   * @param  int $end   Optional index at which to end extraction
1647
   *
1648
   * @return Stringy Object with its $str being the extracted substring
1649
   */
1650 18
  public function slice($start, $end = null)
1651
  {
1652 18
    if ($end === null) {
1653 4
      $length = $this->length();
1654 18
    } elseif ($end >= 0 && $end <= $start) {
1655 4
      return static::create('', $this->encoding);
1656 10
    } elseif ($end < 0) {
1657 2
      $length = $this->length() + $end - $start;
1658 2
    } else {
1659 8
      $length = $end - $start;
1660
    }
1661
1662 14
    $str = UTF8::substr($this->str, $start, $length, $this->encoding);
1663
1664 14
    return static::create($str, $this->encoding);
1665
  }
1666
1667
  /**
1668
   * Splits the string with the provided regular expression, returning an
1669
   * array of Stringy objects. An optional integer $limit will truncate the
1670
   * results.
1671
   *
1672
   * @param  string $pattern The regex with which to split the string
1673
   * @param  int    $limit   Optional maximum number of results to return
1674
   *
1675
   * @return Stringy[] An array of Stringy objects
1676
   */
1677 19
  public function split($pattern, $limit = null)
1678
  {
1679 19
    if ($limit === 0) {
1680 2
      return array();
1681
    }
1682
1683
    // UTF8::split errors when supplied an empty pattern in < PHP 5.4.13
1684
    // and current versions of HHVM (3.8 and below)
1685 17
    if ($pattern === '') {
1686 1
      return array(static::create($this->str, $this->encoding));
1687
    }
1688
1689
    // UTF8::split returns the remaining unsplit string in the last index when
1690
    // supplying a limit
1691 16
    if ($limit > 0) {
1692 8
      $limit += 1;
1693 8
    } else {
1694 8
      $limit = -1;
1695
    }
1696
1697 16
    $array = preg_split('/' . preg_quote($pattern, '/') . '/u', $this->str, $limit);
1698
1699 16
    if ($limit > 0 && count($array) === $limit) {
1700 4
      array_pop($array);
1701 4
    }
1702
1703
    /** @noinspection CallableInLoopTerminationConditionInspection */
1704
    /** @noinspection ForeachInvariantsInspection */
1705 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...
1706 16
      $array[$i] = static::create($array[$i], $this->encoding);
1707 16
    }
1708
1709 16
    return $array;
1710
  }
1711
1712
  /**
1713
   * Surrounds $str with the given substring.
1714
   *
1715
   * @param  string $substring The substring to add to both sides
1716
   *
1717
   * @return Stringy Object whose $str had the substring both prepended and
1718
   *                 appended
1719
   */
1720 5
  public function surround($substring)
1721
  {
1722 5
    $str = implode('', array($substring, $this->str, $substring));
1723
1724 5
    return static::create($str, $this->encoding);
1725
  }
1726
1727
  /**
1728
   * Returns a case swapped version of the string.
1729
   *
1730
   * @return Stringy Object whose $str has each character's case swapped
1731
   */
1732 5
  public function swapCase()
1733
  {
1734 5
    $stringy = static::create($this->str, $this->encoding);
1735
1736 5
    $stringy->str = UTF8::swapCase($stringy->str, $stringy->encoding);
1737
1738 5
    return $stringy;
1739
  }
1740
1741
  /**
1742
   * Returns a string with smart quotes, ellipsis characters, and dashes from
1743
   * Windows-1252 (commonly used in Word documents) replaced by their ASCII
1744
   * equivalents.
1745
   *
1746
   * @return Stringy Object whose $str has those characters removed
1747
   */
1748 4
  public function tidy()
1749
  {
1750 4
    $str = UTF8::normalize_msword($this->str);
1751
1752 4
    return static::create($str, $this->encoding);
1753
  }
1754
1755
  /**
1756
   * Returns a trimmed string with the first letter of each word capitalized.
1757
   * Also accepts an array, $ignore, allowing you to list words not to be
1758
   * capitalized.
1759
   *
1760
   * @param  array $ignore An array of words not to capitalize
1761
   *
1762
   * @return Stringy Object with a titleized $str
1763
   */
1764 5
  public function titleize($ignore = null)
1765
  {
1766 5
    $stringy = static::create($this->trim(), $this->encoding);
1767 5
    $encoding = $this->encoding;
1768
1769 5
    $stringy->str = preg_replace_callback(
1770 5
        '/([\S]+)/u',
1771
        function ($match) use ($encoding, $ignore) {
1772 5
          if ($ignore && in_array($match[0], $ignore, true)) {
1773 2
            return $match[0];
1774
          } else {
1775 5
            $stringy = new Stringy($match[0], $encoding);
1776
1777 5
            return (string)$stringy->toLowerCase()->upperCaseFirst();
1778
          }
1779 5
        },
1780 5
        $stringy->str
1781 5
    );
1782
1783 5
    return $stringy;
1784
  }
1785
1786
  /**
1787
   * Converts all characters in the string to lowercase. An alias for PHP's
1788
   * UTF8::strtolower().
1789
   *
1790
   * @return Stringy Object with all characters of $str being lowercase
1791
   */
1792 27
  public function toLowerCase()
1793
  {
1794 27
    $str = UTF8::strtolower($this->str, $this->encoding);
1795
1796 27
    return static::create($str, $this->encoding);
1797
  }
1798
1799
  /**
1800
   * Returns true if the string is base64 encoded, false otherwise.
1801
   *
1802
   * @return bool Whether or not $str is base64 encoded
1803
   */
1804 7
  public function isBase64()
1805
  {
1806 7
    return UTF8::is_base64($this->str);
1807
  }
1808
1809
  /**
1810
   * Returns an ASCII version of the string. A set of non-ASCII characters are
1811
   * replaced with their closest ASCII counterparts, and the rest are removed
1812
   * unless instructed otherwise.
1813
   *
1814
   * @return Stringy Object whose $str contains only ASCII characters
1815
   */
1816 16
  public function toAscii()
1817
  {
1818 16
    $str = UTF8::toAscii($this->str);
1819
1820 16
    return static::create($str, $this->encoding);
1821
  }
1822
1823
  /**
1824
   * Returns a boolean representation of the given logical string value.
1825
   * For example, 'true', '1', 'on' and 'yes' will return true. 'false', '0',
1826
   * 'off', and 'no' will return false. In all instances, case is ignored.
1827
   * For other numeric strings, their sign will determine the return value.
1828
   * In addition, blank strings consisting of only whitespace will return
1829
   * false. For all other strings, the return value is a result of a
1830
   * boolean cast.
1831
   *
1832
   * @return bool A boolean value for the string
1833
   */
1834 15
  public function toBoolean()
1835
  {
1836 15
    $key = $this->toLowerCase()->str;
1837
    $map = array(
1838 15
        'true'  => true,
1839 15
        '1'     => true,
1840 15
        'on'    => true,
1841 15
        'yes'   => true,
1842 15
        'false' => false,
1843 15
        '0'     => false,
1844 15
        'off'   => false,
1845 15
        'no'    => false,
1846 15
    );
1847
1848 15
    if (array_key_exists($key, $map)) {
1849 10
      return $map[$key];
1850 5
    } elseif (is_numeric($this->str)) {
1851 2
      return ((int)$this->str > 0);
1852
    } else {
1853 3
      return (bool)$this->regexReplace('[[:space:]]', '')->str;
1854
    }
1855
  }
1856
1857
  /**
1858
   * Return Stringy object as string, but you can also use (string) for automatically casting the object into a string.
1859
   *
1860
   * @return string
1861
   */
1862 947
  public function toString()
1863
  {
1864 947
    return (string)$this->str;
1865
  }
1866
1867
  /**
1868
   * Converts each tab in the string to some number of spaces, as defined by
1869
   * $tabLength. By default, each tab is converted to 4 consecutive spaces.
1870
   *
1871
   * @param  int $tabLength Number of spaces to replace each tab with
1872
   *
1873
   * @return Stringy Object whose $str has had tabs switched to spaces
1874
   */
1875 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...
1876
  {
1877 6
    $spaces = UTF8::str_repeat(' ', $tabLength);
1878 6
    $str = UTF8::str_replace("\t", $spaces, $this->str);
1879
1880 6
    return static::create($str, $this->encoding);
1881
  }
1882
1883
  /**
1884
   * Converts each occurrence of some consecutive number of spaces, as
1885
   * defined by $tabLength, to a tab. By default, each 4 consecutive spaces
1886
   * are converted to a tab.
1887
   *
1888
   * @param  int $tabLength Number of spaces to replace with a tab
1889
   *
1890
   * @return Stringy Object whose $str has had spaces switched to tabs
1891
   */
1892 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...
1893
  {
1894 5
    $spaces = UTF8::str_repeat(' ', $tabLength);
1895 5
    $str = UTF8::str_replace($spaces, "\t", $this->str);
1896
1897 5
    return static::create($str, $this->encoding);
1898
  }
1899
1900
  /**
1901
   * Converts the first character of each word in the string to uppercase.
1902
   *
1903
   * @return Stringy Object with all characters of $str being title-cased
1904
   */
1905 5
  public function toTitleCase()
1906
  {
1907
    // "mb_convert_case()" used a polyfill from the "UTF8"-Class
1908 5
    $str = mb_convert_case($this->str, MB_CASE_TITLE, $this->encoding);
1909
1910 5
    return static::create($str, $this->encoding);
1911
  }
1912
1913
  /**
1914
   * Converts all characters in the string to uppercase. An alias for PHP's
1915
   * UTF8::strtoupper().
1916
   *
1917
   * @return Stringy Object with all characters of $str being uppercase
1918
   */
1919 5
  public function toUpperCase()
1920
  {
1921 5
    $str = UTF8::strtoupper($this->str, $this->encoding);
1922
1923 5
    return static::create($str, $this->encoding);
1924
  }
1925
1926
  /**
1927
   * Returns a string with whitespace removed from the start of the string.
1928
   * Supports the removal of unicode whitespace. Accepts an optional
1929
   * string of characters to strip instead of the defaults.
1930
   *
1931
   * @param  string $chars Optional string of characters to strip
1932
   *
1933
   * @return Stringy Object with a trimmed $str
1934
   */
1935 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...
1936
  {
1937 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...
1938 11
      $chars = '[:space:]';
1939 11
    } else {
1940 2
      $chars = preg_quote($chars, '/');
1941
    }
1942
1943 13
    return $this->regexReplace("^[$chars]+", '');
1944
  }
1945
1946
  /**
1947
   * Returns a string with whitespace removed from the end of the string.
1948
   * Supports the removal of unicode whitespace. Accepts an optional
1949
   * string of characters to strip instead of the defaults.
1950
   *
1951
   * @param  string $chars Optional string of characters to strip
1952
   *
1953
   * @return Stringy Object with a trimmed $str
1954
   */
1955 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...
1956
  {
1957 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...
1958 11
      $chars = '[:space:]';
1959 11
    } else {
1960 2
      $chars = preg_quote($chars, '/');
1961
    }
1962
1963 13
    return $this->regexReplace("[$chars]+\$", '');
1964
  }
1965
1966
  /**
1967
   * Truncates the string to a given length. If $substring is provided, and
1968
   * truncating occurs, the string is further truncated so that the substring
1969
   * may be appended without exceeding the desired length.
1970
   *
1971
   * @param  int    $length    Desired length of the truncated string
1972
   * @param  string $substring The substring to append if it can fit
1973
   *
1974
   * @return Stringy Object with the resulting $str after truncating
1975
   */
1976 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...
1977
  {
1978 22
    $stringy = static::create($this->str, $this->encoding);
1979 22
    if ($length >= $stringy->length()) {
1980 4
      return $stringy;
1981
    }
1982
1983
    // Need to further trim the string so we can append the substring
1984 18
    $substringLength = UTF8::strlen($substring, $stringy->encoding);
1985 18
    $length -= $substringLength;
1986
1987 18
    $truncated = UTF8::substr($stringy->str, 0, $length, $stringy->encoding);
1988 18
    $stringy->str = $truncated . $substring;
1989
1990 18
    return $stringy;
1991
  }
1992
1993
  /**
1994
   * Returns a lowercase and trimmed string separated by underscores.
1995
   * Underscores are inserted before uppercase characters (with the exception
1996
   * of the first character of the string), and in place of spaces as well as
1997
   * dashes.
1998
   *
1999
   * @return Stringy Object with an underscored $str
2000
   */
2001 16
  public function underscored()
2002
  {
2003 16
    return $this->delimit('_');
2004
  }
2005
2006
  /**
2007
   * Returns an UpperCamelCase version of the supplied string. It trims
2008
   * surrounding spaces, capitalizes letters following digits, spaces, dashes
2009
   * and underscores, and removes spaces, dashes, underscores.
2010
   *
2011
   * @return Stringy Object with $str in UpperCamelCase
2012
   */
2013 13
  public function upperCamelize()
2014
  {
2015 13
    return $this->camelize()->upperCaseFirst();
2016
  }
2017
2018
  /**
2019
   * Returns a camelCase version of the string. Trims surrounding spaces,
2020
   * capitalizes letters following digits, spaces, dashes and underscores,
2021
   * and removes spaces, dashes, as well as underscores.
2022
   *
2023
   * @return Stringy Object with $str in camelCase
2024
   */
2025 32
  public function camelize()
2026
  {
2027 32
    $encoding = $this->encoding;
2028 32
    $stringy = $this->trim()->lowerCaseFirst();
2029 32
    $stringy->str = preg_replace('/^[-_]+/', '', $stringy->str);
2030
2031 32
    $stringy->str = preg_replace_callback(
2032 32
        '/[-_\s]+(.)?/u',
2033
        function ($match) use ($encoding) {
2034 27
          if (isset($match[1])) {
2035 27
            return UTF8::strtoupper($match[1], $encoding);
2036
          } else {
2037 1
            return '';
2038
          }
2039 32
        },
2040 32
        $stringy->str
2041 32
    );
2042
2043 32
    $stringy->str = preg_replace_callback(
2044 32
        '/[\d]+(.)?/u',
2045
        function ($match) use ($encoding) {
2046 6
          return UTF8::strtoupper($match[0], $encoding);
2047 32
        },
2048 32
        $stringy->str
2049 32
    );
2050
2051 32
    return $stringy;
2052
  }
2053
2054
  /**
2055
   * Convert a string to e.g.: "snake_case"
2056
   *
2057
   * @return Stringy Object with $str in snake_case
2058
   */
2059 20
  public function snakeize()
2060
  {
2061 20
    $str = $this->str;
2062
2063 20
    $str = UTF8::normalize_whitespace($str);
2064 20
    $str = str_replace('-', '_', $str);
2065
2066 20
    $str = preg_replace_callback(
2067 20
        '/([\d|A-Z])/u',
2068 20
        function ($matches) {
2069 8
          $match = $matches[1];
2070 8
          $matchInt = (int)$match;
2071
2072 8
          if ("$matchInt" == $match) {
2073 4
            return '_' . $match . '_';
2074
          } else {
2075 4
            return '_' . UTF8::strtolower($match);
2076
          }
2077 20
        },
2078
        $str
2079 20
    );
2080
2081 20
    $str = preg_replace(
2082
        array(
2083
2084 20
            '/\s+/',      // convert spaces to "_"
2085 20
            '/^\s+|\s+$/',  // trim leading & trailing spaces
2086 20
            '/_+/',         // remove double "_"
2087 20
        ),
2088
        array(
2089 20
            '_',
2090 20
            '',
2091 20
            '_',
2092 20
        ),
2093
        $str
2094 20
    );
2095
2096 20
    $str = UTF8::trim($str, '_'); // trim leading & trailing "_"
2097 20
    $str = UTF8::trim($str); // trim leading & trailing whitespace
2098
2099 20
    return static::create($str, $this->encoding);
2100
  }
2101
2102
  /**
2103
   * Converts the first character of the string to lower case.
2104
   *
2105
   * @return Stringy Object with the first character of $str being lower case
2106
   */
2107 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...
2108
  {
2109 37
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
2110 37
    $rest = UTF8::substr(
2111 37
        $this->str, 1, $this->length() - 1,
2112 37
        $this->encoding
2113 37
    );
2114
2115 37
    $str = UTF8::strtolower($first, $this->encoding) . $rest;
0 ignored issues
show
Security Bug introduced by
It seems like $first defined by \voku\helper\UTF8::subst... 0, 1, $this->encoding) on line 2109 can also be of type false; however, voku\helper\UTF8::strtolower() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
2116
2117 37
    return static::create($str, $this->encoding);
2118
  }
2119
2120
  /**
2121
   * Shorten the string after $length, but also after the next word.
2122
   *
2123
   * @param int    $length
2124
   * @param string $strAddOn
2125
   *
2126
   * @return Stringy
2127
   */
2128 4
  public function shortenAfterWord($length, $strAddOn = '...')
2129
  {
2130 4
    $string = UTF8::str_limit_after_word($this->str, $length, $strAddOn);
2131
2132 4
    return static::create($string);
2133
  }
2134
2135
  /**
2136
   * Line-Wrap the string after $limit, but also after the next word.
2137
   *
2138
   * @param int $limit
2139
   *
2140
   * @return Stringy
2141
   */
2142 1
  public function lineWrapAfterWord($limit)
2143
  {
2144 1
    $strings = preg_split('/\\r\\n|\\r|\\n/', $this->str);
2145
2146 1
    $string = '';
2147 1
    foreach ($strings as $value) {
2148 1
      $string .= wordwrap($value, $limit);
2149 1
      $string .= "\n";
2150 1
    }
2151
2152 1
    return static::create($string);
2153
  }
2154
}
2155