Completed
Push — master ( 6621b2...ba1955 )
by Lars
03:54
created

Stringy::toBoolean()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 16
nc 3
nop 0
dl 0
loc 24
ccs 8
cts 8
cp 1
crap 3
rs 8.9713
c 0
b 0
f 0
1
<?php
2
3
namespace Stringy;
4
5
use voku\helper\AntiXSS;
6
use voku\helper\EmailCheck;
7
use voku\helper\URLify;
8
use voku\helper\UTF8;
9
10
/**
11
 * Class Stringy
12
 *
13
 * @package Stringy
14
 */
15
class Stringy implements \Countable, \IteratorAggregate, \ArrayAccess
16
{
17
  /**
18
   * An instance's string.
19
   *
20
   * @var string
21
   */
22
  protected $str;
23
24
  /**
25
   * The string's encoding, which should be one of the mbstring module's
26
   * supported encodings.
27
   *
28
   * @var string
29
   */
30
  protected $encoding;
31
32
  /**
33
   * Initializes a Stringy object and assigns both str and encoding properties
34
   * the supplied values. $str is cast to a string prior to assignment, and if
35
   * $encoding is not specified, it defaults to mb_internal_encoding(). Throws
36
   * an InvalidArgumentException if the first argument is an array or object
37
   * without a __toString method.
38
   *
39
   * @param  mixed  $str      Value to modify, after being cast to string
40
   * @param  string $encoding The character encoding
41
   *
42
   * @throws \InvalidArgumentException if an array or object without a
43
   *         __toString method is passed as the first argument
44
   */
45 1086
  public function __construct($str = '', $encoding = null)
46
  {
47 1086
    if (is_array($str)) {
48 1
      throw new \InvalidArgumentException(
49
          'Passed value cannot be an array'
50 1
      );
51 1085
    }
52 1
53
    if (
54 1
        is_object($str)
55
        &&
56
        !method_exists($str, '__toString')
57
    ) {
58 1084
      throw new \InvalidArgumentException(
59
          'Passed object must have a __toString method'
60
      );
61
    }
62
63 1084
    // don't throw a notice on PHP 5.3
64
    if (!defined('ENT_SUBSTITUTE')) {
65 1084
      define('ENT_SUBSTITUTE', 8);
66
    }
67 1084
68 850
    // init
69 850
    UTF8::checkForSupport();
70 712
71 712
    $this->str = (string)$str;
72
73
    if ($encoding) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $encoding of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

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

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

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1276
      return $stringy;
1277
    }
1278
1279
    $leftPadding = UTF8::substr(
1280
        UTF8::str_repeat(
1281
            $padStr,
1282 13
            ceil($left / $length)
1283
        ),
1284 13
        0,
1285
        $left,
1286
        $stringy->encoding
1287
    );
1288
1289
    $rightPadding = UTF8::substr(
1290
        UTF8::str_repeat(
1291
            $padStr,
1292
            ceil($right / $length)
1293
        ),
1294
        0,
1295
        $right,
1296 14
        $stringy->encoding
1297
    );
1298 14
1299
    $stringy->str = $leftPadding . $stringy->str . $rightPadding;
1300 14
1301
    return $stringy;
1302
  }
1303
1304
  /**
1305
   * Returns a new string of a given length such that the end of the string
1306
   * is padded. Alias for pad() with a $padType of 'right'.
1307
   *
1308
   * @param  int    $length Desired string length after padding
1309
   * @param  string $padStr String used to pad, defaults to space
1310 2
   *
1311
   * @return static  String with right padding
1312 2
   */
1313
  public function padRight($length, $padStr = ' ')
1314
  {
1315
    return $this->applyPadding(0, $length - $this->length(), $padStr);
1316
  }
1317
1318
  /**
1319
   * Returns a new string of a given length such that both sides of the
1320
   * string are padded. Alias for pad() with a $padType of 'both'.
1321
   *
1322 12
   * @param  int    $length Desired string length after padding
1323
   * @param  string $padStr String used to pad, defaults to space
1324 12
   *
1325
   * @return static  String with padding applied
1326 12
   */
1327 6
  public function padBoth($length, $padStr = ' ')
1328
  {
1329 6
    $padding = $length - $this->length();
1330
1331
    return $this->applyPadding(floor($padding / 2), ceil($padding / 2), $padStr);
1332 6
  }
1333
1334
  /**
1335
   * Returns a new string starting with $string.
1336
   *
1337
   * @param  string $string The string to append
1338
   *
1339
   * @return static  Object with appended $string
1340
   */
1341
  public function prepend($string)
1342 12
  {
1343
    return static::create($string . $this->str, $this->encoding);
1344 12
  }
1345
1346 12
  /**
1347 8
   * Returns a new string with the prefix $substring removed, if present.
1348
   *
1349 8
   * @param  string $substring The prefix to remove
1350
   *
1351
   * @return static  Object having a $str without the prefix $substring
1352 4
   */
1353 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...
1354
  {
1355
    $stringy = static::create($this->str, $this->encoding);
1356
1357
    if ($stringy->startsWith($substring)) {
1358
      $substringLength = UTF8::strlen($substring, $stringy->encoding);
1359
1360
      return $stringy->substr($substringLength);
1361
    }
1362 7
1363
    return $stringy;
1364 7
  }
1365
1366 7
  /**
1367
   * Returns a new string with the suffix $substring removed, if present.
1368
   *
1369
   * @param  string $substring The suffix to remove
1370
   *
1371
   * @return static  Object having a $str without the suffix $substring
1372
   */
1373 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...
1374
  {
1375
    $stringy = static::create($this->str, $this->encoding);
1376
1377
    if ($stringy->endsWith($substring)) {
1378 28
      $substringLength = UTF8::strlen($substring, $stringy->encoding);
1379
1380 28
      return $stringy->substr(0, $stringy->length() - $substringLength);
1381 21
    }
1382 21
1383 7
    return $stringy;
1384
  }
