Passed
Push — master ( adce6e...345a47 )
by Alexander
02:20
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 2
Bugs 0 Features 0
Metric Value
eloc 1
c 2
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Strings;
6
7
use function array_slice;
8
use function mb_strlen;
9
use function mb_strtolower;
10
use function mb_strtoupper;
11
use function mb_substr;
12
13
/**
14
 * Provides static methods to work with strings.
15
 */
16
final class StringHelper
17
{
18
    /**
19
     * Returns the number of bytes in the given string.
20
     * This method ensures the string is treated as a byte array even if `mbstring.func_overload` is turned on
21
     * by using {@see mb_strlen()}.
22
     *
23
     * @param string|null $input The string being measured for length.
24
     *
25
     * @return int The number of bytes in the given string.
26
     */
27 2
    public static function byteLength(?string $input): int
28
    {
29 2
        return mb_strlen((string)$input, '8bit');
30
    }
31
32
    /**
33
     * Returns the portion of string specified by the start and length parameters.
34
     * This method ensures the string is treated as a byte array by using `mb_substr()`.
35
     *
36
     * @param string $input The input string. Must be one character or longer.
37
     * @param int $start The starting position.
38
     * @param int|null $length The desired portion length. If not specified or `null`, there will be
39
     * no limit on length i.e. the output will be until the end of the string.
40
     *
41
     * @return string The extracted part of string, or FALSE on failure or an empty string.
42
     *
43
     * @see http://www.php.net/manual/en/function.substr.php
44
     */
45 1
    public static function byteSubstring(string $input, int $start, int $length = null): string
46
    {
47 1
        return mb_substr($input, $start, $length ?? mb_strlen($input, '8bit'), '8bit');
48
    }
49
50
    /**
51
     * Returns the trailing name component of a path.
52
     * This method is similar to the php function `basename()` except that it will
53
     * treat both \ and / as directory separators, independent of the operating system.
54
     * This method was mainly created to work on php namespaces. When working with real
55
     * file paths, PHP's `basename()` should work fine for you.
56
     * Note: this method is not aware of the actual filesystem, or path components such as "..".
57
     *
58
     * @param string $path A path string.
59
     * @param string $suffix If the name component ends in suffix this will also be cut off.
60
     *
61
     * @return string The trailing name component of the given path.
62
     *
63
     * @see http://www.php.net/manual/en/function.basename.php
64
     */
65 1
    public static function baseName(string $path, string $suffix = ''): string
66
    {
67 1
        $length = mb_strlen($suffix);
68 1
        if ($length > 0 && mb_substr($path, -$length) === $suffix) {
69 1
            $path = mb_substr($path, 0, -$length);
70
        }
71 1
        $path = rtrim(str_replace('\\', '/', $path), '/\\');
72 1
        $position = mb_strrpos($path, '/');
73 1
        if ($position !== false) {
74 1
            return mb_substr($path, $position + 1);
75
        }
76
77 1
        return $path;
78
    }
79
80
    /**
81
     * Returns parent directory's path.
82
     * This method is similar to `dirname()` except that it will treat
83
     * both \ and / as directory separators, independent of the operating system.
84
     *
85
     * @param string $path A path string.
86
     *
87
     * @return string The parent directory's path.
88
     *
89
     * @see http://www.php.net/manual/en/function.basename.php
90
     */
91 1
    public static function directoryName(string $path): string
92
    {
93 1
        $position = mb_strrpos(str_replace('\\', '/', $path), '/');
94 1
        if ($position !== false) {
95 1
            return mb_substr($path, 0, $position);
96
        }
97
98 1
        return '';
99
    }
100
101
    /**
102
     * Get part of string.
103
     *
104
     * @param string $string To get substring from.
105
     * @param int $start Character to start at.
106
     * @param int|null $length Number of characters to get.
107
     * @param string $encoding The encoding to use, defaults to "UTF-8".
108
     *
109
     * @see https://php.net/manual/en/function.mb-substr.php
110
     *
111
     * @return string
112
     */
113 15
    public static function substring(string $string, int $start, int $length = null, string $encoding = 'UTF-8'): string
114
    {
115 15
        return mb_substr($string, $start, $length, $encoding);
116
    }
117
118
    /**
119
     * Replace text within a portion of a string.
120
     *
121
     * @param string $string The input string.
122
     * @param string $replacement The replacement string.
123
     * @param int $start Position to begin replacing substring at.
124
     * If start is non-negative, the replacing will begin at the start'th offset into string.
125
     * If start is negative, the replacing will begin at the start'th character from the end of string.
126
     * @param int|null $length Length of the substring to be replaced.
127
     * If given and is positive, it represents the length of the portion of string which is to be replaced.
128
     * If it is negative, it represents the number of characters from the end of string at which to stop replacing.
129
     * If it is not given, then it will default to the length of the string; i.e. end the replacing at the end of string.
130
     * If length is zero then this function will have the effect of inserting replacement into string at the given start offset.
131
     * @param string $encoding The encoding to use, defaults to "UTF-8".
132
     *
133
     * @return string
134
     */
135 8
    public static function replaceSubstring(string $string, string $replacement, int $start, ?int $length = null, string $encoding = 'UTF-8'): string
136
    {
137 8
        $stringLength = mb_strlen($string, $encoding);
138
139 8
        if ($start < 0) {
140 2
            $start = \max(0, $stringLength + $start);
141 6
        } elseif ($start > $stringLength) {
142
            $start = $stringLength;
143
        }
144
145 8
        if ($length !== null && $length < 0) {
146 3
            $length = \max(0, $stringLength - $start + $length);
147 5
        } elseif ($length === null || $length > $stringLength) {
148 4
            $length = $stringLength;
149
        }
150
151 8
        if (($start + $length) > $stringLength) {
152 3
            $length = $stringLength - $start;
153
        }
154
155 8
        return mb_substr($string, 0, $start, $encoding) . $replacement . mb_substr($string, $start + $length, $stringLength - $start - $length, $encoding);
156
    }
157
158
    /**
159
     * Check if given string starts with specified substring.
160
     * Binary and multibyte safe.
161
     *
162
     * @param string $input Input string.
163
     * @param string|null $with Part to search inside the $string.
164
     *
165
     * @return bool Returns true if first input starts with second input, false otherwise.
166
     */
167 19
    public static function startsWith(string $input, ?string $with): bool
168
    {
169 19
        if ($with === null) {
170 1
            return true;
171
        }
172
173 18
        if (function_exists('\str_starts_with')) {
174 18
            return \str_starts_with($input, $with);
175
        }
176
177
        $bytes = self::byteLength($with);
178
        if ($bytes === 0) {
179
            return true;
180
        }
181
182
        return strncmp($input, $with, $bytes) === 0;
183
    }
184
185
    /**
186
     * Check if given string starts with specified substring ignoring case.
187
     * Binary and multibyte safe.
188
     *
189
     * @param string $input Input string.
190
     * @param string|null $with Part to search inside the $string.
191
     *
192
     * @return bool Returns true if first input starts with second input, false otherwise.
193
     */
194 1
    public static function startsWithIgnoringCase(string $input, ?string $with): bool
195
    {
196 1
        $bytes = self::byteLength($with);
197 1
        if ($bytes === 0) {
198 1
            return true;
199
        }
200
201
        /**
202
         * @psalm-suppress PossiblyNullArgument
203
         */
204 1
        return self::lowercase(self::substring($input, 0, $bytes, '8bit')) === self::lowercase($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::lowercase() 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

204
        return self::lowercase(self::substring($input, 0, $bytes, '8bit')) === self::lowercase(/** @scrutinizer ignore-type */ $with);
Loading history...
205
    }
206
207
    /**
208
     * Check if given string ends with specified substring.
209
     * Binary and multibyte safe.
210
     *
211
     * @param string $input Input string to check.
212
     * @param string|null $with Part to search inside of the $string.
213
     *
214
     * @return bool Returns true if first input ends with second input, false otherwise.
215
     */
216 19
    public static function endsWith(string $input, ?string $with): bool
217
    {
218 19
        if ($with === null) {
219 1
            return true;
220
        }
221
222 18
        if (function_exists('\str_ends_with')) {
223 18
            return \str_ends_with($input, $with);
224
        }
225
226
        $bytes = self::byteLength($with);
227
        if ($bytes === 0) {
228
            return true;
229
        }
230
231
        // Warning check, see http://php.net/manual/en/function.substr-compare.php#refsect1-function.substr-compare-returnvalues
232
        if (self::byteLength($input) < $bytes) {
233
            return false;
234
        }
235
236
        return substr_compare($input, $with, -$bytes, $bytes) === 0;
237
    }
238
239
    /**
240
     * Check if given string ends with specified substring.
241
     * Binary and multibyte safe.
242
     *
243
     * @param string $input Input string to check.
244
     * @param string|null $with Part to search inside of the $string.
245
     *
246
     * @return bool Returns true if first input ends with second input, false otherwise.
247
     */
248 1
    public static function endsWithIgnoringCase(string $input, ?string $with): bool
249
    {
250 1
        $bytes = self::byteLength($with);
251 1
        if ($bytes === 0) {
252 1
            return true;
253
        }
254
255
        /**
256
         * @psalm-suppress PossiblyNullArgument
257
         */
258 1
        return self::lowercase(mb_substr($input, -$bytes, mb_strlen($input, '8bit'), '8bit')) === self::lowercase($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::lowercase() 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

258
        return self::lowercase(mb_substr($input, -$bytes, mb_strlen($input, '8bit'), '8bit')) === self::lowercase(/** @scrutinizer ignore-type */ $with);
Loading history...
259
    }
260
261
    /**
262
     * Truncates a string from the beginning to the number of characters specified.
263
     *
264
     * @param string $input String to process.
265
     * @param int $length Maximum length of the truncated string including trim marker.
266
     * @param string $trimMarker String to append to the beginning.
267
     * @param string $encoding The encoding to use, defaults to "UTF-8".
268
     *
269
     * @return string
270
     */
271 1
    public static function truncateBegin(string $input, int $length, string $trimMarker = '…', string $encoding = 'UTF-8'): string
272
    {
273 1
        $inputLength = mb_strlen($input, $encoding);
274
275 1
        if ($inputLength <= $length) {
276 1
            return $input;
277
        }
278
279 1
        $trimMarkerLength = mb_strlen($trimMarker, $encoding);
280 1
        return self::replaceSubstring($input, $trimMarker, 0, -$length + $trimMarkerLength, $encoding);
281
    }
282
283
    /**
284
     * Truncates a string in the middle. Keeping start and end.
285
     * `StringHelper::truncateMiddle('Hello world number 2', 8)` produces "Hell…r 2".
286
     *
287
     * @param string $input The string to truncate.
288
     * @param int $length Maximum length of the truncated string including trim marker.
289
     * @param string $trimMarker String to append in the middle of truncated string.
290
     * @param string $encoding The encoding to use, defaults to "UTF-8".
291
     *
292
     * @return string The truncated string.
293
     */
294 2
    public static function truncateMiddle(string $input, int $length, string $trimMarker = '…', string $encoding = 'UTF-8'): string
295
    {
296 2
        $inputLength = mb_strlen($input, $encoding);
297
298 2
        if ($inputLength <= $length) {
299 1
            return $input;
300
        }
301
302 1
        $trimMarkerLength = mb_strlen($trimMarker, $encoding);
303 1
        $start = (int)ceil(($length - $trimMarkerLength) / 2);
304 1
        $end = $length - $start - $trimMarkerLength;
305
306 1
        return self::replaceSubstring($input, $trimMarker, $start, -$end, $encoding);
307
    }
308
309
    /**
310
     * Truncates a string from the end to the number of characters specified.
311
     *
312
     * @param string $input The string to truncate.
313
     * @param int $length Maximum length of the truncated string including trim marker.
314
     * @param string $trimMarker String to append to the end of truncated string.
315
     * @param string $encoding The encoding to use, defaults to "UTF-8".
316
     *
317
     * @return string The truncated string.
318
     */
319 1
    public static function truncateEnd(string $input, int $length, string $trimMarker = '…', string $encoding = 'UTF-8'): string
320
    {
321 1
        $inputLength = mb_strlen($input, $encoding);
322
323 1
        if ($inputLength <= $length) {
324 1
            return $input;
325
        }
326
327 1
        $trimMarkerLength = mb_strlen($trimMarker, $encoding);
328 1
        return rtrim(mb_substr($input, 0, $length - $trimMarkerLength, $encoding)) . $trimMarker;
329
    }
330
331
    /**
332
     * Truncates a string to the number of words specified.
333
     *
334
     * @param string $input The string to truncate.
335
     * @param int $count How many words from original string to include into truncated string.
336
     * @param string $trimMarker String to append to the end of truncated string.
337
     *
338
     * @return string The truncated string.
339
     */
340 1
    public static function truncateWords(string $input, int $count, string $trimMarker = '…'): string
341
    {
342 1
        $words = preg_split('/(\s+)/u', trim($input), -1, PREG_SPLIT_DELIM_CAPTURE);
343 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

343
        if (count(/** @scrutinizer ignore-type */ $words) / 2 > $count) {
Loading history...
344 1
            return implode('', array_slice($words, 0, ($count * 2) - 1)) . $trimMarker;
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

344
            return implode('', array_slice(/** @scrutinizer ignore-type */ $words, 0, ($count * 2) - 1)) . $trimMarker;
Loading history...
345
        }
346
347 1
        return $input;
348
    }
349
350
    /**
351
     * Get string length.
352
     *
353
     * @param string $string String to calculate length for.
354
     * @param string $encoding The encoding to use, defaults to "UTF-8".
355
     *
356
     * @see https://php.net/manual/en/function.mb-strlen.php
357
     *
358
     * @return int
359
     */
360 1
    public static function length(string $string, string $encoding = 'UTF-8'): int
361
    {
362 1
        return mb_strlen($string, $encoding);
363
    }
364
365
    /**
366
     * Counts words in a string.
367
     *
368
     * @param string $input
369
     *
370
     * @return int
371
     */
372 1
    public static function countWords(string $input): int
373
    {
374 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

374
        return count(/** @scrutinizer ignore-type */ preg_split('/\s+/u', $input, -1, PREG_SPLIT_NO_EMPTY));
Loading history...
375
    }
376
377
    /**
378
     * Make a string lowercase.
379
     *
380
     * @param string $string String to process.
381
     * @param string $encoding The encoding to use, defaults to "UTF-8".
382
     *
383
     * @see https://php.net/manual/en/function.mb-strtolower.php
384
     *
385
     * @return string
386
     */
387 3
    public static function lowercase(string $string, string $encoding = 'UTF-8'): string
388
    {
389 3
        return mb_strtolower($string, $encoding);
390
    }
391
392
    /**
393
     * Make a string uppercase.
394
     *
395
     * @param string $string String to process.
396
     * @param string $encoding The encoding to use, defaults to "UTF-8".
397
     *
398
     * @see https://php.net/manual/en/function.mb-strtoupper.php
399
     *
400
     * @return string
401
     */
402 15
    public static function uppercase(string $string, string $encoding = 'UTF-8'): string
403
    {
404 15
        return mb_strtoupper($string, $encoding);
405
    }
406
407
    /**
408
     * Make a string's first character uppercase.
409
     *
410
     * @param string $string The string to be processed.
411
     * @param string $encoding The encoding to use, defaults to "UTF-8".
412
     *
413
     * @return string
414
     *
415
     * @see https://php.net/manual/en/function.ucfirst.php
416
     */
417 14
    public static function uppercaseFirstCharacter(string $string, string $encoding = 'UTF-8'): string
418
    {
419 14
        $firstCharacter = self::substring($string, 0, 1, $encoding);
420 14
        $rest = self::substring($string, 1, null, $encoding);
421
422 14
        return self::uppercase($firstCharacter, $encoding) . $rest;
423
    }
424
425
    /**
426
     * Uppercase the first character of each word in a string.
427
     *
428
     * @param string $string The string to be processed.
429
     * @param string $encoding The encoding to use, defaults to "UTF-8".
430
     *
431
     * @see https://php.net/manual/en/function.ucwords.php
432
     *
433
     * @return string
434
     */
435 10
    public static function uppercaseFirstCharacterInEachWord(string $string, string $encoding = 'UTF-8'): string
436
    {
437 10
        $words = preg_split("/\s/u", $string, -1, PREG_SPLIT_NO_EMPTY);
438
439 10
        $wordsWithUppercaseFirstCharacter = array_map(static function ($word) use ($encoding) {
440 9
            return self::uppercaseFirstCharacter($word, $encoding);
441 10
        }, $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

441
        }, /** @scrutinizer ignore-type */ $words);
Loading history...
442
443 10
        return implode(' ', $wordsWithUppercaseFirstCharacter);
444
    }
445
446
    /**
447
     * Encodes string into "Base 64 Encoding with URL and Filename Safe Alphabet" (RFC 4648).
448
     *
449
     * > Note: Base 64 padding `=` may be at the end of the returned string.
450
     * > `=` is not transparent to URL encoding.
451
     *
452
     * @see https://tools.ietf.org/html/rfc4648#page-7
453
     *
454
     * @param string $input The string to encode.
455
     *
456
     * @return string Encoded string.
457
     */
458 4
    public static function base64UrlEncode(string $input): string
459
    {
460 4
        return strtr(base64_encode($input), '+/', '-_');
461
    }
462
463
    /**
464
     * Decodes "Base 64 Encoding with URL and Filename Safe Alphabet" (RFC 4648).
465
     *
466
     * @see https://tools.ietf.org/html/rfc4648#page-7
467
     *
468
     * @param string $input Encoded string.
469
     *
470
     * @return string Decoded string.
471
     */
472 4
    public static function base64UrlDecode(string $input): string
473
    {
474 4
        return base64_decode(strtr($input, '-_', '+/'));
475
    }
476
477
    /**
478
     * Split a string to array with non-empty lines.
479
     * Whitespace from the beginning and end of a each line will be stripped.
480
     *
481
     * @param string $string The input string.
482
     * @param string $separator The boundary string. It is a part of regular expression
483
     * so should be taken into account or properly escaped with {@see preg_quote()}.
484
     *
485
     * @return array
486
     */
487 15
    public static function split(string $string, string $separator = '\R'): array
488
    {
489 15
        $string = preg_replace('(^\s*|\s*$)', '', $string);
490 15
        return preg_split('~\s*' . $separator . '\s*~', $string, -1, PREG_SPLIT_NO_EMPTY);
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_split('~\s*'...gs\PREG_SPLIT_NO_EMPTY) could return the type false which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
491
    }
492
}
493