• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

plank / laravel-mediable / 13689748990

06 Mar 2025 02:19AM UTC coverage: 94.72%. Remained the same
13689748990

push

github

web-flow
Fix template type definition in MediableCollection (#378)

1489 of 1572 relevant lines covered (94.72%)

153.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.41
/src/SourceAdapters/StreamAdapter.php
1
<?php
2
declare(strict_types=1);
3

4
namespace Plank\Mediable\SourceAdapters;
5

6
use GuzzleHttp\Psr7\CachingStream;
7
use Plank\Mediable\Exceptions\MediaUpload\ConfigurationException;
8
use Psr\Http\Message\StreamInterface;
9

10
/**
11
 * Stream Adapter.
12
 *
13
 * Adapts a stream object or resource.
14
 */
15
class StreamAdapter implements SourceAdapterInterface
16
{
17
    const BUFFER_SIZE = 2048;
18

19
    private const TYPE_MEMORY = 'php';
20
    private const TYPE_DATA_URL = 'rfc2397';
21
    private const TYPE_HTTP = 'http';
22
    private const TYPE_FILE = 'plainfile';
23
    private const TYPE_FTP = 'ftp';
24

25
    protected StreamInterface $source;
26

27
    protected StreamInterface $originalSource;
28

29
    /**
30
     * The contents of the stream.
31
     * @var string
32
     */
33
    protected string $contents;
34

35
    protected int $size;
36

37
    /** @var array<string, string> */
38
    protected array $hash;
39

40
    protected string $mimeType;
41

42
    /**
43
     * Constructor.
44
     * @param StreamInterface $source
45
     */
46
    public function __construct(StreamInterface $source)
47
    {
48
        if (!$source->isReadable()) {
192✔
49
            throw ConfigurationException::invalidSource('Stream must be readable');
12✔
50
        }
51

52
        $this->source = $this->originalSource = $source;
180✔
53
        if (!$this->source->isSeekable()) {
180✔
54
            $this->source = new CachingStream($this->source);
30✔
55
        }
56

57
        if ($this->getStreamType() === self::TYPE_HTTP) {
180✔
58
            $code = $this->getHttpResponseCode();
30✔
59
            if (!$code || $code < 200 || $code >= 300) {
30✔
60
                throw ConfigurationException::unrecognizedSource(
×
61
                    "Failed to fetch URL, received HTTP status code $code"
×
62
                );
×
63
            }
64
        }
65
    }
66

67
    /**
68
     * {@inheritdoc}
69
     */
70
    private function path(): ?string
71
    {
72
        $type = $this->getStreamType();
78✔
73
        if (in_array($type, [self::TYPE_DATA_URL, self::TYPE_MEMORY])) {
78✔
74
            return null;
36✔
75
        }
76

77
        return $this->originalSource->getMetadata('uri');
42✔
78
    }
79

80
    /**
81
     * {@inheritdoc}
82
     */
83
    public function filename(): ?string
84
    {
85
        $path = $this->path();
54✔
86
        if (!$path) {
54✔
87
            return null;
30✔
88
        }
89
        return pathinfo(
24✔
90
            parse_url($this->path(), PHP_URL_PATH) ?? '',
24✔
91
            PATHINFO_FILENAME
24✔
92
        ) ?: null;
24✔
93
    }
94

95
    /**
96
     * {@inheritdoc}
97
     */
98
    public function extension(): ?string
99
    {
100
        if ($path = $this->path()) {
78✔
101
            $extension = pathinfo(
42✔
102
                parse_url($path, PHP_URL_PATH) ?? '',
42✔
103
                PATHINFO_EXTENSION
42✔
104
            );
42✔
105
            if ($extension) {
42✔
106
                return $extension;
42✔
107
            }
108
        }
109

110
        return null;
36✔
111
    }
112

113
    /**
114
     * {@inheritdoc}
115
     */
116
    public function mimeType(): string
117
    {
118
        if (!isset($this->mimeType)) {
90✔
119
            $this->scanFile();
78✔
120
        }
121

122
        return $this->mimeType;
90✔
123
    }
124

125
    public function clientMimeType(): ?string
126
    {
127
        // supported primarily by data URLs
128
        if ($mime = $this->originalSource->getMetadata('mediatype')) {
60✔
129
            return $mime;
18✔
130
        }
131

132
        if ($contentType = $this->getHttpHeader('Content-Type')) {
42✔
133
            $mime = explode(';', $contentType)[0];
18✔
134

135
            return $mime;
18✔
136
        }
137

138
        return null;
24✔
139
    }
140

141
    public function getStream(): StreamInterface
142
    {
143
        return $this->source;
174✔
144
    }
145

146
    /**
147
     * {@inheritdoc}
148
     */
149
    public function size(): int
150
    {
151
        $size = $this->source->getSize();
180✔
152

153
        if (!is_null($size)) {
180✔
154
            return $size;
150✔
155
        }
156

157
        if (!isset($this->size)) {
30✔
158
            $this->scanFile();
12✔
159
        }
160

161
        return $this->size;
30✔
162
    }
163

164
    /**
165
     * {@inheritdoc}
166
     * @param string $algo
167
     */
168
    public function hash(string $algo = 'md5'): string
169
    {
170
        if (!isset($this->hash[$algo])) {
96✔
171
            $this->scanFile($algo);
96✔
172
        }
173
        return $this->hash[$algo];
96✔
174
    }
175

176
    /**
177
     * @return array|mixed|null
178
     */
179
    private function getStreamType(): mixed
180
    {
181
        return strtolower($this->originalSource->getMetadata('wrapper_type'));
180✔
182
    }
183

184
    private function getHttpHeader($headerName): ?string
185
    {
186
        if ($this->getStreamType() !== self::TYPE_HTTP) {
42✔
187
            return null;
24✔
188
        }
189

190
        $headers = $this->originalSource->getMetadata('wrapper_data');
18✔
191
        if ($headers) {
18✔
192
            foreach ($headers as $header) {
18✔
193
                if (stripos($header, "$headerName: ") === 0) {
18✔
194
                    return substr($header, strlen($headerName) + 2);
18✔
195
                }
196
            }
197
        }
198

199
        return null;
×
200
    }
201

202
    private function getHttpResponseCode(): ?int
203
    {
204
        if ($this->getStreamType() !== self::TYPE_HTTP) {
30✔
205
            return null;
×
206
        }
207
        $headers = $this->originalSource->getMetadata('wrapper_data');
30✔
208
        if (!empty($headers)
30✔
209
            && preg_match('/HTTP\/\d+\.\d+\s+(\d+)/i', $headers[0], $matches)
30✔
210
        ) {
211
            return (int)$matches[1];
30✔
212
        }
213

214
        return null;
×
215
    }
216

217
    private function scanFile(string $hashAlgorithm = 'md5'): void
218
    {
219
        $this->size = 0;
120✔
220
        $this->source->rewind();
120✔
221
        try {
222
            $hash = hash_init($hashAlgorithm);
120✔
223
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
120✔
224
            while (!$this->source->eof()) {
120✔
225
                $buffer = $this->source->read(self::BUFFER_SIZE);
120✔
226
                if (!isset($this->mimeType)) {
120✔
227
                    $this->mimeType = finfo_buffer($finfo, $buffer);
120✔
228
                }
229
                hash_update($hash, $buffer);
120✔
230
                $this->size += strlen($buffer);
120✔
231
            }
232
            $this->hash[$hashAlgorithm] = hash_final($hash);
120✔
233
            $this->source->rewind();
120✔
234
        } finally {
235
            if (!empty($finfo)) {
120✔
236
                finfo_close($finfo);
120✔
237
            }
238
        }
239
    }
240
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc