Passed
Push — main ( 16bf0e...c3c153 )
by Thomas
02:50
created

Response   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 253
Duplicated Lines 0 %

Test Coverage

Coverage 95.5%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 39
eloc 88
c 1
b 0
f 0
dl 0
loc 253
ccs 106
cts 111
cp 0.955
rs 9.28

26 Methods

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

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