Completed
Push — master ( 5a1e5b...db6490 )
by Lars
02:50
created

Stringy::__construct()   C

Complexity

Conditions 7
Paths 10

Size

Total Lines 35
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 7.0368

Importance

Changes 0
Metric Value
cc 7
eloc 20
nc 10
nop 2
dl 0
loc 35
ccs 20
cts 22
cp 0.9091
crap 7.0368
rs 6.7272
c 0
b 0
f 0
1
<?php
2
3
namespace Stringy;
4
5
use voku\helper\AntiXSS;
6
use voku\helper\EmailCheck;
7
use voku\helper\URLify;
8
use voku\helper\UTF8;
9
10
/**
11
 * Class Stringy
12
 *
13
 * @package Stringy
14
 */
15
class Stringy implements \Countable, \IteratorAggregate, \ArrayAccess
16
{
17
  /**
18
   * An instance's string.
19
   *
20
   * @var string
21
   */
22
  protected $str;
23
24
  /**
25
   * The string's encoding, which should be one of the mbstring module's
26
   * supported encodings.
27
   *
28
   * @var string
29
   */
30
  protected $encoding;
31
32
  /**
33
   * Initializes a Stringy object and assigns both str and encoding properties
34
   * the supplied values. $str is cast to a string prior to assignment, and if
35
   * $encoding is not specified, it defaults to mb_internal_encoding(). Throws
36
   * an InvalidArgumentException if the first argument is an array or object
37
   * without a __toString method.
38
   *
39
   * @param  mixed  $str      Value to modify, after being cast to string
40
   * @param  string $encoding The character encoding
41
   *
42
   * @throws \InvalidArgumentException if an array or object without a
43
   *         __toString method is passed as the first argument
44
   */
45 1086
  public function __construct($str = '', $encoding = null)
46
  {
47 1086
    if (is_array($str)) {
48 1
      throw new \InvalidArgumentException(
49
          'Passed value cannot be an array'
50 1
      );
51 1085
    } 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 Stringy 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 Stringy 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 Stringy 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 Stringy 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 Stringy 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 Stringy 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 Stringy 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 Stringy 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 Stringy 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 Stringy 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 Stringy 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 Stringy 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 Stringy 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 $substrings, false otherwise. By
496
   * 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
  public function startsWithAny(array $substrings, $caseSensitive = true)
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 Stringy 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 the first $n characters of the string.
570
   *
571
   * @param  int $n Number of characters to retrieve from the start
572
   *
573
   * @return Stringy Object with its $str being the first $n chars
574
   */
575 12 View Code Duplication
  public function first($n)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
576
  {
577 12
    $stringy = static::create($this->str, $this->encoding);
578
579 12
    if ($n < 0) {
580 2
      $stringy->str = '';
581 2
    } else {
582 10
      return $stringy->substr(0, $n);
583
    }
584
585 2
    return $stringy;
586
  }
587
588
  /**
589
   * Returns the encoding used by the Stringy object.
590
   *
591
   * @return string The current value of the $encoding property
592
   */
593 3
  public function getEncoding()
594
  {
595 3
    return $this->encoding;
596
  }
597
598
  /**
599
   * Returns a new ArrayIterator, thus implementing the IteratorAggregate
600
   * interface. The ArrayIterator's constructor is passed an array of chars
601
   * in the multibyte string. This enables the use of foreach with instances
602
   * of Stringy\Stringy.
603
   *
604
   * @return \ArrayIterator An iterator for the characters in the string
605
   */
606 1
  public function getIterator()
607
  {
608 1
    return new \ArrayIterator($this->chars());
609
  }
610
611
  /**
612
   * Returns an array consisting of the characters in the string.
613
   *
614
   * @return array An array of string chars
615
   */
616 4
  public function chars()
617
  {
618
    // init
619 4
    $chars = array();
620 4
    $l = $this->length();
621
622 4
    for ($i = 0; $i < $l; $i++) {
623 3
      $chars[] = $this->at($i)->str;
624 3
    }
625
626 4
    return $chars;
627
  }
628
629
  /**
630
   * Returns the character at $index, with indexes starting at 0.
631
   *
632
   * @param  int $index Position of the character
633
   *
634
   * @return Stringy The character at $index
635
   */
636 11
  public function at($index)
637
  {
638 11
    return $this->substr($index, 1);
639
  }
640
641
  /**
642
   * Returns true if the string contains a lower case char, false
643
   * otherwise.
644
   *
645
   * @return bool Whether or not the string contains a lower case character.
646
   */
647 12
  public function hasLowerCase()
648
  {
649 12
    return $this->matchesPattern('.*[[:lower:]]');
650
  }
651
652
  /**
653
   * Returns true if $str matches the supplied pattern, false otherwise.
654
   *
655
   * @param  string $pattern Regex pattern to match against
656
   *
657
   * @return bool   Whether or not $str matches the pattern
658
   */
659 103
  private function matchesPattern($pattern)
660
  {
661 103
    if (preg_match('/' . $pattern . '/u', $this->str)) {
662 64
      return true;
663
    } else {
664 39
      return false;
665
    }
666
  }
667
668
  /**
669
   * Returns true if the string contains an upper case char, false
670
   * otherwise.
671
   *
672
   * @return bool Whether or not the string contains an upper case character.
673
   */
674 12
  public function hasUpperCase()
675
  {
676 12
    return $this->matchesPattern('.*[[:upper:]]');
677
  }
678
679
  /**
680
   * Convert all HTML entities to their applicable characters.
681
   *
682
   * @param  int|null $flags Optional flags
683
   *
684
   * @return Stringy  Object with the resulting $str after being html decoded.
685
   */
686 5
  public function htmlDecode($flags = ENT_COMPAT)
687
  {
688 5
    $str = UTF8::html_entity_decode($this->str, $flags, $this->encoding);
689
690 5
    return static::create($str, $this->encoding);
691
  }
692
693
  /**
694
   * Convert all applicable characters to HTML entities.
695
   *
696
   * @param  int|null $flags Optional flags
697
   *
698
   * @return Stringy  Object with the resulting $str after being html encoded.
699
   */
700 5
  public function htmlEncode($flags = ENT_COMPAT)
701
  {
702 5
    $str = UTF8::htmlentities($this->str, $flags, $this->encoding);
703
704 5
    return static::create($str, $this->encoding);
705
  }
706
707
  /**
708
   * Capitalizes the first word of the string, replaces underscores with
709
   * spaces, and strips '_id'.
710
   *
711
   * @return Stringy Object with a humanized $str
712
   */
713 3
  public function humanize()
714
  {
715 3
    $str = UTF8::str_replace(array('_id', '_'), array('', ' '), $this->str);
716
717 3
    return static::create($str, $this->encoding)->trim()->upperCaseFirst();
718
  }
719
720
  /**
721
   * Converts the first character of the supplied string to upper case.
722
   *
723
   * @return Stringy Object with the first character of $str being upper case
724
   */
725 27 View Code Duplication
  public function upperCaseFirst()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
726
  {
727 27
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
728 27
    $rest = UTF8::substr(
729 27
        $this->str,
730 27
        1,
731 27
        $this->length() - 1,
732 27
        $this->encoding
733 27
    );
734
735 27
    $str = UTF8::strtoupper($first, $this->encoding) . $rest;
0 ignored issues
show
Security Bug introduced by
It seems like $first defined by \voku\helper\UTF8::subst... 0, 1, $this->encoding) on line 727 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...
736
737 27
    return static::create($str, $this->encoding);
738
  }
739
740
  /**
741
   * Returns the index of the last occurrence of $needle in the string,
742
   * and false if not found. Accepts an optional offset from which to begin
743
   * the search. Offsets may be negative to count from the last character
744
   * in the string.
745
   *
746
   * @param  string $needle Substring to look for
747
   * @param  int    $offset Offset from which to search
748
   *
749
   * @return int|bool The last occurrence's index if found, otherwise false
750
   */
751 12
  public function indexOfLast($needle, $offset = 0)
752
  {
753 12
    return UTF8::strrpos($this->str, (string)$needle, (int)$offset, $this->encoding);
754
  }
755
756
  /**
757
   * Inserts $substring into the string at the $index provided.
758
   *
759
   * @param  string $substring String to be inserted
760
   * @param  int    $index     The index at which to insert the substring
761
   *
762
   * @return Stringy Object with the resulting $str after the insertion
763
   */
764 8 View Code Duplication
  public function insert($substring, $index)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
765
  {
766 8
    $stringy = static::create($this->str, $this->encoding);
767 8
    if ($index > $stringy->length()) {
768 1
      return $stringy;
769
    }
770
771 7
    $start = UTF8::substr($stringy->str, 0, $index, $stringy->encoding);
772 7
    $end = UTF8::substr($stringy->str, $index, $stringy->length(), $stringy->encoding);
773
774 7
    $stringy->str = $start . $substring . $end;
775
776 7
    return $stringy;
777
  }
778
779
  /**
780
   * Returns true if the string contains the $pattern, otherwise false.
781
   *
782
   * WARNING: Asterisks ("*") are translated into (".*") zero-or-more regular
783
   * expression wildcards.
784
   *
785
   * @credit Originally from Laravel, thanks Taylor.
786
   *
787
   * @param string $pattern The string or pattern to match against.
788
   *
789
   * @return bool Whether or not we match the provided pattern.
790
   */
791 13
  public function is($pattern)
792
  {
793 13
    if ($this->toString() === $pattern) {
794 1
      return true;
795
    }
796
797 12
    $quotedPattern = preg_quote($pattern, '/');
798 12
    $replaceWildCards = str_replace('\*', '.*', $quotedPattern);
799
800 12
    return $this->matchesPattern('^' . $replaceWildCards . '\z');
801
  }
802
803
  /**
804
   * Returns true if the string contains only alphabetic chars, false
805
   * otherwise.
806
   *
807
   * @return bool Whether or not $str contains only alphabetic chars
808
   */
809 10
  public function isAlpha()
810
  {
811 10
    return $this->matchesPattern('^[[:alpha:]]*$');
812
  }
813
814
  /**
815
   * Determine whether the string is considered to be empty.
816
   *
817
   * A variable is considered empty if it does not exist or if its value equals FALSE.
818
   * empty() does not generate a warning if the variable does not exist.
819
   *
820
   * @return bool
821
   */
822
  public function isEmpty()
823
  {
824
    return empty($this->str);
825
  }
826
827
  /**
828
   * Returns true if the string contains only alphabetic and numeric chars,
829
   * false otherwise.
830
   *
831
   * @return bool Whether or not $str contains only alphanumeric chars
832
   */
833 13
  public function isAlphanumeric()
834
  {
835 13
    return $this->matchesPattern('^[[:alnum:]]*$');
836
  }
837
838
  /**
839
   * Returns true if the string contains only whitespace chars, false
840
   * otherwise.
841
   *
842
   * @return bool Whether or not $str contains only whitespace characters
843
   */
844 15
  public function isBlank()
845
  {
846 15
    return $this->matchesPattern('^[[:space:]]*$');
847
  }
848
849
  /**
850
   * Returns true if the string contains only hexadecimal chars, false
851
   * otherwise.
852
   *
853
   * @return bool Whether or not $str contains only hexadecimal chars
854
   */
855 13
  public function isHexadecimal()
856
  {
857 13
    return $this->matchesPattern('^[[:xdigit:]]*$');
858
  }
859
860
  /**
861
   * Returns true if the string contains HTML-Tags, false otherwise.
862
   *
863
   * @return bool Whether or not $str contains HTML-Tags
864
   */
865 1
  public function isHtml()
866
  {
867 1
    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...
868
  }
869
870
  /**
871
   * Returns true if the string contains a valid E-Mail address, false otherwise.
872
   *
873
   * @param bool $useExampleDomainCheck
874
   * @param bool $useTypoInDomainCheck
875
   * @param bool $useTemporaryDomainCheck
876
   * @param bool $useDnsCheck
877
   *
878
   * @return bool Whether or not $str contains a valid E-Mail address
879
   */
880 1
  public function isEmail($useExampleDomainCheck = false, $useTypoInDomainCheck = false, $useTemporaryDomainCheck = false, $useDnsCheck = false)
881
  {
882 1
    return EmailCheck::isValid($this->str, $useExampleDomainCheck, $useTypoInDomainCheck, $useTemporaryDomainCheck, $useDnsCheck);
883
  }
884
885
  /**
886
   * Returns true if the string is JSON, false otherwise. Unlike json_decode
887
   * in PHP 5.x, this method is consistent with PHP 7 and other JSON parsers,
888
   * in that an empty string is not considered valid JSON.
889
   *
890
   * @return bool Whether or not $str is JSON
891
   */
892 20
  public function isJson()
893
  {
894 20
    if (!isset($this->str[0])) {
895 1
      return false;
896
    }
897
898 19
    json_decode($this->str);
899
900 19
    if (json_last_error() === JSON_ERROR_NONE) {
901 11
      return true;
902
    } else {
903 8
      return false;
904
    }
905
  }
906
907
  /**
908
   * Returns true if the string contains only lower case chars, false
909
   * otherwise.
910
   *
911
   * @return bool Whether or not $str contains only lower case characters
912
   */
913 8
  public function isLowerCase()
914
  {
915 8
    if ($this->matchesPattern('^[[:lower:]]*$')) {
916 3
      return true;
917
    } else {
918 5
      return false;
919
    }
920
  }
921
922
  /**
923
   * Returns true if the string is serialized, false otherwise.
924
   *
925
   * @return bool Whether or not $str is serialized
926
   */
927 7
  public function isSerialized()
928
  {
929 7
    if (!isset($this->str[0])) {
930 1
      return false;
931
    }
932
933
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
934
    if (
935 6
        $this->str === 'b:0;'
936 6
        ||
937 6
        @unserialize($this->str) !== false
938 6
    ) {
939 4
      return true;
940
    } else {
941 2
      return false;
942
    }
943
  }
944
945
  /**
946
   * Returns true if the string contains only lower case chars, false
947
   * otherwise.
948
   *
949
   * @return bool Whether or not $str contains only lower case characters
950
   */
951 8
  public function isUpperCase()
952
  {
953 8
    return $this->matchesPattern('^[[:upper:]]*$');
954
  }
955
956
  /**
957
   * Returns the last $n characters of the string.
958
   *
959
   * @param  int $n Number of characters to retrieve from the end
960
   *
961
   * @return Stringy Object with its $str being the last $n chars
962
   */
963 12 View Code Duplication
  public function last($n)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
964
  {
965 12
    $stringy = static::create($this->str, $this->encoding);
966
967 12
    if ($n <= 0) {
968 4
      $stringy->str = '';
969 4
    } else {
970 8
      return $stringy->substr(-$n);
971
    }
972
973 4
    return $stringy;
974
  }
975
976
  /**
977
   * Splits on newlines and carriage returns, returning an array of Stringy
978
   * objects corresponding to the lines in the string.
979
   *
980
   * @return Stringy[] An array of Stringy objects
981
   */
982 15
  public function lines()
983
  {
984 15
    $array = preg_split('/[\r\n]{1,2}/u', $this->str);
985
    /** @noinspection CallableInLoopTerminationConditionInspection */
986
    /** @noinspection ForeachInvariantsInspection */
987 15 View Code Duplication
    for ($i = 0; $i < count($array); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
988 15
      $array[$i] = static::create($array[$i], $this->encoding);
989 15
    }
990
991 15
    return $array;
992
  }
993
994
  /**
995
   * Returns the longest common prefix between the string and $otherStr.
996
   *
997
   * @param  string $otherStr Second string for comparison
998
   *
999
   * @return Stringy Object with its $str being the longest common prefix
1000
   */
1001 10
  public function longestCommonPrefix($otherStr)
1002
  {
1003 10
    $encoding = $this->encoding;
1004 10
    $maxLength = min($this->length(), UTF8::strlen($otherStr, $encoding));
1005
1006 10
    $longestCommonPrefix = '';
1007 10
    for ($i = 0; $i < $maxLength; $i++) {
1008 8
      $char = UTF8::substr($this->str, $i, 1, $encoding);
1009
1010 8
      if ($char == UTF8::substr($otherStr, $i, 1, $encoding)) {
1011 6
        $longestCommonPrefix .= $char;
1012 6
      } else {
1013 6
        break;
1014
      }
1015 6
    }
1016
1017 10
    return static::create($longestCommonPrefix, $encoding);
1018
  }
1019
1020
  /**
1021
   * Returns the longest common suffix between the string and $otherStr.
1022
   *
1023
   * @param  string $otherStr Second string for comparison
1024
   *
1025
   * @return Stringy Object with its $str being the longest common suffix
1026
   */
1027 10
  public function longestCommonSuffix($otherStr)
1028
  {
1029 10
    $encoding = $this->encoding;
1030 10
    $maxLength = min($this->length(), UTF8::strlen($otherStr, $encoding));
1031
1032 10
    $longestCommonSuffix = '';
1033 10
    for ($i = 1; $i <= $maxLength; $i++) {
1034 8
      $char = UTF8::substr($this->str, -$i, 1, $encoding);
1035
1036 8
      if ($char == UTF8::substr($otherStr, -$i, 1, $encoding)) {
1037 6
        $longestCommonSuffix = $char . $longestCommonSuffix;
1038 6
      } else {
1039 6
        break;
1040
      }
1041 6
    }
1042
1043 10
    return static::create($longestCommonSuffix, $encoding);
1044
  }
1045
1046
  /**
1047
   * Returns the longest common substring between the string and $otherStr.
1048
   * In the case of ties, it returns that which occurs first.
1049
   *
1050
   * @param  string $otherStr Second string for comparison
1051
   *
1052
   * @return Stringy Object with its $str being the longest common substring
1053
   */
1054 10
  public function longestCommonSubstring($otherStr)
1055
  {
1056
    // Uses dynamic programming to solve
1057
    // http://en.wikipedia.org/wiki/Longest_common_substring_problem
1058 10
    $encoding = $this->encoding;
1059 10
    $stringy = static::create($this->str, $encoding);
1060 10
    $strLength = $stringy->length();
1061 10
    $otherLength = UTF8::strlen($otherStr, $encoding);
1062
1063
    // Return if either string is empty
1064 10
    if ($strLength == 0 || $otherLength == 0) {
1065 2
      $stringy->str = '';
1066
1067 2
      return $stringy;
1068
    }
1069
1070 8
    $len = 0;
1071 8
    $end = 0;
1072 8
    $table = array_fill(
1073 8
        0, $strLength + 1,
1074 8
        array_fill(0, $otherLength + 1, 0)
1075 8
    );
1076
1077 8
    for ($i = 1; $i <= $strLength; $i++) {
1078 8
      for ($j = 1; $j <= $otherLength; $j++) {
1079 8
        $strChar = UTF8::substr($stringy->str, $i - 1, 1, $encoding);
1080 8
        $otherChar = UTF8::substr($otherStr, $j - 1, 1, $encoding);
1081
1082 8
        if ($strChar == $otherChar) {
1083 8
          $table[$i][$j] = $table[$i - 1][$j - 1] + 1;
1084 8
          if ($table[$i][$j] > $len) {
1085 8
            $len = $table[$i][$j];
1086 8
            $end = $i;
1087 8
          }
1088 8
        } else {
1089 8
          $table[$i][$j] = 0;
1090
        }
1091 8
      }
1092 8
    }
1093
1094 8
    $stringy->str = UTF8::substr($stringy->str, $end - $len, $len, $encoding);
0 ignored issues
show
Documentation Bug introduced by
It seems like \voku\helper\UTF8::subst... $len, $len, $encoding) can also be of type false. However, the property $str is declared as type string. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1095
1096 8
    return $stringy;
1097
  }
