Passed
Push — master ( 4f4dd1...f4eddc )
by Aleksei
14:21
created

BucketStream   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 165
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 62
dl 0
loc 165
ccs 0
cts 141
cp 0
rs 8.64
c 2
b 0
f 0
wmc 47

27 Methods

Rating   Name   Duplication   Size   Complexity  
A rewind() 0 4 2
A getConverter() 0 3 2
A withBucket() 0 3 1
A tell() 0 3 2
A isWritable() 0 3 1
A getMetadata() 0 12 2
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 getContents() 0 3 1
A read() 0 7 2
A detach() 0 6 1
A __construct() 0 12 4
A hasConverter() 0 3 1
A __toString() 0 9 3
A getSize() 0 3 2
A getMatchedFormat() 0 3 2
A getMatchedResult() 0 3 1
A getBucket() 0 3 1
A isRenderStarted() 0 3 1
A hasBucketFormat() 0 3 2
A hasMatchedFormat() 0 3 1
A initConverter() 0 10 4
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
    public function __construct(ConverterMatcherInterface $converterMatcher, DataBucket $bucket)
22
    {
23
        $this->bucket = $bucket;
24
        $this->converterMatcher = $converterMatcher;
25
26
        // if should be converted
27
        if ($bucket->isConvertable()) {
28
            $result = $this->converterMatcher->match($this->bucket);
29
            if ($result === null && $bucket->hasFormat()) {
30
                throw new ConverterNotFoundException((string)$bucket->getFormat());
31
            }
32
            $this->matchedResult = $result;
33
        }
34
    }
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
    public function withBucket(DataBucket $bucket): self
126
    {
127
        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
    public function hasBucketFormat(): bool
139
    {
140
        return $this->bucket === null ? false : $this->bucket->hasFormat();
141
    }
142
    public function hasMatchedFormat(): bool
143
    {
144
        return $this->matchedResult !== null;
145
    }
146
    public function getMatchedFormat(): ?string
147
    {
148
        return $this->matchedResult === null ? null : $this->matchedResult->getFormat();
149
    }
150
    public function getBucketFormat(): ?string
151
    {
152
        return $this->bucket === null ? null : $this->bucket->getFormat();
153
    }
154
155
    public function isRenderStarted(): bool
156
    {
157
        return $this->stream !== null;
158
    }
159
160
    public function getBucket(): ?DataBucket
161
    {
162
        return $this->bucket;
163
    }
164
    public function getMatchedResult(): ?MatchingResult
165
    {
166
        return $this->matchedResult;
167
    }
168
169
    private function initConverter(): bool
170
    {
171
        if ($this->stream !== null) {
172
            return true;
173
        }
174
        if ($this->matchedResult === null || $this->bucket === null) {
175
            return false;
176
        }
177
        $this->stream = new GeneratorStream($this->matchedResult->getConverter()->convert($this->bucket));
178
        return true;
179
    }
180
}
181