Passed
Push — master ( c3e4c9...e46620 )
by Aleksei
02:36 queued 45s
created

BucketStream   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 167
Duplicated Lines 0 %

Test Coverage

Coverage 21.18%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 62
dl 0
loc 167
ccs 18
cts 85
cp 0.2118
rs 8.64
c 3
b 0
f 0
wmc 47

27 Methods

Rating   Name   Duplication   Size   Complexity  
A rewind() 0 4 2
A withBucket() 0 3 1
A getConverter() 0 3 2
A tell() 0 3 2
A isWritable() 0 3 1
A getMatchedFormat() 0 3 2
A getMetadata() 0 12 2
A getMatchedResult() 0 3 1
A isSeekable() 0 3 1
A seek() 0 3 1
A close() 0 3 1
A write() 0 3 1
A isReadable() 0 7 3
A eof() 0 3 2
A getBucket() 0 3 1
A getContents() 0 3 1
A read() 0 7 2
A detach() 0 6 1
A isRenderStarted() 0 3 1
A __construct() 0 12 4
A hasConverter() 0 3 1
A hasBucketFormat() 0 3 2
A __toString() 0 9 3
A hasMatchedFormat() 0 3 1
A initConverter() 0 10 4
A getSize() 0 3 2
A getBucketFormat() 0 3 2

How to fix   Complexity   

Complex Class

Complex classes like BucketStream 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 BucketStream, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace roxblnfk\SmartStream\Stream;
6
7
use Psr\Http\Message\StreamInterface;
8
use roxblnfk\SmartStream\ConverterInterface;
9
use roxblnfk\SmartStream\ConverterMatcherInterface;
10
use roxblnfk\SmartStream\Data\DataBucket;
11
use roxblnfk\SmartStream\Exception\ConverterNotFoundException;
12
use roxblnfk\SmartStream\Matching\MatchingResult;
13
14
final class BucketStream implements StreamInterface
15
{
16
    private ConverterMatcherInterface $converterMatcher;
17
    private ?DataBucket $bucket;
18
    private ?GeneratorStream $stream = null;
19
    private ?MatchingResult $matchedResult = null;
20
21 5
    public function __construct(ConverterMatcherInterface $converterMatcher, DataBucket $bucket)
22
    {
23 5
        $this->bucket = $bucket;
24 5
        $this->converterMatcher = $converterMatcher;
25
26 5
        if ($bucket->isConvertable()) {
27 5
            $result = $this->converterMatcher->match($this->bucket);
28
            // if should be converted but no converter was found
29 5
            if ($result === null && $bucket->hasFormat()) {
30
                throw new ConverterNotFoundException($bucket->getFormat());
31
            }
32 5
            $this->matchedResult = $result;
33
        }
34 5
    }
35
    public function __toString(): string
36
    {
37
        try {
38
            if ($this->isSeekable()) {
39
                $this->seek(0);
40
            }
41
            return $this->getContents();
42
        } catch (\Exception $e) {
43
            return '';
44
        }
45
    }
46
    public function close(): void
47
    {
48
        $this->detach();
49
    }
50
    public function detach()
51
    {
52
        $this->matchedResult = null;
53
        $this->stream = null;
54
        $this->bucket = null;
55
        return null;
56
    }
57
    public function getSize(): ?int
58
    {
59
        return $this->stream === null ? null : $this->stream->getSize();
60
    }
61
    public function tell(): int
62
    {
63
        return $this->stream === null ? 0 : $this->stream->tell();
64
    }
65
    public function eof(): bool
66
    {
67
        return $this->stream === null ? false : $this->stream->eof();
68
    }
69
    public function isSeekable(): bool
70
    {
71
        return false;
72
    }
73
    public function seek($offset, $whence = \SEEK_SET): void
74
    {
75
        throw new \RuntimeException('Stream is not seekable.');
76
    }
77
    public function rewind(): void
78
    {
79
        if ($this->stream !== null) {
80
            $this->stream->rewind();
81
        }
82
    }
83
    public function isWritable(): bool
84
    {
85
        return false;
86
    }
87
    public function write($string): int
88
    {
89
        throw new \RuntimeException('Cannot write to a non-writable stream.');
90
    }
91
    public function isReadable(): bool
92
    {
93
        if ($this->matchedResult === null) {
94
            return false;
95
        }
96
        $this->initConverter();
97
        return $this->stream === null ? false : $this->stream->isReadable();
98
    }
99
    public function read($length): string
100
    {
101
        if (!$this->isReadable()) {
102
            throw new \RuntimeException('Stream should be rendered.');
103
        }
104
        # read generator stream
105
        return $this->stream->read($length);
0 ignored issues
show
Bug introduced by
The method read() does not exist on null. ( Ignorable by Annotation )

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

105
        return $this->stream->/** @scrutinizer ignore-call */ read($length);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
106
    }
107
    public function getContents(): string
108
    {
109
        return $this->read(PHP_INT_MAX);
110
    }
111
    public function getMetadata($key = null)
112
    {
113
        $meta = [
114
            'seekable' => $this->isSeekable(),
115
            'eof' => $this->eof(),
116
        ];
117
118
        if (null === $key) {
119
            return $meta;
120
        }
121
122
        return $meta[$key] ?? null;
123
    }
124
125 2
    public function withBucket(DataBucket $bucket): self
126
    {
127 2
        return new static($this->converterMatcher, $bucket);
128
    }
129
130
    public function hasConverter(): bool
131
    {
132
        return $this->matchedResult !== null;
133
    }
134
    public function getConverter(): ?ConverterInterface
135
    {
136
        return $this->matchedResult === null ? null : $this->matchedResult->getConverter();
137
    }
138
139 1
    public function hasBucketFormat(): bool
140
    {
141 1
        return $this->bucket === null ? false : $this->bucket->hasFormat();
142
    }
143 1
    public function getBucketFormat(): ?string
144
    {
145 1
        return $this->bucket === null ? null : $this->bucket->getFormat();
146
    }
147
148
    public function hasMatchedFormat(): bool
149
    {
150
        return $this->matchedResult !== null;
151
    }
152
    public function getMatchedFormat(): ?string
153
    {
154
        return $this->matchedResult === null ? null : $this->matchedResult->getFormat();
155
    }
156
157 1
    public function isRenderStarted(): bool
158
    {
159 1
        return $this->stream !== null;
160
    }
161
162 5
    public function getBucket(): ?DataBucket
163
    {
164 5
        return $this->bucket;
165
    }
166
    public function getMatchedResult(): ?MatchingResult
167
    {
168
        return $this->matchedResult;
169
    }
170
171
    private function initConverter(): bool
172
    {
173
        if ($this->stream !== null) {
174
            return true;
175
        }
176
        if ($this->matchedResult === null || $this->bucket === null) {
177
            return false;
178
        }
179
        $this->stream = new GeneratorStream($this->matchedResult->getConverter()->convert($this->bucket));
180
        return true;
181
    }
182
}
183