1098
1099
  /**
1100
   * Returns whether or not a character exists at an index. Offsets may be
1101
   * negative to count from the last character in the string. Implements
1102
   * part of the ArrayAccess interface.
1103
   *
1104
   * @param  mixed $offset The index to check
1105
   *
1106
   * @return boolean Whether or not the index exists
1107
   */
1108 6
  public function offsetExists($offset)
1109
  {
1110
    // init
1111 6
    $length = $this->length();
1112 6
    $offset = (int)$offset;
1113
1114 6
    if ($offset >= 0) {
1115 3
      return ($length > $offset);
1116
    }
1117
1118 3
    return ($length >= abs($offset));
1119
  }
1120
1121
  /**
1122
   * Returns the character at the given index. Offsets may be negative to
1123
   * count from the last character in the string. Implements part of the
1124
   * ArrayAccess interface, and throws an OutOfBoundsException if the index
1125
   * does not exist.
1126
   *
1127
   * @param  mixed $offset The index from which to retrieve the char
1128
   *
1129
   * @return string                 The character at the specified index
1130
   * @throws \OutOfBoundsException If the positive or negative offset does
1131
   *                               not exist
1132
   */
1133 2
  public function offsetGet($offset)
1134
  {
1135
    // init
1136 2
    $offset = (int)$offset;
1137 2
    $length = $this->length();
1138
1139
    if (
1140 2
        ($offset >= 0 && $length <= $offset)
1141
        ||
1142 1
        $length < abs($offset)
1143 2
    ) {
1144 1
      throw new \OutOfBoundsException('No character exists at the index');
1145
    }
1146
1147 1
    return UTF8::substr($this->str, $offset, 1, $this->encoding);
1148
  }
