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.

Str   F
last analyzed

Complexity

Total Complexity 64

Size/Duplication

Total Lines 534
Duplicated Lines 0 %

Test Coverage

Coverage 99.43%

Importance

Changes 18
Bugs 2 Features 1
Metric Value
eloc 125
c 18
b 2
f 1
dl 0
loc 534
rs 3.28
ccs 174
cts 175
cp 0.9943
wmc 64

49 Methods

Rating   Name   Duplication   Size   Complexity  
A of() 0 3 1
A camelize() 0 9 1
A prepend() 0 3 1
A length() 0 3 1
A pregQuote() 0 3 1
A repeat() 0 3 1
A takeEnd() 0 3 1
A toEncoding() 0 3 1
A rightPad() 0 3 1
A split() 0 15 4
A map() 0 3 1
A append() 0 3 1
A substring() 0 9 2
A capture() 0 3 1
A dropEnd() 0 3 1
A wordCount() 0 6 1
A rightTrim() 0 5 2
A startsWith() 0 7 2
A empty() 0 3 1
A matches() 0 3 1
A leftTrim() 0 5 2
A ucfirst() 0 6 1
A replace() 0 11 2
A stripSlashes() 0 3 1
A pad() 0 8 1
A toString() 0 3 1
A leftPad() 0 3 1
A chunk() 0 11 2
A pregReplace() 0 18 2
A reverse() 0 8 1
A contains() 0 3 1
A endsWith() 0 10 2
A pregSplit() 0 11 2
A trim() 0 5 2
A stripCSlashes() 0 3 1
A uniPad() 0 3 1
A flatMap() 0 3 1
A toLower() 0 3 1
A words() 0 12 2
A toUpper() 0 3 1
A lcfirst() 0 6 1
A take() 0 3 1
A position() 0 10 2
A encoding() 0 3 1
A __construct() 0 8 1
A drop() 0 3 1
A join() 0 5 1
A sprintf() 0 3 1
A equals() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Str often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Str, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types = 1);
3
4
namespace Innmind\Immutable;
5
6
use Innmind\Immutable\Exception\InvalidRegex;
7
8
/**
9
 * @psalm-immutable
10
 */
