Passed
Push — master ( 68a02e...9d4783 )
by Alexander
02:32 queued 01:08
created

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

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

118
            return implode('', array_slice(/** @scrutinizer ignore-type */ $words, 0, ($count * 2) - 1)) . $suffix;
Loading history...
119
        }
120
121 1
        return $string;
122
    }
123
124
    /**
125
     * Truncate the string from the beginning
126
     *
127
     * @param string $string string to process
128
     * @param int $length total of character to truncate
129
     * @param string $suffix String to append to the beginning
130
     * @return string
131
     */
132 1
    public static function truncateBegin(string $string, int $length, string $suffix = '…'): string
133
    {
134 1
        return substr_replace($string, $suffix, 0, $length);
135
    }
136
137
    /**
138
     * Truncates a string in the middle. Keeping start and end.
139
     * `StringHelper::truncateMiddle('Hello world number 2', 8)` produces "Hell...er 2".
140
     *
141
     * This method does not support HTML. It will strip all tags even if length is smaller than the string including tags.
142
     *
143
     * @param string $string The string to truncate.
144
     * @param int $length How many characters from original string to include into truncated string.
145
     * @param string $separator String to append in the middle of truncated string.
146
     * @param string $encoding The charset to use, defaults to charset currently used by application.
147
     * @return string the truncated string.
148
     */
149 1
    public static function truncateMiddle(string $string, int $length, string $separator = '...', string $encoding = 'UTF-8'): string
150
    {
151 1
        $strLen = mb_strlen($string, $encoding);
152
153 1
        if ($strLen <= $length) {
154
            return $string;
155
        }
156
157 1
        $partLen = floor($length / 2);
158 1
        $left = ltrim(mb_substr($string, 0, $partLen, $encoding));
0 ignored issues
show
Bug introduced by
$partLen of type double is incompatible with the type integer expected by parameter $length of mb_substr(). ( Ignorable by Annotation )

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

158
        $left = ltrim(mb_substr($string, 0, /** @scrutinizer ignore-type */ $partLen, $encoding));
Loading history...
159 1
        $right = rtrim(mb_substr($string, -$partLen, $partLen, $encoding));
0 ignored issues
show
Bug introduced by
-$partLen of type double is incompatible with the type integer expected by parameter $start of mb_substr(). ( Ignorable by Annotation )

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

159
        $right = rtrim(mb_substr($string, /** @scrutinizer ignore-type */ -$partLen, $partLen, $encoding));
Loading history...
160
161 1
        return $left . $separator . $right;
162
    }
163
164
    /**
165
     * Check if given string starts with specified substring.
166
     * Binary and multibyte safe.
167
     *
168
     * @param string $string Input string
169
     * @param string $with Part to search inside the $string
170
     * @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.
171
     * @return bool Returns true if first input starts with second input, false otherwise
172
     */
173 19
    public static function startsWith(string $string, ?string $with, bool $caseSensitive = true): bool
174
    {
175 19
        if (!$bytes = static::byteLength($with)) {
176 3
            return true;
177
        }
178 16
        if ($caseSensitive) {
179 15
            return strncmp($string, $with, $bytes) === 0;
180
        }
181
182 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

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

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

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

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