Passed
Push — master ( cd4f87...bcd86c )
by Alexander
01:46
created

StringHelper::base64UrlDecode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
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
 * The Yii string helper 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 by using `mb_strlen()`.
22
     * @param string $string the string being measured for length
23
     * @return int the number of bytes in the given string.
24
     */
25 39
    public static function byteLength(?string $string): int
26
    {
27 39
        return mb_strlen((string)$string, '8bit');
28
    }
29
30
    /**
31
     * Returns the portion of string specified by the start and length parameters.
32
     * This method ensures the string is treated as a byte array by using `mb_substr()`.
33
     * @param string $string the input string. Must be one character or longer.
34
     * @param int $start the starting position
35
     * @param int $length the desired portion length. If not specified or `null`, there will be
36
     * no limit on length i.e. the output will be until the end of the string.
37
     * @return string the extracted part of string, or FALSE on failure or an empty string.
38
     * @see http://www.php.net/manual/en/function.substr.php
39
     */
40 1
    public static function byteSubstr(string $string, int $start, int $length = null): string
41
    {
42 1
        return mb_substr($string, $start, $length ?? mb_strlen($string, '8bit'), '8bit');
43
    }
44
45
    /**
46
     * Returns the trailing name component of a path.
47
     * This method is similar to the php function `basename()` except that it will
48
     * treat both \ and / as directory separators, independent of the operating system.
49
     * This method was mainly created to work on php namespaces. When working with real
50
     * file paths, PHP's `basename()` should work fine for you.
51
     * Note: this method is not aware of the actual filesystem, or path components such as "..".
52
     *
53
     * @param string $path A path string.
54
     * @param string $suffix If the name component ends in suffix this will also be cut off.
55
     * @return string the trailing name component of the given path.
56
     * @see http://www.php.net/manual/en/function.basename.php
57
     */
58 1
    public static function basename(string $path, string $suffix = ''): string
59
    {
60 1
        if (($len = mb_strlen($suffix)) > 0 && mb_substr($path, -$len) === $suffix) {
61 1
            $path = mb_substr($path, 0, -$len);
62
        }
63 1
        $path = rtrim(str_replace('\\', '/', $path), '/\\');
64 1
        if (($pos = mb_strrpos($path, '/')) !== false) {
65 1
            return mb_substr($path, $pos + 1);
66
        }
67
68 1
        return $path;
69
    }
70
71
    /**
72
     * Returns parent directory's path.
73
     * This method is similar to `dirname()` except that it will treat
74
     * both \ and / as directory separators, independent of the operating system.
75
     *
76
     * @param string $path A path string.
77
     * @return string the parent directory's path.
78
     * @see http://www.php.net/manual/en/function.basename.php
79
     */
80 1
    public static function dirname(string $path): string
81
    {
82 1
        $pos = mb_strrpos(str_replace('\\', '/', $path), '/');
83 1
        if ($pos !== false) {
84 1
            return mb_substr($path, 0, $pos);
85
        }
86
87 1
        return '';
88
    }
89
90
    /**
91
     * Truncates a string to the number of characters specified.
92
     *
93
     * @param string $string The string to truncate.
94
     * @param int $length How many characters from original string to include into truncated string.
95
     * @param string $suffix String to append to the end of truncated string.
96
     * @param string $encoding The charset to use, defaults to charset currently used by application.
97
     * @return string the truncated string.
98
     */
99 1
    public static function truncateCharacters(string $string, int $length, string $suffix = '…', string $encoding = null): string
100
    {
101 1
        if (static::strlen($string, $encoding) > $length) {
102 1
            return rtrim(static::substr($string, 0, $length, $encoding)) . $suffix;
103
        }
104
105 1
        return $string;
106
    }
107
108
    /**
109
     * Truncates a string to the number of words specified.
110
     *
111
     * @param string $string The string to truncate.
112
     * @param int $count How many words from original string to include into truncated string.
113
     * @param string $suffix String to append to the end of truncated string.
114
     * @return string the truncated string.
115
     */
116 1
    public static function truncateWords(string $string, int $count, string $suffix = '…'): string
117
    {
118 1
        $words = preg_split('/(\s+)/u', trim($string), -1, PREG_SPLIT_DELIM_CAPTURE);
119 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

119
        if (count(/** @scrutinizer ignore-type */ $words) / 2 > $count) {
Loading history...
120 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

120
            return implode('', array_slice(/** @scrutinizer ignore-type */ $words, 0, ($count * 2) - 1)) . $suffix;
Loading history...
121
        }
122
123 1
        return $string;
124
    }
125
126
    /**
127
     * Truncate the string from the beginning
128
     *
129
     * @param string $string string to process
130
     * @param int $length total of character to truncate
131
     * @param string $suffix String to append to the beginning
132
     * @return string
133
     */
