GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( cc39b3...2b121f )
by Anton
04:25 queued 01:05
created

ByteString::slice()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 2
b 0
f 0
nc 1
nop 2
dl 0
loc 6
rs 10
1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Symfony\Component\String;
13
14
use Random\Randomizer;
0 ignored issues
show
Bug introduced by
The type Random\Randomizer was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
15
use Symfony\Component\String\Exception\ExceptionInterface;
16
use Symfony\Component\String\Exception\InvalidArgumentException;
17
use Symfony\Component\String\Exception\RuntimeException;
18
19
/**
20
 * Represents a binary-safe string of bytes.
21
 *
22
 * @author Nicolas Grekas <[email protected]>
23
 * @author Hugo Hamon <[email protected]>
24
 *
25
 * @throws ExceptionInterface
26
 */
27
class ByteString extends AbstractString
28
{
29
    private const ALPHABET_ALPHANUMERIC = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
30
31
    public function __construct(string $string = '')
32
    {
33
        $this->string = $string;
34
    }
35
36
    /*
37
     * The following method was derived from code of the Hack Standard Library (v4.40 - 2020-05-03)
38
     *
39
     * https://github.com/hhvm/hsl/blob/80a42c02f036f72a42f0415e80d6b847f4bf62d5/src/random/private.php#L16
40
     *
41
     * Code subject to the MIT license (https://github.com/hhvm/hsl/blob/master/LICENSE).
42
     *
43
     * Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/)
44
     */
45
46
    public static function fromRandom(int $length = 16, ?string $alphabet = null): self
47
    {
48
        if ($length <= 0) {
49
            throw new InvalidArgumentException(sprintf('A strictly positive length is expected, "%d" given.', $length));
50
        }
51
52
        $alphabet ??= self::ALPHABET_ALPHANUMERIC;
53
        $alphabetSize = \strlen($alphabet);
54
        $bits = (int) ceil(log($alphabetSize, 2.0));
55
        if ($bits <= 0 || $bits > 56) {
56
            throw new InvalidArgumentException('The length of the alphabet must in the [2^1, 2^56] range.');
57
        }
58
59
        if (\PHP_VERSION_ID >= 80300) {
60
            return new static((new Randomizer())->getBytesFromString($alphabet, $length));
61
        }
62
63
        $ret = '';
64
        while ($length > 0) {
65
            $urandomLength = (int) ceil(2 * $length * $bits / 8.0);
66
            $data = random_bytes($urandomLength);
67
            $unpackedData = 0;
68
            $unpackedBits = 0;
69
            for ($i = 0; $i < $urandomLength && $length > 0; ++$i) {
70
                // Unpack 8 bits
71
                $unpackedData = ($unpackedData << 8) | \ord($data[$i]);
72
                $unpackedBits += 8;
73
74
                // While we have enough bits to select a character from the alphabet, keep
75
                // consuming the random data
76
                for (; $unpackedBits >= $bits && $length > 0; $unpackedBits -= $bits) {
77
                    $index = ($unpackedData & ((1 << $bits) - 1));
78
                    $unpackedData >>= $bits;
79
                    // Unfortunately, the alphabet size is not necessarily a power of two.
80
                    // Worst case, it is 2^k + 1, which means we need (k+1) bits and we
81
                    // have around a 50% chance of missing as k gets larger
82
                    if ($index < $alphabetSize) {
83
                        $ret .= $alphabet[$index];
84
                        --$length;
85
                    }
86
                }
87
            }
88
        }
89
90
        return new static($ret);
91
    }
92
93
    public function bytesAt(int $offset): array
94
    {
95
        $str = $this->string[$offset] ?? '';
96
97
        return '' === $str ? [] : [\ord($str)];
98
    }
99
100
    public function append(string ...$suffix): static
101
    {
102
        $str = clone $this;
103
        $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix);
104
105
        return $str;
106
    }
107
108
    public function camel(): static