1149
1150
  /**
1151
   * Implements part of the ArrayAccess interface, but throws an exception
1152
   * when called. This maintains the immutability of Stringy objects.
1153
   *
1154
   * @param  mixed $offset The index of the character
1155
   * @param  mixed $value  Value to set
1156
   *
1157
   * @throws \Exception When called
1158
   */
1159 1
  public function offsetSet($offset, $value)
1160
  {
1161
    // Stringy is immutable, cannot directly set char
1162 1
    throw new \Exception('Stringy object is immutable, cannot modify char');
1163
  }
1164
1165
  /**
1166
   * Implements part of the ArrayAccess interface, but throws an exception
1167
   * when called. This maintains the immutability of Stringy objects.
1168
   *
1169
   * @param  mixed $offset The index of the character
1170
   *
1171
   * @throws \Exception When called
1172
   */
1173 1
  public function offsetUnset($offset)
1174
  {
1175
    // Don't allow directly modifying the string
1176 1
    throw new \Exception('Stringy object is immutable, cannot unset char');
1177
  }
1178
1179
  /**
1180
   * Pads the string to a given length with $padStr. If length is less than
1181
   * or equal to the length of the string, no padding takes places. The
1182
   * default string used for padding is a space, and the default type (one of
1183
   * 'left', 'right', 'both') is 'right'. Throws an InvalidArgumentException
1184
   * if $padType isn't one of those 3 values.
1185
   *
1186
   * @param  int    $length  Desired string length after padding
1187
   * @param  string $padStr  String used to pad, defaults to space
1188
   * @param  string $padType One of 'left', 'right', 'both'
1189
   *
1190
   * @return Stringy Object with a padded $str
1191
   * @throws \InvalidArgumentException If $padType isn't one of 'right', 'left' or 'both'
1192
   */
1193 13
  public function pad($length, $padStr = ' ', $padType = 'right')
1194
  {
1195 13
    if (!in_array($padType, array('left', 'right', 'both'), true)) {
1196 1
      throw new \InvalidArgumentException(
1197
          'Pad expects $padType ' . "to be one of 'left', 'right' or 'both'"
1198 1
      );
1199
    }
1200
1201
    switch ($padType) {
1202 12
      case 'left':
1203 3
        return $this->padLeft($length, $padStr);
1204 9
      case 'right':
1205 6
        return $this->padRight($length, $padStr);
1206 3
      default:
1207 3
        return $this->padBoth($length, $padStr);
1208 3
    }
1209
  }
1210
1211
  /**
1212
   * Returns a new string of a given length such that the beginning of the
1213
   * string is padded. Alias for pad() with a $padType of 'left'.
1214
   *
1215
   * @param  int    $length Desired string length after padding
1216
   * @param  string $padStr String used to pad, defaults to space
1217
   *
1218
   * @return Stringy String with left padding
1219
   */
1220 10
  public function padLeft($length, $padStr = ' ')
1221
  {
1222 10
    return $this->applyPadding($length - $this->length(), 0, $padStr);
1223
  }
1224
1225
  /**
1226
   * Adds the specified amount of left and right padding to the given string.
1227
   * The default character used is a space.
1228
   *
1229
   * @param  int    $left   Length of left padding
1230
   * @param  int    $right  Length of right padding
1231
   * @param  string $padStr String used to pad
1232
   *
1233
   * @return Stringy String with padding applied
1234
   */
1235 37
  private function applyPadding($left = 0, $right = 0, $padStr = ' ')
