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.
Completed
Push — develop ( 03a9d8...d2240e )
by Baptiste
04:51
created

Str   F

Complexity

Total Complexity 73

Size/Duplication

Total Lines 577
Duplicated Lines 0 %

Test Coverage

Coverage 97.42%

Importance

Changes 4
Bugs 1 Features 1
Metric Value
eloc 161
c 4
b 1
f 1
dl 0
loc 577
ccs 189
cts 194
cp 0.9742
rs 2.56
wmc 73

51 Methods

Rating   Name   Duplication   Size   Complexity  
A of() 0 3 1
A toEncoding() 0 3 1
A toPrimitive() 0 3 1
A __toString() 0 3 1
A encoding() 0 7 2
A __construct() 0 4 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 rightPad() 0 3 1
A split() 0 14 4
A append() 0 3 1
A cspn() 0 14 2
A substring() 0 9 2
A capture() 0 7 2
A dropEnd() 0 3 1
A wordCount() 0 6 1
A rightTrim() 0 5 2
A startsWith() 0 10 3
A empty() 0 3 1
A matches() 0 7 2
A leftTrim() 0 5 2
A ucfirst() 0 6 1
A getMatches() 0 6 1
A replace() 0 9 2
A stripSlashes() 0 3 1
A pad() 0 11 1
A leftPad() 0 3 1
A str() 0 12 2
A chunk() 0 11 2
A pregReplace() 0 17 2
A reverse() 0 6 1
A contains() 0 8 2
A endsWith() 0 7 2
A pregSplit() 0 10 2
A shuffle() 0 6 1
A trim() 0 5 2
A stripCSlashes() 0 3 1
A uniPad() 0 3 1
A toLower() 0 3 1
A words() 0 10 2
A toUpper() 0 3 1
A lcfirst() 0 6 1
A take() 0 3 1
A position() 0 12 2
A drop() 0 3 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\{
7
    Exception\RegexException,
8
    Exception\SubstringException,
9
    Exception\LogicException
10
};
11
12
class Str implements PrimitiveInterface, StringableInterface
13
{
14
    const PAD_RIGHT = STR_PAD_RIGHT;
15
    const PAD_LEFT = STR_PAD_LEFT;
16
    const PAD_BOTH = STR_PAD_BOTH;
17
    const PREG_NO_FLAGS = 0;
18
    const PREG_SPLIT_NO_EMPTY = PREG_SPLIT_NO_EMPTY;
19
    const PREG_SPLIT_DELIM_CAPTURE = PREG_SPLIT_DELIM_CAPTURE;
20
    const PREG_SPLIT_OFFSET_CAPTURE = PREG_SPLIT_OFFSET_CAPTURE;
21
    const PREG_OFFSET_CAPTURE = PREG_OFFSET_CAPTURE;
22
23
    private $value;
24
    private $encoding;
25
26 548
    public function __construct(string $value, string $encoding = null)
27
    {
28 548
        $this->value = $value;
29 548
        $this->encoding = $encoding;
30 548
    }
31
32 10
    public static function of(string $value, string $encoding = null): self
33
    {
34 10
        return new self($value, $encoding);
35
    }
36
37
    /**
38
     * {@inheritdoc}
39
     */
40 2
    public function toPrimitive(): string
41
    {
42 2
        return $this->value;
43
    }
44
45
    /**
46
     * {@inheritdoc}
47
     */
48 346
    public function __toString(): string
49
    {
50 346
        return $this->value;
51
    }
52
53 68
    public function encoding(): self
54
    {
55 68
        if (\is_null($this->encoding)) {
56 58
            $this->encoding = \mb_internal_encoding();
57
        }
58
59 68
        return new self($this->encoding);
0 ignored issues
show
Bug introduced by
It seems like $this->encoding can also be of type boolean; however, parameter $value of Innmind\Immutable\Str::__construct() 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

59
        return new self(/** @scrutinizer ignore-type */ $this->encoding);
Loading history...
60
    }
61
62 30
    public function toEncoding(string $encoding): self
63
    {
64 30
        return new self($this->value, $encoding);
65
    }
66
67
    /**
68
     * Split the string into a collection of ones
69
     *
70
     * @return StreamInterface<self>
71
     */
72 14
    public function split(string $delimiter = null): StreamInterface
73
    {
74 14
        if (\is_null($delimiter) || $delimiter === '') {
75 6
            return $this->chunk();
76
        }
77
78 10
        $parts = \explode($delimiter, $this->value);
79 10
        $stream = new Stream(self::class);
80
81 10
        foreach ($parts as $part) {
82 10
            $stream = $stream->add(new self($part, $this->encoding));
83
        }
84
85 10
        return $stream;
86
    }
87
88
    /**
89
     * Returns a collection of the string splitted by the given chunk size
90
     *
91
     * @return StreamInterface<self>
92
     */
93 14
    public function chunk(int $size = 1): StreamInterface
94
    {
95 14
        $stream = new Stream(self::class);
96 14
        $string = $this;
97
98 14
        while ($string->length() > 0) {
99 14
            $stream = $stream->add($string->substring(0, $size));
100 14
            $string = $string->substring($size);
101
        }
102
103 14
        return $stream;
104
    }
105
106
    /**
107
     * Returns the position of the first occurence of the string
108
     *
109
     * @throws SubstringException If the string is not found
110
     */
111 14
    public function position(string $needle, int $offset = 0): int
112
    {
113 14
        $position = \mb_strpos($this->value, $needle, $offset, (string) $this->encoding());
114
115 14
        if ($position === false) {
116 8
            throw new SubstringException(\sprintf(
117 8
                'Substring "%s" not found',
118 8
                $needle
119
            ));
120
        }
121
122 12
        return (int) $position;
123
    }
124
125
    /**
126
     * Replace all occurences of the search string with the replacement one
127
     */
128 4
    public function replace(string $search, string $replacement): self
129
    {
130 4
        if (!$this->contains($search)) {
131 2
            return $this;
132
        }
133
134
        return $this
135 4
            ->split($search)
136 4
            ->join($replacement);
137
    }
138
139
    /**
140
     * Returns the string following the given delimiter
141
     *
142
     * @throws SubstringException If the string is not found
143
     */
144 6
    public function str(string $delimiter): self
145
    {
146 6
        $sub = \mb_strstr($this->value, $delimiter, false, (string) $this->encoding());
147
148 6
        if ($sub === false) {
149 2
            throw new SubstringException(\sprintf(
150 2
                'Substring "%s" not found',
151 2
                $delimiter
152
            ));
153
        }
154
155 4
        return new self($sub, $this->encoding);
0 ignored issues
show
Bug introduced by
It seems like $this->encoding can also be of type boolean; however, parameter $encoding of Innmind\Immutable\Str::__construct() does only seem to accept null|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

155
        return new self($sub, /** @scrutinizer ignore-type */ $this->encoding);
Loading history...
156
    }
157
158
    /**
159
     * Return the string in upper case
160
     */
161 6
    public function toUpper(): self
162
    {
163 6
        return new self(\mb_strtoupper($this->value), $this->encoding);
164
    }
165
166
    /**
167
     * Return the string in lower case
168
     */
169 4
    public function toLower(): self
170
    {
171 4
        return new self(\mb_strtolower($this->value), $this->encoding);
172
    }
173
174
    /**
175
     * Return the string length
176
     */
177 40
    public function length(): int
178
    {
179 40
        return \mb_strlen($this->value, (string) $this->encoding());
180
    }
181
182 2
    public function empty(): bool
183
    {
184 2
        return $this->value === '';
185
    }
186
187
    /**
188
     * Reverse the string
189
     */
190 2
    public function reverse(): self
191
    {
192
        return $this
193 2
            ->chunk()
194 2
            ->reverse()
195 2
            ->join('');
196
    }
197
198
    /**
199
     * Pad to the right
200
     */
201 2
    public function rightPad(int $length, string $character = ' '): self
202
    {
203 2
        return $this->pad($length, $character, self::PAD_RIGHT);
204
    }
205
206
    /**
207
     * Pad to the left
208
     */
209 2
    public function leftPad(int $length, string $character = ' '): self
210
    {
211 2
        return $this->pad($length, $character, self::PAD_LEFT);
212
    }
213
214
    /**
215
     * Pad both sides
216
     */
217 2
    public function uniPad(int $length, string $character = ' '): self
218
    {
219 2
        return $this->pad($length, $character, self::PAD_BOTH);
220
    }
221
222
    /**
223
     * Find length of initial segment not matching mask
224
     */
225 2
    public function cspn(string $mask, int $start = 0, int $length = null): int
226
    {
227 2
        if ($length === null) {
228 2
            $value = \strcspn($this->value, $mask, $start);
229
        } else {
230 2
            $value = \strcspn(
231 2
                $this->value,
232 2
                $mask,
233 2
                $start,
234 2
                $length
235
            );
236
        }
237
238 2
        return (int) $value;
239
    }
240
241
    /**
242
     * Repeat the string n times
243
     */
244 2
    public function repeat(int $repeat): self
245
    {
246 2
        return new self(\str_repeat($this->value, $repeat), $this->encoding);
247
    }
248
249
    /**
250
     * Shuffle the string
251
     */
252 4
    public function shuffle(): self
253
    {
254 4
        $parts = $this->chunk()->toPrimitive();
255 4
        \shuffle($parts);
256
257 4
        return new self(\implode('', $parts), $this->encoding);
258
    }
259
260 2
    public function stripSlashes(): self
261
    {
262 2
        return new self(\stripslashes($this->value), $this->encoding);
263
    }
264
265
    /**
266
     * Strip C-like slashes
267
     */
268 2
    public function stripCSlashes(): self
269
    {
270 2
        return new self(\stripcslashes($this->value), $this->encoding);
271
    }
272
273
    /**
274
     * Return the word count
275
     */
276 2
    public function wordCount(string $charlist = ''): int
277
    {
278 2
        return (int) \str_word_count(
279 2
            $this->value,
280 2
            0,
281 2
            $charlist
282
        );
283
    }
284
285
    /**
286
     * Return the collection of words
287
     *
288
     * @return MapInterface<int, self>
289
     */
290 2
    public function words(string $charlist = ''): MapInterface
291
    {
292 2
        $words = \str_word_count($this->value, 2, $charlist);
293 2
        $map = new Map('int', self::class);
294
295 2
        foreach ($words as $position => $word) {
296 2
            $map = $map->put($position, new self($word, $this->encoding));
297
        }
298
299 2
        return $map;
300
    }
301
302
    /**
303
     * Split the string using a regular expression
304
     *
305
     * @return StreamInterface<self>
306
     */
307 4
    public function pregSplit(string $regex, int $limit = -1): StreamInterface
308
    {
309 4
        $strings = \preg_split($regex, $this->value, $limit);
310 4
        $stream = new Stream(self::class);
311
312 4
        foreach ($strings as $string) {
313 4
            $stream = $stream->add(new self($string, $this->encoding));
314
        }
315
316 4
        return $stream;
317
    }
318
319
    /**
320
     * Check if the string match the given regular expression
321
     *
322
     * @throws Exception If the regex failed
323
     */
324 4
    public function matches(string $regex): bool
325
    {
326 4
        if (\func_num_args() !== 1) {
327
            throw new LogicException('Offset is no longer supported');
328
        }
329
330 4
        return RegExp::of($regex)->matches($this);
331
    }
332
333
    /**
334
     * Return a collection of the elements matching the regex
335
     *
336
     * @deprecated replaced by self::capture, to be removed in 3.0
337
     *
338
     * @throws Exception If the regex failed
339
     *
340
     * @return MapInterface<scalar, self>
341
     */
342
    public function getMatches(
343
        string $regex,
344
        int $offset = 0,
345
        int $flags = self::PREG_NO_FLAGS
346
    ): MapInterface {
347
        return $this->capture($regex, $offset, $flags);
348
    }
349
350
    /**
351
     * Return a collection of the elements matching the regex
352
     *
353
     * @throws Exception If the regex failed
354
     *
355
     * @return MapInterface<scalar, self>
356
     */
357 6
    public function capture(string $regex): MapInterface
358
    {
359 6
        if (\func_num_args() !== 1) {
360
            throw new LogicException('Offset and flags are no longer supported');
361
        }
362
363 6
        return RegExp::of($regex)->capture($this);
364
    }
365
366
    /**
367
     * Replace part of the string by using a regular expression
368
     *
369
     * @throws Exception If the regex failed
370
     */
371 2
    public function pregReplace(
372
        string $regex,
373
        string $replacement,
374
        int $limit = -1
375
    ): self {
376 2
        $value = \preg_replace(
377 2
            $regex,
378 2
            $replacement,
379 2
            $this->value,
380 2
            $limit
381
        );
382
383 2
        if ($value === null) {
384
            throw new RegexException('', \preg_last_error());
385
        }
386
387 2
        return new self($value, $this->encoding);
388
    }
389
390
    /**
391
     * Return part of the string
392
     */
393 36
    public function substring(int $start, int $length = null): self
394
    {
395 36
        if ($this->length() === 0) {
396 2
            return $this;
397
        }
398
399 36
        $sub = \mb_substr($this->value, $start, $length, (string) $this->encoding());
400
401 36
        return new self($sub, $this->encoding);
0 ignored issues
show
Bug introduced by
It seems like $this->encoding can also be of type boolean; however, parameter $encoding of Innmind\Immutable\Str::__construct() does only seem to accept null|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

401
        return new self($sub, /** @scrutinizer ignore-type */ $this->encoding);
Loading history...
402
    }
403
404 2
    public function take(int $size): self
405
    {
406 2
        return $this->substring(0, $size);
407
    }
408
409 4
    public function takeEnd(int $size): self
410
    {
411 4
        return $this->substring(-$size);
412
    }
413
414 2
    public function drop(int $size): self
415
    {
416 2
        return $this->substring($size);
417
    }
418
419 2
    public function dropEnd(int $size): self
420
    {
421 2
        return $this->substring(0, $this->length() - $size);
422
    }
423
424
    /**
425
     * Return a formatted string
426
     */
427 2
    public function sprintf(...$values): self
428
    {
429 2
        return new self(\sprintf($this->value, ...$values), $this->encoding);
430
    }
431
432
    /**
433
     * Return the string with the first letter as uppercase
434
     */
435 4
    public function ucfirst(): self
436
    {
437
        return $this
438 4
            ->substring(0, 1)
439 4
            ->toUpper()
440 4
            ->append((string) $this->substring(1));
441
    }
442
443
    /**
444
     * Return the string with the first letter as lowercase
445
     */
446 2
    public function lcfirst(): self
447
    {
448
        return $this
449 2
            ->substring(0, 1)
450 2
            ->toLower()
451 2
            ->append((string) $this->substring(1));
452
    }
453
454
    /**
455
     * Return a CamelCase representation of the string
456
     */
457 2
    public function camelize(): self
458
    {
459
        return $this
460 2
            ->pregSplit('/_| /')
461
            ->map(function(self $part) {
462 2
                return $part->ucfirst();
463 2
            })
464 2
            ->join('')
465 2
            ->toEncoding((string) $this->encoding());
466
    }
467
468
    /**
469
     * Append a string at the end of the current one
470
     */
471 8
    public function append(string $string): self
472
    {
473 8
        return new self((string) $this.$string, $this->encoding);
474
    }
475
476
    /**
477
     * Prepend a string at the beginning of the current one
478
     */
479 2
    public function prepend(string $string): self
480
    {
481 2
        return new self($string.(string) $this, $this->encoding);
482
    }
483
484
    /**
485
     * Check if the 2 strings are equal
486
     */
487 76
    public function equals(self $string): bool
488
    {
489 76
        return (string) $this === (string) $string;
490
    }
491
492
    /**
493
     * Trim the string
494
     */
495 2
    public function trim(string $mask = null): self
496
    {
497 2
        return new self(
498 2
            $mask === null ? \trim((string) $this) : \trim((string) $this, $mask),
499 2
            $this->encoding
500
        );
501
    }
502
503
    /**
504
     * Trim the right side of the string
505
     */
506 2
    public function rightTrim(string $mask = null): self
507
    {
508 2
        return new self(
509 2
            $mask === null ? \rtrim((string) $this) : \rtrim((string) $this, $mask),
510 2
            $this->encoding
511
        );
512
    }
513
514
    /**
515
     * Trim the left side of the string
516
     */
517 2
    public function leftTrim(string $mask = null): self
518
    {
519 2
        return new self(
520 2
            $mask === null ? \ltrim((string) $this) : \ltrim((string) $this, $mask),
521 2
            $this->encoding
522
        );
523
    }
524
525
    /**
526
     * Check if the given string is present in the current one
527
     */
528 6
    public function contains(string $value): bool
529
    {
530
        try {
531 6
            $this->position($value);
532
533 6
            return true;
534 4
        } catch (SubstringException $e) {
535 4
            return false;
536
        }
537
    }
538
539
    /**
540
     * Check if the current string starts with the given string
541
     */
542 2
    public function startsWith(string $value): bool
543
    {
544 2
        if ($value === '') {
545 2
            return true;
546
        }
547
548
        try {
549 2
            return $this->position($value) === 0;
550 2
        } catch (SubstringException $e) {
551 2
            return false;
552
        }
553
    }
554
555
    /**
556
     * Check if the current string ends with the given string
557
     */
558 2
    public function endsWith(string $value): bool
559
    {
560 2
        if ($value === '') {
561 2
            return true;
562
        }
563
564 2
        return (string) $this->takeEnd(self::of($value, $this->encoding)->length()) === $value;
565
    }
566
567
    /**
568
     * Quote regular expression characters
569
     */
570 2
    public function pregQuote(string $delimiter = ''): self
571
    {
572 2
        return new self(\preg_quote((string) $this, $delimiter), $this->encoding);
573
    }
574
575
    /**
576
     * Pad the string
577
     */
578 2
    private function pad(
579
        int $length,
580
        string $character = ' ',
581
        int $direction = self::PAD_RIGHT
582
    ): self {
583 2
        return new self(\str_pad(
584 2
            $this->value,
585 2
            $length,
586 2
            $character,
587 2
            $direction
588 2
        ), $this->encoding);
589
    }
590
}
591