109
    {
110
        $str = clone $this;
111
112
        $parts = explode(' ', trim(ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->string))));
113
        $parts[0] = 1 !== \strlen($parts[0]) && ctype_upper($parts[0]) ? $parts[0] : lcfirst($parts[0]);
114
        $str->string = implode('', $parts);
115
116
        return $str;
117
    }
118
119
    public function chunk(int $length = 1): array
120
    {
121
        if (1 > $length) {
122
            throw new InvalidArgumentException('The chunk length must be greater than zero.');
123
        }
124
125
        if ('' === $this->string) {
126
            return [];
127
        }
128
129
        $str = clone $this;
130
        $chunks = [];
131
132
        foreach (str_split($this->string, $length) as $chunk) {
133
            $str->string = $chunk;
134
            $chunks[] = clone $str;
135
        }
136
137
        return $chunks;
138
    }
139
140
    public function endsWith(string|iterable|AbstractString $suffix): bool
141
    {
142
        if ($suffix instanceof AbstractString) {
143
            $suffix = $suffix->string;
144
        } elseif (!\is_string($suffix)) {
145
            return parent::endsWith($suffix);
146
        }
147
148
        return '' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase);
0 ignored issues
show
Bug introduced by
It seems like $this->ignoreCase can also be of type null; however, parameter $case_insensitive of substr_compare() does only seem to accept boolean, 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

148
        return '' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, /** @scrutinizer ignore-type */ $this->ignoreCase);
Loading history...
149
    }
150
151
    public function equalsTo(string|iterable|AbstractString $string): bool
152
    {
153
        if ($string instanceof AbstractString) {
154
            $string = $string->string;
155
        } elseif (!\is_string($string)) {
156
            return parent::equalsTo($string);
157
        }
158
159
        if ('' !== $string && $this->ignoreCase) {
160
            return 0 === strcasecmp($string, $this->string);
161
        }
162
163
        return $string === $this->string;
164
    }
165
166
    public function folded(): static
167
    {
168
        $str = clone $this;
169
        $str->string = strtolower($str->string);
170
171
        return $str;
172
    }
173
174
    public function indexOf(string|iterable|AbstractString $needle, int $offset = 0): ?int
175
    {
176
        if ($needle instanceof AbstractString) {
177
            $needle = $needle->string;
178
        } elseif (!\is_string($needle)) {
179
            return parent::indexOf($needle, $offset);
180
        }
181
182
        if ('' === $needle) {
183
            return null;
184
        }
185
186
        $i = $this->ignoreCase ? stripos($this->string, $needle, $offset) : strpos($this->string, $needle, $offset);
187
188
        return false === $i ? null : $i;
0 ignored issues
show
introduced by
The condition false === $i is always false.
Loading history...
189
    }
190
191
    public function indexOfLast(string|iterable|AbstractString $needle, int $offset = 0): ?int
192
    {
193
        if ($needle instanceof AbstractString) {
194
            $needle = $needle->string;
195
        } elseif (!\is_string($needle)) {
196
            return parent::indexOfLast($needle, $offset);
197
        }
198
199
        if ('' === $needle) {
200
            return null;
201
        }
202
203
        $i = $this->ignoreCase ? strripos($this->string, $needle, $offset) : strrpos($this->string, $needle, $offset);
204
205
        return false === $i ? null : $i;
0 ignored issues
show
introduced by
The condition false === $i is always false.
Loading history...
206
    }
207
208
    public function isUtf8(): bool
209
    {
210
        return '' === $this->string || preg_match('//u', $this->string);
211
    }
212
213
    public function join(array $strings, ?string $lastGlue = null): static
214
    {
215
        $str = clone $this;
216
217
        $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : '';
218
        $str->string = implode($this->string, $strings).$tail;
219
220
        return $str;
221
    }
222
223
    public function length(): int
224
    {
225
        return \strlen($this->string);
226
    }
227
228
    public function lower(): static
