Completed
Push — master ( cc370f...f8592e )
by Frederik
02:45 queued 01:07
created

Base64DecodedStream::getSize()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 12
cts 12
cp 1
rs 9.6666
c 0
b 0
f 0
cc 3
nc 4
nop 0
crap 3
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 13
    public function __construct($resource)
24
    {
25 13
        $this->decoratedStream = new ResourceStream($resource);
26
27 13
        $this->applyFilter();
28 13
    }
29
30
    /**
31
     * @param string $string
32
     * @return Base64DecodedStream
33
     */
34 13
    public static function fromString(string $string): Base64DecodedStream
35
    {
36 13
        $resource = \fopen('php://memory', 'r+');
37 13
        if ($resource === false) {
38
            throw new \UnexpectedValueException('Cannot open php://memory for writing');
39
        }
40
41 13
        \fwrite($resource, $string);
42 13
        return new self($resource);
43
    }
44
    
45 13
    private function applyFilter(): void
46
    {
47 13
        $filter = \stream_filter_prepend(
48 13
            $this->decoratedStream->detach(),
49 13
            'convert.base64-decode',
50 13
            STREAM_FILTER_READ
51
        );
52
53 13
        if ($filter === false) {
54
            throw new \UnexpectedValueException('Cannot append filter to stream');
55
        }
56
57 13
        $this->filter = $filter;
58 13
    }
59
60 7
    private function removeFilter(): void
61
    {
62 7
        if ($this->filter !== null) {
63 7
            \stream_filter_remove($this->filter);
64
        }
65 7
    }
66
67
    /**
68
     * @return string
69
     */
70 4
    public function __toString(): string
71
    {
72 4
        $this->rewind();
73 4
        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 2
    public function getSize(): ?int
93
    {
94 2
        $this->removeFilter();
95 2
        $contents = \preg_replace("/\r\n/", '', (string)$this->decoratedStream->getContents());
96 2
        $lastCharacters = \substr($contents, -2);
97 2
        $this->decoratedStream->rewind();
98 2
        $this->applyFilter();
99
100 2
        $padding = 0;
101 2
        if ($lastCharacters[0] === '=') {
102 1
            $padding++;
103
        }
104 2
        if ($lastCharacters[1] === '=') {
105 2
            $padding++;
106
        }
107
108 2
        return (int) ((\strlen($contents) / 4) * 3) - $padding;
109
    }
110
111
    /**
112
     * @return int
113
     * @throws \RuntimeException
114
     */
115 2
    public function tell(): int
116
    {
117 2
        return $this->decoratedStream->tell();
118
    }
119
120
    /**
121
     * @return bool
122
     */
123
    public function eof(): bool
124
    {
125
        return $this->decoratedStream->eof();
126
    }
127
128
    /**
129
     * @return bool
130
     */
131
    public function isSeekable(): bool
132
    {
133
        return false;
134
    }
135
136
    /**
137
     * @param int $offset
138
     * @param int $whence
139
     * @return int
140
     */
141 2
    public function seek(int $offset, int $whence = SEEK_SET): int
142
    {
143 2
        return -1;
144
    }
145
146
    /**
147
     * @return bool
148
     */
149 6
    public function rewind(): bool
150
    {
151 6
        $this->removeFilter();
152 6
        if (!$this->decoratedStream->rewind()) {
153
            return false;
154
        }
155
156 6
        $this->applyFilter();
157 6
        return true;
158
    }
159
160
    /**
161
     * @return bool
162
     */
163 2
    public function isWritable(): bool
164
    {
165 2
        return false;
166
    }
167
168
    /**
169
     * @param string $string
170
     * @return int
171
     */
172 2
    public function write($string): int
173
    {
174 2
        throw new \RuntimeException('Cannot write to stream');
175
    }
176
177
    /**
178
     * @return bool
179
     */
180
    public function isReadable(): bool
181
    {
182
        return true;
183
    }
184
185
    /**
186
     * @param int $length
187
     * @return string
188
     */
189 2
    public function read(int $length): string
190
    {
191 2
        return $this->decoratedStream->read($length);
192
    }
193
194
    /**
195
     * @return string
196
     */
197 4
    public function getContents(): string
198
    {
199 4
        return $this->decoratedStream->getContents();
200
    }
201
202
    /**
203
     * @param array<int, string> $keys
204
     * @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...
205
     */
206
    public function getMetadata(array $keys = []): array
207
    {
208
        return $this->decoratedStream->getMetadata();
209
    }
210
}
211