11
final class Str
12
{
13
    private string $value;
14
    private string $encoding;
15
16
    private function __construct(string $value, string $encoding = null)
17
    {
18
        $this->value = $value;
19
        /**
20
         * @psalm-suppress ImpureFunctionCall
21
         * @var string
22
         */
23
        $this->encoding = $encoding ?? \mb_internal_encoding();
24
    }
25
26 406
    /**
27
     * @psalm-pure
28 406
     */
29 406
    public static function of(string $value, string $encoding = null): self
30 406
    {
31
        return new self($value, $encoding);
32 406
    }
33
34 406
    /**
35
     * Concatenate all elements with the given separator
36
     *
37
     * @param Set<string>|Sequence<string> $structure
38
     */
39
    public function join(Set|Sequence $structure): self
40 404
    {
41
        return new self(
42 404
            \implode($this->value, $structure->toList()),
43
            $this->encoding,
44
        );
45 384
    }
46
47 384
    public function toString(): string
48
    {
49 380
        return $this->value;
50
    }
51
52 384
    public function encoding(): self
53
    {
54
        return new self($this->encoding);
55 16
    }
56
57 16
    public function toEncoding(string $encoding): self
58
    {
59
        return new self($this->value, $encoding);
60
    }
61
62
    /**
63
     * Split the string into a collection of ones
64
     *
65 20
     * @return Sequence<self>
66
     */
67 20
    public function split(string $delimiter = null): Sequence
68 3
    {
69
        if (\is_null($delimiter) || $delimiter === '') {
70
            return $this->chunk();
71 18
        }
72
73 18
        $parts = \explode($delimiter, $this->value);
74
        /** @var Sequence<self> */
75 18
        $sequence = Sequence::of();
76 18
77
        foreach ($parts as $part) {
78
            $sequence = ($sequence)(new self($part, $this->encoding));
79 18
        }
80
81
        return $sequence;
82
    }
83
84
    /**
85
     * Returns a collection of the string splitted by the given chunk size
86
     *
87 8
     * @param positive-int $size
0 ignored issues
show
Documentation Bug introduced by
The doc comment positive-int at position 0 could not be parsed: Unknown type name 'positive-int' at position 0 in positive-int.
Loading history...
88
     *
89
     * @return Sequence<self>
90 8
     */
91
    public function chunk(int $size = 1): Sequence
92 8
    {
93
        /** @var Sequence<self> */
94 8
        $sequence = Sequence::of();
95 8
        $parts = \mb_str_split($this->value, $size, $this->encoding);
96
97
        foreach ($parts as $value) {
98 8
            $sequence = ($sequence)(new self($value, $this->encoding));
99
        }
100
101
        return $sequence;
102
    }
103
104
    /**
105
     * Returns the position of the first occurence of the string
106 367
     *
107
     * @return Maybe<int>
108 367
     */
109
    public function position(string $needle, int $offset = 0): Maybe
110 367
    {
111 365
        $position = \mb_strpos($this->value, $needle, $offset, $this->encoding);
112 365
113 365
        if ($position === false) {
114
            /** @var Maybe<int> */
115
            return Maybe::nothing();
116
        }
117 19
118
        return Maybe::just($position);
119
    }
120
121
    /**
122
     * Replace all occurences of the search string with the replacement one
123 2
     */
124
    public function replace(string $search, string $replacement): self
125 2
    {
126 1
        if (!$this->contains($search)) {
127
            return $this;
128
        }
129
130
        $parts = $this
131
            ->split($search)
132
            ->map(static fn($v) => $v->toString());
133
134 2
        return self::of($replacement, $this->encoding)->join($parts);
135 2
    }
136
137 2
    /**
138
     * Return the string in upper case
139
     */
140
    public function toUpper(): self
141
    {
142
        return new self(\mb_strtoupper($this->value), $this->encoding);
143
    }
144
145 3
    /**
146
     * Return the string in lower case
147 3
     */
148
    public function toLower(): self
149 3
    {
150 1
        return new self(\mb_strtolower($this->value), $this->encoding);
151 1
    }
152 1
153
    /**
154
     * Return the string length
155
     */
156 2
    public function length(): int
157
    {
158
        return \mb_strlen($this->value, $this->encoding);
159
    }
160
161
    public function empty(): bool
162 3
    {
163
        return $this->value === '';
164 3
    }
165
166
    /**
167
     * Reverse the string
168
     */
169
    public function reverse(): self
170 3
    {
171
        $parts = $this
172 3
            ->chunk()
173
            ->reverse()
174
            ->map(static fn($v) => $v->toString());
175
176
        return self::of('', $this->encoding)->join($parts);
177
    }
178 5
179
    /**
180 5
     * Pad to the right
181
     *
182
     * @param positive-int $length
0 ignored issues
show
Documentation Bug introduced by
The doc comment positive-int at position 0 could not be parsed: Unknown type name 'positive-int' at position 0 in positive-int.
Loading history...
183 12
     */
184
    public function rightPad(int $length, string $character = ' '): self
185 12
    {
186
        return $this->pad($length, $character, \STR_PAD_RIGHT);
187
    }
188
189
    /**
190
     * Pad to the left
191 2
     *
192
     * @param positive-int $length
0 ignored issues
show
Documentation Bug introduced by
The doc comment positive-int at position 0 could not be parsed: Unknown type name 'positive-int' at position 0 in positive-int.
Loading history...
193
     */
194
    public function leftPad(int $length, string $character = ' '): self
195
    {
196
        return $this->pad($length, $character, \STR_PAD_LEFT);
197
    }
198 2
199 2
    /**
200 2
     * Pad both sides
201
     *
202 2
     * @param positive-int $length
0 ignored issues
show
Documentation Bug introduced by
The doc comment positive-int at position 0 could not be parsed: Unknown type name 'positive-int' at position 0 in positive-int.
Loading history...
203
     */
204
    public function uniPad(int $length, string $character = ' '): self
205
    {
206
        return $this->pad($length, $character, \STR_PAD_BOTH);
207
    }
208 1
209
    /**
210 1
     * Repeat the string n times
211
     *
212
     * @param positive-int $repeat
0 ignored issues
show
Documentation Bug introduced by
The doc comment positive-int at position 0 could not be parsed: Unknown type name 'positive-int' at position 0 in positive-int.
Loading history...
213
     */
214
    public function repeat(int $repeat): self
215
    {
216 1
        return new self(\str_repeat($this->value, $repeat), $this->encoding);
217
    }
218 1
219
    public function stripSlashes(): self
220
    {
221
        return new self(\stripslashes($this->value), $this->encoding);
222
    }
223
224 1
    /**
225
     * Strip C-like slashes
226 1
     */
227
    public function stripCSlashes(): self
228
    {
229
        return new self(\stripcslashes($this->value), $this->encoding);
230
    }
231
232 1
    /**
233
     * Return the word count
234 1
     */
235 1
    public function wordCount(string $charlist = ''): int
236
    {
237 1
        return \str_word_count(
238 1
            $this->value,
239 1
            0,
240 1
            $charlist,
241 1
        );
242
    }
243
244
    /**
245 1
     * Return the collection of words
246
     *
247
     * @return Map<int, self>
248
     */
249
    public function words(string $charlist = ''): Map
250
    {
251 1
        /** @var list<string> */
252
        $words = \str_word_count($this->value, 2, $charlist);
253 1
        /** @var Map<int, self> */
254
        $map = Map::of();
255
256
        foreach ($words as $position => $word) {
257
            $map = ($map)($position, new self($word, $this->encoding));
258
        }
259 2
260
        return $map;
261
    }
262 2
263
    /**
264 2
     * Split the string using a regular expression
265 2
     *
266
     * @return Sequence<self>
267 2
     */
268
    public function pregSplit(string $regex, int $limit = -1): Sequence
269 2
    {
270
        $strings = \preg_split($regex, $this->value, $limit);
271
        /** @var Sequence<self> */
272 1
        $sequence = Sequence::of();
273
274 1
        foreach ($strings as $string) {
275
            $sequence = ($sequence)(new self($string, $this->encoding));
276
        }
277
278
        return $sequence;
279
    }
280 1
281
    /**
282 1
     * Check if the string match the given regular expression
283
     *
284
     * @throws InvalidRegex If the regex failed
285
     */
286
    public function matches(string $regex): bool
287
    {
288 1
        return RegExp::of($regex)->matches($this);
289
    }
290 1
291 1
    /**
292 1
     * Return a collection of the elements matching the regex
293 1
     *
294
     * @throws InvalidRegex If the regex failed
295
     *
296
     * @return Map<int|string, self>
297
     */
298
    public function capture(string $regex): Map
299
    {
300
        return RegExp::of($regex)->capture($this);
301
    }
302 1
303
    /**
304
     * Replace part of the string by using a regular expression
305 1
     *
306
     * @throws InvalidRegex If the regex failed
307 1
     */
308
    public function pregReplace(
309 1
        string $regex,
310 1
        string $replacement,
311
        int $limit = -1,
312
    ): self {
313 1
        $value = \preg_replace(
314
            $regex,
315
            $replacement,
316
            $this->value,
317
            $limit,
318
        );
319
320
        if ($value === null) {
321 2
            /** @psalm-suppress ImpureFunctionCall */
322
            throw new InvalidRegex('', \preg_last_error());
323 2
        }
324
325 2
        return new self($value, $this->encoding);
326
    }
327 2
328 2
    /**
329
     * Return part of the string
330
     */
331 2
    public function substring(int $start, int $length = null): self
332
    {
333
        if ($this->empty()) {
334
            return $this;
335
        }
336
337
        $sub = \mb_substr($this->value, $start, $length, $this->encoding);
338
339 2
        return new self($sub, $this->encoding);
340
    }
341 2
342
    /**
343
     * @param positive-int $size
0 ignored issues
show
Documentation Bug introduced by
The doc comment positive-int at position 0 could not be parsed: Unknown type name 'positive-int' at position 0 in positive-int.
Loading history...
344
     */
345
    public function take(int $size): self
346
    {
347
        return $this->substring(0, $size);
348
    }
349
350
    /**
351 3
     * @param positive-int $size
0 ignored issues
show
Documentation Bug introduced by
The doc comment positive-int at position 0 could not be parsed: Unknown type name 'positive-int' at position 0 in positive-int.
Loading history...
352
     */
353 3
    public function takeEnd(int $size): self
354
    {
355
        return $this->substring(-$size);
356
    }
357
358
    /**
359
     * @param positive-int $size
0 ignored issues
show
Documentation Bug introduced by
The doc comment positive-int at position 0 could not be parsed: Unknown type name 'positive-int' at position 0 in positive-int.
Loading history...
360
     */
361 1
    public function drop(int $size): self
362
    {
363
        return $this->substring($size);
364
    }
365
366 1
    /**
367 1
     * @param positive-int $size
0 ignored issues
show
Documentation Bug introduced by
The doc comment positive-int at position 0 could not be parsed: Unknown type name 'positive-int' at position 0 in positive-int.
Loading history...
368 1
     */
369 1
    public function dropEnd(int $size): self
370 1
    {
371
        return $this->substring(0, $this->length() - $size);
372
    }
373 1
374
    /**
375
     * Return a formatted string
376
     */
377 1
    public function sprintf(string ...$values): self
378
    {
379
        return new self(\sprintf($this->value, ...$values), $this->encoding);
380
    }
381
382
    /**
383 12
     * Return the string with the first letter as uppercase
384
     */
385 12
    public function ucfirst(): self
386 1
    {
387
        return $this
388
            ->substring(0, 1)
389 12
            ->toUpper()
390
            ->append($this->substring(1)->toString());
391 12
    }
392
393
    /**
394 1
     * Return the string with the first letter as lowercase
395
     */
396 1
    public function lcfirst(): self
397
    {
398
        return $this
399 2
            ->substring(0, 1)
400
            ->toLower()
401 2
            ->append($this->substring(1)->toString());
402
    }
403
404 2
    /**
405
     * Return a camelCase representation of the string
406 2
     */
407
    public function camelize(): self
408
    {
409 1
        $words = $this
410
            ->pregSplit('/_| /')
411 1
            ->map(static fn(self $part) => $part->ucfirst()->toString());
412
413
        return self::of('', $this->encoding)
414
            ->join($words)
415
            ->lcfirst();
416
    }
417 1
418
    /**
419 1
     * Append a string at the end of the current one
420
     */
421
    public function append(string $string): self
422
    {
423
        return new self($this->value.$string, $this->encoding);
424
    }
425 2
426
    /**
427
     * Prepend a string at the beginning of the current one
428 2
     */
429 2
    public function prepend(string $string): self
430 2
    {
431
        return new self($string.$this->value, $this->encoding);
432
    }
433
434
    /**
435
     * Check if the 2 strings are equal
436 2
     */
437
    public function equals(self $string): bool
438
    {
439 2
        return $this->toString() === $string->toString();
440 2
    }
441 2
442
    /**
443
     * Trim the string
444
     */
445
    public function trim(string $mask = null): self
446
    {
447 1
        return new self(
448
            $mask === null ? \trim($this->value) : \trim($this->value, $mask),
449
            $this->encoding,
450
        );
451
    }
452
453
    /**
454 1
     * Trim the right side of the string
455
     */
456 1
    public function rightTrim(string $mask = null): self
457 1
    {
458 1
        return new self(
459
            $mask === null ? \rtrim($this->value) : \rtrim($this->value, $mask),
460 1
            $this->encoding,
461 1
        );
462 1
    }
463
464
    /**
465
     * Trim the left side of the string
466
     */
467
    public function leftTrim(string $mask = null): self
468 4
    {
469
        return new self(
470 4
            $mask === null ? \ltrim($this->value) : \ltrim($this->value, $mask),
471
            $this->encoding,
472
        );
473
    }
474
475
    /**
476 1
     * Check if the given string is present in the current one
477
     */
478 1
    public function contains(string $value): bool
479
    {
480
        return \mb_strpos($this->value, $value, 0, $this->encoding) !== false;
481
    }
482
483
    /**
484 1
     * Check if the current string starts with the given string
485
     */
486 1
    public function startsWith(string $value): bool
487
    {
488
        if ($value === '') {
489
            return true;
490
        }
491
492 1
        return \mb_strpos($this->value, $value, 0, $this->encoding) === 0;
493
    }
494 1
495 1
    /**
496 1
     * Check if the current string ends with the given string
497
     */
498
    public function endsWith(string $value): bool
499
    {
500
        if ($value === '') {
501
            return true;
502
        }
503 1
504
        /** @var positive-int */
505 1
        $length = self::of($value, $this->encoding)->length();
506 1
507 1
        return $this->takeEnd($length)->toString() === $value;
508
    }
509
510
    /**
511
     * Quote regular expression characters
512
     */
513
    public function pregQuote(string $delimiter = ''): self
514 1
    {
515
        return new self(\preg_quote($this->value, $delimiter), $this->encoding);
516 1
    }
517 1
518 1
    /**
519
     * @param callable(string, string): string $map Second string is the encoding
520
     */
521
    public function map(callable $map): self
522
    {
523
        return new self($map($this->value, $this->encoding), $this->encoding);
524
    }
525 364
526
    /**
527
     * @param callable(string, string): self $map Second string is the encoding
528 364
     */
529
    public function flatMap(callable $map): self
530 17
    {
531 363
        return $map($this->value, $this->encoding);
532 363
    }
533
534
    /**
535
     * Pad the string
536
     */
537
    private function pad(int $length, string $character, int $direction): self
538
    {
539 363
        return new self(\str_pad(
540
            $this->value,
541 363
            $length,
542 1
            $character,
543
            $direction,
544
        ), $this->encoding);
545
    }
546
}
547