Passed
Push — master ( d1cfcd...93bfe2 )
by Alexander
01:23
created

StringHelper::endsWithIgnoringCase()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 8
ccs 5
cts 5
cp 1
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Strings;
6
7
use function array_slice;
8
use function htmlspecialchars;
9
use function mb_strlen;
10
use function mb_strtolower;
11
use function mb_strtoupper;
12
use function mb_substr;
13
14
/**
15
 * Provides static methods allowing you to deal with strings more efficiently.
16
 */
17
final class StringHelper
18
{
19
    /**
20
     * Returns the number of bytes in the given string.
21
     * This method ensures the string is treated as a byte array even if `mbstring.func_overload` is turned on
22
     * by using {@see mb_strlen()}.
23
     * @param string|null $input The string being measured for length.
24
     * @return int The number of bytes in the given string.
25
     */
26 40
    public static function byteLength(?string $input): int
27
    {
28 40
        return mb_strlen((string)$input, '8bit');
29
    }
30
31
    /**
32
     * Returns the portion of string specified by the start and length parameters.
33
     * This method ensures the string is treated as a byte array by using `mb_substr()`.
34
     * @param string $input The input string. Must be one character or longer.
35
     * @param int $start The starting position.
36
     * @param int|null $length The desired portion length. If not specified or `null`, there will be
37
     * no limit on length i.e. the output will be until the end of the string.
38
     * @return string The extracted part of string, or FALSE on failure or an empty string.
39
     * @see http://www.php.net/manual/en/function.substr.php
40
     */
41 1
    public static function byteSubstr(string $input, int $start, int $length = null): string
42
    {
43 1
        return mb_substr($input, $start, $length ?? mb_strlen($input, '8bit'), '8bit');
44
    }
45
46
    /**
47
     * Returns the trailing name component of a path.
48
     * This method is similar to the php function `basename()` except that it will
49
     * treat both \ and / as directory separators, independent of the operating system.
50
     * This method was mainly created to work on php namespaces. When working with real
51
     * file paths, PHP's `basename()` should work fine for you.
52
     * Note: this method is not aware of the actual filesystem, or path components such as "..".
53
     *
54
     * @param string $path A path string.
55
     * @param string $suffix If the name component ends in suffix this will also be cut off.
56
     * @return string The trailing name component of the given path.
57
     * @see http://www.php.net/manual/en/function.basename.php
58
     */
59 1
    public static function basename(string $path, string $suffix = ''): string
60
    {
61 1
        $length = mb_strlen($suffix);
62 1
        if ($length > 0 && mb_substr($path, -$length) === $suffix) {
63 1
            $path = mb_substr($path, 0, -$length);
64
        }
65 1
        $path = rtrim(str_replace('\\', '/', $path), '/\\');
66 1
        $position = mb_strrpos($path, '/');
67 1
        if ($position !== false) {
68 1
            return mb_substr($path, $position + 1);
69
        }
70
71 1
        return $path;
72
    }
73
74
    /**
75
     * Returns parent directory's path.
76
     * This method is similar to `dirname()` except that it will treat
77
     * both \ and / as directory separators, independent of the operating system.
78
     *
79
     * @param string $path A path string.
80
     * @return string The parent directory's path.
81
     * @see http://www.php.net/manual/en/function.basename.php
82
     */
83 1
    public static function dirname(string $path): string
84
    {
85 1
        $position = mb_strrpos(str_replace('\\', '/', $path), '/');
86 1
        if ($position !== false) {
87 1
            return mb_substr($path, 0, $position);
88
        }
89
90 1
        return '';
91
    }
92
93
    /**
94
     * Truncates a string to the number of characters specified.
95
     *
96
     * @param string $input The string to truncate.
97
     * @param int $length How many characters from original string to include into truncated string.
98
     * @param string $suffix String to append to the end of truncated string.
99
     * @param string $encoding The encoding to use, defaults to "UTF-8".
100
     * @return string The truncated string.
101
     */
102 1
    public static function truncateCharacters(string $input, int $length, string $suffix = '…', string $encoding = 'UTF-8'): string
103
    {
104 1
        if (static::strlen($input, $encoding) > $length) {
105 1
            return rtrim(static::substr($input, 0, $length, $encoding)) . $suffix;
106
        }
107
108 1
        return $input;
109
    }
110
111
    /**
112
     * Truncates a string to the number of words specified.
113
     *
114
     * @param string $input The string to truncate.
115
     * @param int $count How many words from original string to include into truncated string.
116
     * @param string $suffix String to append to the end of truncated string.
117
     * @return string The truncated string.
118
     */
119 1
    public static function truncateWords(string $input, int $count, string $suffix = '…'): string
120
    {
121 1
        $words = preg_split('/(\s+)/u', trim($input), -1, PREG_SPLIT_DELIM_CAPTURE);
122 1
        if (count($words) / 2 > $count) {
0 ignored issues
show
Bug introduced by
It seems like $words can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

122
        if (count(/** @scrutinizer ignore-type */ $words) / 2 > $count) {
Loading history...
123 1
            return implode('', array_slice($words, 0, ($count * 2) - 1)) . $suffix;
0 ignored issues
show
Bug introduced by
It seems like $words can also be of type false; however, parameter $array of array_slice() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

123
            return implode('', array_slice(/** @scrutinizer ignore-type */ $words, 0, ($count * 2) - 1)) . $suffix;
Loading history...
124
        }
125
126 1
        return $input;
127
    }
128
129
    /**
130
     * Truncate the string from the beginning.
131
     *
132
     * @param string $input String to process.
133
     * @param int $length Total of character to truncate.
134
     * @param string $suffix String to append to the beginning.
135
     * @return string
136
     */
137 1
    public static function truncateBegin(string $input, int $length, string $suffix = '…'): string
138
    {
139 1
        return substr_replace($input, $suffix, 0, $length);
140
    }
141
142
    /**
143
     * Truncates a string in the middle. Keeping start and end.
144
     * `StringHelper::truncateMiddle('Hello world number 2', 8)` produces "Hell...er 2".
145
     *
146
     * This method does not support HTML. It will strip all tags even if length is smaller than the string including tags.
147
     *
148
     * @param string $input The string to truncate.
149
     * @param int $length How many characters from original string to include into truncated string.
150
     * @param string $separator String to append in the middle of truncated string.
151
     * @param string $encoding The encoding to use, defaults to "UTF-8".
152
     * @return string The truncated string.
153
     */
154 2
    public static function truncateMiddle(string $input, int $length, string $separator = '...', string $encoding = 'UTF-8'): string
155
    {
156 2
        $strLen = mb_strlen($input, $encoding);
157
158 2
        if ($strLen <= $length) {
159 1
            return $input;
160
        }
161
162 1
        $partLen = (int)(floor($length / 2));
163 1
        $left = ltrim(mb_substr($input, 0, $partLen, $encoding));
164 1
        $right = rtrim(mb_substr($input, -$partLen, $partLen, $encoding));
165
166 1
        return $left . $separator . $right;
167
    }
168
169
    /**
170
     * Check if given string starts with specified substring.
171
     * Binary and multibyte safe.
172
     *
173
     * @param string $input Input string.
174
     * @param string|null $with Part to search inside the $string.
175
     * @return bool Returns true if first input starts with second input, false otherwise.
176
     */
177 19
    public static function startsWith(string $input, ?string $with): bool
178
    {
179 19
        $bytes = static::byteLength($with);
180 19
        if ($bytes === 0) {
181 3
            return true;
182
        }
183
184 16
        return strncmp($input, $with, $bytes) === 0;
185
    }
186
187
    /**
188
     * Check if given string starts with specified substring ignoring case.
189
     * Binary and multibyte safe.
190
     *
191
     * @param string $input Input string.
192
     * @param string|null $with Part to search inside the $string.
193
     * @return bool Returns true if first input starts with second input, false otherwise.
194
     */
195 1
    public static function startsWithIgnoringCase(string $input, ?string $with): bool
196
    {
197 1
        $bytes = static::byteLength($with);
198 1
        if ($bytes === 0) {
199 1
            return true;
200
        }
201
202 1
        return static::strtolower(static::substr($input, 0, $bytes, '8bit')) === static::strtolower($with);
0 ignored issues
show
Bug introduced by
It seems like $with can also be of type null; however, parameter $string of Yiisoft\Strings\StringHelper::strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

202
        return static::strtolower(static::substr($input, 0, $bytes, '8bit')) === static::strtolower(/** @scrutinizer ignore-type */ $with);
Loading history...
203
    }
204
205
    /**
206
     * Check if given string ends with specified substring.
207
     * Binary and multibyte safe.
208
     *
209
     * @param string $input Input string to check.
210
     * @param string|null $with Part to search inside of the $string.
211
     * @return bool Returns true if first input ends with second input, false otherwise.
212
     */
213 19
    public static function endsWith(string $input, ?string $with): bool
214
    {
215 19
        $bytes = static::byteLength($with);
216 19
        if ($bytes === 0) {
217 3
            return true;
218
        }
219
220
        // Warning check, see http://php.net/manual/en/function.substr-compare.php#refsect1-function.substr-compare-returnvalues
221 16
        if (static::byteLength($input) < $bytes) {
222 3
            return false;
223
        }
224
225 13
        return substr_compare($input, $with, -$bytes, $bytes) === 0;
226
    }
227
228
    /**
229
     * Check if given string ends with specified substring.
230
     * Binary and multibyte safe.
231
     *
232
     * @param string $input Input string to check.
233
     * @param string|null $with Part to search inside of the $string.
234
     * @return bool Returns true if first input ends with second input, false otherwise.
235
     */
236 1
    public static function endsWithIgnoringCase(string $input, ?string $with): bool
237
    {
238 1
        $bytes = static::byteLength($with);
239 1
        if ($bytes === 0) {
240 1
            return true;
241
        }
242
243 1
        return static::strtolower(mb_substr($input, -$bytes, mb_strlen($input, '8bit'), '8bit')) === static::strtolower($with);
0 ignored issues
show
Bug introduced by
It seems like $with can also be of type null; however, parameter $string of Yiisoft\Strings\StringHelper::strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

243
        return static::strtolower(mb_substr($input, -$bytes, mb_strlen($input, '8bit'), '8bit')) === static::strtolower(/** @scrutinizer ignore-type */ $with);
Loading history...
244
    }
245
246
    /**
247
     * Explodes string into array, optionally trims values and skips empty ones.
248
     *
249
     * @param string $input String to be exploded.
250
     * @param string $delimiter Delimiter. Default is ','.
251
     * @param mixed $trim Whether to trim each element. Can be:
252
     *   - boolean - to trim normally;
253
     *   - string - custom characters to trim. Will be passed as a second argument to `trim()` function.
254
     *   - callable - will be called for each value instead of trim. Takes the only argument - value.
255
     * @param bool $skipEmpty Whether to skip empty strings between delimiters. Default is false.
256
     * @return array
257
     */
258 1
    public static function explode(string $input, string $delimiter = ',', $trim = true, bool $skipEmpty = false): array
259
    {
260 1
        $result = explode($delimiter, $input);
261 1
        if ($trim !== false) {
262 1
            if ($trim === true) {
263 1
                $trim = 'trim';
264 1
            } elseif (!\is_callable($trim)) {
265 1
                $trim = static function ($v) use ($trim) {
266 1
                    return trim($v, $trim);
267 1
                };
268
            }
269 1
            $result = array_map($trim, $result);
270
        }
271 1
        if ($skipEmpty) {
272
            // Wrapped with array_values to make array keys sequential after empty values removing
273 1
            $result = array_values(array_filter($result, static function ($value) {
274 1
                return $value !== '';
275 1
            }));
276
        }
277
278 1
        return $result;
279
    }
280
281
    /**
282
     * Counts words in a string.
283
     *
284
     * @param string $input
285
     * @return int
286
     */
287 1
    public static function countWords(string $input): int
288
    {
289 1
        return count(preg_split('/\s+/u', $input, -1, PREG_SPLIT_NO_EMPTY));
0 ignored issues
show
Bug introduced by
It seems like preg_split('/\s+/u', $in...gs\PREG_SPLIT_NO_EMPTY) can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

289
        return count(/** @scrutinizer ignore-type */ preg_split('/\s+/u', $input, -1, PREG_SPLIT_NO_EMPTY));
Loading history...
290
    }
291
292
    /**
293
     * Returns string representation of number value with replaced commas to dots, if decimal point
294
     * of current locale is comma.
295
     * @param int|float|string $value
296
     * @return string
297
     */
298
    public static function normalizeNumber($value): string
299
    {
300
        $value = (string)$value;
301
302
        $localeInfo = localeconv();
303
        $decimalSeparator = $localeInfo['decimal_point'] ?? null;
304
305
        if ($decimalSeparator !== null && $decimalSeparator !== '.') {
306
            $value = str_replace($decimalSeparator, '.', $value);
307
        }
308
309
        return $value;
310
    }
311
312
    /**
313
     * Encodes string into "Base 64 Encoding with URL and Filename Safe Alphabet" (RFC 4648).
314
     *
315
     * > Note: Base 64 padding `=` may be at the end of the returned string.
316
     * > `=` is not transparent to URL encoding.
317
     *
318
     * @see https://tools.ietf.org/html/rfc4648#page-7
319
     * @param string $input The string to encode.
320
     * @return string Encoded string.
321
     */
322 4
    public static function base64UrlEncode(string $input): string
323
    {
324 4
        return strtr(base64_encode($input), '+/', '-_');
325
    }
326
327
    /**
328
     * Decodes "Base 64 Encoding with URL and Filename Safe Alphabet" (RFC 4648).
329
     *
330
     * @see https://tools.ietf.org/html/rfc4648#page-7
331
     * @param string $input Encoded string.
332
     * @return string Decoded string.
333
     */
334 4
    public static function base64UrlDecode(string $input): string
335
    {
336 4
        return base64_decode(strtr($input, '-_', '+/'));
337
    }
338
339
    /**
340
     * Safely casts a float to string independent of the current locale.
341
     *
342
     * The decimal separator will always be `.`.
343
     * @param float|int $number A floating point number or integer.
344
     * @return string The string representation of the number.
345
     */
346 1
    public static function floatToString($number): string
347
    {
348
        // . and , are the only decimal separators known in ICU data,
349
        // so its safe to call str_replace here
350 1
        return str_replace(',', '.', (string) $number);
351
    }
352
353
    /**
354
     * Checks if the passed string would match the given shell wildcard pattern.
355
     * This function emulates {@see fnmatch()}, which may be unavailable at certain environment, using PCRE.
356
     * @param string $pattern The shell wildcard pattern.
357
     * @param string $string The tested string.
358
     * @param array $options Options for matching. Valid options are:
359
     *
360
     * - caseSensitive: bool, whether pattern should be case sensitive. Defaults to `true`.
361
     * - escape: bool, whether backslash escaping is enabled. Defaults to `true`.
362
     * - filePath: bool, whether slashes in string only matches slashes in the given pattern. Defaults to `false`.
363
     *
364
     * @return bool Whether the string matches pattern or not.
365
     */
366 54
    public static function matchWildcard(string $pattern, string $string, array $options = []): bool
367
    {
368 54
        if ($pattern === '*' && empty($options['filePath'])) {
369 2
            return true;
370
        }
371
372
        $replacements = [
373 52
            '\\\\\\\\' => '\\\\',
374
            '\\\\\\*' => '[*]',
375
            '\\\\\\?' => '[?]',
376
            '\*' => '.*',
377
            '\?' => '.',
378
            '\[\!' => '[^',
379
            '\[' => '[',
380
            '\]' => ']',
381
            '\-' => '-',
382
        ];
383
384 52
        if (isset($options['escape']) && !$options['escape']) {
385 5
            unset($replacements['\\\\\\\\'], $replacements['\\\\\\*'], $replacements['\\\\\\?']);
386
        }
387
388 52
        if (!empty($options['filePath'])) {
389 10
            $replacements['\*'] = '[^/\\\\]*';
390 10
            $replacements['\?'] = '[^/\\\\]';
391
        }
392
393 52
        $pattern = strtr(preg_quote($pattern, '#'), $replacements);
394 52
        $pattern = '#^' . $pattern . '$#us';
395
396 52
        if (isset($options['caseSensitive']) && !$options['caseSensitive']) {
397 1
            $pattern .= 'i';
398
        }
399
400 52
        return preg_match($pattern, $string) === 1;
401
    }
402
403
    /**
404
     * This method provides a unicode-safe implementation of built-in PHP function `ucfirst()`.
405
     *
406
     * @param string $string The string to be processed.
407
     * @param string $encoding The encoding to use, defaults to "UTF-8".
408
     * @return string
409
     * @see https://php.net/manual/en/function.ucfirst.php
410
     */
411 23
    public static function ucfirst(string $string, string $encoding = 'UTF-8'): string
412
    {
413 23
        $firstChar = static::substr($string, 0, 1, $encoding);
414 23
        $rest = static::substr($string, 1, null, $encoding);
415
416 23
        return static::strtoupper($firstChar, $encoding) . $rest;
417
    }
418
419
    /**
420
     * This method provides a unicode-safe implementation of built-in PHP function `ucwords()`.
421
     *
422
     * @param string $string The string to be processed.
423
     * @param string $encoding The encoding to use, defaults to "UTF-8".
424
     * @see https://php.net/manual/en/function.ucwords.php
425
     * @return string
426
     */
427 19
    public static function ucwords(string $string, string $encoding = 'UTF-8'): string
428
    {
429 19
        $words = preg_split("/\s/u", $string, -1, PREG_SPLIT_NO_EMPTY);
430
431 19
        $ucfirst = array_map(static function ($word) use ($encoding) {
432 18
            return static::ucfirst($word, $encoding);
433 19
        }, $words);
0 ignored issues
show
Bug introduced by
It seems like $words can also be of type false; however, parameter $arr1 of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

433
        }, /** @scrutinizer ignore-type */ $words);
Loading history...
434
435 19
        return implode(' ', $ucfirst);
436
    }
