Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
96.36% |
53 / 55 |
|
90.00% |
9 / 10 |
CRAP | |
0.00% |
0 / 1 |
HttpResponse | |
96.36% |
53 / 55 |
|
90.00% |
9 / 10 |
29 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setResponse | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
remove | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getBody | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getJsonData | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
render | |
81.82% |
9 / 11 |
|
0.00% |
0 / 1 |
6.22 | |||
getRaw | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
removeWhitespaceAndLineBreak | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
toArray | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
7 | |||
endAllOutputBuffering | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 |
1 | <?php |
2 | /** |
3 | * Jingga |
4 | * |
5 | * PHP Version 8.1 |
6 | * |
7 | * @package phpOMS\Message\Http |
8 | * @copyright Dennis Eichhorn |
9 | * @license OMS License 2.0 |
10 | * @version 1.0.0 |
11 | * @link https://jingga.app |
12 | */ |
13 | declare(strict_types=1); |
14 | |
15 | namespace phpOMS\Message\Http; |
16 | |
17 | use phpOMS\Contract\RenderableInterface; |
18 | use phpOMS\Localization\Localization; |
19 | use phpOMS\Log\FileLogger; |
20 | use phpOMS\Message\ResponseAbstract; |
21 | use phpOMS\System\MimeType; |
22 | use phpOMS\Utils\ArrayUtils; |
23 | use phpOMS\Utils\StringUtils; |
24 | use phpOMS\Views\View; |
25 | |
26 | /** |
27 | * Response class. |
28 | * |
29 | * @property \phpOMS\Message\Http\HttpHeader $header Http header |
30 | * |
31 | * @package phpOMS\Message\Http |
32 | * @license OMS License 2.0 |
33 | * @link https://jingga.app |
34 | * @since 1.0.0 |
35 | */ |
36 | final class HttpResponse extends ResponseAbstract implements RenderableInterface |
37 | { |
38 | /** |
39 | * Constructor. |
40 | * |
41 | * @param Localization $l11n Localization |
42 | * |
43 | * @since 1.0.0 |
44 | */ |
45 | public function __construct(Localization $l11n = null) |
46 | { |
47 | $this->header = new HttpHeader(); |
48 | $this->header->l11n = $l11n ?? new Localization(); |
49 | } |
50 | |
51 | /** |
52 | * Set response. |
53 | * |
54 | * @param array $response Response to set |
55 | * |
56 | * @return void |
57 | * |
58 | * @since 1.0.0 |
59 | */ |
60 | public function setResponse(array $response) : void |
61 | { |
62 | $this->data = $response; |
63 | } |
64 | |
65 | /** |
66 | * Remove response by ID. |
67 | * |
68 | * @param string $id Response ID |
69 | * |
70 | * @return bool |
71 | * |
72 | * @since 1.0.0 |
73 | */ |
74 | public function remove(string $id) : bool |
75 | { |
76 | if (isset($this->data[$id])) { |
77 | unset($this->data[$id]); |
78 | |
79 | return true; |
80 | } |
81 | |
82 | return false; |
83 | } |
84 | |
85 | /** |
86 | * {@inheritdoc} |
87 | */ |
88 | public function getBody(bool $optimize = false) : string |
89 | { |
90 | return $this->render($optimize); |
91 | } |
92 | |
93 | /** |
94 | * {@inheritdoc} |
95 | */ |
96 | public function getJsonData() : array |
97 | { |
98 | $json = \json_decode($this->getRaw(), true); |
99 | |
100 | return \is_array($json) ? $json : []; |
101 | } |
102 | |
103 | /** |
104 | * Generate response based on header. |
105 | * |
106 | * @param mixed ...$data Data passt to render function. (0 => bool: $optimize) |
107 | * |
108 | * @return string |
109 | * |
110 | * @since 1.0.0 |
111 | */ |
112 | public function render(mixed ...$data) : string |
113 | { |
114 | $types = $this->header->get('Content-Type'); |
115 | foreach ($types as $type) { |
116 | if (\stripos($type, MimeType::M_JSON) !== false) { |
117 | return (string) \json_encode($this->jsonSerialize()); |
118 | } elseif (\stripos($type, MimeType::M_CSV) !== false) { |
119 | return ArrayUtils::arrayToCsv($this->toArray()); |
120 | } elseif (\stripos($type, MimeType::M_XML) !== false) { |
121 | return ArrayUtils::arrayToXml($this->toArray()); |
122 | } elseif (\stripos($type, MimeType::M_HTML) !== false) { |
123 | /** @var array{0:bool} $data */ |
124 | return $this->getRaw($data[0] ?? false); |
125 | } |
126 | } |
127 | |
128 | /** @var array{0:bool} $data */ |
129 | return $this->getRaw($data[0] ?? false); |
130 | } |
131 | |
132 | /** |
133 | * Generate raw response. |
134 | * |
135 | * @param bool $optimize Optimize response / minify |
136 | * |
137 | * @return string |
138 | * |
139 | * @since 1.0.0 |
140 | */ |
141 | private function getRaw(bool $optimize = false) : string |
142 | { |
143 | $render = ''; |
144 | foreach ($this->data as $response) { |
145 | // @note: Api functions return void -> null, this is where the null value is "ignored"/rendered as '' |
146 | $render .= StringUtils::stringify($response); |
147 | } |
148 | |
149 | if ($optimize) { |
150 | return $this->removeWhitespaceAndLineBreak($render); |
151 | } |
152 | |
153 | return $render; |
154 | } |
155 | |
156 | /** |
157 | * Remove whitespace and line break from render |
158 | * |
159 | * @param string $render Rendered string |
160 | * |
161 | * @return string |
162 | * |
163 | * @since 1.0.0 |
164 | */ |
165 | private function removeWhitespaceAndLineBreak(string $render) : string |
166 | { |
167 | $types = $this->header->get('Content-Type'); |
168 | if (\stripos($types[0], MimeType::M_HTML) !== false) { |
169 | $clean = \preg_replace('/(?s)<pre[^<]*>.*?<\/pre>(*SKIP)(*F)|(\s{2,}|\n|\t)/', ' ', $render); |
170 | |
171 | return \trim($clean ?? ''); |
172 | } |
173 | |
174 | return $render; |
175 | } |
176 | |
177 | /** |
178 | * {@inheritdoc} |
179 | */ |
180 | public function toArray() : array |
181 | { |
182 | $result = []; |
183 | |
184 | foreach ($this->data as $response) { |
185 | if ($response instanceof View) { |
186 | $result[] = $response->toArray(); |
187 | } elseif (\is_array($response) || \is_scalar($response)) { |
188 | $result[] = $response; |
189 | } elseif ($response instanceof \JsonSerializable) { |
190 | $result[] = $response->jsonSerialize(); |
191 | } elseif ($response === null) { |
192 | continue; |
193 | } else { |
194 | FileLogger::getInstance() |
195 | ->error( |
196 | FileLogger::MSG_FULL, [ |
197 | 'message' => 'Unknown type.', |
198 | 'line' => __LINE__, |
199 | 'file' => self::class, |
200 | ] |
201 | ); |
202 | } |
203 | } |
204 | |
205 | return $result; |
206 | } |
207 | |
208 | /** |
209 | * Ends the output buffering |
210 | * |
211 | * This is helpful in case the output buffering should be stopped for streamed/chunked responses (e.g. large data) |
212 | * |
213 | * @param int $levels Levels to close |
214 | * |
215 | * @return void |
216 | * |
217 | * @since 1.0.0 |
218 | */ |
219 | public function endAllOutputBuffering(int $levels = 0) : void |
220 | { |
221 | if (!$this->header->isLocked()) { |
222 | $this->header->push(); // @codeCoverageIgnore |
223 | } |
224 | |
225 | $levels = $levels === 0 ? \ob_get_level() : $levels; |
226 | for ($i = 0; $i < $levels; ++$i) { |
227 | \ob_end_clean(); |
228 | } |
229 | } |
230 | } |