Passed
Push — master ( 1af0f9...e3c5be )
by Aleksei
06:41
created

BucketStream::isReadable()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 4
c 1
b 0
f 0
nc 3
nop 0
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 3
rs 10
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
use RuntimeException;
14
15
final class BucketStream implements StreamInterface
16
{
17
    private ConverterMatcherInterface $converterMatcher;
18
    private ?DataBucket $bucket;
19
    private ?GeneratorStream $stream = null;
20
    private ?MatchingResult $matchedResult = null;
21
22 89
    public function __construct(ConverterMatcherInterface $converterMatcher, DataBucket $bucket)
23
    {
24 89
        $this->bucket = $bucket;
25 89
        $this->converterMatcher = $converterMatcher;
26
27 89
        if ($bucket->isConvertable()) {
28 88
            $result = $this->converterMatcher->match($this->bucket);
29
            // if should be converted but no converter was found
30 88
            if ($result === null && $bucket->hasFormat()) {
31 1
                throw new ConverterNotFoundException($bucket->getFormat());
32
            }
33 87
            $this->matchedResult = $result;
34
        }
35 88
    }
36 4
    public function __toString(): string
37
    {
38
        try {
39 4
            return $this->getContents();
40 3
        } catch (\Exception $e) {
41 3
            return '';
42
        }
43
    }
44 2
    public function close(): void
45
    {
46 2
        $this->detach();
47 2
    }
48 18
    public function detach()
49
    {
50 18
        $this->matchedResult = null;
51 18
        $this->stream = null;
52 18
        $this->bucket = null;
53 18
        return null;
54
    }
55 9
    public function getSize(): ?int
56
    {
57 9
        return $this->stream === null ? null : $this->stream->getSize();
58
    }
59 7
    public function tell(): int
60
    {
61 7
        return $this->stream === null ? 0 : $this->stream->tell();
62
    }
63 20
    public function eof(): bool
64
    {
65 20
        if ($this->bucket === null) {
66 6
            return true;
67
        }
68 14
        return $this->stream === null ? false : $this->stream->eof();
69
    }
70 6
    public function isSeekable(): bool
71
    {
72 6
        return false;
73
    }
74 2
    public function seek($offset, $whence = \SEEK_SET): void
75
    {
76 2
        throw new RuntimeException('Stream is not seekable.');
77
    }
78 3
    public function rewind(): void
79
    {
80 3
        if ($this->stream !== null) {
81 1
            $this->stream->rewind();
82
        }
83 3
    }
84 2
    public function isWritable(): bool
85
    {
86 2
        return false;
87
    }
88 2
    public function write($string): int
89
    {
90 2
        throw new RuntimeException('Cannot write to a non-writable stream.');
91
    }
92 24
    public function isReadable(): bool
93
    {
94 24
        if ($this->matchedResult === null) {
95 13
            return false;
96
        }
97 11
        $this->initConverter();
98 11
        return $this->stream === null ? false : $this->stream->isReadable();
99
    }
100 13
    public function read($length): string
101
    {
102 13
        if (!$this->isReadable()) {
103 5
            throw new RuntimeException('Stream should be rendered.');
104
        }
105
        # read generator stream
106 8
        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

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