Passed
Push — master ( 8d8aff...14bf48 )
by Aleksei
07:24
created

BucketStream::withBucket()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 1
b 0
f 0
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 bool $calculated = false;
20
    private ?MatchingResult $matchedResult = null;
21
22
    public function __construct(ConverterMatcherInterface $converterMatcher, DataBucket $bucket)
23
    {
24
        $this->bucket = $bucket;
25
        $this->converterMatcher = $converterMatcher;
26
        $this->prepareConverter();
27
    }
28
    public function __toString(): string
29
    {
30
        try {
31
            if ($this->isSeekable()) {
32
                $this->seek(0);
33
            }
34
            return $this->getContents();
35
        } catch (\Exception $e) {
36
            return '';
37
        }
38
    }
39
    public function close(): void
40
    {
41
        $this->detach();
42
    }
43
    public function detach()
44
    {
45
        $this->matchedResult = null;
46
        $this->stream = null;
47
        $result = $this->bucket;
48
        $this->bucket = null;
49
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type roxblnfk\SmartStream\Data\DataBucket which is incompatible with the return type mandated by Psr\Http\Message\StreamInterface::detach() of null|resource.
Loading history...
50
    }
51
    public function getSize(): ?int
52
    {
53
        return $this->stream === null ? null : $this->stream->getSize();
54
    }
55
    public function tell(): int
56
    {
57
        return $this->stream === null ? 0 : $this->stream->tell();
58
    }
59
    public function eof(): bool
60
    {
61
        return $this->stream === null ? false : $this->stream->eof();
62
    }
63
    public function isSeekable(): bool
64
    {
65
        return false;
66
    }
67
    public function seek($offset, $whence = \SEEK_SET): void
68
    {
69
        throw new \RuntimeException('Stream is not seekable.');
70
    }
71
    public function rewind(): void
72
    {
73
        if ($this->stream !== null) {
74
            $this->stream->rewind();
75
            $this->caret = 0;
0 ignored issues
show
Bug Best Practice introduced by
The property caret does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
76
            $this->started = false;
0 ignored issues
show
Bug Best Practice introduced by
The property started does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
77
        }
78
    }
79
    public function isWritable(): bool
80
    {
81
        return false;
82
    }
83
    public function write($string): int
84
    {
85
        throw new \RuntimeException('Cannot write to a non-writable stream.');
86
    }
87
    public function isReadable(): bool
88
    {
89
        if ($this->matchedResult === null) {
90
            return false;
91
        }
92
        $this->initConverter();
93
        return $this->stream === null ? false : $this->stream->isReadable();
94
    }
95
    public function read($length): string
96
    {
97
        if (!$this->isReadable()) {
98
            throw new \RuntimeException('Stream should be rendered.');
99
        }
100
        # read generator stream
101
        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

101
        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...
102
    }
103
    public function getContents(): string
104
    {
105
        return $this->read(PHP_INT_MAX);
106
    }
107
    public function getMetadata($key = null)
108
    {
109
        $meta = [
110
            'seekable' => $this->isSeekable(),
111
            'eof' => $this->eof(),
112
        ];
113
114
        if (null === $key) {
115
            return $meta;
116
        }
117
118
        return $meta[$key] ?? null;
119
    }
120
121
    public function withBucket(DataBucket $bucket): self
122
    {
123
        return new static($this->converterMatcher, $bucket);
124
    }
125
126
    public function hasConverter(): bool
127
    {
128
        return $this->matchedResult !== null;
129
    }
130
    public function getConverter(): ?ConverterInterface
131
    {
132
        return $this->matchedResult === null ? null : $this->matchedResult->getConverter();
133
    }
134
    public function hasFormat(): bool
135
    {
136
        return $this->matchedResult !== null;
137
    }
138
    public function getFormat(): ?string
139
    {
140
        return $this->matchedResult === null ? null : $this->matchedResult->getFormat();
141
    }
142
143
    public function isRenderStarted(): bool
144
    {
145
        return $this->stream !== null;
146
    }
147
148
    public function getBucket(): ?DataBucket
149
    {
150
        return $this->bucket;
151
    }
152
    public function getMatchedResult(): ?MatchingResult
153
    {
154
        return $this->matchedResult;
155
    }
156
157
    private function initConverter(): bool
158
    {
159
        if ($this->stream !== null) {
160
            return true;
161
        }
162
        if ($this->matchedResult === null || $this->bucket === null) {
163
            return false;
164
        }
165
        $this->stream = new GeneratorStream($this->matchedResult->getConverter()->convert($this->bucket));
166
        return true;
167
    }
168
    private function prepareConverter(): void
169
    {
170
        if ($this->calculated) {
171
            return;
172
        }
173
        // if should be converted
174
        if ($this->bucket->isFormatable() && $this->bucket->hasFormat()) {
0 ignored issues
show
Bug introduced by
The method isFormatable() 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

174
        if ($this->bucket->/** @scrutinizer ignore-call */ isFormatable() && $this->bucket->hasFormat()) {

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...
175
            $result = $this->converterMatcher->match($this->bucket);
0 ignored issues
show
Bug introduced by
It seems like $this->bucket can also be of type null; however, parameter $bucket of roxblnfk\SmartStream\Con...tcherInterface::match() does only seem to accept roxblnfk\SmartStream\Data\DataBucket, 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

175
            $result = $this->converterMatcher->match(/** @scrutinizer ignore-type */ $this->bucket);
Loading history...
176
            if ($result === null) {
177
                throw new ConverterNotFoundException((string)$this->bucket->getFormat());
178
            }
179
            $this->matchedResult = $result;
180
        }
181
        $this->calculated = true;
182
    }
183
}
184