134 1
    public static function truncateBegin(string $string, int $length, string $suffix = '…'): string
135
    {
136 1
        return substr_replace($string, $suffix, 0, $length);
137
    }
138
139
    /**
140
     * Truncates a string in the middle. Keeping start and end.
141
     * `StringHelper::truncateMiddle('Hello world number 2', 8)` produces "Hell...er 2".
142
     *
143
     * This method does not support HTML. It will strip all tags even if length is smaller than the string including tags.
144
     *
145
     * @param string $string The string to truncate.
146
     * @param int $length How many characters from original string to include into truncated string.
147
     * @param string $separator String to append in the middle of truncated string.
148
     * @param string $encoding The charset to use, defaults to charset currently used by application.
149
     * @return string the truncated string.
150
     */
151 2
    public static function truncateMiddle(string $string, int $length, string $separator = '...', string $encoding = 'UTF-8'): string
152
    {
153 2
        $strLen = mb_strlen($string, $encoding);
154
155 2
        if ($strLen <= $length) {
156 1
            return $string;
157
        }
158
159 1
        $partLen = (int)(floor($length / 2));
160 1
        $left = ltrim(mb_substr($string, 0, $partLen, $encoding));
161 1
        $right = rtrim(mb_substr($string, -$partLen, $partLen, $encoding));
162
163 1
        return $left . $separator . $right;
164
    }
165
166
    /**
167
     * Check if given string starts with specified substring.
168
     * Binary and multibyte safe.
169
     *
170
     * @param string $string Input string
171
     * @param string $with Part to search inside the $string
172
     * @param bool $caseSensitive Case sensitive search. Default is true. When case sensitive is enabled, $with must exactly match the starting of the string in order to get a true value.
173
     * @return bool Returns true if first input starts with second input, false otherwise
174
     */
175 19
    public static function startsWith(string $string, ?string $with, bool $caseSensitive = true): bool