229
    {
230
        $str = clone $this;
231
        $str->string = strtolower($str->string);
232
233
        return $str;
234
    }
235
236
    public function match(string $regexp, int $flags = 0, int $offset = 0): array
237
    {
238
        $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match';
239
240
        if ($this->ignoreCase) {
241
            $regexp .= 'i';
242
        }
243
244
        set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m));
245
246
        try {
247
            if (false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $matches does not exist. Did you maybe mean $match?
Loading history...
248
                throw new RuntimeException('Matching failed with error: '.preg_last_error_msg());
249
            }
250
        } finally {
251
            restore_error_handler();
252
        }
253
254
        return $matches;
255
    }
256
257
    public function padBoth(int $length, string $padStr = ' '): static
258
    {
259
        $str = clone $this;
260
        $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_BOTH);
261
262
        return $str;
263
    }
264
265
    public function padEnd(int $length, string $padStr = ' '): static
266
    {
267
        $str = clone $this;
268
        $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_RIGHT);
269
270
        return $str;
271
    }
272
273
    public function padStart(int $length, string $padStr = ' '): static
274
    {
275
        $str = clone $this;
276
        $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_LEFT);
277
278
        return $str;
279
    }
280
281
    public function prepend(string ...$prefix): static
282
    {
283
        $str = clone $this;
284
        $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$str->string;
285
286
        return $str;
287
    }
288
289
    public function replace(string $from, string $to): static
290
    {
291
        $str = clone $this;
292
293
        if ('' !== $from) {
294
            $str->string = $this->ignoreCase ? str_ireplace($from, $to, $this->string) : str_replace($from, $to, $this->string);
295
        }
296
297
        return $str;
298
    }
299
300
    public function replaceMatches(string $fromRegexp, string|callable $to): static
301
    {
302
        if ($this->ignoreCase) {
303
            $fromRegexp .= 'i';
304
        }
305
306
        $replace = \is_array($to) || $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace';
307
308
        set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m));
309
310
        try {
311
            if (null === $string = $replace($fromRegexp, $to, $this->string)) {
312
                $lastError = preg_last_error();
313
314
                foreach (get_defined_constants(true)['pcre'] as $k => $v) {
315
                    if ($lastError === $v && str_ends_with($k, '_ERROR')) {
316
                        throw new RuntimeException('Matching failed with '.$k.'.');
317
                    }
318
                }
319
320
                throw new RuntimeException('Matching failed with unknown error code.');
321
            }
322
        } finally {
323
            restore_error_handler();
324
        }
325
326
        $str = clone $this;
327
        $str->string = $string;
328
329
        return $str;
330
    }
331
332
    public function reverse(): static
333
    {
334
        $str = clone $this;
335
        $str->string = strrev($str->string);
336
337
        return $str;
338
    }
339
340
    public function slice(int $start = 0, ?int $length = null): static
341
    {
342
        $str = clone $this;
343
        $str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX);
344
345
        return $str;
346
    }
347
348
    public function snake(): static
349
    {
350
        $str = $this->camel();
351
        $str->string = strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], '\1_\2', $str->string));
352
353
        return $str;
354
    }
355
356
    public function splice(string $replacement, int $start = 0, ?int $length = null): static
357
    {
358
        $str = clone $this;
359
        $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX);
360
361
        return $str;
362
    }
363
364
    public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array
365
    {
366
        if (1 > $limit ??= \PHP_INT_MAX) {
367
            throw new InvalidArgumentException('Split limit must be a positive integer.');
368
        }
369
370
        if ('' === $delimiter) {
371
            throw new InvalidArgumentException('Split delimiter is empty.');
372
        }
373
374
        if (null !== $flags) {
375
            return parent::split($delimiter, $limit, $flags);
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::split($delimiter, $limit, $flags) returns the type array<mixed,array|string> which is incompatible with the return type mandated by Symfony\Component\String\AbstractString::split() of array<mixed,Symfony\Comp...\String\AbstractString>.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
376
        }
377
378
        $str = clone $this;
379
        $chunks = $this->ignoreCase
380
            ? preg_split('{'.preg_quote($delimiter).'}iD', $this->string, $limit)
381
            : explode($delimiter, $this->string, $limit);
382
383
        foreach ($chunks as &$chunk) {
384
            $str->string = $chunk;
385
            $chunk = clone $str;
386
        }
387
388
        return $chunks;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $chunks returns the type string[] which is incompatible with the return type mandated by Symfony\Component\String\AbstractString::split() of array<mixed,Symfony\Comp...\String\AbstractString>.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
389
    }