1385
1386 28
  /**
1387
   * Returns a repeated string given a multiplier.
1388
   *
1389
   * @param  int $multiplier The number of times to repeat the string
1390
   *
1391
   * @return static  Object with a repeated str
1392
   */
1393
  public function repeat($multiplier)
1394
  {
1395
    $repeated = UTF8::str_repeat($this->str, $multiplier);
1396
1397
    return static::create($repeated, $this->encoding);
1398 30
  }
1399
1400 30
  /**
1401 23
   * Replaces all occurrences of $search in $str by $replacement.
1402 23
   *
1403 7
   * @param string $search        The needle to search for
1404
   * @param string $replacement   The string to replace with
1405
   * @param bool   $caseSensitive Whether or not to enforce case-sensitivity
1406 30
   *
1407
   * @return static  Object with the resulting $str after the replacements
1408
   */
1409 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...
1410
  {
1411
    if ($caseSensitive) {
1412
      $return = UTF8::str_replace($search, $replacement, $this->str);
1413
    } else {
1414
      $return = UTF8::str_ireplace($search, $replacement, $this->str);
1415
    }
1416
1417 16
    return static::create($return);
1418
  }
1419 16
1420
  /**
1421 16
   * Replaces all occurrences of $search in $str by $replacement.
1422
   *
1423
   * @param array        $search        The elements to search for
1424
   * @param string|array $replacement   The string to replace with
1425
   * @param bool         $caseSensitive Whether or not to enforce case-sensitivity
1426
   *
1427
   * @return static  Object with the resulting $str after the replacements
1428
   */
1429 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...
1430
  {
1431
    if ($caseSensitive) {
1432 16
      $return = UTF8::str_replace($search, $replacement, $this->str);
1433
    } else {
1434 16
      $return = UTF8::str_ireplace($search, $replacement, $this->str);
1435
    }
1436 16
1437
    return static::create($return);
1438
  }
1439
1440
  /**
1441
   * Replaces all occurrences of $search from the beginning of string with $replacement
1442
   *
1443
   * @param string $search
1444 5
   * @param string $replacement
1445
   *
1446 5
   * @return static  Object with the resulting $str after the replacements
1447
   */
1448 5
  public function replaceBeginning($search, $replacement)
1449
  {
1450
    $str = $this->regexReplace('^' . preg_quote($search, '/'), UTF8::str_replace('\\', '\\\\', $replacement));
1451
1452
    return static::create($str, $this->encoding);
1453
  }
1454
1455
  /**
1456
   * Replaces all occurrences of $search from the ending of string with $replacement
1457
   *
1458
   * @param string $search
1459
   * @param string $replacement
1460
   *
1461
   * @return static  Object with the resulting $str after the replacements
1462 23
   */
1463
  public function replaceEnding($search, $replacement)
1464 23
  {
1465 23
    $str = $this->regexReplace(preg_quote($search, '/') . '$', UTF8::str_replace('\\', '\\\\', $replacement));
1466 4
1467
    return static::create($str, $this->encoding);
1468
  }
1469
1470 19
  /**
1471 19
   * Returns a reversed string. A multibyte version of strrev().
1472 19
   *
1473
   * @return static  Object with a reversed $str
1474 19
   */
1475
  public function reverse()
1476
  {
1477 19
    $reversed = UTF8::strrev($this->str);
1478 19
1479
    return static::create($reversed, $this->encoding);
1480 12
  }
1481
1482 12
  /**
1483 11
   * Truncates the string to a given length, while ensuring that it does not
1484 11
   * split words. If $substring is provided, and truncating occurs, the
1485 12
   * string is further truncated so that the substring may be appended without
1486
   * exceeding the desired length.
1487 19
   *
1488
   * @param  int    $length    Desired length of the truncated string
1489 19
   * @param  string $substring The substring to append if it can fit
1490
   *
1491
   * @return static  Object with the resulting $str after truncating
1492
   */
1493
  public function safeTruncate($length, $substring = '')
1494
  {
1495
    $stringy = static::create($this->str, $this->encoding);
1496
    if ($length >= $stringy->length()) {
1497
      return $stringy;
1498 3
    }
1499
1500 3
    // Need to further trim the string so we can append the substring
1501
    $encoding = $stringy->encoding;
1502 3
    $substringLength = UTF8::strlen($substring, $encoding);
1503
    $length -= $substringLength;
1504
1505
    $truncated = UTF8::substr($stringy->str, 0, $length, $encoding);
1506
1507
    // If the last word was truncated
1508
    $strPosSpace = UTF8::strpos($stringy->str, ' ', $length - 1, $encoding);
1509
    if ($strPosSpace != $length) {
1510
      // Find pos of the last occurrence of a space, get up to that
1511
      $lastPos = UTF8::strrpos($truncated, ' ', 0, $encoding);
0 ignored issues
show
Security Bug introduced by
It seems like $truncated defined by \voku\helper\UTF8::subst... 0, $length, $encoding) on line 1505 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...
1512
1513
      if ($lastPos !== false || $strPosSpace !== false) {
1514
        $truncated = UTF8::substr($truncated, 0, $lastPos, $encoding);
0 ignored issues
show
Security Bug introduced by
It seems like $truncated defined by \voku\helper\UTF8::subst...0, $lastPos, $encoding) on line 1514 can also be of type false; however, voku\helper\UTF8::substr() does only seem to accept string, did you maybe forget to handle an error condition?

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

Consider the follow example

<?php

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

    return false;
}

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

Loading history...
Bug introduced by
It seems like $lastPos defined by \voku\helper\UTF8::strrp...ted, ' ', 0, $encoding) on line 1511 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...
1515
      }
1516
    }
1517
1518 15
    $stringy->str = $truncated . $substring;