1236
  {
1237 37
    $stringy = static::create($this->str, $this->encoding);
1238
1239 37
    $length = UTF8::strlen($padStr, $stringy->encoding);
1240
1241 37
    $strLength = $stringy->length();
1242 37
    $paddedLength = $strLength + $left + $right;
1243
1244 37
    if (!$length || $paddedLength <= $strLength) {
1245 3
      return $stringy;
1246
    }
1247
1248 34
    $leftPadding = UTF8::substr(
1249 34
        UTF8::str_repeat(
1250 34
            $padStr,
1251 34
            ceil($left / $length)
1252 34
        ),
1253 34
        0,
1254 34
        $left,
1255 34
        $stringy->encoding
1256 34
    );
1257
1258 34
    $rightPadding = UTF8::substr(
1259 34
        UTF8::str_repeat(
1260 34
            $padStr,
1261 34
            ceil($right / $length)
1262 34
        ),
1263 34
        0,
1264 34
        $right,
1265 34
        $stringy->encoding
1266 34
    );
1267
1268 34
    $stringy->str = $leftPadding . $stringy->str . $rightPadding;
1269
1270 34
    return $stringy;
1271
  }
1272
1273
  /**
1274
   * Returns a new string of a given length such that the end of the string
1275
   * is padded. Alias for pad() with a $padType of 'right'.
1276
   *
1277
   * @param  int    $length Desired string length after padding
1278
   * @param  string $padStr String used to pad, defaults to space
1279
   *
1280
   * @return Stringy String with right padding
1281
   */
1282 13
  public function padRight($length, $padStr = ' ')
1283
  {
1284 13
    return $this->applyPadding(0, $length - $this->length(), $padStr);
1285
  }
1286
1287
  /**
1288
   * Returns a new string of a given length such that both sides of the
1289
   * string are padded. Alias for pad() with a $padType of 'both'.
1290
   *
1291
   * @param  int    $length Desired string length after padding
1292
   * @param  string $padStr String used to pad, defaults to space
1293
   *
1294
   * @return Stringy String with padding applied
1295
   */
1296 14
  public function padBoth($length, $padStr = ' ')
1297
  {
1298 14
    $padding = $length - $this->length();
1299
1300 14
    return $this->applyPadding(floor($padding / 2), ceil($padding / 2), $padStr);
1301
  }
1302
1303
  /**
1304
   * Returns a new string starting with $string.
1305
   *
1306
   * @param  string $string The string to append
1307
   *
1308
   * @return Stringy Object with appended $string
1309
   */
1310 2
  public function prepend($string)
1311
  {
1312 2
    return static::create($string . $this->str, $this->encoding);
1313
  }
1314
1315
  /**
1316
   * Returns a new string with the prefix $substring removed, if present.
1317
   *
1318
   * @param  string $substring The prefix to remove
1319
   *
1320
   * @return Stringy Object having a $str without the prefix $substring
1321
   */
1322 12 View Code Duplication
  public function removeLeft($substring)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1323
  {
1324 12
    $stringy = static::create($this->str, $this->encoding);
1325
1326 12
    if ($stringy->startsWith($substring)) {
1327 6
      $substringLength = UTF8::strlen($substring, $stringy->encoding);
1328
1329 6
      return $stringy->substr($substringLength);
1330
    }
1331
1332 6
    return $stringy;
1333
  }
1334
1335
  /**
1336
   * Returns a new string with the suffix $substring removed, if present.
1337
   *
1338
   * @param  string $substring The suffix to remove
1339
   *
1340
   * @return Stringy Object having a $str without the suffix $substring
1341
   */
1342 12 View Code Duplication
  public function removeRight($substring)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1343
  {
1344 12
    $stringy = static::create($this->str, $this->encoding);
1345
1346 12
    if ($stringy->endsWith($substring)) {
1347 8
      $substringLength = UTF8::strlen($substring, $stringy->encoding);
1348
1349 8
      return $stringy->substr(0, $stringy->length() - $substringLength);
1350
    }
1351
1352 4
    return $stringy;
1353
  }
1354
1355
  /**
1356
   * Returns a repeated string given a multiplier.
1357
   *
1358
   * @param  int $multiplier The number of times to repeat the string
1359
   *
1360
   * @return Stringy Object with a repeated str
1361
   */
1362 7
  public function repeat($multiplier)
1363
  {
1364 7
    $repeated = UTF8::str_repeat($this->str, $multiplier);
1365
1366 7
    return static::create($repeated, $this->encoding);
1367
  }
1368
1369
  /**
1370
   * Replaces all occurrences of $search in $str by $replacement.
1371
   *
1372
   * @param string $search        The needle to search for
1373
   * @param string $replacement   The string to replace with
1374
   * @param bool   $caseSensitive Whether or not to enforce case-sensitivity
1375
   *
1376
   * @return Stringy Object with the resulting $str after the replacements
1377
   */
1378 28 View Code Duplication
  public function replace($search, $replacement, $caseSensitive = true)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1379
  {
1380 28
    if ($caseSensitive) {
1381 21
      $return = UTF8::str_replace($search, $replacement, $this->str);
1382 21
    } else {
1383 7
      $return = UTF8::str_ireplace($search, $replacement, $this->str);
1384
    }
1385
1386 28
    return static::create($return);
1387
  }
1388
1389
  /**
1390
   * Replaces all occurrences of $search in $str by $replacement.
1391
   *
1392
   * @param array        $search        The elements to search for
1393
   * @param string|array $replacement   The string to replace with
1394
   * @param bool         $caseSensitive Whether or not to enforce case-sensitivity
1395
   *
1396
   * @return Stringy Object with the resulting $str after the replacements
1397
   */
1398 30 View Code Duplication
  public function replaceAll(array $search, $replacement, $caseSensitive = true)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1399
  {
1400 30
    if ($caseSensitive) {
1401 23
      $return = UTF8::str_replace($search, $replacement, $this->str);
1402 23
    } else {
1403 7
      $return = UTF8::str_ireplace($search, $replacement, $this->str);
1404
    }
1405
1406 30
    return static::create($return);
1407
  }
1408
1409
  /**
1410
   * Replaces all occurrences of $search from the beginning of string with $replacement
1411
   *
1412
   * @param string $search
1413
   * @param string $replacement
1414
   *
1415
   * @return Stringy Object with the resulting $str after the replacements
1416
   */
1417 16
  public function replaceBeginning($search, $replacement)
1418
  {
1419 16
    $str = $this->regexReplace('^' . preg_quote($search, '/'), UTF8::str_replace('\\', '\\\\', $replacement));
1420
1421 16
    return static::create($str, $this->encoding);
1422
  }
1423
1424
  /**
1425
   * Replaces all occurrences of $search from the ending of string with $replacement
1426
   *
1427
   * @param string $search
1428
   * @param string $replacement
1429
   *
1430
   * @return Stringy Object with the resulting $str after the replacements
1431
   */
1432 16
  public function replaceEnding($search, $replacement)
1433
  {
1434 16
    $str = $this->regexReplace(preg_quote($search, '/') . '$', UTF8::str_replace('\\', '\\\\', $replacement));
1435
1436 16
    return static::create($str, $this->encoding);
1437
  }
1438
1439
  /**
1440
   * Returns a reversed string. A multibyte version of strrev().
1441
   *
1442
   * @return Stringy Object with a reversed $str
1443
   */
1444 5
  public function reverse()
1445
  {
1446 5
    $reversed = UTF8::strrev($this->str);
1447
1448 5
    return static::create($reversed, $this->encoding);
1449
  }
1450
1451
  /**
1452
   * Truncates the string to a given length, while ensuring that it does not
1453
   * split words. If $substring is provided, and truncating occurs, the
1454
   * string is further truncated so that the substring may be appended without
1455
   * exceeding the desired length.
1456
   *
1457
   * @param  int    $length    Desired length of the truncated string
1458
   * @param  string $substring The substring to append if it can fit
1459
   *
1460
   * @return Stringy Object with the resulting $str after truncating
1461
   */
1462 23
  public function safeTruncate($length, $substring = '')
1463
  {
1464 23
    $stringy = static::create($this->str, $this->encoding);
1465 23
    if ($length >= $stringy->length()) {
1466 4
      return $stringy;
1467
    }
1468
1469
    // Need to further trim the string so we can append the substring
1470 19
    $encoding = $stringy->encoding;
1471 19
    $substringLength = UTF8::strlen($substring, $encoding);
1472 19
    $length -= $substringLength;
1473
1474 19
    $truncated = UTF8::substr($stringy->str, 0, $length, $encoding);
1475
1476
    // If the last word was truncated
1477 19
    $strPosSpace = UTF8::strpos($stringy->str, ' ', $length - 1, $encoding);
1478 19
    if ($strPosSpace != $length) {
1479
      // Find pos of the last occurrence of a space, get up to that
1480 12
      $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 1474 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...
1481
1482 12
      if ($lastPos !== false || $strPosSpace !== false) {
1483 11
        $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 1483 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 1480 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...
1484 11
      }
1485 12
    }
1486
1487 19
    $stringy->str = $truncated . $substring;
1488
1489 19
    return $stringy;
1490
  }
