Completed
Push — master ( bedd78...252b5f )
by Lars
18:07
created

Stringy::is()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 6
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 11
ccs 0
cts 2
cp 0
crap 6
rs 9.4285
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
    } elseif (is_object($str) && !method_exists($str, '__toString')) {
52 1
      throw new \InvalidArgumentException(
53
          'Passed object must have a __toString method'
54 1
      );
55
    }
56
57
    // don't throw a notice on PHP 5.3
58 1084
    if (!defined('ENT_SUBSTITUTE')) {
59
      define('ENT_SUBSTITUTE', 8);
60
    }
61
62
    // init
63 1084
    UTF8::checkForSupport();
64
65 1084
    $this->str = (string)$str;
66
67 1084
    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...
68 850
      $this->encoding = $encoding;
69 850
    } else {
70 712
      UTF8::mbstring_loaded();
71 712
      $this->encoding = mb_internal_encoding();
72
    }
73
74 1084
    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...
75 850
      $this->encoding = $encoding;
76 850
    } else {
77 712
      $this->encoding = mb_internal_encoding();
78
    }
79 1084
  }
80
81
  /**
82
   * Returns the value in $str.
83
   *
84
   * @return string The current value of the $str property
85
   */
86 163
  public function __toString()
87
  {
88 163
    return (string)$this->str;
89
  }
90
91
  /**
92
   * Returns a new string with $string appended.
93
   *
94
   * @param  string $string The string to append
95
   *
96
   * @return static  Object with appended $string
97
   */
98 5
  public function append($string)
99
  {
100 5
    return static::create($this->str . $string, $this->encoding);
101
  }
102
103
  /**
104
   * Append an password (limited to chars that are good readable).
105
   *
106
   * @param int $length length of the random string
107
   *
108
   * @return static  Object with appended password
109
   */
110 1
  public function appendPassword($length)
111
  {
112 1
    $possibleChars = '2346789bcdfghjkmnpqrtvwxyzBCDFGHJKLMNPQRTVWXYZ';
113
114 1
    return $this->appendRandomString($length, $possibleChars);
115
  }
116
117
  /**
118
   * Append an unique identifier.
119
   *
120
   * @param string|int $extraPrefix
121
   *
122
   * @return static  Object with appended unique identifier as md5-hash
123
   */
124 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...
125
  {
126 1
    $prefix = mt_rand() .
127 1
              session_id() .
128 1
              (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '') .
129 1
              (isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '') .
130 1
              $extraPrefix;
131
132 1
    return $this->append(md5(uniqid($prefix, true) . $prefix));
133
  }
134
135
  /**
136
   * Append an random string.
137
   *
138
   * @param int    $length        length of the random string
139
   * @param string $possibleChars characters string for the random selection
140
   *
141
   * @return static  Object with appended random string
142
   */
143 2
  public function appendRandomString($length, $possibleChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')
144
  {
145
    // init
146 2
    $i = 0;
147 2
    $length = (int)$length;
148 2
    $str = $this->str;
149 2
    $maxlength = UTF8::strlen($possibleChars, $this->encoding);
150
151 2
    if ($maxlength === 0) {
152 1
      return $this;
153
    }
154
155
    // add random chars
156 2
    while ($i < $length) {
157 2
      $char = UTF8::substr($possibleChars, mt_rand(0, $maxlength - 1), 1, $this->encoding);
158 2
      $str .= $char;
159 2
      $i++;
160 2
    }
161
162 2
    return $this->append($str);
163
  }
164
165
  /**
166
   * Creates a Stringy object and assigns both str and encoding properties
167
   * the supplied values. $str is cast to a string prior to assignment, and if
168
   * $encoding is not specified, it defaults to mb_internal_encoding(). It
169
   * then returns the initialized object. Throws an InvalidArgumentException
170
   * if the first argument is an array or object without a __toString method.
171
   *
172
   * @param  mixed  $str      Value to modify, after being cast to string
173
   * @param  string $encoding The character encoding
174
   *
175
   * @return static  A Stringy object
176
   * @throws \InvalidArgumentException if an array or object without a
177
   *         __toString method is passed as the first argument
178
   */
179 1076
  public static function create($str = '', $encoding = null)
180
  {
181 1076
    return new static($str, $encoding);
182
  }
183
184
  /**
185
   * Returns the substring between $start and $end, if found, or an empty
186
   * string. An optional offset may be supplied from which to begin the
187
   * search for the start string.
188
   *
189
   * @param  string $start  Delimiter marking the start of the substring
190
   * @param  string $end    Delimiter marking the end of the substring
191
   * @param  int    $offset Index from which to begin the search
192
   *
193
   * @return static  Object whose $str is a substring between $start and $end
194
   */
195 16
  public function between($start, $end, $offset = 0)
196
  {
197 16
    $startIndex = $this->indexOf($start, $offset);
198 16
    if ($startIndex === false) {
199 2
      return static::create('', $this->encoding);
200
    }
201
202 14
    $substrIndex = $startIndex + UTF8::strlen($start, $this->encoding);
203 14
    $endIndex = $this->indexOf($end, $substrIndex);
204 14
    if ($endIndex === false) {
205 2
      return static::create('', $this->encoding);
206
    }
207
208 12
    return $this->substr($substrIndex, $endIndex - $substrIndex);
209
  }
210
211
  /**
212
   * Returns the index of the first occurrence of $needle in the string,
213
   * and false if not found. Accepts an optional offset from which to begin
214
   * the search.
215
   *
216
   * @param  string $needle Substring to look for
217
   * @param  int    $offset Offset from which to search
218
   *
219
   * @return int|bool The occurrence's index if found, otherwise false
220
   */
221 28
  public function indexOf($needle, $offset = 0)
222
  {
223 28
    return UTF8::strpos($this->str, (string)$needle, (int)$offset, $this->encoding);
224
  }
225
226
  /**
227
   * Returns the substring beginning at $start with the specified $length.
228
   * It differs from the UTF8::substr() function in that providing a $length of
229
   * null will return the rest of the string, rather than an empty string.
230
   *
231
   * @param  int $start  Position of the first character to use
232
   * @param  int $length Maximum number of characters used
233
   *
234
   * @return static  Object with its $str being the substring
235
   */
236 64
  public function substr($start, $length = null)
237
  {
238 64
    if ($length === null) {
239 19
      $length = $this->length();
240 19
    }
241
242 64
    $str = UTF8::substr($this->str, $start, $length, $this->encoding);
243
244 64
    return static::create($str, $this->encoding);
245
  }
246
247
  /**
248
   * Returns the length of the string.
249
   *
250
   * @return int The number of characters in $str given the encoding
251
   */
252 248
  public function length()
253
  {
254 248
    return UTF8::strlen($this->str, $this->encoding);
255
  }
256
257
  /**
258
   * Trims the string and replaces consecutive whitespace characters with a
259
   * single space. This includes tabs and newline characters, as well as
260
   * multibyte whitespace such as the thin space and ideographic space.
261
   *
262
   * @return static  Object with a trimmed $str and condensed whitespace
263
   */
264 52
  public function collapseWhitespace()
265
  {
266 52
    return $this->regexReplace('[[:space:]]+', ' ')->trim();
267
  }
268
269
  /**
270
   * Returns a string with whitespace removed from the start and end of the
271
   * string. Supports the removal of unicode whitespace. Accepts an optional
272
   * string of characters to strip instead of the defaults.
273
   *
274
   * @param  string $chars Optional string of characters to strip
275
   *
276
   * @return static  Object with a trimmed $str
277
   */
278 153 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...
279
  {
280 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...
281 152
      $chars = '[:space:]';
282 152
    } else {
283 1
      $chars = preg_quote($chars, '/');
284
    }
285
286 153
    return $this->regexReplace("^[$chars]+|[$chars]+\$", '');
287
  }
288
289
  /**
290
   * Replaces all occurrences of $pattern in $str by $replacement.
291
   *
292
   * @param  string $pattern     The regular expression pattern
293
   * @param  string $replacement The string to replace with
294
   * @param  string $options     Matching conditions to be used
295
   *
296
   * @return static  Object with the result2ing $str after the replacements
297
   */
298 223
  public function regexReplace($pattern, $replacement, $options = '')
299
  {
300 223
    if ($options === 'msr') {
301 8
      $options = 'ms';
302 8
    }
303
304 223
    $str = preg_replace(
305 223
        '/' . $pattern . '/u' . $options,
306 223
        $replacement,
307 223
        $this->str
308 223
    );
309
310 223
    return static::create($str, $this->encoding);
311
  }
312
313
  /**
314
   * Returns true if the string contains all $needles, false otherwise. By
315
   * default the comparison is case-sensitive, but can be made insensitive by
316
   * setting $caseSensitive to false.
317
   *
318
   * @param  array $needles       SubStrings to look for
319
   * @param  bool  $caseSensitive Whether or not to enforce case-sensitivity
320
   *
321
   * @return bool   Whether or not $str contains $needle
322
   */
323 43 View Code Duplication
  public function containsAll($needles, $caseSensitive = true)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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

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

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

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

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

    return array();
}

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

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

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

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

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

Loading history...
462
  {
463 10
    $stringy = static::create($this->str, $this->encoding);
464
465 10
    if (!$stringy->startsWith($substring)) {
466 4
      $stringy->str = $substring . $stringy->str;
467 4
    }
468
469 10
    return $stringy;
470
  }
471
472
  /**
473
   * Returns true if the string begins with $substring, false otherwise. By
474
   * default, the comparison is case-sensitive, but can be made insensitive
475
   * by setting $caseSensitive to false.
476
   *
477
   * @param  string $substring     The substring to look for
478
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
479
   *
480
   * @return bool   Whether or not $str starts with $substring
481
   */