1519
1520 15
    return $stringy;
1521
  }
1522 15
1523
  /**
1524
   * A multibyte string shuffle function. It returns a string with its
1525
   * characters in random order.
1526
   *
1527
   * @return static  Object with a shuffled $str
1528
   */
1529
  public function shuffle()
1530 1
  {
1531
    $shuffledStr = UTF8::str_shuffle($this->str);
1532 1
1533
    return static::create($shuffledStr, $this->encoding);
1534 1
  }
1535
1536
  /**
1537
   * Converts the string into an URL slug. This includes replacing non-ASCII
1538
   * characters with their closest ASCII equivalents, removing remaining
1539
   * non-ASCII and non-alphanumeric characters, and replacing whitespace with
1540
   * $replacement. The replacement defaults to a single dash, and the string
1541
   * is also converted to lowercase.
1542
   *
1543
   * @param string $replacement The string used to replace whitespace
1544 1
   * @param string $language    The language for the url
1545
   * @param bool   $strToLower  string to lower
1546 1
   *
1547
   * @return static  Object whose $str has been converted to an URL slug
1548 1
   */
1549
  public function slugify($replacement = '-', $language = 'de', $strToLower = true)
1550
  {
1551
    $slug = URLify::slug($this->str, $language, $replacement, $strToLower);
1552
1553
    return static::create($slug, $this->encoding);
1554
  }
1555
1556 1
  /**
1557
   * Remove css media-queries.
1558 1
   *
1559
   * @return static
1560
   */
1561
  public function stripeCssMediaQueries()
1562
  {
1563
    $pattern = '#@media\\s+(?:only\\s)?(?:[\\s{\\(]|screen|all)\\s?[^{]+{.*}\\s*}\\s*#misU';
1564
1565
    return static::create(preg_replace($pattern, '', $this->str));
1566 6
  }
1567
1568 6
  /**
1569 6
   * Strip all whitespace characters. This includes tabs and newline characters,
1570 6
   * as well as multibyte whitespace such as the thin space and ideographic space.
1571 6
   *
1572 6
   * @return Stringy
1573
   */
1574 6
  public function stripWhitespace()
1575
  {
1576
    return static::create(UTF8::strip_whitespace($this->str));
1577
  }
1578
1579
  /**
1580
   * Remove empty html-tag.
1581
   *
1582
   * e.g.: <tag></tag>
1583
   *
1584
   * @return static
1585
   */
1586 1
  public function stripeEmptyHtmlTags()
1587
  {
1588
    $pattern = "/<[^\/>]*>(([\s]?)*|)<\/[^>]*>/i";
1589 1
1590
    return static::create(preg_replace($pattern, '', $this->str));
1591 1
  }
1592 1
1593
  /**
1594
   * Converts the string into an valid UTF-8 string.
1595 1
   *
1596
   * @return static
1597 1
   */
1598 1
  public function utf8ify()
1599 1
  {
1600
    return static::create(UTF8::cleanup($this->str));
1601 1
  }
1602
1603
  /**
1604
   * escape html
1605
   *
1606
   * @return static
1607
   */
1608
  public function escape()
1609
  {
1610
    $str = UTF8::htmlspecialchars(
1611
        $this->str,
1612
        ENT_QUOTES | ENT_SUBSTITUTE,
1613
        $this->encoding
1614
    );
1615
1616
    return static::create($str, $this->encoding);
1617
  }
1618
1619
  /**
1620
   * Create an extract from a text, so if the search-string was found, it will be centered in the output.
1621 1
   *
1622 1
   * @param string   $search
1623 1
   * @param int|null $length
1624 1
   * @param string   $ellipsis
1625 1
   *
1626 1
   * @return static
1627 1
   */
1628
  public function extractText($search = '', $length = null, $ellipsis = '...')
1629 1
  {
1630
    // init
1631 1
    $text = $this->str;
1632 1
1633
    if (empty($text)) {
1634 1
      return static::create('', $this->encoding);
1635 1
    }
1636 1
1637
    $trimChars = "\t\r\n -_()!~?=+/*\\,.:;\"'[]{}`&";
1638 1
1639 1
    if ($length === null) {
1640
      $length = (int)round($this->length() / 2, 0);
1641
    }
1642 1
1643 1
    if (empty($search)) {
1644 1
1645
      $stringLength = UTF8::strlen($text, $this->encoding);
1646 1
      $end = ($length - 1) > $stringLength ? $stringLength : ($length - 1);
1647
      $pos = min(UTF8::strpos($text, ' ', $end, 0, $this->encoding), UTF8::strpos($text, '.', $end));
0 ignored issues
show
Documentation introduced by
$this->encoding is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1648
1649
      if ($pos) {
1650 1
        return static::create(
1651 1
            rtrim(
1652 1
                UTF8::substr($text, 0, $pos, $this->encoding),
1653 1
                $trimChars
1654
            ) . $ellipsis,
1655 1
            $this->encoding
1656 1
        );
1657 1
      }
1658 1
1659 1
      return static::create($text, $this->encoding);
1660 1
    }
1661 1
1662 1
    $wordPos = UTF8::strpos(
1663
        UTF8::strtolower($text),
1664 1
        UTF8::strtolower($search, $this->encoding),
1665 1
        null,
1666 1
        $this->encoding
1667 1
    );
1668 1
    $halfSide = (int)($wordPos - $length / 2 + UTF8::strlen($search, $this->encoding) / 2);
1669 1
1670 1
    if ($halfSide > 0) {
1671 1
1672 1
      $halfText = UTF8::substr($text, 0, $halfSide, $this->encoding);
1673
      $pos_start = max(UTF8::strrpos($halfText, ' ', 0), UTF8::strrpos($halfText, '.', 0));
0 ignored issues
show
Security Bug introduced by
It seems like $halfText defined by \voku\helper\UTF8::subst...fSide, $this->encoding) on line 1672 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...
1674 1
1675
      if (!$pos_start) {
1676
        $pos_start = 0;
1677 1
      }
1678
1679 1
    } else {
1680 1
      $pos_start = 0;
1681
    }