1491
1492
  /**
1493
   * A multibyte string shuffle function. It returns a string with its
1494
   * characters in random order.
1495
   *
1496
   * @return Stringy Object with a shuffled $str
1497
   */
1498 3
  public function shuffle()
1499
  {
1500 3
    $shuffledStr = UTF8::str_shuffle($this->str);
1501
1502 3
    return static::create($shuffledStr, $this->encoding);
1503
  }
1504
1505
  /**
1506
   * Converts the string into an URL slug. This includes replacing non-ASCII
1507
   * characters with their closest ASCII equivalents, removing remaining
1508
   * non-ASCII and non-alphanumeric characters, and replacing whitespace with
1509
   * $replacement. The replacement defaults to a single dash, and the string
1510
   * is also converted to lowercase.
1511
   *
1512
   * @param string $replacement The string used to replace whitespace
1513
   * @param string $language    The language for the url
1514
   * @param bool   $strToLower  string to lower
1515
   *
1516
   * @return Stringy Object whose $str has been converted to an URL slug
1517
   */
1518 15
  public function slugify($replacement = '-', $language = 'de', $strToLower = true)
1519
  {
1520 15
    $slug = URLify::slug($this->str, $language, $replacement, $strToLower);
1521
1522 15
    return static::create($slug, $this->encoding);
1523
  }
1524
1525
  /**
1526
   * Remove css media-queries.
1527
   *
1528
   * @return Stringy
1529
   */
1530 1
  public function stripeCssMediaQueries()
1531
  {
1532 1
    $pattern = '#@media\\s+(?:only\\s)?(?:[\\s{\\(]|screen|all)\\s?[^{]+{.*}\\s*}\\s*#misU';
1533
1534 1
    return static::create(preg_replace($pattern, '', $this->str));
1535
  }
1536
1537
  /**
1538
   * Remove empty html-tag.
1539
   *
1540
   * e.g.: <tag></tag>
1541
   *
1542
   * @return Stringy
1543
   */
1544 1
  public function stripeEmptyHtmlTags()
1545
  {
1546 1
    $pattern = "/<[^\/>]*>(([\s]?)*|)<\/[^>]*>/i";
1547
1548 1
    return static::create(preg_replace($pattern, '', $this->str));
1549
  }
1550
1551
  /**
1552
   * Converts the string into an valid UTF-8 string.
1553
   *
1554
   * @return Stringy
1555
   */
1556 1
  public function utf8ify()
1557
  {
1558 1
    return static::create(UTF8::cleanup($this->str));
1559
  }
1560
1561
  /**
1562
   * escape html
1563
   *
1564
   * @return Stringy
1565
   */
1566 6
  public function escape()
1567
  {
1568 6
    $str = UTF8::htmlspecialchars(
1569 6
        $this->str,
1570 6
        ENT_QUOTES | ENT_SUBSTITUTE,
1571 6
        $this->encoding
1572 6
    );
1573
1574 6
    return static::create($str, $this->encoding);
1575
  }
1576
1577
  /**
1578
   * Create an extract from a text, so if the search-string was found, it will be centered in the output.
1579
   *
1580
   * @param string   $search
1581
   * @param int|null $length
1582
   * @param string   $ellipsis
1583
   *
1584
   * @return Stringy
1585
   */
1586 1
  public function extractText($search = '', $length = null, $ellipsis = '...')
1587
  {
1588
    // init
1589 1
    $text = $this->str;
1590
1591 1
    if (empty($text)) {
1592 1
      return static::create('', $this->encoding);
1593
    }
1594
1595 1
    $trimChars = "\t\r\n -_()!~?=+/*\\,.:;\"'[]{}`&";
1596
1597 1
    if ($length === null) {
1598 1
      $length = $this->length() / 2;
1599 1
    }
1600
1601 1
    if (empty($search)) {
1602
1603
      $stringLength = UTF8::strlen($text, $this->encoding);
1604
      $end = ($length - 1) > $stringLength ? $stringLength : ($length - 1);
1605
      $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...
1606
1607
      if ($pos) {
1608
        return static::create(
1609
            rtrim(
1610
                UTF8::substr($text, 0, $pos, $this->encoding),
1611
                $trimChars
1612
            ) . $ellipsis,
1613
            $this->encoding
1614
        );
1615
      } else {
1616
        return static::create($text, $this->encoding);
1617
      }
1618
1619
    }
1620
1621 1
    $wordPos = UTF8::strpos(
1622 1
        UTF8::strtolower($text),
1623 1
        UTF8::strtolower($search, $this->encoding),
1624 1
        null,
1625 1
        $this->encoding
1626 1
    );
1627 1
    $halfSide = (int)($wordPos - $length / 2 + UTF8::strlen($search, $this->encoding) / 2);
1628
1629 1
    if ($halfSide > 0) {
1630
1631 1
      $halfText = UTF8::substr($text, 0, $halfSide, $this->encoding);
1632 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 1631 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...
1633
1634 1
      if (!$pos_start) {
1635 1
        $pos_start = 0;
1636 1
      }
1637
1638 1
    } else {
1639 1
      $pos_start = 0;
1640
    }
1641
1642 1
    if ($wordPos && $halfSide > 0) {
1643 1
      $l = $pos_start + $length - 1;
1644 1
      $realLength = UTF8::strlen($text, $this->encoding);
1645
1646 1
      if ($l > $realLength) {
1647
        $l = $realLength;
1648
      }
1649
1650 1
      $pos_end = min(
1651 1
                     UTF8::strpos($text, ' ', $l, $this->encoding),
1652 1
                     UTF8::strpos($text, '.', $l, $this->encoding)
1653 1
                 ) - $pos_start;
1654
1655 1
      if (!$pos_end || $pos_end <= 0) {
1656 1
        $extract = $ellipsis . ltrim(
1657 1
                UTF8::substr(
1658 1
                    $text,
1659 1
                    $pos_start,
1660 1
                    UTF8::strlen($text),
1661 1
                    $this->encoding
1662 1
                ),
1663
                $trimChars
1664 1
            );
1665 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...
1666 1
        $extract = $ellipsis . trim(
1667 1
                UTF8::substr(
1668 1
                    $text,
1669 1
                    $pos_start,
1670 1
                    $pos_end,
1671 1
                    $this->encoding
1672 1
                ),
1673
                $trimChars
1674 1
            ) . $ellipsis;
1675
      }
1676
1677 1
    } else {
1678
1679 1
      $l = $length - 1;
1680 1
      $trueLength = UTF8::strlen($text, $this->encoding);
1681
1682 1
      if ($l > $trueLength) {
1683
        $l = $trueLength;
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
      );
1690
1691 1 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...
1692 1
        $extract = rtrim(
1693 1
                       UTF8::substr($text, 0, $pos_end, $this->encoding),
1694
                       $trimChars
1695 1
                   ) . $ellipsis;
1696 1
      } else {
1697 1
        $extract = $text;
1698
      }
1699
    }
1700
1701 1
    return static::create($extract, $this->encoding);
1702
  }
1703
1704
1705
  /**
1706
   * remove xss from html
1707
   *
1708
   * @return Stringy
1709
   */
1710 6
  public function removeXss()
1711
  {
1712 6
    static $antiXss = null;
1713
1714 6
    if ($antiXss === null) {
1715 1
      $antiXss = new AntiXSS();
1716 1
    }
1717
1718 6
    $str = $antiXss->xss_clean($this->str);
1719
1720 6
    return static::create($str, $this->encoding);
1721
  }
1722
1723
  /**
1724
   * remove html-break [br | \r\n | \r | \n | ...]
1725
   *
1726
   * @param string $replacement
1727
   *
1728
   * @return Stringy
1729
   */
1730 6
  public function removeHtmlBreak($replacement = '')
1731
  {
1732 6
    $str = preg_replace('#/\r\n|\r|\n|<br.*/?>#isU', $replacement, $this->str);
1733
1734 6
    return static::create($str, $this->encoding);
1735
  }
1736
1737
  /**
1738
   * remove html
1739
   *
1740
   * @param $allowableTags
1741
   *
1742
   * @return Stringy
1743
   */
1744 6
  public function removeHtml($allowableTags = null)