482 45
  public function startsWith($substring, $caseSensitive = true)
483
  {
484 45
    $str = $this->str;
485
486 45 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...
487 8
      $substring = UTF8::strtolower($substring, $this->encoding);
488 8
      $str = UTF8::strtolower($this->str, $this->encoding);
489 8
    }
490
491 45
    return UTF8::strpos($str, $substring, $this->encoding) === 0;
492
  }
493
494
  /**
495
   * Returns true if the string begins with any of $substrings, false otherwise.
496
   * By default the comparison is case-sensitive, but can be made insensitive by
497
   * setting $caseSensitive to false.
498
   *
499
   * @param  array $substrings    Substrings to look for
500
   * @param  bool  $caseSensitive Whether or not to enforce case-sensitivity
501
   *
502
   * @return bool   Whether or not $str starts with $substring
503
   */
504 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...
505
  {
506 12
    if (empty($substrings)) {
507
      return false;
508
    }
509
510 12
    foreach ($substrings as $substring) {
511 12
      if ($this->startsWith($substring, $caseSensitive)) {
512 6
        return true;
513
      }
514 6
    }
515
516 6
    return false;
517
  }
518
519
  /**
520
   * Ensures that the string ends with $substring. If it doesn't, it's
521
   * appended.
522
   *
523
   * @param  string $substring The substring to add if not present
524
   *
525
   * @return static  Object with its $str suffixed by the $substring
526
   */
527 10 View Code Duplication
  public function ensureRight($substring)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
528
  {
529 10
    $stringy = static::create($this->str, $this->encoding);
530
531 10
    if (!$stringy->endsWith($substring)) {
532 4
      $stringy->str .= $substring;
533 4
    }
534
535 10
    return $stringy;
536
  }
537
538
  /**
539
   * Returns true if the string ends with $substring, false otherwise. By
540
   * default, the comparison is case-sensitive, but can be made insensitive
541
   * by setting $caseSensitive to false.
542
   *
543
   * @param  string $substring     The substring to look for
544
   * @param  bool   $caseSensitive Whether or not to enforce case-sensitivity
545
   *
546
   * @return bool   Whether or not $str ends with $substring
547
   */
548 33
  public function endsWith($substring, $caseSensitive = true)
549
  {
550 33
    $substringLength = UTF8::strlen($substring, $this->encoding);
551 33
    $strLength = $this->length();
552
553 33
    $endOfStr = UTF8::substr(
554 33
        $this->str,
555 33
        $strLength - $substringLength,
556 33
        $substringLength,
557 33
        $this->encoding
558 33
    );
559
560 33 View Code Duplication
    if (!$caseSensitive) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
561 4
      $substring = UTF8::strtolower($substring, $this->encoding);
562 4
      $endOfStr = UTF8::strtolower($endOfStr, $this->encoding);
0 ignored issues
show
Security Bug introduced by
It seems like $endOfStr can also be of type false; however, voku\helper\UTF8::strtolower() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
563 4
    }
564
565 33
    return (string)$substring === $endOfStr;
566
  }
567
568
  /**
569
   * Returns true if the string ends with any of $substrings, false otherwise.
570
   * By default, the comparison is case-sensitive, but can be made insensitive
571
   * by setting $caseSensitive to false.
572
   *
573
   * @param  string[] $substrings    Substrings to look for
574
   * @param  bool     $caseSensitive Whether or not to enforce
575 12
   *                                 case-sensitivity
576
   * @return bool     Whether or not $str ends with $substring
577 12
   */
578 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...
579 12
  {
580 2
    if (empty($substrings)) {
581 2
      return false;
582 10
    }
583
584
    foreach ($substrings as $substring) {
585 2
      if ($this->endsWith($substring, $caseSensitive)) {
586
        return true;
587
      }
588
    }
589
590
    return false;
591
  }
592
593 3
  /**
594
   * Returns the first $n characters of the string.
595 3
   *
596
   * @param  int $n Number of characters to retrieve from the start
597
   *
598
   * @return static  Object with its $str being the first $n chars
599
   */
600 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...
601
  {
602
    $stringy = static::create($this->str, $this->encoding);
603
604
    if ($n < 0) {
605
      $stringy->str = '';
606 1
    } else {
607
      return $stringy->substr(0, $n);
608 1
    }
609
610
    return $stringy;
611
  }
612
613
  /**
614
   * Returns the encoding used by the Stringy object.
615
   *
616 4
   * @return string The current value of the $encoding property
617
   */
618
  public function getEncoding()
619 4
  {
620 4
    return $this->encoding;
621
  }
622 4
623 3
  /**
624 3
   * Returns a new ArrayIterator, thus implementing the IteratorAggregate
625
   * interface. The ArrayIterator's constructor is passed an array of chars
626 4
   * in the multibyte string. This enables the use of foreach with instances
627
   * of Stringy\Stringy.
628
   *
629
   * @return \ArrayIterator An iterator for the characters in the string
630
   */
631
  public function getIterator()
632
  {
633
    return new \ArrayIterator($this->chars());
634
  }
635
636 11
  /**
637
   * Returns an array consisting of the characters in the string.
638 11
   *
639
   * @return array An array of string chars
640
   */
641
  public function chars()
642
  {
643
    // init
644
    $chars = array();
645
    $l = $this->length();
646
647 12
    for ($i = 0; $i < $l; $i++) {
648
      $chars[] = $this->at($i)->str;
649 12
    }
650
651
    return $chars;
652
  }
653
654
  /**
655
   * Returns the character at $index, with indexes starting at 0.
656
   *
657
   * @param  int $index Position of the character
658
   *
659 103
   * @return static  The character at $index
660
   */
661 103
  public function at($index)
662 64
  {
663
    return $this->substr($index, 1);
664 39
  }
665
666
  /**
667
   * Returns true if the string contains a lower case char, false
668
   * otherwise.
669
   *
670
   * @return bool Whether or not the string contains a lower case character.
671
   */
672
  public function hasLowerCase()
673
  {
674 12
    return $this->matchesPattern('.*[[:lower:]]');
675
  }
676 12
677
  /**
678
   * Returns true if $str matches the supplied pattern, false otherwise.
679
   *
680
   * @param  string $pattern Regex pattern to match against
681
   *
682
   * @return bool   Whether or not $str matches the pattern
683
   */
684
  protected function matchesPattern($pattern)
685
  {
686 5
    if (preg_match('/' . $pattern . '/u', $this->str)) {
687
      return true;
688 5
    } else {
689
      return false;
690 5
    }
691
  }
692
693
  /**
694
   * Returns true if the string contains an upper case char, false
695
   * otherwise.
696
   *
697
   * @return bool Whether or not the string contains an upper case character.
698
   */
699
  public function hasUpperCase()
700 5
  {
701
    return $this->matchesPattern('.*[[:upper:]]');
702 5
  }
703
704 5
  /**
705
   * Convert all HTML entities to their applicable characters.
706
   *
707
   * @param  int|null $flags Optional flags
708
   *
709
   * @return static   Object with the resulting $str after being html decoded.
710
   */
711
  public function htmlDecode($flags = ENT_COMPAT)
712
  {
713 3
    $str = UTF8::html_entity_decode($this->str, $flags, $this->encoding);
714
715 3
    return static::create($str, $this->encoding);
716
  }
717 3
718
  /**
719
   * Convert all applicable characters to HTML entities.
720
   *
721
   * @param  int|null $flags Optional flags
722
   *
723
   * @return static   Object with the resulting $str after being html encoded.
724
   */
725 27
  public function htmlEncode($flags = ENT_COMPAT)
726
  {
727 27
    $str = UTF8::htmlentities($this->str, $flags, $this->encoding);
728 27
729 27
    return static::create($str, $this->encoding);
730 27
  }
731 27
732 27
  /**
733 27
   * Capitalizes the first word of the string, replaces underscores with
734
   * spaces, and strips '_id'.
735 27
   *
736
   * @return static  Object with a humanized $str
737 27
   */
738
  public function humanize()
739
  {
740
    $str = UTF8::str_replace(array('_id', '_'), array('', ' '), $this->str);
741
742
    return static::create($str, $this->encoding)->trim()->upperCaseFirst();
743
  }
744
745
  /**
746
   * Converts the first character of the supplied string to upper case.
747
   *
748
   * @return static  Object with the first character of $str being upper case
749
   */
750 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...
751 12
  {
752
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
753 12
    $rest = UTF8::substr(
754
        $this->str,
755
        1,
756
        $this->length() - 1,
757
        $this->encoding
758
    );
759
760
    $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 752 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...
761
762
    return static::create($str, $this->encoding);
763
  }
764 8
765
  /**
766 8
   * Returns the index of the last occurrence of $needle in the string,
767 8
   * and false if not found. Accepts an optional offset from which to begin
768 1
   * the search. Offsets may be negative to count from the last character
769
   * in the string.
770
   *
771 7
   * @param  string $needle Substring to look for
772 7
   * @param  int    $offset Offset from which to search
773
   *
774 7
   * @return int|bool The last occurrence's index if found, otherwise false
775
   */
776 7
  public function indexOfLast($needle, $offset = 0)
777
  {
778
    return UTF8::strrpos($this->str, (string)$needle, (int)$offset, $this->encoding);
779
  }
780
781
  /**
782
   * Inserts $substring into the string at the $index provided.
783
   *
784
   * @param  string $substring String to be inserted
785
   * @param  int    $index     The index at which to insert the substring
786
   *
787
   * @return static  Object with the resulting $str after the insertion
788
   */