1682 1
1683
    if ($wordPos && $halfSide > 0) {
1684
      $l = $pos_start + $length - 1;
1685
      $realLength = UTF8::strlen($text, $this->encoding);
1686 1
1687 1
      if ($l > $realLength) {
1688 1
        $l = $realLength;
1689 1
      }
1690
1691 1
      $pos_end = min(
1692 1
                     UTF8::strpos($text, ' ', $l, $this->encoding),
1693 1
                     UTF8::strpos($text, '.', $l, $this->encoding)
1694
                 ) - $pos_start;
1695 1
1696 1
      if (!$pos_end || $pos_end <= 0) {
1697 1
        $extract = $ellipsis . ltrim(
1698
                UTF8::substr(
1699
                    $text,
1700
                    $pos_start,
1701 1
                    UTF8::strlen($text),
1702
                    $this->encoding
1703
                ),
1704
                $trimChars
1705
            );
1706 View Code Duplication
      } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1707
        $extract = $ellipsis . trim(
1708
                UTF8::substr(
1709
                    $text,
1710 6
                    $pos_start,
1711
                    $pos_end,
1712 6
                    $this->encoding
1713
                ),
1714 6
                $trimChars
1715 1
            ) . $ellipsis;
1716 1
      }
1717
1718 6
    } else {
1719
1720 6
      $l = $length - 1;
1721
      $trueLength = UTF8::strlen($text, $this->encoding);
1722
1723
      if ($l > $trueLength) {
1724
        $l = $trueLength;
1725
      }
1726
1727
      $pos_end = min(
1728
          UTF8::strpos($text, ' ', $l, $this->encoding),
1729
          UTF8::strpos($text, '.', $l, $this->encoding)
1730 6
      );
1731
1732 6 View Code Duplication
      if ($pos_end) {
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...
1733
        $extract = rtrim(
1734 6
                       UTF8::substr($text, 0, $pos_end, $this->encoding),
1735
                       $trimChars
1736
                   ) . $ellipsis;
1737
      } else {
1738
        $extract = $text;
1739
      }
1740
    }
1741
1742
    return static::create($extract, $this->encoding);
1743
  }
1744 6
1745
1746 6
  /**
1747
   * remove xss from html
1748 6
   *
1749
   * @return static
1750
   */
1751
  public function removeXss()
1752
  {
1753
    static $antiXss = null;
1754
1755
    if ($antiXss === null) {
1756
      $antiXss = new AntiXSS();
1757
    }
1758
1759
    $str = $antiXss->xss_clean($this->str);
1760
1761
    return static::create($str, $this->encoding);
1762 18
  }
1763
1764 18
  /**
1765 4
   * remove html-break [br | \r\n | \r | \n | ...]
1766 18
   *
1767 4
   * @param string $replacement
1768 10
   *
1769 2
   * @return static
1770 2
   */
1771 8
  public function removeHtmlBreak($replacement = '')
1772
  {
1773
    $str = preg_replace('#/\r\n|\r|\n|<br.*/?>#isU', $replacement, $this->str);
1774 14
1775
    return static::create($str, $this->encoding);
1776 14
  }
1777
1778
  /**
1779
   * remove html
1780
   *
1781
   * @param $allowableTags
1782
   *
1783
   * @return static
1784
   */
1785
  public function removeHtml($allowableTags = null)
1786
  {
1787
    $str = strip_tags($this->str, $allowableTags);
1788
1789 19
    return static::create($str, $this->encoding);
1790
  }
1791 19
1792 2
  /**
1793
   * Returns the substring beginning at $start, and up to, but not including
1794
   * the index specified by $end. If $end is omitted, the function extracts
1795
   * the remaining string. If $end is negative, it is computed from the end
1796
   * of the string.
1797 17
   *
1798 1
   * @param  int $start Initial index from which to begin extraction
1799
   * @param  int $end   Optional index at which to end extraction
1800
   *
1801
   * @return static  Object with its $str being the extracted substring
1802
   */
1803 16
  public function slice($start, $end = null)
1804 8
  {
1805 8
    if ($end === null) {
1806 8
      $length = $this->length();
1807
    } elseif ($end >= 0 && $end <= $start) {
1808
      return static::create('', $this->encoding);
1809 16
    } elseif ($end < 0) {
1810
      $length = $this->length() + $end - $start;
1811 16
    } else {
1812 4
      $length = $end - $start;
1813 4
    }
1814
1815
    $str = UTF8::substr($this->str, $start, $length, $this->encoding);
1816
1817 16
    return static::create($str, $this->encoding);
1818 16
  }
1819 16
1820
  /**
1821 16
   * Splits the string with the provided regular expression, returning an
1822
   * array of Stringy objects. An optional integer $limit will truncate the
1823
   * results.
1824
   *
1825
   * @param  string $pattern The regex with which to split the string
1826
   * @param  int    $limit   Optional maximum number of results to return
1827
   *
1828
   * @return static [] An array of Stringy objects
1829
   */
1830
  public function split($pattern, $limit = null)
1831
  {
1832 5
    if ($limit === 0) {
1833
      return array();
1834 5
    }
1835
1836 5
    // this->split errors when supplied an empty pattern in < PHP 5.4.13
1837
    // and current versions of HHVM (3.8 and below)
1838
    if ($pattern === '') {
1839
      return array(static::create($this->str, $this->encoding));
1840
    }
1841
1842
    // this->split returns the remaining unsplit string in the last index when
1843
    // supplying a limit
1844 5
    if ($limit > 0) {
1845
      $limit += 1;
1846 5
    } else {
1847
      $limit = -1;
1848 5
    }
1849
1850 5
    $array = preg_split('/' . preg_quote($pattern, '/') . '/u', $this->str, $limit);
1851
1852
    if ($limit > 0 && count($array) === $limit) {
1853
      array_pop($array);
1854
    }
1855
1856
    /** @noinspection CallableInLoopTerminationConditionInspection */
1857
    /** @noinspection ForeachInvariantsInspection */
1858 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...
1859
      $array[$i] = static::create($array[$i], $this->encoding);
1860 4
    }