1745
  {
1746 6
    $str = strip_tags($this->str, $allowableTags);
1747
1748 6
    return static::create($str, $this->encoding);
1749
  }
1750
1751
  /**
1752
   * Returns the substring beginning at $start, and up to, but not including
1753
   * the index specified by $end. If $end is omitted, the function extracts
1754
   * the remaining string. If $end is negative, it is computed from the end
1755
   * of the string.
1756
   *
1757
   * @param  int $start Initial index from which to begin extraction
1758
   * @param  int $end   Optional index at which to end extraction
1759
   *
1760
   * @return Stringy Object with its $str being the extracted substring
1761
   */
1762 18
  public function slice($start, $end = null)
1763
  {
1764 18
    if ($end === null) {
1765 4
      $length = $this->length();
1766 18
    } elseif ($end >= 0 && $end <= $start) {
1767 4
      return static::create('', $this->encoding);
1768 10
    } elseif ($end < 0) {
1769 2
      $length = $this->length() + $end - $start;
1770 2
    } else {
1771 8
      $length = $end - $start;
1772
    }
1773
1774 14
    $str = UTF8::substr($this->str, $start, $length, $this->encoding);
1775
1776 14
    return static::create($str, $this->encoding);
1777
  }
1778
1779
  /**
1780
   * Splits the string with the provided regular expression, returning an
1781
   * array of Stringy objects. An optional integer $limit will truncate the
1782
   * results.
1783
   *
1784
   * @param  string $pattern The regex with which to split the string
1785
   * @param  int    $limit   Optional maximum number of results to return
1786
   *
1787
   * @return Stringy[] An array of Stringy objects
1788
   */
1789 19
  public function split($pattern, $limit = null)
1790
  {
1791 19
    if ($limit === 0) {
1792 2
      return array();
1793
    }
1794
1795
    // UTF8::split errors when supplied an empty pattern in < PHP 5.4.13
1796
    // and current versions of HHVM (3.8 and below)
1797 17
    if ($pattern === '') {
1798 1
      return array(static::create($this->str, $this->encoding));
1799
    }
1800
1801
    // UTF8::split returns the remaining unsplit string in the last index when
1802
    // supplying a limit
1803 16
    if ($limit > 0) {
1804 8
      $limit += 1;
1805 8
    } else {
1806 8
      $limit = -1;
1807
    }
1808
1809 16
    $array = preg_split('/' . preg_quote($pattern, '/') . '/u', $this->str, $limit);
1810
1811 16
    if ($limit > 0 && count($array) === $limit) {
1812 4
      array_pop($array);
1813 4
    }
1814
1815
    /** @noinspection CallableInLoopTerminationConditionInspection */
1816
    /** @noinspection ForeachInvariantsInspection */
1817 16 View Code Duplication
    for ($i = 0; $i < count($array); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1818 16
      $array[$i] = static::create($array[$i], $this->encoding);
1819 16
    }
1820
1821 16
    return $array;
1822
  }
1823
1824
  /**
1825
   * Surrounds $str with the given substring.
1826
   *
1827
   * @param  string $substring The substring to add to both sides
1828
   *
1829
   * @return Stringy Object whose $str had the substring both prepended and
1830
   *                 appended
1831
   */
1832 5
  public function surround($substring)
1833
  {
1834 5
    $str = implode('', array($substring, $this->str, $substring));
1835
1836 5
    return static::create($str, $this->encoding);
1837
  }
1838
1839
  /**
1840
   * Returns a case swapped version of the string.
1841
   *
1842
   * @return Stringy Object whose $str has each character's case swapped
1843
   */
1844 5
  public function swapCase()
1845
  {
1846 5
    $stringy = static::create($this->str, $this->encoding);
1847
1848 5
    $stringy->str = UTF8::swapCase($stringy->str, $stringy->encoding);
1849
1850 5
    return $stringy;
1851
  }
1852
1853
  /**
1854
   * Returns a string with smart quotes, ellipsis characters, and dashes from
1855
   * Windows-1252 (commonly used in Word documents) replaced by their ASCII
1856
   * equivalents.
1857
   *
1858
   * @return Stringy Object whose $str has those characters removed
1859
   */
1860 4
  public function tidy()
1861
  {
1862 4
    $str = UTF8::normalize_msword($this->str);
1863
1864 4
    return static::create($str, $this->encoding);
1865
  }
1866
1867
  /**
1868
   * Returns a trimmed string with the first letter of each word capitalized.
1869
   * Also accepts an array, $ignore, allowing you to list words not to be
1870
   * capitalized.
1871
   *
1872
   * @param  array $ignore An array of words not to capitalize
1873
   *
1874
   * @return Stringy Object with a titleized $str
1875
   */
1876 5
  public function titleize($ignore = null)
1877
  {
1878 5
    $stringy = static::create($this->trim(), $this->encoding);
1879 5
    $encoding = $this->encoding;
1880
1881 5
    $stringy->str = preg_replace_callback(
1882 5
        '/([\S]+)/u',
1883
        function ($match) use ($encoding, $ignore) {
1884 5
          if ($ignore && in_array($match[0], $ignore, true)) {
1885 2
            return $match[0];
1886
          } else {
1887 5
            $stringy = new Stringy($match[0], $encoding);
1888
1889 5
            return (string)$stringy->toLowerCase()->upperCaseFirst();
1890
          }
1891 5
        },
1892 5
        $stringy->str
1893 5
    );
1894
1895 5
    return $stringy;
1896
  }
1897
1898
  /**
1899
   * Converts all characters in the string to lowercase. An alias for PHP's
1900
   * UTF8::strtolower().
1901
   *
1902
   * @return Stringy Object with all characters of $str being lowercase
1903
   */
1904 27
  public function toLowerCase()
1905
  {
1906 27
    $str = UTF8::strtolower($this->str, $this->encoding);
1907
1908 27
    return static::create($str, $this->encoding);
1909
  }
1910
1911
  /**
1912
   * Returns true if the string is base64 encoded, false otherwise.
1913
   *
1914
   * @return bool Whether or not $str is base64 encoded
1915
   */
1916 7
  public function isBase64()
1917
  {
1918 7
    return UTF8::is_base64($this->str);
1919
  }
1920
1921
  /**
1922
   * Returns an ASCII version of the string. A set of non-ASCII characters are
1923
   * replaced with their closest ASCII counterparts, and the rest are removed
1924
   * unless instructed otherwise.
1925
   *
1926
   * @param $strict [optional] <p>Use "transliterator_transliterate()" from PHP-Intl | WARNING: bad performance</p>
1927
   *
1928
   * @return Stringy Object whose $str contains only ASCII characters
1929
   */
1930 16
  public function toAscii($strict = false)
1931
  {
1932 16
    $str = UTF8::to_ascii($this->str, '?', $strict);
1933
1934 16
    return static::create($str, $this->encoding);
1935
  }
1936
1937
  /**
1938
   * Returns a boolean representation of the given logical string value.
1939
   * For example, 'true', '1', 'on' and 'yes' will return true. 'false', '0',
1940
   * 'off', and 'no' will return false. In all instances, case is ignored.
1941
   * For other numeric strings, their sign will determine the return value.
1942
   * In addition, blank strings consisting of only whitespace will return
1943
   * false. For all other strings, the return value is a result of a
1944
   * boolean cast.
1945
   *
1946
   * @return bool A boolean value for the string
1947
   */
1948 15
  public function toBoolean()
1949
  {
1950 15
    $key = $this->toLowerCase()->str;
1951
    $map = array(
1952 15
        'true'  => true,
1953 15
        '1'     => true,
1954 15
        'on'    => true,
1955 15
        'yes'   => true,
1956 15
        'false' => false,
1957 15
        '0'     => false,
1958 15
        'off'   => false,
1959 15
        'no'    => false,
1960 15
    );
1961
1962 15
    if (array_key_exists($key, $map)) {
1963 10
      return $map[$key];
1964 5
    } elseif (is_numeric($this->str)) {
1965 2
      return ((int)$this->str > 0);
1966
    } else {
1967 3
      return (bool)$this->regexReplace('[[:space:]]', '')->str;
1968
    }
1969
  }
1970
1971
  /**
1972
   * Return Stringy object as string, but you can also use (string) for automatically casting the object into a string.
1973
   *
1974
   * @return string
1975
   */
1976 993
  public function toString()
1977
  {
1978 993
    return (string)$this->str;
1979
  }
1980
1981
  /**
1982
   * Converts each tab in the string to some number of spaces, as defined by
1983
   * $tabLength. By default, each tab is converted to 4 consecutive spaces.
1984
   *
1985
   * @param  int $tabLength Number of spaces to replace each tab with
1986
   *
1987
   * @return Stringy Object whose $str has had tabs switched to spaces
1988
   */
1989 6 View Code Duplication
  public function toSpaces($tabLength = 4)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1990
  {
1991 6
    $spaces = UTF8::str_repeat(' ', $tabLength);
1992 6
    $str = UTF8::str_replace("\t", $spaces, $this->str);
1993
1994 6
    return static::create($str, $this->encoding);
1995
  }
1996
1997
  /**
1998
   * Converts each occurrence of some consecutive number of spaces, as
1999
   * defined by $tabLength, to a tab. By default, each 4 consecutive spaces
2000
   * are converted to a tab.
2001
   *
2002
   * @param  int $tabLength Number of spaces to replace with a tab
2003
   *
2004
   * @return Stringy Object whose $str has had spaces switched to tabs
2005
   */
2006 5 View Code Duplication
  public function toTabs($tabLength = 4)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2007
  {
2008 5
    $spaces = UTF8::str_repeat(' ', $tabLength);
2009 5
    $str = UTF8::str_replace($spaces, "\t", $this->str);
2010
2011 5
    return static::create($str, $this->encoding);
2012
  }
2013
2014
  /**
2015
   * Converts the first character of each word in the string to uppercase.
2016
   *
2017
   * @return Stringy Object with all characters of $str being title-cased
2018
   */
2019 5
  public function toTitleCase()
2020
  {
2021
    // "mb_convert_case()" used a polyfill from the "UTF8"-Class
2022 5
    $str = mb_convert_case($this->str, MB_CASE_TITLE, $this->encoding);
2023
2024 5
    return static::create($str, $this->encoding);
2025
  }
2026
2027
  /**
2028
   * Converts all characters in the string to uppercase. An alias for PHP's
2029
   * UTF8::strtoupper().
2030
   *
2031
   * @return Stringy Object with all characters of $str being uppercase
2032
   */
2033 5
  public function toUpperCase()
2034
  {
2035 5
    $str = UTF8::strtoupper($this->str, $this->encoding);
2036
2037 5
    return static::create($str, $this->encoding);
2038
  }
2039
2040
  /**
2041
   * Returns a string with whitespace removed from the start of the string.
2042
   * Supports the removal of unicode whitespace. Accepts an optional
2043
   * string of characters to strip instead of the defaults.
2044
   *
2045
   * @param  string $chars Optional string of characters to strip
2046
   *
2047
   * @return Stringy Object with a trimmed $str
2048
   */
2049 13 View Code Duplication
  public function trimLeft($chars = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2052 11
      $chars = '[:space:]';
2053 11
    } else {
2054 2
      $chars = preg_quote($chars, '/');
2055
    }
2056
2057 13
    return $this->regexReplace("^[$chars]+", '');
2058
  }
2059
2060
  /**
2061
   * Returns a string with whitespace removed from the end of the string.
2062
   * Supports the removal of unicode whitespace. Accepts an optional
2063
   * string of characters to strip instead of the defaults.
2064
   *
2065
   * @param  string $chars Optional string of characters to strip
2066
   *
2067
   * @return Stringy Object with a trimmed $str
2068
   */
2069 13 View Code Duplication
  public function trimRight($chars = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2072 11
      $chars = '[:space:]';
2073 11
    } else {
2074 2
      $chars = preg_quote($chars, '/');
2075
    }
2076
2077 13
    return $this->regexReplace("[$chars]+\$", '');
2078
  }
2079
2080
  /**
2081
   * Truncates the string to a given length. If $substring is provided, and
2082
   * truncating occurs, the string is further truncated so that the substring
2083
   * may be appended without exceeding the desired length.
2084
   *
2085
   * @param  int    $length    Desired length of the truncated string
2086
   * @param  string $substring The substring to append if it can fit
2087
   *
2088
   * @return Stringy Object with the resulting $str after truncating
2089
   */
2090 22 View Code Duplication
  public function truncate($length, $substring = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2091
  {
2092 22
    $stringy = static::create($this->str, $this->encoding);
2093 22
    if ($length >= $stringy->length()) {
2094 4
      return $stringy;
2095
    }
2096
2097
    // Need to further trim the string so we can append the substring
2098 18
    $substringLength = UTF8::strlen($substring, $stringy->encoding);
2099 18
    $length -= $substringLength;
2100
2101 18
    $truncated = UTF8::substr($stringy->str, 0, $length, $stringy->encoding);
2102 18
    $stringy->str = $truncated . $substring;
2103
2104 18
    return $stringy;
2105
  }
2106
2107
  /**
2108
   * Returns a lowercase and trimmed string separated by underscores.
2109
   * Underscores are inserted before uppercase characters (with the exception
2110
   * of the first character of the string), and in place of spaces as well as
2111
   * dashes.
2112
   *
2113
   * @return Stringy Object with an underscored $str
2114
   */
2115 16
  public function underscored()
2116
  {
2117 16
    return $this->delimit('_');
2118
  }
2119
2120
  /**
2121
   * Returns an UpperCamelCase version of the supplied string. It trims
2122
   * surrounding spaces, capitalizes letters following digits, spaces, dashes
2123
   * and underscores, and removes spaces, dashes, underscores.
2124
   *
2125
   * @return Stringy Object with $str in UpperCamelCase
2126
   */
2127 13
  public function upperCamelize()
2128
  {
2129 13
    return $this->camelize()->upperCaseFirst();
2130
  }
2131
2132
  /**
2133
   * Returns a camelCase version of the string. Trims surrounding spaces,
2134
   * capitalizes letters following digits, spaces, dashes and underscores,
2135
   * and removes spaces, dashes, as well as underscores.
2136
   *
2137
   * @return Stringy Object with $str in camelCase
2138
   */
2139 32
  public function camelize()
2140
  {
2141 32
    $encoding = $this->encoding;
2142 32
    $stringy = $this->trim()->lowerCaseFirst();
2143 32
    $stringy->str = preg_replace('/^[-_]+/', '', $stringy->str);
2144
2145 32
    $stringy->str = preg_replace_callback(
2146 32
        '/[-_\s]+(.)?/u',
2147
        function ($match) use ($encoding) {
2148 27
          if (isset($match[1])) {
2149 27
            return UTF8::strtoupper($match[1], $encoding);
2150
          } else {
2151 1
            return '';
2152
          }
2153 32
        },
2154 32
        $stringy->str
2155 32
    );
2156
2157 32
    $stringy->str = preg_replace_callback(
2158 32
        '/[\d]+(.)?/u',
2159
        function ($match) use ($encoding) {
2160 6
          return UTF8::strtoupper($match[0], $encoding);
2161 32
        },
2162 32
        $stringy->str
2163 32
    );
2164
2165 32
    return $stringy;
2166
  }
2167
2168
  /**
2169
   * Convert a string to e.g.: "snake_case"
2170
   *
2171
   * @return Stringy Object with $str in snake_case
2172
   */
2173 20
  public function snakeize()
2174
  {
2175 20
    $str = $this->str;
2176
2177 20
    $encoding = $this->encoding;
2178 20
    $str = UTF8::normalize_whitespace($str);
2179 20
    $str = str_replace('-', '_', $str);
2180
2181 20
    $str = preg_replace_callback(
2182 20
        '/([\d|A-Z])/u',
2183 20
        function ($matches) use ($encoding) {
2184 8
          $match = $matches[1];
2185 8
          $matchInt = (int)$match;
2186
2187 8
          if ("$matchInt" == $match) {
2188 4
            return '_' . $match . '_';
2189
          } else {
2190 4
            return '_' . UTF8::strtolower($match, $encoding);
2191
          }
2192 20
        },
2193
        $str
2194 20
    );
2195
2196 20
    $str = preg_replace(
2197
        array(
2198
2199 20
            '/\s+/',      // convert spaces to "_"
2200 20
            '/^\s+|\s+$/',  // trim leading & trailing spaces
2201 20
            '/_+/',         // remove double "_"
2202 20
        ),
2203
        array(
2204 20
            '_',
2205 20
            '',
2206 20
            '_',
2207 20
        ),
2208
        $str
2209 20
    );
2210
2211 20
    $str = UTF8::trim($str, '_'); // trim leading & trailing "_"
2212 20
    $str = UTF8::trim($str); // trim leading & trailing whitespace
2213
2214 20
    return static::create($str, $this->encoding);
2215
  }
2216
2217
  /**
2218
   * Converts the first character of the string to lower case.
2219
   *
2220
   * @return Stringy Object with the first character of $str being lower case
2221
   */
2222 37 View Code Duplication
  public function lowerCaseFirst()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2223
  {
2224 37
    $first = UTF8::substr($this->str, 0, 1, $this->encoding);
2225 37
    $rest = UTF8::substr($this->str, 1, $this->length() - 1, $this->encoding);
2226
2227 37
    $str = UTF8::strtolower($first, $this->encoding) . $rest;
0 ignored issues
show
Security Bug introduced by
It seems like $first defined by \voku\helper\UTF8::subst... 0, 1, $this->encoding) on line 2224 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...
2228
2229 37
    return static::create($str, $this->encoding);
2230
  }
2231
2232
  /**
2233
   * Shorten the string after $length, but also after the next word.
2234
   *
2235
   * @param int    $length
2236
   * @param string $strAddOn
2237
   *
2238
   * @return Stringy
2239
   */
2240 4
  public function shortenAfterWord($length, $strAddOn = '...')
2241
  {
2242 4
    $string = UTF8::str_limit_after_word($this->str, $length, $strAddOn);
2243
2244 4
    return static::create($string);
2245
  }
2246
2247
  /**
2248
   * Line-Wrap the string after $limit, but also after the next word.
2249
   *
2250
   * @param int $limit
2251
   *
2252
   * @return Stringy
2253
   */
2254 1
  public function lineWrapAfterWord($limit)
2255
  {
2256 1
    $strings = preg_split('/\\r\\n|\\r|\\n/', $this->str);
2257
2258 1
    $string = '';
2259 1
    foreach ($strings as $value) {
2260 1
      $string .= wordwrap($value, $limit);
2261 1
      $string .= "\n";
2262 1
    }
2263
2264 1
    return static::create($string);
2265
  }
2266
2267
  /**
2268
   * Gets the substring after the first occurrence of a separator.
2269
   * If no match is found returns new empty Stringy object.
2270
   *
2271
   * @param string $separator
2272
   *
2273
   * @return Stringy
2274
   */
2275 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...
2276
  {
2277 1
    if (($offset = $this->indexOf($separator)) === false) {
2278 1
      return static::create('');
2279
    }
2280
2281 1
    return static::create(
2282 1
        UTF8::substr(
2283 1
            $this->str,
2284 1
            $offset + UTF8::strlen($separator, $this->encoding),
2285 1
            null,
2286 1
            $this->encoding
2287 1
        ),
2288 1
        $this->encoding
2289 1
    );
2290
  }
2291
2292
  /**
2293
   * Gets the substring after the last occurrence of a separator.
2294
   * If no match is found returns new empty Stringy object.
2295
   *
2296
   * @param string $separator
2297
   *
2298
   * @return Stringy
2299
   */
2300 1
  public function afterLast($separator)
2301
  {
2302 1
    $offset = $this->indexOfLast($separator);
2303 1
    if ($offset === false) {
2304 1
      return static::create('', $this->encoding);
2305
    }
2306
2307 1
    return static::create(
2308 1
        UTF8::substr(
2309 1
            $this->str,
2310 1
            $offset + UTF8::strlen($separator, $this->encoding),
2311 1
            null,
2312 1
            $this->encoding
2313 1
        ),
2314 1
        $this->encoding
2315 1
    );
2316
  }
2317
2318
  /**
2319
   * Gets the substring before the first occurrence of a separator.
2320
   * If no match is found returns new empty Stringy object.
2321
   *
2322
   * @param string $separator
2323
   *
2324
   * @return Stringy
2325
   */
2326 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...
2327
  {
2328 1
    $offset = $this->indexOf($separator);
2329 1
    if ($offset === false) {
2330 1
      return static::create('', $this->encoding);
2331
    }
2332
2333 1
    return static::create(
2334 1
        UTF8::substr(
2335 1
            $this->str,
2336 1
            0,
2337 1
            $offset,
2338 1
            $this->encoding
2339 1
        ),
2340 1
        $this->encoding
2341 1
    );
2342
  }
2343
2344
  /**
2345
   * Gets the substring before the last occurrence of a separator.
2346
   * If no match is found returns new empty Stringy object.
2347
   *
2348
   * @param string $separator
2349
   *
2350
   * @return Stringy
2351
   */
2352 1 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...
2353
  {
2354 1
    $offset = $this->indexOfLast($separator);
2355 1
    if ($offset === false) {
2356 1
      return static::create('', $this->encoding);
2357
    }
2358
2359 1
    return static::create(
2360 1
        UTF8::substr(
2361 1
            $this->str,
2362 1
            0,
2363 1
            $offset,
2364 1
            $this->encoding
2365 1
        ),
2366 1
        $this->encoding
2367 1
    );
2368
  }
2369
2370
  /**
2371
   * Returns the string with the first letter of each word capitalized,
2372
   * except for when the word is a name which shouldn't be capitalized.
2373
   *
2374
   * @return Stringy Object with $str capitalized
2375
   */
2376 39
  public function capitalizePersonalName()
2377
  {
2378 39
    $stringy = $this->collapseWhitespace();
2379 39
    $stringy->str = $this->capitalizePersonalNameByDelimiter($stringy->str, ' ');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->capitalizePersona...ter($stringy->str, ' ') of type this<Stringy\Stringy> is incompatible with the declared type string of property $str.

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

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

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

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

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

Loading history...
2381
2382 39
    return static::create($stringy, $this->encoding);
2383
  }
2384
2385
  /**
2386
   * @param string $word
2387
   *
2388
   * @return string
2389
   */
2390 7
  private function capitalizeWord($word)
2391
  {
2392 7
    $encoding = $this->encoding;
2393
2394 7
    $firstCharacter = UTF8::substr($word, 0, 1, $encoding);
2395 7
    $restOfWord = UTF8::substr($word, 1, null, $encoding);
2396 7
    $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 2394 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...
2397
2398 7
    return new static($firstCharacterUppercased . $restOfWord, $encoding);
2399
  }
2400
2401
  /**
2402
   * Personal names such as "Marcus Aurelius" are sometimes typed incorrectly using lowercase ("marcus aurelius").
2403
   *
2404
   * @param string $names
2405
   * @param string $delimiter
2406
   *
2407
   * @return string
2408
   */
2409 39
  private function capitalizePersonalNameByDelimiter($names, $delimiter)
2410
  {
2411
    // init
2412 39
    $names = explode($delimiter, $names);
2413 39
    $encoding = $this->encoding;
2414
2415
    $specialCases = array(
2416
        'names'    => array(
2417 39
            'ab',
2418 39
            'af',
2419 39
            'al',
2420 39
            'and',
2421 39
            'ap',
2422 39
            'bint',
2423 39
            'binte',
2424 39
            'da',
2425 39
            'de',
2426 39
            'del',
2427 39
            'den',
2428 39
            'der',
2429 39
            'di',
2430 39
            'dit',
2431 39
            'ibn',
2432 39
            'la',
2433 39
            'mac',
2434 39
            'nic',
2435 39
            'of',
2436 39
            'ter',
2437 39
            'the',
2438 39
            'und',
2439 39
            'van',
2440 39
            'von',
2441 39
            'y',
2442 39
            'zu',
2443 39
        ),
2444
        'prefixes' => array(
2445 39
            'al-',
2446 39
            "d'",
2447 39
            'ff',
2448 39
            "l'",
2449 39
            'mac',
2450 39
            'mc',
2451 39
            'nic',
2452 39
        ),
2453 39
    );
2454
2455 39
    foreach ($names as &$name) {
2456 39
      if (in_array($name, $specialCases['names'], true)) {
2457 27
        continue;
2458
      }
2459
2460 13
      $continue = false;
2461
2462 13
      if ($delimiter == '-') {
2463 13 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...
2464 13
          if (UTF8::strpos($name, $beginning, null, $encoding) === 0) {
2465 2
            $continue = true;
2466 2
          }
2467 13
        }
2468 13
      }
2469
2470 13 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...
2471 13
        if (UTF8::strpos($name, $beginning, null, $encoding) === 0) {
2472 7
          $continue = true;
2473 7
        }
2474 13
      }
2475
2476 13
      if ($continue) {
2477 7
        continue;
2478
      }
2479
2480 7
      $name = $this->capitalizeWord($name);
2481 39
    }
2482
2483 39
    return new static(implode($delimiter, $names), $encoding);
2484
  }
2485
}
2486