Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
10 / 10
CRAP
100.00% covered (success)
100.00%
1 / 1
ResponseBuilder
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
10 / 10
11
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withResponse
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 withHeader
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 addHeader
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 withStatus
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withRawBody
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 withHtmlBody
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 withJsonBody
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 build
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 copy
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3// ╔════════════════════════════════════════════════════════════╗
4// ║ MIT Licence (#Expat) - https://opensource.org/licenses/MIT ║
5// ║ Copyright 2026 Frederic Poeydomenge <dyno@phexium.com>     ║
6// ╚════════════════════════════════════════════════════════════╝
7
8declare(strict_types=1);
9
10namespace Phexium\Presentation;
11
12use Override;
13use Psr\Http\Message\ResponseFactoryInterface;
14use Psr\Http\Message\ResponseInterface;
15
16final readonly class ResponseBuilder implements ResponseBuilderInterface
17{
18    public function __construct(
19        private ResponseFactoryInterface $responseFactory,
20        private int $statusCode = 200,
21        private array $headers = [],
22        private string $body = '',
23    ) {}
24
25    #[Override]
26    public function withResponse(ResponseInterface $response): self
27    {
28        return $this->copy(
29            statusCode: $response->getStatusCode(),
30            headers: $response->getHeaders(),
31            body: (string) $response->getBody(),
32        );
33    }
34
35    #[Override]
36    public function withHeader(string $name, string $value): self
37    {
38        $headers = $this->headers;
39        $headers[$name] = [$value];
40
41        return $this->copy(headers: $headers);
42    }
43
44    #[Override]
45    public function addHeader(string $name, string $value): self
46    {
47        $headers = $this->headers;
48        $headers[$name][] = $value;
49
50        return $this->copy(headers: $headers);
51    }
52
53    #[Override]
54    public function withStatus(int $code): self
55    {
56        return $this->copy(statusCode: $code);
57    }
58
59    #[Override]
60    public function withRawBody(string $content, string $contentType): self
61    {
62        return $this
63            ->withHeader('Content-Type', $contentType)
64            ->copy(body: $content)
65        ;
66    }
67
68    #[Override]
69    public function withHtmlBody(string $html, string $charset = 'utf-8'): self
70    {
71        return $this->withRawBody(
72            $html,
73            "text/html; charset={$charset}"
74        );
75    }
76
77    #[Override]
78    public function withJsonBody(array $data, string $charset = 'utf-8'): self
79    {
80        return $this->withRawBody(
81            json_encode($data, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR),
82            "application/json; charset={$charset}",
83        );
84    }
85
86    #[Override]
87    public function build(): ResponseInterface
88    {
89        $response = $this->responseFactory->createResponse($this->statusCode);
90
91        foreach ($this->headers as $name => $values) {
92            $response = $response->withHeader($name, $values);
93        }
94
95        $response->getBody()->write($this->body);
96
97        return $response;
98    }
99
100    private function copy(?int $statusCode = null, ?array $headers = null, ?string $body = null): self
101    {
102        return new self(
103            responseFactory: $this->responseFactory,
104            statusCode: $statusCode ?? $this->statusCode,
105            headers: $headers ?? $this->headers,
106            body: $body ?? $this->body,
107        );
108    }
109}