789 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...
790
  {
791 13
    $stringy = static::create($this->str, $this->encoding);
792
    if ($index > $stringy->length()) {
793 13
      return $stringy;
794 1
    }
795
796
    $start = UTF8::substr($stringy->str, 0, $index, $stringy->encoding);
797 12
    $end = UTF8::substr($stringy->str, $index, $stringy->length(), $stringy->encoding);
798 12
799
    $stringy->str = $start . $substring . $end;
800 12
801
    return $stringy;
802
  }
803
804
  /**
805
   * Returns true if the string contains the $pattern, otherwise false.
806
   *
807
   * WARNING: Asterisks ("*") are translated into (".*") zero-or-more regular
808
   * expression wildcards.
809 10
   *
810
   * @credit Originally from Laravel, thanks Taylor.
811 10
   *
812
   * @param string $pattern The string or pattern to match against.
813
   *
814
   * @return bool Whether or not we match the provided pattern.
815
   */
816
  public function is($pattern)
817
  {
818
    if ($this->toString() === $pattern) {
819
      return true;
820
    }
821
822
    $quotedPattern = preg_quote($pattern, '/');
823
    $replaceWildCards = str_replace('\*', '.*', $quotedPattern);
824
825
    return $this->matchesPattern('^' . $replaceWildCards . '\z');
826
  }
827
828
  /**
829
   * Returns true if the string contains only alphabetic chars, false
830
   * otherwise.
831
   *
832
   * @return bool Whether or not $str contains only alphabetic chars
833 13
   */
834
  public function isAlpha()
835 13
  {
836
    return $this->matchesPattern('^[[:alpha:]]*$');
837
  }
838
839
  /**
840
   * Determine whether the string is considered to be empty.
841
   *
842
   * A variable is considered empty if it does not exist or if its value equals FALSE.
843
   * empty() does not generate a warning if the variable does not exist.
844 15
   *
845
   * @return bool
846 15
   */
847
  public function isEmpty()
848
  {
849
    return empty($this->str);
850
  }
851
852
  /**
853
   * Returns true if the string contains only alphabetic and numeric chars,
854
   * false otherwise.
855 13
   *
856
   * @return bool Whether or not $str contains only alphanumeric chars
857 13
   */
858
  public function isAlphanumeric()
859
  {
860
    return $this->matchesPattern('^[[:alnum:]]*$');
861
  }
862
863
  /**
864
   * Returns true if the string contains only whitespace chars, false
865 1
   * otherwise.
866
   *
867 1
   * @return bool Whether or not $str contains only whitespace characters
868
   */
869
  public function isBlank()
870
  {
871
    return $this->matchesPattern('^[[:space:]]*$');
872
  }
873
874
  /**
875
   * Returns true if the string contains only hexadecimal chars, false
876
   * otherwise.
877
   *
878
   * @return bool Whether or not $str contains only hexadecimal chars
879
   */
880 1
  public function isHexadecimal()
881
  {
882 1
    return $this->matchesPattern('^[[:xdigit:]]*$');
883
  }
884
885
  /**
886
   * Returns true if the string contains HTML-Tags, false otherwise.
887
   *
888
   * @return bool Whether or not $str contains HTML-Tags
889
   */
890
  public function isHtml()
891
  {
892 20
    return UTF8::isHtml($this->str);
0 ignored issues
show
Deprecated Code introduced by
The method voku\helper\UTF8::isHtml() has been deprecated.

This method has been deprecated.

Loading history...
893
  }
894 20
895 1
  /**
896
   * Returns true if the string contains a valid E-Mail address, false otherwise.
897
   *
898 19
   * @param bool $useExampleDomainCheck
899
   * @param bool $useTypoInDomainCheck
900 19
   * @param bool $useTemporaryDomainCheck
901 11
   * @param bool $useDnsCheck
902
   *
903 8
   * @return bool Whether or not $str contains a valid E-Mail address
904
   */
905
  public function isEmail($useExampleDomainCheck = false, $useTypoInDomainCheck = false, $useTemporaryDomainCheck = false, $useDnsCheck = false)
906
  {
907
    return EmailCheck::isValid($this->str, $useExampleDomainCheck, $useTypoInDomainCheck, $useTemporaryDomainCheck, $useDnsCheck);
908
  }
909
910
  /**
911
   * Returns true if the string is JSON, false otherwise. Unlike json_decode
912
   * in PHP 5.x, this method is consistent with PHP 7 and other JSON parsers,
913 8
   * in that an empty string is not considered valid JSON.
914
   *
915 8
   * @return bool Whether or not $str is JSON
916 3
   */
917
  public function isJson()
918 5
  {
919
    if (!isset($this->str[0])) {
920
      return false;
921
    }
922
923
    json_decode($this->str);
924
925
    if (json_last_error() === JSON_ERROR_NONE) {
926
      return true;
927 7
    } else {
928
      return false;
929 7
    }
930 1
  }
931
932
  /**
933
   * Returns true if the string contains only lower case chars, false
934
   * otherwise.
935 6
   *
936 6
   * @return bool Whether or not $str contains only lower case characters
937 6
   */
938 6
  public function isLowerCase()
939 4
  {
940
    if ($this->matchesPattern('^[[:lower:]]*$')) {
941 2
      return true;
942
    } else {
943
      return false;
944
    }
945
  }
946
947
  /**
948
   * Returns true if the string is serialized, false otherwise.
949
   *
950
   * @return bool Whether or not $str is serialized
951 8
   */
952
  public function isSerialized()
953 8
  {
954
    if (!isset($this->str[0])) {
955
      return false;
956
    }
957
958
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
959
    if (
960
        $this->str === 'b:0;'
961
        ||
962
        @unserialize($this->str) !== false
963 12
    ) {
964
      return true;
965 12
    } else {
966
      return false;
967 12
    }
968 4
  }
969 4
970 8
  /**
971
   * Returns true if the string contains only lower case chars, false
972
   * otherwise.
973 4
   *
974
   * @return bool Whether or not $str contains only lower case characters
975
   */
976
  public function isUpperCase()
977
  {
978
    return $this->matchesPattern('^[[:upper:]]*$');
979
  }
980
981
  /**
982 15
   * Returns the last $n characters of the string.
983
   *
984 15
   * @param  int $n Number of characters to retrieve from the end
985
   *
986
   * @return static  Object with its $str being the last $n chars
987 15
   */
988 15 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...
989 15
  {
990
    $stringy = static::create($this->str, $this->encoding);
991 15
992
    if ($n <= 0) {
993
      $stringy->str = '';
994
    } else {
995
      return $stringy->substr(-$n);
996
    }
997
998
    return $stringy;
999
  }
1000
1001 10
  /**
1002
   * Splits on newlines and carriage returns, returning an array of Stringy
1003 10
   * objects corresponding to the lines in the string.
1004 10
   *
1005
   * @return static [] An array of Stringy objects
1006 10
   */
1007 10
  public function lines()
1008 8
  {
1009
    $array = preg_split('/[\r\n]{1,2}/u', $this->str);
1010 8
    /** @noinspection CallableInLoopTerminationConditionInspection */
1011 6
    /** @noinspection ForeachInvariantsInspection */
1012 6 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...
1013 6
      $array[$i] = static::create($array[$i], $this->encoding);
1014
    }
1015 6
1016
    return $array;
1017 10
  }
1018
1019
  /**
1020
   * Returns the longest common prefix between the string and $otherStr.
1021
   *
1022
   * @param  string $otherStr Second string for comparison
1023
   *
1024
   * @return static  Object with its $str being the longest common prefix
1025
   */
1026
  public function longestCommonPrefix($otherStr)
1027 10
  {
1028
    $encoding = $this->encoding;
1029 10
    $maxLength = min($this->length(), UTF8::strlen($otherStr, $encoding));
1030 10
1031
    $longestCommonPrefix = '';
1032 10
    for ($i = 0; $i < $maxLength; $i++) {
1033 10
      $char = UTF8::substr($this->str, $i, 1, $encoding);
1034 8
1035
      if ($char == UTF8::substr($otherStr, $i, 1, $encoding)) {
1036 8
        $longestCommonPrefix .= $char;
1037 6
      } else {
1038 6
        break;
1039 6
      }
1040
    }
1041 6
1042
    return static::create($longestCommonPrefix, $encoding);
1043 10
  }
1044
1045
  /**
1046
   * Returns the longest common suffix between the string and $otherStr.
1047
   *
1048
   * @param  string $otherStr Second string for comparison
1049
   *
1050
   * @return static  Object with its $str being the longest common suffix
1051
   */
1052
  public function longestCommonSuffix($otherStr)
1053
  {
1054 10
    $encoding = $this->encoding;
1055
    $maxLength = min($this->length(), UTF8::strlen($otherStr, $encoding));
1056
1057
    $longestCommonSuffix = '';
1058 10
    for ($i = 1; $i <= $maxLength; $i++) {
1059 10
      $char = UTF8::substr($this->str, -$i, 1, $encoding);
1060 10
1061 10
      if ($char == UTF8::substr($otherStr, -$i, 1, $encoding)) {
1062
        $longestCommonSuffix = $char . $longestCommonSuffix;
1063
      } else {
1064 10
        break;
1065 2
      }
1066
    }
1067 2
1068
    return static::create($longestCommonSuffix, $encoding);
1069
  }
1070 8
1071 8
  /**
1072 8
   * Returns the longest common substring between the string and $otherStr.
1073 8
   * In the case of ties, it returns that which occurs first.
1074 8
   *
1075 8
   * @param  string $otherStr Second string for comparison
1076
   *
1077 8
   * @return static  Object with its $str being the longest common substring
1078 8
   */
1079 8
  public function longestCommonSubstring($otherStr)