390
391
    public function startsWith(string|iterable|AbstractString $prefix): bool
392
    {
393
        if ($prefix instanceof AbstractString) {
394
            $prefix = $prefix->string;
395
        } elseif (!\is_string($prefix)) {
396
            return parent::startsWith($prefix);
397
        }
398
399
        return '' !== $prefix && 0 === ($this->ignoreCase ? strncasecmp($this->string, $prefix, \strlen($prefix)) : strncmp($this->string, $prefix, \strlen($prefix)));
400
    }
401
402
    public function title(bool $allWords = false): static
403
    {
404
        $str = clone $this;
405
        $str->string = $allWords ? ucwords($str->string) : ucfirst($str->string);
406
407
        return $str;
408
    }
409
410
    public function toUnicodeString(?string $fromEncoding = null): UnicodeString
411
    {
412
        return new UnicodeString($this->toCodePointString($fromEncoding)->string);
413
    }
414
415
    public function toCodePointString(?string $fromEncoding = null): CodePointString
416
    {
417
        $u = new CodePointString();
418
419
        if (\in_array($fromEncoding, [null, 'utf8', 'utf-8', 'UTF8', 'UTF-8'], true) && preg_match('//u', $this->string)) {
420
            $u->string = $this->string;
421
422
            return $u;
423
        }
424
425
        set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m));
426
427
        try {
428
            try {
429
                $validEncoding = false !== mb_detect_encoding($this->string, $fromEncoding ?? 'Windows-1252', true);
430
            } catch (InvalidArgumentException $e) {
431
                if (!\function_exists('iconv')) {
432
                    throw $e;
433
                }
434
435
                $u->string = iconv($fromEncoding ?? 'Windows-1252', 'UTF-8', $this->string);
436
437
                return $u;
438
            }
439
        } finally {
440
            restore_error_handler();
441
        }
442
443
        if (!$validEncoding) {
444
            throw new InvalidArgumentException(sprintf('Invalid "%s" string.', $fromEncoding ?? 'Windows-1252'));
445
        }
446
447
        $u->string = mb_convert_encoding($this->string, 'UTF-8', $fromEncoding ?? 'Windows-1252');
448
449
        return $u;
450
    }
451
452
    public function trim(string $chars = " \t\n\r\0\x0B\x0C"): static
453
    {
454
        $str = clone $this;
455
        $str->string = trim($str->string, $chars);
456
457
        return $str;
458
    }
459
460
    public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C"): static
461
    {
462
        $str = clone $this;
463
        $str->string = rtrim($str->string, $chars);
464
465
        return $str;
466
    }
467
468
    public function trimStart(string $chars = " \t\n\r\0\x0B\x0C"): static
469
    {
470
        $str = clone $this;
471
        $str->string = ltrim($str->string, $chars);
472
473
        return $str;
474
    }
475
476
    public function upper(): static
477
    {
478
        $str = clone $this;
479
        $str->string = strtoupper($str->string);
480
481
        return $str;
482
    }
483
484
    public function width(bool $ignoreAnsiDecoration = true): int
485
    {
486
        $string = preg_match('//u', $this->string) ? $this->string : preg_replace('/[\x80-\xFF]/', '?', $this->string);
487
488
        return (new CodePointString($string))->width($ignoreAnsiDecoration);
489
    }
490
}
491