Completed
Push — master ( e58835...7cb891 )
by Frederik
02:40
created

Base64DecodedStream::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
declare(strict_types=1);
3
4
namespace Genkgo\Mail\Stream;
5
6
use Genkgo\Mail\StreamInterface;
7
8
final class Base64DecodedStream implements StreamInterface
9
{
10
    /**
11
     * @var StreamInterface
12
     */
13
    private $decoratedStream;
14
15
    /**
16
     * @var resource
17
     */
18
    private $filter;
19
20
    /**
21
     * @param resource $resource
22
     */
23 7
    public function __construct($resource)
24
    {
25 7
        $this->decoratedStream = new ResourceStream($resource);
26
27 7
        $this->applyFilter();
28 7
    }
29
30
    /**
31
     * @param string $string
32
     * @return Base64DecodedStream
33
     */
34 7
    public static function fromString(string $string): Base64DecodedStream
35
    {
36 7
        $resource = \fopen('php://memory', 'r+');
37 7
        if ($resource === false) {
38
            throw new \UnexpectedValueException('Cannot open php://memory for writing');
39
        }
40
41 7
        \fwrite($resource, $string);
42 7
        return new self($resource);
43
    }
44
    
45 7
    private function applyFilter(): void
46
    {
47 7
        $filter = \stream_filter_prepend(
48 7
            $this->decoratedStream->detach(),
49 7
            'convert.base64-decode',
50 7
            STREAM_FILTER_READ
51
        );
52
53 7
        if ($filter === false) {
54
            throw new \UnexpectedValueException('Cannot append filter to stream');
55
        }
56
57 7
        $this->filter = $filter;
58 7
    }
59
60 4
    private function removeFilter(): void
61
    {
62 4
        if ($this->filter !== null) {
63 4
            \stream_filter_remove($this->filter);
64
        }
65 4
    }
66
67
    /**
68
     * @return string
69
     */
70 3
    public function __toString(): string
71
    {
72 3
        $this->rewind();
73 3
        return $this->decoratedStream->__toString();
74
    }
75
    
76
    public function close(): void
77
    {
78
        $this->decoratedStream->close();
79
    }
80
81
    /**
82
     * @return mixed
83
     */
84
    public function detach()
85
    {
86
        return $this->decoratedStream->detach();
87
    }
88
89
    /**
90
     * @return int|null
91
     */
92 1
    public function getSize(): ?int
93
    {
94 1
        $this->removeFilter();
95 1
        $lastCharacters = \substr($this->decoratedStream->getContents(), -2);
96 1
        $this->decoratedStream->rewind();
97 1
        $this->applyFilter();
98
99 1
        $padding = 0;
100 1
        if ($lastCharacters[0] === '=') {
101
            $padding++;
102
        }
103 1
        if ($lastCharacters[1] === '=') {
104 1
            $padding++;
105
        }
106
107 1
        return (int) (($this->decoratedStream->getSize() / 4) * 3) - $padding;
108
    }
109
110
    /**
111
     * @return int
112
     * @throws \RuntimeException
113
     */
114 1
    public function tell(): int
115
    {
116 1
        return $this->decoratedStream->tell();
117
    }
118
119
    /**
120
     * @return bool
121
     */
122
    public function eof(): bool
123
    {
124
        return $this->decoratedStream->eof();
125
    }
126
127
    /**
128
     * @return bool
129
     */
130
    public function isSeekable(): bool
131
    {
132
        return false;
133
    }
134
135
    /**
136
     * @param int $offset
137
     * @param int $whence
138
     * @return int
139
     */
140 1
    public function seek(int $offset, int $whence = SEEK_SET): int
141
    {
142 1
        return -1;
143
    }
144
145
    /**
146
     * @return bool
147
     */
148 4
    public function rewind(): bool
149
    {
150 4
        $this->removeFilter();
151 4
        if (!$this->decoratedStream->rewind()) {
152
            return false;
153
        }
154
155 4
        $this->applyFilter();
156 4
        return true;
157
    }
158
159
    /**
160
     * @return bool
161
     */
162 1
    public function isWritable(): bool
163
    {
164 1
        return false;
165
    }
166
167
    /**
168
     * @param string $string
169
     * @return int
170
     */
171 1
    public function write($string): int
172
    {
173 1
        throw new \RuntimeException('Cannot write to stream');
174
    }
175
176
    /**
177
     * @return bool
178
     */
179
    public function isReadable(): bool
180
    {
181
        return true;
182
    }
183
184
    /**
185
     * @param int $length
186
     * @return string
187
     */
188 1
    public function read(int $length): string
189
    {
190 1
        return $this->decoratedStream->read($length);
191
    }
192
193
    /**
194
     * @return string
195
     */
196 2
    public function getContents(): string
197
    {
198 2
        return $this->decoratedStream->getContents();
199
    }
200
201
    /**
202
     * @param array<int, string> $keys
203
     * @return array<string, mixed>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
204
     */
205
    public function getMetadata(array $keys = []): array
206
    {
207
        return $this->decoratedStream->getMetadata();
208
    }
209
}
210