1861
1862 4
    return $array;
1863
  }
1864 4
1865
  /**
1866
   * Surrounds $str with the given substring.
1867
   *
1868
   * @param  string $substring The substring to add to both sides
1869
   *
1870
   * @return static  Object whose $str had the substring both prepended and
1871
   *                 appended
1872
   */
1873
  public function surround($substring)
1874
  {
1875
    $str = implode('', array($substring, $this->str, $substring));
1876 5
1877
    return static::create($str, $this->encoding);
1878 5
  }
1879 5
1880
  /**
1881 5
   * Returns a case swapped version of the string.
1882 5
   *
1883
   * @return static  Object whose $str has each character's case swapped
1884 5
   */
1885 2
  public function swapCase()
1886
  {
1887 5
    $stringy = static::create($this->str, $this->encoding);
1888
1889 5
    $stringy->str = UTF8::swapCase($stringy->str, $stringy->encoding);
1890
1891 5
    return $stringy;
1892 5
  }
1893 5
1894
  /**
1895 5
   * Returns a string with smart quotes, ellipsis characters, and dashes from
1896
   * Windows-1252 (commonly used in Word documents) replaced by their ASCII
1897
   * equivalents.
1898
   *
1899
   * @return static  Object whose $str has those characters removed
1900
   */
1901
  public function tidy()
1902
  {
1903
    $str = UTF8::normalize_msword($this->str);
1904 27
1905
    return static::create($str, $this->encoding);
1906 27
  }
1907
1908 27
  /**
1909
   * Returns a trimmed string with the first letter of each word capitalized.
1910
   * Also accepts an array, $ignore, allowing you to list words not to be
1911
   * capitalized.
1912
   *
1913
   * @param  array $ignore An array of words not to capitalize
1914
   *
1915
   * @return static  Object with a titleized $str
1916 7
   */
1917
  public function titleize($ignore = null)
1918 7
  {
1919
    $stringy = static::create($this->trim(), $this->encoding);
1920
    $encoding = $this->encoding;
1921
1922
    $stringy->str = preg_replace_callback(
1923
        '/([\S]+)/u',
1924
        function ($match) use ($encoding, $ignore) {
1925
          if ($ignore && in_array($match[0], $ignore, true)) {
1926
            return $match[0];
1927
          }
1928
1929
          $stringy = new Stringy($match[0], $encoding);
1930 16
1931
          return (string)$stringy->toLowerCase()->upperCaseFirst();
1932 16
        },
1933
        $stringy->str
1934 16
    );
1935
1936
    return $stringy;
1937
  }
1938
1939
  /**
1940
   * Converts all characters in the string to lowercase. An alias for PHP's
1941
   * UTF8::strtolower().
1942
   *
1943
   * @return static  Object with all characters of $str being lowercase
1944
   */
1945
  public function toLowerCase()
1946
  {
1947
    $str = UTF8::strtolower($this->str, $this->encoding);
1948 15
1949
    return static::create($str, $this->encoding);
1950 15
  }
1951
1952 15
  /**
1953 15
   * Returns true if the string is base64 encoded, false otherwise.
1954 15
   *
1955 15
   * @return bool Whether or not $str is base64 encoded
1956 15
   */
1957 15
  public function isBase64()
1958 15
  {
1959 15
    return UTF8::is_base64($this->str);
1960 15
  }
1961
1962 15
  /**
1963 10
   * Returns an ASCII version of the string. A set of non-ASCII characters are
1964 5
   * replaced with their closest ASCII counterparts, and the rest are removed
1965 2
   * unless instructed otherwise.
1966
   *
1967 3
   * @param $strict [optional] <p>Use "transliterator_transliterate()" from PHP-Intl | WARNING: bad performance</p>
1968
   *
1969
   * @return static  Object whose $str contains only ASCII characters
1970
   */
1971
  public function toAscii($strict = false)
1972
  {
1973
    $str = UTF8::to_ascii($this->str, '?', $strict);
1974
1975
    return static::create($str, $this->encoding);
1976 993
  }
1977
1978 993
  /**
1979
   * Returns a boolean representation of the given logical string value.
1980
   * For example, 'true', '1', 'on' and 'yes' will return true. 'false', '0',
1981
   * 'off', and 'no' will return false. In all instances, case is ignored.
1982
   * For other numeric strings, their sign will determine the return value.
1983
   * In addition, blank strings consisting of only whitespace will return
1984
   * false. For all other strings, the return value is a result of a
1985
   * boolean cast.
1986
   *
1987
   * @return bool A boolean value for the string
1988
   */
1989 6
  public function toBoolean()
1990
  {
1991 6
    $key = $this->toLowerCase()->str;
1992 6
    $map = array(
1993
        'true'  => true,
1994 6
        '1'     => true,
1995
        'on'    => true,
1996
        'yes'   => true,
1997
        'false' => false,
1998
        '0'     => false,
1999
        'off'   => false,
2000
        'no'    => false,
2001
    );
2002
2003
    if (array_key_exists($key, $map)) {
2004
      return $map[$key];
2005
    }
2006 5
2007
    if (is_numeric($this->str)) {
2008 5
      return ((int)$this->str > 0);
2009 5
    }
2010
2011 5
    return (bool)$this->regexReplace('[[:space:]]', '')->str;
2012
  }
