Passed
Push — main ( 3c3dd7...47074e )
by Thomas
13:06
created

Response::download()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 14
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 14
ccs 7
cts 7
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 1
nop 5
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Conia\Chuck;
6
7
use Conia\Chuck\Exception\HttpNotFound;
8
use Conia\Chuck\Exception\RuntimeException;
9
use Conia\Chuck\Http\WrapsMessage;
10
use Conia\Chuck\Psr\Factory;
11
use finfo;
12
use Psr\Http\Message\ResponseInterface as PsrResponse;
13
use Psr\Http\Message\StreamInterface as PsrStream;
14
use Traversable;
15
16
class Response implements ResponseWrapper
17
{
18
    use WrapsMessage;
19
20 71
    public function __construct(
21
        protected PsrResponse $psr,
22
        protected readonly Factory|null $factory = null,
23
    ) {
24 71
    }
25
26 56
    public static function fromFactory(Factory $factory): self
27
    {
28 56
        return new self($factory->response(), $factory);
29
    }
30
31 36
    public function psr(): PsrResponse
32
    {
33 36
        return $this->psr;
34
    }
35
36 1
    public function setPsr(PsrResponse $psr): static
37
    {
38 1
        $this->psr = $psr;
39
40 1
        return $this;
41
    }
42
43 15
    public function status(int $statusCode, ?string $reasonPhrase = null): static
44
    {
45 15
        if (empty($reasonPhrase)) {
46 14
            $this->psr = $this->psr->withStatus($statusCode);
47
        } else {
48 1
            $this->psr = $this->psr->withStatus($statusCode, $reasonPhrase);
49
        }
50
51 15
        return $this;
52
    }
53
54 5
    public function getStatusCode(): int
55
    {
56 5
        return $this->psr->getStatusCode();
57
    }
58
59 3
    public function getReasonPhrase(): string
60
    {
61 3
        return $this->psr->getReasonPhrase();
62
    }
63
64 1
    public function protocolVersion(string $protocol): static
65
    {
66 1
        $this->psr = $this->psr->withProtocolVersion($protocol);
67
68 1
        return $this;
69
    }
70
71 18
    public function header(string $name, string $value): static
72
    {
73 18
        $this->psr = $this->psr->withAddedHeader($name, $value);
74
75 18
        return $this;
76
    }
77
78 1
    public function removeHeader(string $name): static
79
    {
80 1
        $this->psr = $this->psr->withoutHeader($name);
81
82 1
        return $this;
83
    }
84
85 3
    public function headers(): array
86
    {
87 3
        return $this->psr->getHeaders();
88
    }
89
90 14
    public function getHeader(string $name): array
91
    {
92 14
        return $this->psr->getHeader($name);
93
    }
94
95 3
    public function hasHeader(string $name): bool
96
    {
97 3
        return $this->psr->hasHeader($name);
98
    }
99
100 14
    public function body(PsrStream|string $body): static
101
    {
102 14
        if ($body instanceof PsrStream) {
103 1
            $this->psr = $this->psr->withBody($body);
104
105 1
            return $this;
106
        }
107
108 13
        if ($this->factory) {
109 12
            $this->psr = $this->psr->withBody($this->factory->stream($body));
110
111 12
            return $this;
112
        }
113
114 1
        throw new RuntimeException('No factory instance set in response object');
115
    }
116
117 26
    public function getBody(): PsrStream
118
    {
119 26
        return $this->psr->getBody();
120
    }
121
122 3
    public function write(string $content): static
123
    {
124 3
        $this->psr->getBody()->write($content);
125
126 3
        return $this;
127
    }
128
129 2
    public function redirect(string $url, int $code = 302): static
130
    {
131 2
        $this->header('Location', $url);
132 2
        $this->status($code);
133
134 2
        return $this;
135
    }
136
137
    /**
138
     * @param null|PsrStream|resource|string $body
139
     */
140 38
    public function withContentType(
141
        string $contentType,
142
        mixed $body = null,
143
        int $code = 200,
144
        string $reasonPhrase = ''
145
    ): static {
146 38
        $this->psr = $this->psr
147 38
            ->withStatus($code, $reasonPhrase)
148 38
            ->withAddedHeader('Content-Type', $contentType);
149
150 38
        if ($body) {
151 37
            assert(isset($this->factory));
152 37
            $this->psr = $this->psr->withBody($this->factory->stream($body));
0 ignored issues
show
Bug introduced by
The method stream() 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

152
            $this->psr = $this->psr->withBody($this->factory->/** @scrutinizer ignore-call */ stream($body));

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...
153
        }
154
155 37
        return $this;
156
    }
157
158
    /**
159
     * @param null|PsrStream|resource|string $body
160
     */
161 18
    public function html(mixed $body = null, int $code = 200, string $reasonPhrase = ''): static
162
    {
163 18
        return $this->withContentType('text/html', $body, $code, $reasonPhrase);
164
    }
165
166
    /**
167
     * @param null|PsrStream|resource|string $body
168
     */
169 13
    public function text(mixed $body = null, int $code = 200, string $reasonPhrase = ''): static
170
    {
171 13
        return $this->withContentType('text/plain', $body, $code, $reasonPhrase);
172
    }
173
174 7
    public function json(
175
        mixed $data,
176
        int $code = 200,
177
        string $reasonPhrase = '',
178
        int $flags = JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR,
179
    ): static {
180 7
        if ($data instanceof Traversable) {
181 1
            $body = json_encode(iterator_to_array($data), $flags);
182
        } else {
183 6
            $body = json_encode($data, $flags);
184
        }
185
186 7
        return $this->withContentType('application/json', $body, $code, $reasonPhrase);
187
    }
188
189 6
    public function file(
190
        string $file,
191
        bool $throwNotFound = true,
192
        int $code = 200,
193
        string $reasonPhrase = '',
194
    ): static {
195 6
        $this->validateFile($file, $throwNotFound);
196
197 4
        $finfo = new finfo(FILEINFO_MIME_TYPE);
198 4
        $contentType = $finfo->file($file);
199 4
        $finfo = new finfo(FILEINFO_MIME_ENCODING);
200 4
        $encoding = $finfo->file($file);
201 4
        assert(isset($this->factory));
202 4
        $stream = $this->factory->streamFromFile($file, 'rb');
203
204 4
        $this->psr = $this->psr
205 4
            ->withStatus($code, $reasonPhrase)
206 4
            ->withAddedHeader('Content-Type', $contentType)
207 4
            ->withAddedHeader('Content-Transfer-Encoding', $encoding)
208 4
            ->withBody($stream);
209
210 4
        $size = $stream->getSize();
211
212 4
        if (!is_null($size)) {
213 4
            $this->psr = $this->psr->withAddedHeader('Content-Length', (string)$size);
214
        }
215
216 4
        return $this;
217
    }
218
219 3
    public function download(
220
        string $file,
221
        string $newName = '',
222
        bool $throwNotFound = true,
223
        int $code = 200,
224
        string $reasonPhrase = '',
225
    ): static {
226 3
        $response = $this->file($file, $throwNotFound, $code, $reasonPhrase);
227 3
        $response->header(
228 3
            'Content-Disposition',
229 3
            'attachment; filename="' . ($newName ?: basename($file)) . '"'
230 3
        );
231
232 3
        return $response;
233
    }
234
235 1
    public function sendfile(
236
        string $file,
237
        bool $throwNotFound = true,
238
        int $code = 200,
239
        string $reasonPhrase = '',
240
    ): static {
241 1
        $this->validateFile($file, $throwNotFound);
242 1
        $server = strtolower($_SERVER['SERVER_SOFTWARE'] ?? '');
243 1
        $this->psr = $this->psr->withStatus($code, $reasonPhrase);
244
245 1
        if (strpos($server, 'nginx') !== false) {
246 1
            $this->psr = $this->psr->withAddedHeader('X-Accel-Redirect', $file);
247
        } else {
248 1
            $this->psr = $this->psr->withAddedHeader('X-Sendfile', $file);
249
        }
250
251 1
        return $this;
252
    }
253
254 7
    protected function validateFile(string $file, bool $throwNotFound): void
255
    {
256 7
        if (!is_file($file)) {
257 2
            if ($throwNotFound) {
258 1
                throw new HttpNotFound();
259
            }
260
261 1
            throw new RuntimeException('File not found');
262
        }
263
    }
264
}
265