437
438
    /**
439
     * Get string length.
440
     *
441
     * @param string $string String to calculate length for.
442
     * @param string $encoding The encoding to use, defaults to "UTF-8".
443
     * @see https://php.net/manual/en/function.mb-strlen.php
444
     * @return int
445
     */
446 2
    public static function strlen(string $string, string $encoding = 'UTF-8'): int
447
    {
448 2
        return mb_strlen($string, $encoding);
449
    }
450
451
    /**
452
     * Get part of string.
453
     *
454
     * @param string $string To get substring from.
455
     * @param int $start Character to start at.
456
     * @param int|null $length Number of characters to get.
457
     * @param string $encoding The encoding to use, defaults to "UTF-8".
458
     * @see https://php.net/manual/en/function.mb-substr.php
459
     * @return string
460
     */
461 25
    public static function substr(string $string, int $start, int $length = null, string $encoding = 'UTF-8'): string
462
    {
463 25
        return mb_substr($string, $start, $length, $encoding);
464
    }
465
466
    /**
467
     * Make a string lowercase.
468
     *
469
     * @param string $string String to process.
470
     * @param string $encoding The encoding to use, defaults to "UTF-8".
471
     * @see https://php.net/manual/en/function.mb-strtolower.php
472
     * @return string
473
     */
474 3
    public static function strtolower(string $string, string $encoding = 'UTF-8'): string
475
    {
476 3
        return mb_strtolower($string, $encoding);
477
    }
478
479
    /**
480
     * Make a string uppercase.
481
     *
482
     * @param string $string String to process.
483
     * @param string $encoding The encoding to use, defaults to "UTF-8".
484
     * @see https://php.net/manual/en/function.mb-strtoupper.php
485
     * @return string
486
     */
487 24
    public static function strtoupper(string $string, string $encoding = 'UTF-8'): string
488
    {
489 24
        return mb_strtoupper($string, $encoding);
490
    }
491
492
    /**
493
     * Convert special characters to HTML entities.
494
     *
495
     * @param string $string String to process.
496
     * @param int $flags A bitmask of one or more flags.
497
     * @param bool $doubleEncode If set to false, method will not encode existing HTML entities.
498
     * @param string|null $encoding The encoding to use, defaults to `ini_get('default_charset')`.
499
     * @return string
500
     * @see https://php.net/manual/en/function.htmlspecialchars.php
501
     */
502 1
    public static function htmlspecialchars(string $string, int $flags, bool $doubleEncode = true, string $encoding = null): string
503
    {
504 1
        return $encoding === null && $doubleEncode
505 1
            ? htmlspecialchars($string, $flags)
506 1
            : htmlspecialchars($string, $flags, $encoding ?: ini_get('default_charset'), $doubleEncode);
507
    }
508
}
509