2013
2014
  /**
2015
   * Return Stringy object as string, but you can also use (string) for automatically casting the object into a string.
2016
   *
2017
   * @return string
2018
   */
2019 5
  public function toString()
2020
  {
2021
    return (string)$this->str;
2022 5
  }
2023
2024 5
  /**
2025
   * Converts each tab in the string to some number of spaces, as defined by
2026
   * $tabLength. By default, each tab is converted to 4 consecutive spaces.
2027
   *
2028
   * @param  int $tabLength Number of spaces to replace each tab with
2029
   *
2030
   * @return static  Object whose $str has had tabs switched to spaces
2031
   */
2032 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...
2033 5
  {
2034
    $spaces = UTF8::str_repeat(' ', $tabLength);
2035 5
    $str = UTF8::str_replace("\t", $spaces, $this->str);
2036
2037 5
    return static::create($str, $this->encoding);
2038
  }
2039
2040
  /**
2041
   * Converts each occurrence of some consecutive number of spaces, as
2042
   * defined by $tabLength, to a tab. By default, each 4 consecutive spaces
2043
   * are converted to a tab.
2044
   *
2045
   * @param  int $tabLength Number of spaces to replace with a tab
2046
   *
2047
   * @return static  Object whose $str has had spaces switched to tabs
2048
   */
2049 13 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...
2050
  {
2051 13
    $spaces = UTF8::str_repeat(' ', $tabLength);
2052 11
    $str = UTF8::str_replace($spaces, "\t", $this->str);
2053 11
2054 2
    return static::create($str, $this->encoding);
2055
  }
2056
2057 13
  /**
2058
   * Converts the first character of each word in the string to uppercase.
2059
   *
2060
   * @return static  Object with all characters of $str being title-cased
2061
   */
2062
  public function toTitleCase()
2063
  {
2064
    // "mb_convert_case()" used a polyfill from the "UTF8"-Class
2065
    $str = mb_convert_case($this->str, MB_CASE_TITLE, $this->encoding);
2066
2067
    return static::create($str, $this->encoding);
2068
  }
2069 13
2070
  /**
2071 13
   * Converts all characters in the string to uppercase. An alias for PHP's
2072 11
   * UTF8::strtoupper().
2073 11
   *
2074 2
   * @return static  Object with all characters of $str being uppercase
2075
   */
2076
  public function toUpperCase()
2077 13
  {
2078
    $str = UTF8::strtoupper($this->str, $this->encoding);
2079
2080
    return static::create($str, $this->encoding);
2081
  }
2082
2083
  /**
2084
   * Returns a string with whitespace removed from the start of the string.
2085
   * Supports the removal of unicode whitespace. Accepts an optional
2086
   * string of characters to strip instead of the defaults.
2087
   *
2088
   * @param  string $chars Optional string of characters to strip
2089
   *
2090 22
   * @return static  Object with a trimmed $str
2091
   */
2092 22 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...
2093 22
  {
2094 4
    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...
2095
      $chars = '[:space:]';
2096
    } else {
2097
      $chars = preg_quote($chars, '/');
2098 18
    }
2099 18
2100
    return $this->regexReplace("^[$chars]+", '');
2101 18
  }
2102 18
2103
  /**
2104 18
   * Returns a string with whitespace removed from the end of the string.
2105
   * Supports the removal of unicode whitespace. Accepts an optional
2106
   * string of characters to strip instead of the defaults.
2107
   *
2108
   * @param  string $chars Optional string of characters to strip
2109
   *
2110
   * @return static  Object with a trimmed $str
2111
   */
2112 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...
2113
  {
2114
    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...
2115 16
      $chars = '[:space:]';
2116
    } else {
2117 16
      $chars = preg_quote($chars, '/');
2118
    }
2119
2120
    return $this->regexReplace("[$chars]+\$", '');
2121
  }
2122
2123
  /**
2124
   * Truncates the string to a given length. If $substring is provided, and
2125
   * truncating occurs, the string is further truncated so that the substring
2126
   * may be appended without exceeding the desired length.
2127 13
   *
2128
   * @param  int    $length    Desired length of the truncated string
2129 13
   * @param  string $substring The substring to append if it can fit
2130
   *
2131
   * @return static  Object with the resulting $str after truncating
2132
   */
2133 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...
2134
  {
2135
    $stringy = static::create($this->str, $this->encoding);
2136
    if ($length >= $stringy->length()) {
2137
      return $stringy;
2138
    }
2139 32
2140
    // Need to further trim the string so we can append the substring
2141 32
    $substringLength = UTF8::strlen($substring, $stringy->encoding);
2142 32
    $length -= $substringLength;
2143 32
2144
    $truncated = UTF8::substr($stringy->str, 0, $length, $stringy->encoding);
2145 32
    $stringy->str = $truncated . $substring;
2146 32
2147
    return $stringy;
2148 27
  }
2149 27
2150
  /**
2151 1
   * Returns a lowercase and trimmed string separated by underscores.
2152
   * Underscores are inserted before uppercase characters (with the exception
2153 32
   * of the first character of the string), and in place of spaces as well as
2154 32
   * dashes.
2155 32
   *
2156
   * @return static  Object with an underscored $str
2157 32
   */
2158 32
  public function underscored()
2159
  {
2160 6
    return $this->delimit('_');
2161 32
  }
2162 32
2163 32
  /**
2164
   * Returns an UpperCamelCase version of the supplied string. It trims
2165 32
   * surrounding spaces, capitalizes letters following digits, spaces, dashes
2166
   * and underscores, and removes spaces, dashes, underscores.
2167
   *
2168
   * @return static  Object with $str in UpperCamelCase
2169
   */
2170
  public function upperCamelize()
2171
  {
2172
    return $this->camelize()->upperCaseFirst();
2173 20
  }