1080 8
  {
1081
    // Uses dynamic programming to solve
1082 8
    // http://en.wikipedia.org/wiki/Longest_common_substring_problem
1083 8
    $encoding = $this->encoding;
1084 8
    $stringy = static::create($this->str, $encoding);
1085 8
    $strLength = $stringy->length();
1086 8
    $otherLength = UTF8::strlen($otherStr, $encoding);
1087 8
1088 8
    // Return if either string is empty
1089 8
    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...
1090
      $stringy->str = '';
1091 8
1092 8
      return $stringy;
1093
    }
1094 8
1095
    $len = 0;
1096 8
    $end = 0;
1097
    $table = array_fill(
1098
        0, $strLength + 1,
1099
        array_fill(0, $otherLength + 1, 0)
1100
    );
1101
1102
    for ($i = 1; $i <= $strLength; $i++) {
1103
      for ($j = 1; $j <= $otherLength; $j++) {
1104
        $strChar = UTF8::substr($stringy->str, $i - 1, 1, $encoding);
1105
        $otherChar = UTF8::substr($otherStr, $j - 1, 1, $encoding);
1106
1107
        if ($strChar == $otherChar) {
1108 6
          $table[$i][$j] = $table[$i - 1][$j - 1] + 1;
1109
          if ($table[$i][$j] > $len) {
1110
            $len = $table[$i][$j];
1111 6
            $end = $i;
1112 6
          }
1113
        } else {
1114 6
          $table[$i][$j] = 0;
1115 3
        }
1116
      }
1117
    }
1118 3
1119
    $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...
1120
1121
    return $stringy;
1122
  }
1123
1124
  /**
1125
   * Returns whether or not a character exists at an index. Offsets may be
1126
   * negative to count from the last character in the string. Implements
1127
   * part of the ArrayAccess interface.
1128
   *
1129
   * @param  mixed $offset The index to check
1130
   *
1131
   * @return boolean Whether or not the index exists
1132
   */
1133 2
  public function offsetExists($offset)
1134
  {
1135
    // init
1136 2
    $length = $this->length();
1137 2
    $offset = (int)$offset;
1138
1139
    if ($offset >= 0) {
1140 2
      return ($length > $offset);
1141
    }
1142 1
1143 2
    return ($length >= abs($offset));
1144 1
  }
1145
1146
  /**
1147 1
   * Returns the character at the given index. Offsets may be negative to
1148
   * count from the last character in the string. Implements part of the
1149
   * ArrayAccess interface, and throws an OutOfBoundsException if the index
1150
   * does not exist.
1151
   *
1152
   * @param  mixed $offset The index from which to retrieve the char
1153
   *
1154
   * @return string                 The character at the specified index
1155
   * @throws \OutOfBoundsException If the positive or negative offset does
1156
   *                               not exist
1157
   */
1158
  public function offsetGet($offset)
1159 1
  {
1160
    // init
1161
    $offset = (int)$offset;
1162 1
    $length = $this->length();
1163
1164
    if (
1165
        ($offset >= 0 && $length <= $offset)
1166
        ||
1167
        $length < abs($offset)
1168
    ) {
1169
      throw new \OutOfBoundsException('No character exists at the index');
1170
    }
1171
1172
    return UTF8::substr($this->str, $offset, 1, $this->encoding);
1173 1
  }
1174
1175
  /**
1176 1
   * Implements part of the ArrayAccess interface, but throws an exception
1177
   * when called. This maintains the immutability of Stringy objects.
1178
   *
1179
   * @param  mixed $offset The index of the character
1180
   * @param  mixed $value  Value to set
1181
   *
1182
   * @throws \Exception When called
1183
   */
1184
  public function offsetSet($offset, $value)
1185
  {
1186
    // Stringy is immutable, cannot directly set char
1187
    throw new \Exception('Stringy object is immutable, cannot modify char');
1188
  }
1189
1190
  /**
1191
   * Implements part of the ArrayAccess interface, but throws an exception
1192
   * when called. This maintains the immutability of Stringy objects.
1193 13
   *
1194
   * @param  mixed $offset The index of the character
1195 13
   *
1196 1
   * @throws \Exception When called
1197
   */
1198 1
  public function offsetUnset($offset)
1199
  {
1200
    // Don't allow directly modifying the string
1201
    throw new \Exception('Stringy object is immutable, cannot unset char');
1202 12
  }
1203 3
1204 9
  /**
1205 6
   * Pads the string to a given length with $padStr. If length is less than
1206 3
   * or equal to the length of the string, no padding takes places. The
1207 3
   * default string used for padding is a space, and the default type (one of
1208 3
   * 'left', 'right', 'both') is 'right'. Throws an InvalidArgumentException
1209
   * if $padType isn't one of those 3 values.
1210
   *
1211
   * @param  int    $length  Desired string length after padding
1212
   * @param  string $padStr  String used to pad, defaults to space
1213
   * @param  string $padType One of 'left', 'right', 'both'
1214
   *
1215
   * @return static  Object with a padded $str
1216
   * @throws \InvalidArgumentException If $padType isn't one of 'right', 'left' or 'both'
1217
   */
1218
  public function pad($length, $padStr = ' ', $padType = 'right')
1219
  {
1220 10
    if (!in_array($padType, array('left', 'right', 'both'), true)) {
1221
      throw new \InvalidArgumentException(
1222 10
          'Pad expects $padType ' . "to be one of 'left', 'right' or 'both'"
1223
      );
1224
    }
1225
1226
    switch ($padType) {
1227
      case 'left':
1228
        return $this->padLeft($length, $padStr);
1229
      case 'right':
1230
        return $this->padRight($length, $padStr);
1231
      default:
1232
        return $this->padBoth($length, $padStr);
1233
    }
1234
  }
1235 37
1236
  /**
1237 37
   * Returns a new string of a given length such that the beginning of the
1238
   * string is padded. Alias for pad() with a $padType of 'left'.
1239 37
   *
1240
   * @param  int    $length Desired string length after padding
1241 37
   * @param  string $padStr String used to pad, defaults to space
1242 37
   *
1243
   * @return static  String with left padding
1244 37
   */
1245 3
  public function padLeft($length, $padStr = ' ')
1246
  {
1247
    return $this->applyPadding($length - $this->length(), 0, $padStr);
1248 34
  }
1249 34
1250 34
  /**
1251 34
   * Adds the specified amount of left and right padding to the given string.
1252 34
   * The default character used is a space.
1253 34
   *
1254 34
   * @param  int    $left   Length of left padding
1255 34
   * @param  int    $right  Length of right padding
1256 34
   * @param  string $padStr String used to pad
1257
   *
1258 34
   * @return static  String with padding applied
1259 34
   */
1260 34
  protected function applyPadding($left = 0, $right = 0, $padStr = ' ')
1261 34
  {
1262 34
    $stringy = static::create($this->str, $this->encoding);
1263 34
1264 34
    $length = UTF8::strlen($padStr, $stringy->encoding);
1265 34
1266 34
    $strLength = $stringy->length();
1267
    $paddedLength = $strLength + $left + $right;
1268 34
1269
    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...
1270 34
      return $stringy;
1271
    }
1272
1273
    $leftPadding = UTF8::substr(
1274
        UTF8::str_repeat(
1275
            $padStr,
1276
            ceil($left / $length)
1277
        ),
1278
        0,
1279
        $left,
1280
        $stringy->encoding
1281
    );
1282 13
1283
    $rightPadding = UTF8::substr(
1284 13
        UTF8::str_repeat(
1285
            $padStr,
1286
            ceil($right / $length)
1287
        ),
1288
        0,
1289
        $right,
1290
        $stringy->encoding
1291
    );
1292
1293
    $stringy->str = $leftPadding . $stringy->str . $rightPadding;
1294
1295
    return $stringy;
1296 14
  }
1297
1298 14
  /**
1299
   * Returns a new string of a given length such that the end of the string
1300 14
   * is padded. Alias for pad() with a $padType of 'right'.
1301
   *
1302
   * @param  int    $length Desired string length after padding
1303
   * @param  string $padStr String used to pad, defaults to space
1304
   *
1305
   * @return static  String with right padding
1306
   */
1307
  public function padRight($length, $padStr = ' ')
1308
  {
1309
    return $this->applyPadding(0, $length - $this->length(), $padStr);
1310 2
  }
1311
1312 2
  /**
1313
   * Returns a new string of a given length such that both sides of the
1314
   * string are padded. Alias for pad() with a $padType of 'both'.
1315
   *
1316
   * @param  int    $length Desired string length after padding
1317
   * @param  string $padStr String used to pad, defaults to space
1318
   *
1319
   * @return static  String with padding applied
1320
   */
1321
  public function padBoth($length, $padStr = ' ')
1322 12
  {
1323
    $padding = $length - $this->length();
1324 12
1325
    return $this->applyPadding(floor($padding / 2), ceil($padding / 2), $padStr);
1326 12
  }
1327 6
1328
  /**
1329 6
   * Returns a new string starting with $string.
1330
   *
1331
   * @param  string $string The string to append
1332 6
   *
1333
   * @return static  Object with appended $string
1334
   */
1335
  public function prepend($string)
1336
  {
1337
    return static::create($string . $this->str, $this->encoding);
1338
  }
1339
1340
  /**
1341
   * Returns a new string with the prefix $substring removed, if present.
1342 12
   *
1343
   * @param  string $substring The prefix to remove
1344 12
   *
1345
   * @return static  Object having a $str without the prefix $substring
1346 12
   */
1347 8 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...
1348
  {
1349 8
    $stringy = static::create($this->str, $this->encoding);
1350
1351
    if ($stringy->startsWith($substring)) {
1352 4
      $substringLength = UTF8::strlen($substring, $stringy->encoding);
1353
1354
      return $stringy->substr($substringLength);
1355
    }
1356
1357
    return $stringy;
1358
  }
1359
1360
  /**
1361
   * Returns a new string with the suffix $substring removed, if present.
1362 7
   *
1363
   * @param  string $substring The suffix to remove
1364 7
   *
1365
   * @return static  Object having a $str without the suffix $substring
1366 7
   */