176
    {
177 19
        if (!$bytes = static::byteLength($with)) {
178 3
            return true;
179
        }
180 16
        if ($caseSensitive) {
181 15
            return strncmp($string, $with, $bytes) === 0;
182
        }
183
184 15
        return static::strtolower(static::substr($string, 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

184
        return static::strtolower(static::substr($string, 0, $bytes, '8bit')) === static::strtolower(/** @scrutinizer ignore-type */ $with);
Loading history...
185
    }
186
187
    /**
188
     * Check if given string ends with specified substring.
189
     * Binary and multibyte safe.
190
     *
191
     * @param string $string Input string to check
192
     * @param string $with Part to search inside of the $string.
193
     * @param bool $caseSensitive Case sensitive search. Default is true. When case sensitive is enabled, $with must exactly match the ending of the string in order to get a true value.
194
     * @return bool Returns true if first input ends with second input, false otherwise
195
     */
196 19
    public static function endsWith(string $string, ?string $with, bool $caseSensitive = true): bool
197
    {
198 19
        if (!$bytes = static::byteLength($with)) {
199 3
            return true;
200
        }
201 16
        if ($caseSensitive) {
202
            // Warning check, see http://php.net/manual/en/function.substr-compare.php#refsect1-function.substr-compare-returnvalues
203 15
            if (static::byteLength($string) < $bytes) {
204 3
                return false;
205
            }
206
207 12
            return substr_compare($string, $with, -$bytes, $bytes) === 0;
208
        }
209
210 15
        return static::strtolower(mb_substr($string, -$bytes, mb_strlen($string, '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

210
        return static::strtolower(mb_substr($string, -$bytes, mb_strlen($string, '8bit'), '8bit')) === static::strtolower(/** @scrutinizer ignore-type */ $with);
Loading history...
211
    }
212
213
    /**
214
     * Explodes string into array, optionally trims values and skips empty ones.
215
     *
216
     * @param string $string String to be exploded.
217
     * @param string $delimiter Delimiter. Default is ','.
218
     * @param mixed $trim Whether to trim each element. Can be:
219
     *   - boolean - to trim normally;
220
     *   - string - custom characters to trim. Will be passed as a second argument to `trim()` function.
221
     *   - callable - will be called for each value instead of trim. Takes the only argument - value.
222
     * @param bool $skipEmpty Whether to skip empty strings between delimiters. Default is false.
223
     * @return array
224
     */
225 1
    public static function explode(string $string, string $delimiter = ',', $trim = true, bool $skipEmpty = false): array
226
    {
227 1
        $result = explode($delimiter, $string);
228 1
        if ($trim !== false) {
229 1
            if ($trim === true) {
230 1
                $trim = 'trim';
231 1
            } elseif (!is_callable($trim)) {
232 1
                $trim = static function ($v) use ($trim) {
233 1
                    return trim($v, $trim);
234 1
                };
235
            }
236 1
            $result = array_map($trim, $result);
237
        }
238 1
        if ($skipEmpty) {
239
            // Wrapped with array_values to make array keys sequential after empty values removing
240 1
            $result = array_values(array_filter($result, static function ($value) {
241 1
                return $value !== '';
242 1
            }));
243
        }
244
245 1
        return $result;
246
    }
247
248
    /**
249
     * Counts words in a string.
250
     *
251
     * @param string $string
252
     * @return int
253
     */
254 1
    public static function countWords(string $string): int
255
    {
256 1
        return count(preg_split('/\s+/u', $string, -1, PREG_SPLIT_NO_EMPTY));
0 ignored issues
show
Bug introduced by
It seems like preg_split('/\s+/u', $st...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

256
        return count(/** @scrutinizer ignore-type */ preg_split('/\s+/u', $string, -1, PREG_SPLIT_NO_EMPTY));
Loading history...
257
    }
258
259
    /**
260
     * Returns string representation of number value with replaced commas to dots, if decimal point
261
     * of current locale is comma.
262
     * @param int|float|string $value
263
     * @return string
264
     */
265
    public static function normalizeNumber($value): string
266
    {
267
        $value = (string)$value;
268
269
        $localeInfo = localeconv();
270
        $decimalSeparator = $localeInfo['decimal_point'] ?? null;
271
272
        if ($decimalSeparator !== null && $decimalSeparator !== '.') {
273
            $value = str_replace($decimalSeparator, '.', $value);
274
        }
275
276
        return $value;
277
    }
278
279
    /**
280
     * Encodes string into "Base 64 Encoding with URL and Filename Safe Alphabet" (RFC 4648).
281
     *
282
     * > Note: Base 64 padding `=` may be at the end of the returned string.
283
     * > `=` is not transparent to URL encoding.
284
     *
285
     * @see https://tools.ietf.org/html/rfc4648#page-7
286
     * @param string $input the string to encode.
287
     * @return string encoded string.
288
     */
289 4
    public static function base64UrlEncode(string $input): string
290
    {
291 4
        return strtr(base64_encode($input), '+/', '-_');
292
    }
293
294
    /**
295
     * Decodes "Base 64 Encoding with URL and Filename Safe Alphabet" (RFC 4648).
296
     *
297
     * @see https://tools.ietf.org/html/rfc4648#page-7
298
     * @param string $input encoded string.
299
     * @return string decoded string.
300
     */
301 4
    public static function base64UrlDecode(string $input): string
302
    {
303 4
        return base64_decode(strtr($input, '-_', '+/'));
304
    }
305
306
    /**
307
     * Safely casts a float to string independent of the current locale.
308
     *
309
     * The decimal separator will always be `.`.
310
     * @param float|int $number a floating point number or integer.
311
     * @return string the string representation of the number.
312
     */
313 1
    public static function floatToString($number): string
314
    {
315
        // . and , are the only decimal separators known in ICU data,
316
        // so its safe to call str_replace here
317 1
        return str_replace(',', '.', (string) $number);
318
    }
319
320
    /**
321
     * Checks if the passed string would match the given shell wildcard pattern.
322
     * This function emulates [[fnmatch()]], which may be unavailable at certain environment, using PCRE.
323
     * @param string $pattern the shell wildcard pattern.
324
     * @param string $string the tested string.
325
     * @param array $options options for matching. Valid options are:
326
     *
327
     * - caseSensitive: bool, whether pattern should be case sensitive. Defaults to `true`.
328
     * - escape: bool, whether backslash escaping is enabled. Defaults to `true`.
329
     * - filePath: bool, whether slashes in string only matches slashes in the given pattern. Defaults to `false`.
330
     *
331
     * @return bool whether the string matches pattern or not.
332
     */
333 54
    public static function matchWildcard(string $pattern, string $string, array $options = []): bool
334
    {
335 54
        if ($pattern === '*' && empty($options['filePath'])) {
336 2
            return true;
337
        }
338
339
        $replacements = [
340 52
            '\\\\\\\\' => '\\\\',
341
            '\\\\\\*' => '[*]',
342
            '\\\\\\?' => '[?]',
343
            '\*' => '.*',
344
            '\?' => '.',
345
            '\[\!' => '[^',
346
            '\[' => '[',
347
            '\]' => ']',
348
            '\-' => '-',
349
        ];
350
351 52
        if (isset($options['escape']) && !$options['escape']) {
352 5
            unset($replacements['\\\\\\\\'], $replacements['\\\\\\*'], $replacements['\\\\\\?']);
353
        }
354
355 52
        if (!empty($options['filePath'])) {
356 10
            $replacements['\*'] = '[^/\\\\]*';
357 10
            $replacements['\?'] = '[^/\\\\]';
358
        }
359
360 52
        $pattern = strtr(preg_quote($pattern, '#'), $replacements);
361 52
        $pattern = '#^' . $pattern . '$#us';
362
363 52
        if (isset($options['caseSensitive']) && !$options['caseSensitive']) {
364 1
            $pattern .= 'i';
365
        }
366
367 52
        return preg_match($pattern, $string) === 1;
368
    }
369
370
    /**
371
     * This method provides a unicode-safe implementation of built-in PHP function `ucfirst()`.
372
     *
373
     * @param string $string the string to be processed
374
     * @param string $encoding Optional, defaults to "UTF-8"
375
     * @return string
376
     * @see https://php.net/manual/en/function.ucfirst.php
377
     */
378 24
    public static function ucfirst(string $string, string $encoding = null): string
379
    {
380 24
        $firstChar = static::substr($string, 0, 1, $encoding);
381 24
        $rest = static::substr($string, 1, null, $encoding);
382
383 24
        return static::strtoupper($firstChar, $encoding) . $rest;
384
    }
385
386
    /**
387
     * This method provides a unicode-safe implementation of built-in PHP function `ucwords()`.
388
     *
389
     * @param string $string the string to be processed
390
     * @param string $encoding Optional, defaults to "UTF-8"
391
     * @see https://php.net/manual/en/function.ucwords.php
392
     * @return string
393
     */
394 20
    public static function ucwords(string $string, string $encoding = null): string
395
    {
396 20
        $words = preg_split("/\s/u", $string, -1, PREG_SPLIT_NO_EMPTY);
397
398 20
        $ucfirst = array_map(static function ($word) use ($encoding) {
399 19
            return static::ucfirst($word, $encoding);
400 20
        }, $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

400
        }, /** @scrutinizer ignore-type */ $words);
Loading history...
401
402 20
        return implode(' ', $ucfirst);
403
    }
404
405
    /**
406
     * Get string length
407
     *
408
     * @param string $string string to calculate length for
409
     * @param string|null $encoding Optional, defaults to "UTF-8"
410
     * @see https://php.net/manual/en/function.mb-strlen.php
411
     * @return int
412
     */
413 1
    public static function strlen(string $string, string $encoding = null): int
414
    {
415 1
        return empty($encoding) ? mb_strlen($string) : mb_strlen($string, $encoding);
416
    }
417
418
    /**
419
     * Get part of string
420
     *
421
     * @param string $string to get substring from
422
     * @param int $start character to start at
423
     * @param int|null $length number of characters to get
424
     * @param string|null $encoding Optional, defaults to "UTF-8"
425
     * @see https://php.net/manual/en/function.mb-substr.php
426
     * @return string
427
     */
428 40
    public static function substr(string $string, int $start, int $length = null, string $encoding = null): string
429
    {
430 40
        return empty($encoding) ? mb_substr($string, $start, $length) : mb_substr($string, $start, $length, $encoding);
431
    }
432
433
    /**
434
     * Make a string lowercase
435
     *
436
     * @param string $string string to process
437
     * @param string|null $encoding Optional, defaults to "UTF-8"
438
     * @see https://php.net/manual/en/function.mb-strtolower.php
439
     * @return string
440
     */
441 30
    public static function strtolower(string $string, string $encoding = null): string
442
    {
443 30
        return empty($encoding) ? mb_strtolower($string) : mb_strtolower($string, $encoding);
444
    }
445
446
    /**
447
     * Make a string uppercase
448
     *
449
     * @param string $string string to process
450
     * @param string|null $encoding Optional, defaults to "UTF-8"
451
     * @see https://php.net/manual/en/function.mb-strtoupper.php
452
     * @return string
453
     */
454 24
    public static function strtoupper(string $string, string $encoding = null): string
455
    {
456 24
        return empty($encoding) ? mb_strtoupper($string) : mb_strtoupper($string, $encoding);
457
    }
458
459
    /**
460
     * Convert special characters to HTML entities
461
     *
462
     * @param string $string string to process
463
     * @param int $flags A bitmask of one or more flags
464
     * @param string|null $encoding Optional, defaults to "UTF-8"
465
     * @param bool $double_encode if set to false, method will not encode existing HTML entities
466
     * @see https://php.net/manual/en/function.htmlspecialchars.php
467
     * @return string
468
     */
469 1
    public static function htmlspecialchars(string $string, int $flags, string $encoding = null, bool $double_encode = true): string
470
    {
471 1
        return empty($encoding) && $double_encode
472 1
            ? htmlspecialchars($string, $flags)
473 1
            : htmlspecialchars($string, $flags, $encoding ?: ini_get('default_charset'), $double_encode);
474
    }
475
}
476