2174
2175 20
  /**
2176
   * Returns a camelCase version of the string. Trims surrounding spaces,
2177 20
   * capitalizes letters following digits, spaces, dashes and underscores,
2178 20
   * and removes spaces, dashes, as well as underscores.
2179 20
   *
2180
   * @return static  Object with $str in camelCase
2181 20
   */
2182 20
  public function camelize()
2183 20
  {
2184 8
    $encoding = $this->encoding;
2185 8
    $stringy = $this->trim()->lowerCaseFirst();
2186
    $stringy->str = preg_replace('/^[-_]+/', '', $stringy->str);
2187 8
2188 4
    $stringy->str = preg_replace_callback(
2189
        '/[-_\s]+(.)?/u',
2190 4
        function ($match) use ($encoding) {
2191
          if (isset($match[1])) {
2192 20
            return UTF8::strtoupper($match[1], $encoding);
2193
          }
2194 20
2195
          return '';
2196 20
        },
2197
        $stringy->str
2198
    );
2199 20
2200 20
    $stringy->str = preg_replace_callback(
2201 20
        '/[\d]+(.)?/u',
2202 20
        function ($match) use ($encoding) {
2203
          return UTF8::strtoupper($match[0], $encoding);
2204 20
        },
2205 20
        $stringy->str
2206 20
    );
2207 20
2208
    return $stringy;
2209 20
  }
2210
2211 20
  /**
2212 20
   * Convert a string to e.g.: "snake_case"
2213
   *
2214 20
   * @return static  Object with $str in snake_case
2215
   */
2216
  public function snakeize()
2217
  {
2218
    $str = $this->str;
2219
2220
    $encoding = $this->encoding;
2221
    $str = UTF8::normalize_whitespace($str);
2222 37
    $str = str_replace('-', '_', $str);
2223
2224 37
    $str = preg_replace_callback(
2225 37
        '/([\d|A-Z])/u',
2226
        function ($matches) use ($encoding) {
2227 37
          $match = $matches[1];
2228
          $matchInt = (int)$match;
2229 37
2230
          if ("$matchInt" == $match) {
2231
            return '_' . $match . '_';
2232
          }
2233
2234
          return '_' . UTF8::strtolower($match, $encoding);
2235
        },
2236
        $str
2237
    );
2238
2239
    $str = preg_replace(
2240 4
        array(
2241
2242 4
            '/\s+/',      // convert spaces to "_"
2243
            '/^\s+|\s+$/',  // trim leading & trailing spaces
2244 4
            '/_+/',         // remove double "_"
2245
        ),
2246
        array(
2247
            '_',
2248
            '',
2249
            '_',
2250
        ),
2251
        $str
2252
    );
2253
2254 1
    $str = UTF8::trim($str, '_'); // trim leading & trailing "_"
2255
    $str = UTF8::trim($str); // trim leading & trailing whitespace
2256 1
2257
    return static::create($str, $this->encoding);
2258 1
  }
2259 1
2260 1
  /**
2261 1
   * Converts the first character of the string to lower case.
2262 1
   *
2263
   * @return static  Object with the first character of $str being lower case
2264 1
   */
2265 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...
2266
  {
2267
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
2268
    $rest = UTF8::substr($this->str, 1, $this->length() - 1, $this->encoding);
2269
2270
    $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 2267 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...
2271
2272
    return static::create($str, $this->encoding);
2273
  }
2274
2275 1
  /**
2276
   * Shorten the string after $length, but also after the next word.
2277 1
   *
2278 1
   * @param int    $length
2279
   * @param string $strAddOn
2280
   *
2281 1
   * @return static
2282 1
   */
2283 1
  public function shortenAfterWord($length, $strAddOn = '...')
2284 1
  {
2285 1
    $string = UTF8::str_limit_after_word($this->str, $length, $strAddOn);
2286 1
2287 1
    return static::create($string);
2288 1
  }
2289 1
2290
  /**
2291
   * Line-Wrap the string after $limit, but also after the next word.
2292
   *
2293
   * @param int $limit
2294
   *
2295
   * @return static
2296
   */
2297
  public function lineWrapAfterWord($limit)
2298
  {
2299
    $strings = preg_split('/\\r\\n|\\r|\\n/', $this->str);
2300 1
2301
    $string = '';
2302 1
    foreach ($strings as $value) {
2303 1
      $string .= wordwrap($value, $limit);
2304 1
      $string .= "\n";
2305
    }
2306
2307 1
    return static::create($string);
2308 1
  }
2309 1
2310 1
  /**
2311 1
   * Gets the substring after the first occurrence of a separator.
2312 1
   * If no match is found returns new empty Stringy object.
2313 1
   *
2314 1
   * @param string $separator
2315 1
   *
2316
   * @return static
2317
   */
2318 View Code Duplication
  public function afterFirst($separator)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2319
  {
2320
    if (($offset = $this->indexOf($separator)) === false) {
2321
      return static::create('');
2322
    }
2323
2324
    return static::create(
2325
        UTF8::substr(
2326 1
            $this->str,
2327
            $offset + UTF8::strlen($separator, $this->encoding),
2328 1
            null,
2329 1
            $this->encoding
2330 1
        ),
2331
        $this->encoding
2332
    );
2333 1
  }
2334 1
2335 1
  /**
2336 1
   * Gets the substring after the last occurrence of a separator.
2337 1
   * If no match is found returns new empty Stringy object.
2338 1
   *
2339 1
   * @param string $separator
2340 1
   *
2341 1
   * @return static
2342
   */
2343
  public function afterLast($separator)
2344
  {
2345
    $offset = $this->indexOfLast($separator);
2346
    if ($offset === false) {
2347
      return static::create('', $this->encoding);
2348
    }
2349
2350
    return static::create(
2351
        UTF8::substr(
2352 1
            $this->str,
2353
            $offset + UTF8::strlen($separator, $this->encoding),
2354 1
            null,
2355 1
            $this->encoding
2356 1
        ),
2357
        $this->encoding
2358
    );
2359 1
  }
