Passed
Push — main ( c3c153...4badf1 )
by Thomas
14:00
created

Response   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 250
Duplicated Lines 0 %

Test Coverage

Coverage 97.3%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 39
eloc 88
c 1
b 0
f 0
dl 0
loc 250
ccs 108
cts 111
cp 0.973
rs 9.28

26 Methods

Rating   Name   Duplication   Size   Complexity  
A getStatusCode() 0 3 1
A header() 0 5 1
A hasHeader() 0 3 1
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 status() 0 9 2
A body() 0 15 3
A getProtocolVersion() 0 3 1
A wrap() 0 5 1
A unwrap() 0 3 1
A __construct() 0 4 1
A protocolVersion() 0 5 1
A html() 0 3 1
A sendfile() 0 16 2
A json() 0 13 2
A validateFile() 0 4 2
A file() 0 27 2
A download() 0 13 2
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 1
    public function headers(): array
91
    {
92 1
        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 7
    public function withContentType(
143
        string $contentType,
144
        mixed $body = null,
145
        int $code = 200,
146
        string $reasonPhrase = ''
147
    ): static {
148 7
        $this->psrResponse = $this->psrResponse
149 7
            ->withStatus($code, $reasonPhrase)
150 7
            ->withAddedHeader('Content-Type', $contentType);
151
152 7
        if ($body) {
153 7
            assert(isset($this->streamFactory));
154
155 7
            if ($body instanceof PsrStream) {
156
                $stream = $body;
157 7
            } elseif (is_string($body) || $body instanceof Stringable) {
158 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

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