1367 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...
1368
  {
1369
    $stringy = static::create($this->str, $this->encoding);
1370
1371
    if ($stringy->endsWith($substring)) {
1372
      $substringLength = UTF8::strlen($substring, $stringy->encoding);
1373
1374
      return $stringy->substr(0, $stringy->length() - $substringLength);
1375
    }
1376
1377
    return $stringy;
1378 28
  }
1379
1380 28
  /**
1381 21
   * Returns a repeated string given a multiplier.
1382 21
   *
1383 7
   * @param  int $multiplier The number of times to repeat the string
1384
   *
1385
   * @return static  Object with a repeated str
1386 28
   */
1387
  public function repeat($multiplier)
1388
  {
1389
    $repeated = UTF8::str_repeat($this->str, $multiplier);
1390
1391
    return static::create($repeated, $this->encoding);
1392
  }
1393
1394
  /**
1395
   * Replaces all occurrences of $search in $str by $replacement.
1396
   *
1397
   * @param string $search        The needle to search for
1398 30
   * @param string $replacement   The string to replace with
1399
   * @param bool   $caseSensitive Whether or not to enforce case-sensitivity
1400 30
   *
1401 23
   * @return static  Object with the resulting $str after the replacements
1402 23
   */
1403 7 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...
1404
  {
1405
    if ($caseSensitive) {
1406 30
      $return = UTF8::str_replace($search, $replacement, $this->str);
1407
    } else {
1408
      $return = UTF8::str_ireplace($search, $replacement, $this->str);
1409
    }
1410
1411
    return static::create($return);
1412
  }
1413
1414
  /**
1415
   * Replaces all occurrences of $search in $str by $replacement.
1416
   *
1417 16
   * @param array        $search        The elements to search for
1418
   * @param string|array $replacement   The string to replace with
1419 16
   * @param bool         $caseSensitive Whether or not to enforce case-sensitivity
1420
   *
1421 16
   * @return static  Object with the resulting $str after the replacements
1422
   */
1423 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...
1424
  {
1425
    if ($caseSensitive) {
1426
      $return = UTF8::str_replace($search, $replacement, $this->str);
1427
    } else {
1428
      $return = UTF8::str_ireplace($search, $replacement, $this->str);
1429
    }
1430
1431
    return static::create($return);
1432 16
  }
1433
1434 16
  /**
1435
   * Replaces all occurrences of $search from the beginning of string with $replacement
1436 16
   *
1437
   * @param string $search
1438
   * @param string $replacement
1439
   *
1440
   * @return static  Object with the resulting $str after the replacements
1441
   */
1442
  public function replaceBeginning($search, $replacement)
1443
  {
1444 5
    $str = $this->regexReplace('^' . preg_quote($search, '/'), UTF8::str_replace('\\', '\\\\', $replacement));
1445
1446 5
    return static::create($str, $this->encoding);
1447
  }
1448 5
1449
  /**
1450
   * Replaces all occurrences of $search from the ending of string with $replacement
1451
   *
1452
   * @param string $search
1453
   * @param string $replacement
1454
   *
1455
   * @return static  Object with the resulting $str after the replacements
1456
   */
1457
  public function replaceEnding($search, $replacement)
1458
  {
1459
    $str = $this->regexReplace(preg_quote($search, '/') . '$', UTF8::str_replace('\\', '\\\\', $replacement));
1460
1461
    return static::create($str, $this->encoding);
1462 23
  }
1463
1464 23
  /**
1465 23
   * Returns a reversed string. A multibyte version of strrev().
1466 4
   *
1467
   * @return static  Object with a reversed $str
1468
   */
1469
  public function reverse()
1470 19
  {
1471 19
    $reversed = UTF8::strrev($this->str);
1472 19
1473
    return static::create($reversed, $this->encoding);
1474 19
  }
1475
1476
  /**
1477 19
   * Truncates the string to a given length, while ensuring that it does not
1478 19
   * split words. If $substring is provided, and truncating occurs, the
1479
   * string is further truncated so that the substring may be appended without
1480 12
   * exceeding the desired length.
1481
   *
1482 12
   * @param  int    $length    Desired length of the truncated string
1483 11
   * @param  string $substring The substring to append if it can fit
1484 11
   *
1485 12
   * @return static  Object with the resulting $str after truncating
1486
   */
1487 19
  public function safeTruncate($length, $substring = '')
1488
  {
1489 19
    $stringy = static::create($this->str, $this->encoding);
1490
    if ($length >= $stringy->length()) {
1491
      return $stringy;
1492
    }
1493
1494
    // Need to further trim the string so we can append the substring
1495
    $encoding = $stringy->encoding;
1496
    $substringLength = UTF8::strlen($substring, $encoding);
1497
    $length -= $substringLength;
1498 3
1499
    $truncated = UTF8::substr($stringy->str, 0, $length, $encoding);
1500 3
1501
    // If the last word was truncated
1502 3
    $strPosSpace = UTF8::strpos($stringy->str, ' ', $length - 1, $encoding);
1503
    if ($strPosSpace != $length) {
1504
      // Find pos of the last occurrence of a space, get up to that
1505
      $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 1499 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...
1506
1507
      if ($lastPos !== false || $strPosSpace !== false) {
1508
        $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 1508 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 1505 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...
1509
      }
1510
    }
1511
1512
    $stringy->str = $truncated . $substring;
1513
1514
    return $stringy;
1515
  }
1516
1517
  /**
1518 15
   * A multibyte string shuffle function. It returns a string with its
1519
   * characters in random order.
1520 15
   *
1521
   * @return static  Object with a shuffled $str
1522 15
   */
1523
  public function shuffle()
1524
  {
1525
    $shuffledStr = UTF8::str_shuffle($this->str);
1526
1527
    return static::create($shuffledStr, $this->encoding);
1528
  }
1529
1530 1
  /**
1531
   * Converts the string into an URL slug. This includes replacing non-ASCII
1532 1
   * characters with their closest ASCII equivalents, removing remaining
1533
   * non-ASCII and non-alphanumeric characters, and replacing whitespace with
1534 1
   * $replacement. The replacement defaults to a single dash, and the string
1535
   * is also converted to lowercase.
1536
   *
1537
   * @param string $replacement The string used to replace whitespace
1538
   * @param string $language    The language for the url
1539
   * @param bool   $strToLower  string to lower
1540
   *
1541
   * @return static  Object whose $str has been converted to an URL slug
1542
   */
1543
  public function slugify($replacement = '-', $language = 'de', $strToLower = true)
1544 1
  {
1545
    $slug = URLify::slug($this->str, $language, $replacement, $strToLower);
1546 1
1547
    return static::create($slug, $this->encoding);
1548 1
  }
1549
1550
  /**
1551
   * Remove css media-queries.
1552
   *
1553
   * @return static 
1554
   */
1555
  public function stripeCssMediaQueries()
1556 1
  {
1557
    $pattern = '#@media\\s+(?:only\\s)?(?:[\\s{\\(]|screen|all)\\s?[^{]+{.*}\\s*}\\s*#misU';
1558 1
1559
    return static::create(preg_replace($pattern, '', $this->str));
1560
  }
1561
1562
  /**
1563
   * Strip all whitespace characters. This includes tabs and newline characters,
1564
   * as well as multibyte whitespace such as the thin space and ideographic space.
1565
   *
1566 6
   * @return Stringy
1567
   */
1568 6
  public function stripWhitespace()
1569 6
  {
1570 6
    return static::create(UTF8::strip_whitespace($this->str));
1571 6
  }
1572 6
1573
  /**
1574 6
   * Remove empty html-tag.
1575
   *
1576
   * e.g.: <tag></tag>
1577
   *
1578
   * @return static 
1579
   */
1580
  public function stripeEmptyHtmlTags()
1581
  {
1582
    $pattern = "/<[^\/>]*>(([\s]?)*|)<\/[^>]*>/i";
1583
1584
    return static::create(preg_replace($pattern, '', $this->str));
1585
  }
1586 1
1587
  /**
1588
   * Converts the string into an valid UTF-8 string.
1589 1
   *
1590
   * @return static 
1591 1
   */
1592 1
  public function utf8ify()
1593
  {
1594
    return static::create(UTF8::cleanup($this->str));
1595 1
  }
1596
1597 1
  /**
1598 1
   * escape html
1599 1
   *
1600
   * @return static 
1601 1
   */
1602
  public function escape()
1603
  {
1604
    $str = UTF8::htmlspecialchars(
1605
        $this->str,
1606
        ENT_QUOTES | ENT_SUBSTITUTE,
1607
        $this->encoding
1608
    );
1609
1610
    return static::create($str, $this->encoding);
1611
  }
1612
1613
  /**
1614
   * Create an extract from a text, so if the search-string was found, it will be centered in the output.
1615
   *
1616
   * @param string   $search
1617
   * @param int|null $length
1618
   * @param string   $ellipsis
1619
   *
1620
   * @return static 
1621 1
   */
1622 1
  public function extractText($search = '', $length = null, $ellipsis = '...')
1623 1
  {
1624 1
    // init
1625 1
    $text = $this->str;
1626 1
1627 1
    if (empty($text)) {
1628
      return static::create('', $this->encoding);
1629 1
    }
1630
1631 1
    $trimChars = "\t\r\n -_()!~?=+/*\\,.:;\"'[]{}`&";
1632 1
1633
    if ($length === null) {
1634 1
      $length = (int)round($this->length() / 2, 0);
1635 1
    }
1636 1
1637
    if (empty($search)) {
1638 1
1639 1
      $stringLength = UTF8::strlen($text, $this->encoding);
1640
      $end = ($length - 1) > $stringLength ? $stringLength : ($length - 1);
1641
      $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...
1642 1
1643 1
      if ($pos) {
1644 1
        return static::create(
1645
            rtrim(
1646 1
                UTF8::substr($text, 0, $pos, $this->encoding),
1647
                $trimChars
1648
            ) . $ellipsis,
1649
            $this->encoding
1650 1
        );
1651 1
      } else {
1652 1
        return static::create($text, $this->encoding);
1653 1
      }
1654
1655 1
    }
1656 1
1657 1
    $wordPos = UTF8::strpos(
1658 1
        UTF8::strtolower($text),
1659 1
        UTF8::strtolower($search, $this->encoding),
1660 1
        null,
1661 1
        $this->encoding
1662 1
    );
1663
    $halfSide = (int)($wordPos - $length / 2 + UTF8::strlen($search, $this->encoding) / 2);
1664 1
1665 1
    if ($halfSide > 0) {
1666 1
1667 1
      $halfText = UTF8::substr($text, 0, $halfSide, $this->encoding);
1668 1
      $pos_start = max(UTF8::strrpos($halfText, ' ', 0), UTF8::strrpos($halfText, '.', 0));
0 ignored issues
show
Security Bug introduced by
It seems like $halfText defined by \voku\helper\UTF8::subst...fSide, $this->encoding) on line 1667 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...
1669 1
1670 1
      if (!$pos_start) {
1671 1
        $pos_start = 0;
1672 1
      }
1673
1674 1
    } else {
1675
      $pos_start = 0;
1676
    }
1677 1
1678
    if ($wordPos && $halfSide > 0) {
1679 1
      $l = $pos_start + $length - 1;
1680 1
      $realLength = UTF8::strlen($text, $this->encoding);
1681
1682 1
      if ($l > $realLength) {
1683
        $l = $realLength;
1684
      }
1685
1686 1
      $pos_end = min(
1687 1
                     UTF8::strpos($text, ' ', $l, $this->encoding),
1688 1
                     UTF8::strpos($text, '.', $l, $this->encoding)
1689 1
                 ) - $pos_start;
1690
1691 1
      if (!$pos_end || $pos_end <= 0) {
1692 1
        $extract = $ellipsis . ltrim(
1693 1
                UTF8::substr(
1694
                    $text,
1695 1
                    $pos_start,
1696 1
                    UTF8::strlen($text),
1697 1
                    $this->encoding
1698
                ),
1699
                $trimChars
1700
            );
1701 1 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...
1702
        $extract = $ellipsis . trim(
1703
                UTF8::substr(
1704
                    $text,
1705
                    $pos_start,
1706
                    $pos_end,
1707
                    $this->encoding
1708
                ),
1709
                $trimChars
1710 6
            ) . $ellipsis;
1711
      }
1712 6
1713
    } else {
1714 6
1715 1
      $l = $length - 1;
1716 1
      $trueLength = UTF8::strlen($text, $this->encoding);
1717
1718 6
      if ($l > $trueLength) {
1719
        $l = $trueLength;
1720 6
      }
1721
1722
      $pos_end = min(
1723
          UTF8::strpos($text, ' ', $l, $this->encoding),
1724
          UTF8::strpos($text, '.', $l, $this->encoding)
1725
      );
1726
1727 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...
1728
        $extract = rtrim(
1729
                       UTF8::substr($text, 0, $pos_end, $this->encoding),
1730 6
                       $trimChars
1731
                   ) . $ellipsis;
1732 6
      } else {
1733
        $extract = $text;
1734 6
      }
1735
    }
1736
1737
    return static::create($extract, $this->encoding);
1738
  }
1739
1740
1741
  /**
1742
   * remove xss from html
1743
   *
1744 6
   * @return static 
1745
   */
1746 6
  public function removeXss()
1747
  {
1748 6
    static $antiXss = null;
1749
1750
    if ($antiXss === null) {
1751
      $antiXss = new AntiXSS();
1752
    }
1753
1754
    $str = $antiXss->xss_clean($this->str);
1755
1756
    return static::create($str, $this->encoding);
1757
  }
1758
1759
  /**
1760
   * remove html-break [br | \r\n | \r | \n | ...]
1761
   *
1762 18
   * @param string $replacement
1763
   *
1764 18
   * @return static 
1765 4
   */
1766 18
  public function removeHtmlBreak($replacement = '')
1767 4
  {
1768 10
    $str = preg_replace('#/\r\n|\r|\n|<br.*/?>#isU', $replacement, $this->str);
1769 2
1770 2
    return static::create($str, $this->encoding);
1771 8
  }
1772
1773
  /**
1774 14
   * remove html
1775
   *
1776 14
   * @param $allowableTags
1777
   *
1778
   * @return static 
1779
   */
1780
  public function removeHtml($allowableTags = null)
1781
  {
1782
    $str = strip_tags($this->str, $allowableTags);
1783
1784
    return static::create($str, $this->encoding);
1785
  }
1786
1787
  /**
1788
   * Returns the substring beginning at $start, and up to, but not including
1789 19
   * the index specified by $end. If $end is omitted, the function extracts
1790
   * the remaining string. If $end is negative, it is computed from the end
1791 19
   * of the string.
1792 2
   *
1793
   * @param  int $start Initial index from which to begin extraction
1794
   * @param  int $end   Optional index at which to end extraction
1795
   *
1796
   * @return static  Object with its $str being the extracted substring
1797 17
   */
1798 1
  public function slice($start, $end = null)
1799
  {
1800
    if ($end === null) {
1801
      $length = $this->length();
1802
    } elseif ($end >= 0 && $end <= $start) {
1803 16
      return static::create('', $this->encoding);
1804 8
    } elseif ($end < 0) {
1805 8
      $length = $this->length() + $end - $start;
1806 8
    } else {
1807
      $length = $end - $start;
1808
    }
1809 16
1810
    $str = UTF8::substr($this->str, $start, $length, $this->encoding);
1811 16
1812 4
    return static::create($str, $this->encoding);
1813 4
  }
1814
1815
  /**
1816
   * Splits the string with the provided regular expression, returning an
1817 16
   * array of Stringy objects. An optional integer $limit will truncate the
1818 16
   * results.
1819 16
   *
1820
   * @param  string $pattern The regex with which to split the string
1821 16
   * @param  int    $limit   Optional maximum number of results to return
1822
   *
1823
   * @return static [] An array of Stringy objects
1824
   */
1825
  public function split($pattern, $limit = null)
1826
  {
1827
    if ($limit === 0) {
1828
      return array();
1829
    }
1830
1831
    // UTF8::split errors when supplied an empty pattern in < PHP 5.4.13
1832 5
    // and current versions of HHVM (3.8 and below)
1833
    if ($pattern === '') {
1834 5
      return array(static::create($this->str, $this->encoding));
1835
    }
1836 5
1837
    // UTF8::split returns the remaining unsplit string in the last index when
1838
    // supplying a limit
1839
    if ($limit > 0) {
1840
      $limit += 1;
1841
    } else {
1842
      $limit = -1;
1843
    }
1844 5
1845
    $array = preg_split('/' . preg_quote($pattern, '/') . '/u', $this->str, $limit);
1846 5
1847
    if ($limit > 0 && count($array) === $limit) {
1848 5
      array_pop($array);
1849
    }
1850 5
1851
    /** @noinspection CallableInLoopTerminationConditionInspection */
1852
    /** @noinspection ForeachInvariantsInspection */
1853 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...
1854
      $array[$i] = static::create($array[$i], $this->encoding);
1855
    }
1856
1857
    return $array;
1858
  }
1859
1860 4
  /**
1861
   * Surrounds $str with the given substring.
1862 4
   *
1863
   * @param  string $substring The substring to add to both sides
1864 4
   *
1865
   * @return static  Object whose $str had the substring both prepended and
1866
   *                 appended
1867
   */
1868
  public function surround($substring)
1869
  {
1870
    $str = implode('', array($substring, $this->str, $substring));
1871
1872
    return static::create($str, $this->encoding);
1873
  }
1874
1875
  /**
1876 5
   * Returns a case swapped version of the string.
1877
   *
1878 5
   * @return static  Object whose $str has each character's case swapped
1879 5
   */
1880
  public function swapCase()
1881 5
  {
1882 5
    $stringy = static::create($this->str, $this->encoding);
1883
1884 5
    $stringy->str = UTF8::swapCase($stringy->str, $stringy->encoding);
1885 2
1886
    return $stringy;
1887 5
  }
1888
1889 5
  /**
1890
   * Returns a string with smart quotes, ellipsis characters, and dashes from
1891 5
   * Windows-1252 (commonly used in Word documents) replaced by their ASCII
1892 5
   * equivalents.
1893 5
   *
1894
   * @return static  Object whose $str has those characters removed
1895 5
   */
1896
  public function tidy()
1897
  {
1898
    $str = UTF8::normalize_msword($this->str);
1899
1900
    return static::create($str, $this->encoding);
1901
  }
1902
1903
  /**
1904 27
   * Returns a trimmed string with the first letter of each word capitalized.
1905
   * Also accepts an array, $ignore, allowing you to list words not to be
1906 27
   * capitalized.
1907
   *
1908 27
   * @param  array $ignore An array of words not to capitalize
1909
   *
1910
   * @return static  Object with a titleized $str
1911
   */
1912
  public function titleize($ignore = null)
1913
  {
1914
    $stringy = static::create($this->trim(), $this->encoding);
1915
    $encoding = $this->encoding;
1916 7
1917
    $stringy->str = preg_replace_callback(
1918 7
        '/([\S]+)/u',
1919
        function ($match) use ($encoding, $ignore) {
1920
          if ($ignore && in_array($match[0], $ignore, true)) {
1921
            return $match[0];
1922
          } else {
1923
            $stringy = new Stringy($match[0], $encoding);
1924
1925
            return (string)$stringy->toLowerCase()->upperCaseFirst();
1926
          }
1927
        },
1928
        $stringy->str
1929
    );
1930 16
1931
    return $stringy;
1932 16
  }