2360 1
2361 1
  /**
2362 1
   * Gets the substring before the first occurrence of a separator.
2363 1
   * If no match is found returns new empty Stringy object.
2364 1
   *
2365 1
   * @param string $separator
2366 1
   *
2367 1
   * @return static
2368
   */
2369 View Code Duplication
  public function beforeFirst($separator)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2370
  {
2371
    $offset = $this->indexOf($separator);
2372
    if ($offset === false) {
2373
      return static::create('', $this->encoding);
2374
    }
2375
2376 39
    return static::create(
2377
        UTF8::substr(
2378 39
            $this->str,
2379 39
            0,
2380 39
            $offset,
2381
            $this->encoding
2382 39
        ),
2383
        $this->encoding
2384
    );
2385
  }
2386
2387
  /**
2388
   * Gets the substring before the last occurrence of a separator.
2389
   * If no match is found returns new empty Stringy object.
2390 7
   *
2391
   * @param string $separator
2392 7
   *
2393
   * @return static
2394 7
   */
2395 7 View Code Duplication
  public function beforeLast($separator)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2396 7
  {
2397
    $offset = $this->indexOfLast($separator);
2398 7
    if ($offset === false) {
2399
      return static::create('', $this->encoding);
2400
    }
2401
2402
    return static::create(
2403
        UTF8::substr(
2404
            $this->str,
2405
            0,
2406
            $offset,
2407
            $this->encoding
2408
        ),
2409 39
        $this->encoding
2410
    );
2411
  }
2412 39
2413 39
  /**
2414
   * Returns the string with the first letter of each word capitalized,
2415
   * except for when the word is a name which shouldn't be capitalized.
2416
   *
2417 39
   * @return static  Object with $str capitalized
2418 39
   */
2419 39
  public function capitalizePersonalName()
2420 39
  {
2421 39
    $stringy = $this->collapseWhitespace();
2422 39
    $stringy->str = $this->capitalizePersonalNameByDelimiter($stringy->str, ' ');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->capitalizePersona...ter($stringy->str, ' ') of type this<Stringy\Stringy> is incompatible with the declared type string of property $str.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
2423 39
    $stringy->str = $this->capitalizePersonalNameByDelimiter($stringy->str, '-');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->capitalizePersona...ter($stringy->str, '-') of type this<Stringy\Stringy> is incompatible with the declared type string of property $str.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
2424 39
2425 39
    return static::create($stringy, $this->encoding);
2426 39
  }
2427 39
2428 39
  /**
2429 39
   * @param string $word
2430 39
   *
2431 39
   * @return string
2432 39
   */
2433 39
  protected function capitalizeWord($word)
2434 39
  {
2435 39
    $encoding = $this->encoding;
2436 39
2437 39
    $firstCharacter = UTF8::substr($word, 0, 1, $encoding);
2438 39
    $restOfWord = UTF8::substr($word, 1, null, $encoding);
2439 39
    $firstCharacterUppercased = UTF8::strtoupper($firstCharacter, $encoding);
0 ignored issues
show
Security Bug introduced by
It seems like $firstCharacter defined by \voku\helper\UTF8::substr($word, 0, 1, $encoding) on line 2437 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...
2440 39
2441 39
    return new static($firstCharacterUppercased . $restOfWord, $encoding);
2442 39
  }
2443 39
2444
  /**
2445 39
   * Personal names such as "Marcus Aurelius" are sometimes typed incorrectly using lowercase ("marcus aurelius").
2446 39
   *
2447 39
   * @param string $names
2448 39
   * @param string $delimiter
2449 39
   *
2450 39
   * @return string
2451 39
   */
2452 39
  protected function capitalizePersonalNameByDelimiter($names, $delimiter)
2453 39
  {
2454
    // init
2455 39
    $names = explode((string)$delimiter, (string)$names);
2456 39
    $encoding = $this->encoding;
2457 27
2458
    $specialCases = array(
2459
        'names'    => array(
2460 13
            'ab',
2461
            'af',
2462 13
            'al',
2463 13
            'and',
2464 13
            'ap',
2465 2
            'bint',
2466 2
            'binte',
2467 13
            'da',
2468 13
            'de',
2469
            'del',
2470 13
            'den',
2471 13
            'der',
2472 7
            'di',
2473 7
            'dit',
2474 13
            'ibn',
2475
            'la',
2476 13
            'mac',
2477 7
            'nic',
2478
            'of',
2479
            'ter',
2480 7
            'the',
2481 39
            'und',
2482
            'van',
2483 39
            'von',
2484
            'y',
2485
            'zu',
2486
        ),
2487
        'prefixes' => array(
2488
            'al-',
2489
            "d'",
2490
            'ff',
2491
            "l'",
2492
            'mac',
2493
            'mc',
2494
            'nic',
2495
        ),
2496
    );
2497
2498
    foreach ($names as &$name) {
2499
      if (in_array($name, $specialCases['names'], true)) {
2500
        continue;
2501
      }
2502
2503
      $continue = false;
2504
2505
      if ($delimiter == '-') {
2506 View Code Duplication
        foreach ($specialCases['names'] as $beginning) {
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...
2507
          if (UTF8::strpos($name, $beginning, null, $encoding) === 0) {
2508
            $continue = true;
2509
          }
2510
        }
2511
      }
2512
2513 View Code Duplication
      foreach ($specialCases['prefixes'] as $beginning) {
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...
2514
        if (UTF8::strpos($name, $beginning, null, $encoding) === 0) {
2515
          $continue = true;
2516
        }
2517
      }
2518
2519
      if ($continue) {
2520
        continue;
2521
      }
2522
2523
      $name = $this->capitalizeWord($name);
2524
    }
2525
2526
    return new static(implode($delimiter, $names), $encoding);
2527
  }
2528
}
2529