1933
1934 16
  /**
1935
   * Converts all characters in the string to lowercase. An alias for PHP's
1936
   * UTF8::strtolower().
1937
   *
1938
   * @return static  Object with all characters of $str being lowercase
1939
   */
1940
  public function toLowerCase()
1941
  {
1942
    $str = UTF8::strtolower($this->str, $this->encoding);
1943
1944
    return static::create($str, $this->encoding);
1945
  }
1946
1947
  /**
1948 15
   * Returns true if the string is base64 encoded, false otherwise.
1949
   *
1950 15
   * @return bool Whether or not $str is base64 encoded
1951
   */
1952 15
  public function isBase64()
1953 15
  {
1954 15
    return UTF8::is_base64($this->str);
1955 15
  }
1956 15
1957 15
  /**
1958 15
   * Returns an ASCII version of the string. A set of non-ASCII characters are
1959 15
   * replaced with their closest ASCII counterparts, and the rest are removed
1960 15
   * unless instructed otherwise.
1961
   *
1962 15
   * @param $strict [optional] <p>Use "transliterator_transliterate()" from PHP-Intl | WARNING: bad performance</p>
1963 10
   *
1964 5
   * @return static  Object whose $str contains only ASCII characters
1965 2
   */
1966
  public function toAscii($strict = false)
1967 3
  {
1968
    $str = UTF8::to_ascii($this->str, '?', $strict);
1969
1970
    return static::create($str, $this->encoding);
1971
  }
1972
1973
  /**
1974
   * Returns a boolean representation of the given logical string value.
1975
   * For example, 'true', '1', 'on' and 'yes' will return true. 'false', '0',
1976 993
   * 'off', and 'no' will return false. In all instances, case is ignored.
1977
   * For other numeric strings, their sign will determine the return value.
1978 993
   * In addition, blank strings consisting of only whitespace will return
1979
   * false. For all other strings, the return value is a result of a
1980
   * boolean cast.
1981
   *
1982
   * @return bool A boolean value for the string
1983
   */
1984
  public function toBoolean()
1985
  {
1986
    $key = $this->toLowerCase()->str;
1987
    $map = array(
1988
        'true'  => true,
1989 6
        '1'     => true,
1990
        'on'    => true,
1991 6
        'yes'   => true,
1992 6
        'false' => false,
1993
        '0'     => false,
1994 6
        'off'   => false,
1995
        'no'    => false,
1996
    );
1997
1998
    if (array_key_exists($key, $map)) {
1999
      return $map[$key];
2000
    } elseif (is_numeric($this->str)) {
2001
      return ((int)$this->str > 0);
2002
    } else {
2003
      return (bool)$this->regexReplace('[[:space:]]', '')->str;
2004
    }
2005
  }
2006 5
2007
  /**
2008 5
   * Return Stringy object as string, but you can also use (string) for automatically casting the object into a string.
2009 5
   *
2010
   * @return string
2011 5
   */
2012
  public function toString()
2013
  {
2014
    return (string)$this->str;
2015
  }
2016
2017
  /**
2018
   * Converts each tab in the string to some number of spaces, as defined by
2019 5
   * $tabLength. By default, each tab is converted to 4 consecutive spaces.
2020
   *
2021
   * @param  int $tabLength Number of spaces to replace each tab with
2022 5
   *
2023
   * @return static  Object whose $str has had tabs switched to spaces
2024 5
   */
2025 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...
2026
  {
2027
    $spaces = UTF8::str_repeat(' ', $tabLength);
2028
    $str = UTF8::str_replace("\t", $spaces, $this->str);
2029
2030
    return static::create($str, $this->encoding);
2031
  }
2032
2033 5
  /**
2034
   * Converts each occurrence of some consecutive number of spaces, as
2035 5
   * defined by $tabLength, to a tab. By default, each 4 consecutive spaces
2036
   * are converted to a tab.
2037 5
   *
2038
   * @param  int $tabLength Number of spaces to replace with a tab
2039
   *
2040
   * @return static  Object whose $str has had spaces switched to tabs
2041
   */
2042 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...
2043
  {
2044
    $spaces = UTF8::str_repeat(' ', $tabLength);
2045
    $str = UTF8::str_replace($spaces, "\t", $this->str);
2046
2047
    return static::create($str, $this->encoding);
2048
  }
2049 13
2050
  /**
2051 13
   * Converts the first character of each word in the string to uppercase.
2052 11
   *
2053 11
   * @return static  Object with all characters of $str being title-cased
2054 2
   */
2055
  public function toTitleCase()
2056
  {
2057 13
    // "mb_convert_case()" used a polyfill from the "UTF8"-Class
2058
    $str = mb_convert_case($this->str, MB_CASE_TITLE, $this->encoding);
2059
2060
    return static::create($str, $this->encoding);
2061
  }
2062
2063
  /**
2064
   * Converts all characters in the string to uppercase. An alias for PHP's
2065
   * UTF8::strtoupper().
2066
   *
2067
   * @return static  Object with all characters of $str being uppercase
2068
   */
2069 13
  public function toUpperCase()
2070
  {
2071 13
    $str = UTF8::strtoupper($this->str, $this->encoding);
2072 11
2073 11
    return static::create($str, $this->encoding);
2074 2
  }
2075
2076
  /**
2077 13
   * Returns a string with whitespace removed from the start of the string.
2078
   * Supports the removal of unicode whitespace. Accepts an optional
2079
   * string of characters to strip instead of the defaults.
2080
   *
2081
   * @param  string $chars Optional string of characters to strip
2082
   *
2083
   * @return static  Object with a trimmed $str
2084
   */
2085 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...
2086
  {
2087
    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...
2088
      $chars = '[:space:]';
2089
    } else {
2090 22
      $chars = preg_quote($chars, '/');
2091
    }
2092 22
2093 22
    return $this->regexReplace("^[$chars]+", '');
2094 4
  }
2095
2096
  /**
2097
   * Returns a string with whitespace removed from the end of the string.
2098 18
   * Supports the removal of unicode whitespace. Accepts an optional
2099 18
   * string of characters to strip instead of the defaults.
2100
   *
2101 18
   * @param  string $chars Optional string of characters to strip
2102 18
   *
2103
   * @return static  Object with a trimmed $str
2104 18
   */
2105 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...
2106
  {
2107
    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...
2108
      $chars = '[:space:]';
2109
    } else {
2110
      $chars = preg_quote($chars, '/');
2111
    }
2112
2113
    return $this->regexReplace("[$chars]+\$", '');
2114
  }
2115 16
2116
  /**
2117 16
   * Truncates the string to a given length. If $substring is provided, and
2118
   * truncating occurs, the string is further truncated so that the substring
2119
   * may be appended without exceeding the desired length.
2120
   *
2121
   * @param  int    $length    Desired length of the truncated string
2122
   * @param  string $substring The substring to append if it can fit
2123
   *
2124
   * @return static  Object with the resulting $str after truncating
2125
   */
2126 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...
2127 13
  {
2128
    $stringy = static::create($this->str, $this->encoding);
2129 13
    if ($length >= $stringy->length()) {
2130
      return $stringy;
2131
    }
2132
2133
    // Need to further trim the string so we can append the substring
2134
    $substringLength = UTF8::strlen($substring, $stringy->encoding);
2135
    $length -= $substringLength;
2136
2137
    $truncated = UTF8::substr($stringy->str, 0, $length, $stringy->encoding);
2138
    $stringy->str = $truncated . $substring;
2139 32
2140
    return $stringy;
2141 32
  }
2142 32
2143 32
  /**
2144
   * Returns a lowercase and trimmed string separated by underscores.
2145 32
   * Underscores are inserted before uppercase characters (with the exception
2146 32
   * of the first character of the string), and in place of spaces as well as
2147
   * dashes.
2148 27
   *
2149 27
   * @return static  Object with an underscored $str
2150
   */
2151 1
  public function underscored()
2152
  {
2153 32
    return $this->delimit('_');
2154 32
  }
2155 32
2156
  /**
2157 32
   * Returns an UpperCamelCase version of the supplied string. It trims
2158 32
   * surrounding spaces, capitalizes letters following digits, spaces, dashes
2159
   * and underscores, and removes spaces, dashes, underscores.
2160 6
   *
2161 32
   * @return static  Object with $str in UpperCamelCase
2162 32
   */
2163 32
  public function upperCamelize()
2164
  {
2165 32
    return $this->camelize()->upperCaseFirst();
2166
  }
2167
2168
  /**
2169
   * Returns a camelCase version of the string. Trims surrounding spaces,
2170
   * capitalizes letters following digits, spaces, dashes and underscores,
2171
   * and removes spaces, dashes, as well as underscores.
2172
   *
2173 20
   * @return static  Object with $str in camelCase
2174
   */
2175 20
  public function camelize()
2176
  {
2177 20
    $encoding = $this->encoding;
2178 20
    $stringy = $this->trim()->lowerCaseFirst();
2179 20
    $stringy->str = preg_replace('/^[-_]+/', '', $stringy->str);
2180
2181 20
    $stringy->str = preg_replace_callback(
2182 20
        '/[-_\s]+(.)?/u',
2183 20
        function ($match) use ($encoding) {
2184 8
          if (isset($match[1])) {
2185 8
            return UTF8::strtoupper($match[1], $encoding);
2186
          } else {
2187 8
            return '';
2188 4
          }
2189
        },
2190 4
        $stringy->str
2191
    );
2192 20
2193
    $stringy->str = preg_replace_callback(
2194 20
        '/[\d]+(.)?/u',
2195
        function ($match) use ($encoding) {
2196 20
          return UTF8::strtoupper($match[0], $encoding);
2197
        },
2198
        $stringy->str
2199 20
    );
2200 20
2201 20
    return $stringy;
2202 20
  }
2203
2204 20
  /**
2205 20
   * Convert a string to e.g.: "snake_case"
2206 20
   *
2207 20
   * @return static  Object with $str in snake_case
2208
   */
2209 20
  public function snakeize()
2210
  {
2211 20
    $str = $this->str;
2212 20
2213
    $encoding = $this->encoding;
2214 20
    $str = UTF8::normalize_whitespace($str);
2215
    $str = str_replace('-', '_', $str);
2216
2217
    $str = preg_replace_callback(
2218
        '/([\d|A-Z])/u',
2219
        function ($matches) use ($encoding) {
2220
          $match = $matches[1];
2221
          $matchInt = (int)$match;
2222 37
2223
          if ("$matchInt" == $match) {
2224 37
            return '_' . $match . '_';
2225 37
          } else {
2226
            return '_' . UTF8::strtolower($match, $encoding);
2227 37
          }
2228
        },
2229 37
        $str
2230
    );
2231
2232
    $str = preg_replace(
2233
        array(
2234
2235
            '/\s+/',      // convert spaces to "_"
2236
            '/^\s+|\s+$/',  // trim leading & trailing spaces
2237
            '/_+/',         // remove double "_"
2238
        ),
2239
        array(
2240 4
            '_',
2241
            '',
2242 4
            '_',
2243
        ),
2244 4
        $str
2245
    );
2246
2247
    $str = UTF8::trim($str, '_'); // trim leading & trailing "_"
2248
    $str = UTF8::trim($str); // trim leading & trailing whitespace
2249
2250
    return static::create($str, $this->encoding);
2251
  }
2252
2253
  /**
2254 1
   * Converts the first character of the string to lower case.
2255
   *
2256 1
   * @return static  Object with the first character of $str being lower case
2257
   */
2258 1 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...
2259 1
  {
2260 1
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
2261 1
    $rest = UTF8::substr($this->str, 1, $this->length() - 1, $this->encoding);
2262 1
2263
    $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 2260 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...
2264 1
2265
    return static::create($str, $this->encoding);
2266
  }
2267
2268
  /**
2269
   * Shorten the string after $length, but also after the next word.
2270
   *
2271
   * @param int    $length
2272
   * @param string $strAddOn
2273
   *
2274
   * @return static 
2275 1
   */
2276
  public function shortenAfterWord($length, $strAddOn = '...')
2277 1
  {
2278 1
    $string = UTF8::str_limit_after_word($this->str, $length, $strAddOn);
2279
2280
    return static::create($string);
2281 1
  }
2282 1
2283 1
  /**
2284 1
   * Line-Wrap the string after $limit, but also after the next word.
2285 1
   *
2286 1
   * @param int $limit
2287 1
   *
2288 1
   * @return static 
2289 1
   */
2290
  public function lineWrapAfterWord($limit)
2291
  {
2292
    $strings = preg_split('/\\r\\n|\\r|\\n/', $this->str);
2293
2294
    $string = '';
2295
    foreach ($strings as $value) {
2296
      $string .= wordwrap($value, $limit);
2297
      $string .= "\n";
2298
    }
2299
2300 1
    return static::create($string);
2301
  }
2302 1
2303 1
  /**
2304 1
   * Gets the substring after the first occurrence of a separator.
2305
   * If no match is found returns new empty Stringy object.
2306
   *
2307 1
   * @param string $separator
2308 1
   *
2309 1
   * @return static 
2310 1
   */
2311 1 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...
2312 1
  {
2313 1
    if (($offset = $this->indexOf($separator)) === false) {
2314 1
      return static::create('');
2315 1
    }
2316
2317
    return static::create(
2318
        UTF8::substr(
2319
            $this->str,
2320
            $offset + UTF8::strlen($separator, $this->encoding),
2321
            null,
2322
            $this->encoding
2323
        ),
2324
        $this->encoding
2325
    );
2326 1
  }
2327
2328 1
  /**
2329 1
   * Gets the substring after the last occurrence of a separator.
2330 1
   * If no match is found returns new empty Stringy object.
2331
   *
2332
   * @param string $separator
2333 1
   *
2334 1
   * @return static 
2335 1
   */
2336 1
  public function afterLast($separator)
2337 1
  {
2338 1
    $offset = $this->indexOfLast($separator);
2339 1
    if ($offset === false) {
2340 1
      return static::create('', $this->encoding);
2341 1
    }
2342
2343
    return static::create(
2344
        UTF8::substr(
2345
            $this->str,
2346
            $offset + UTF8::strlen($separator, $this->encoding),
2347
            null,
2348
            $this->encoding
2349
        ),
2350
        $this->encoding
2351
    );
2352 1
  }
2353
2354 1
  /**
2355 1
   * Gets the substring before the first occurrence of a separator.
2356 1
   * If no match is found returns new empty Stringy object.
2357
   *
2358
   * @param string $separator
2359 1
   *
2360 1
   * @return static 
2361 1
   */
2362 1 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...
2363 1
  {
2364 1
    $offset = $this->indexOf($separator);
2365 1
    if ($offset === false) {
2366 1
      return static::create('', $this->encoding);
2367 1
    }
2368
2369
    return static::create(
2370
        UTF8::substr(
2371
            $this->str,
2372
            0,
2373
            $offset,
2374
            $this->encoding
2375
        ),
2376 39
        $this->encoding
2377
    );
2378 39
  }
2379 39
2380 39
  /**
2381
   * Gets the substring before the last occurrence of a separator.
2382 39
   * If no match is found returns new empty Stringy object.
2383
   *
2384
   * @param string $separator
2385
   *
2386
   * @return static 
2387
   */
2388 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...
2389
  {
2390 7
    $offset = $this->indexOfLast($separator);
2391
    if ($offset === false) {
2392 7
      return static::create('', $this->encoding);
2393
    }
2394 7
2395 7
    return static::create(
2396 7
        UTF8::substr(
2397
            $this->str,
2398 7
            0,
2399
            $offset,
2400
            $this->encoding
2401
        ),
2402
        $this->encoding
2403
    );
2404
  }
2405
2406
  /**
2407
   * Returns the string with the first letter of each word capitalized,
2408
   * except for when the word is a name which shouldn't be capitalized.
2409 39
   *
2410
   * @return static  Object with $str capitalized
2411
   */
2412 39
  public function capitalizePersonalName()
2413 39
  {
2414
    $stringy = $this->collapseWhitespace();
2415
    $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...
2416
    $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...
2417 39
2418 39
    return static::create($stringy, $this->encoding);
2419 39
  }
2420 39
2421 39
  /**
2422 39
   * @param string $word
2423 39
   *
2424 39
   * @return string
2425 39
   */
2426 39
  protected function capitalizeWord($word)
2427 39
  {
2428 39
    $encoding = $this->encoding;
2429 39
2430 39
    $firstCharacter = UTF8::substr($word, 0, 1, $encoding);
2431 39
    $restOfWord = UTF8::substr($word, 1, null, $encoding);
2432 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 2430 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...
2433 39
2434 39
    return new static($firstCharacterUppercased . $restOfWord, $encoding);
2435 39
  }
2436 39
2437 39
  /**
2438 39
   * Personal names such as "Marcus Aurelius" are sometimes typed incorrectly using lowercase ("marcus aurelius").
2439 39
   *
2440 39
   * @param string $names
2441 39
   * @param string $delimiter
2442 39
   *
2443 39
   * @return string
2444
   */
2445 39
  protected function capitalizePersonalNameByDelimiter($names, $delimiter)
2446 39
  {
2447 39
    // init
2448 39
    $names = explode((string)$delimiter, (string)$names);
2449 39
    $encoding = $this->encoding;
2450 39
2451 39
    $specialCases = array(
2452 39
        'names'    => array(
2453 39
            'ab',
2454
            'af',
2455 39
            'al',
2456 39
            'and',
2457 27
            'ap',
2458
            'bint',
2459
            'binte',
2460 13
            'da',
2461
            'de',
2462 13
            'del',
2463 13
            'den',
2464 13
            'der',
2465 2
            'di',
2466 2
            'dit',
2467 13
            'ibn',
2468 13
            'la',
2469
            'mac',
2470 13
            'nic',
2471 13
            'of',
2472 7
            'ter',
2473 7
            'the',
2474 13
            'und',
2475
            'van',
2476 13
            'von',
2477 7
            'y',
2478
            'zu',
2479
        ),
2480 7
        'prefixes' => array(
2481 39
            'al-',
2482
            "d'",
2483 39
            'ff',
2484
            "l'",
2485
            'mac',
2486
            'mc',
2487
            'nic',
2488
        ),
2489
    );
2490
2491
    foreach ($names as &$name) {
2492
      if (in_array($name, $specialCases['names'], true)) {
2493
        continue;
2494
      }
2495
2496
      $continue = false;
2497
2498
      if ($delimiter == '-') {
2499 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...
2500
          if (UTF8::strpos($name, $beginning, null, $encoding) === 0) {
2501
            $continue = true;
2502
          }
2503
        }
2504
      }
2505
2506 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...
2507
        if (UTF8::strpos($name, $beginning, null, $encoding) === 0) {
2508
          $continue = true;
2509
        }
2510
      }
2511
2512
      if ($continue) {
2513
        continue;
2514
      }
2515
2516
      $name = $this->capitalizeWord($name);
2517
    }
2518
2519
    return new static(implode($delimiter, $names), $encoding);
2520
  }
2521
}
2522