Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.36% covered (success)
96.36%
53 / 55
90.00% covered (success)
90.00%
9 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
HttpResponse
96.36% covered (success)
96.36%
53 / 55
90.00% covered (success)
90.00%
9 / 10
29
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setResponse
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 remove
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getBody
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getJsonData
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 render
81.82% covered (warning)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
6.22
 getRaw
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 removeWhitespaceAndLineBreak
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 toArray
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
7
 endAllOutputBuffering
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
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 */
13declare(strict_types=1);
14
15namespace phpOMS\Message\Http;
16
17use phpOMS\Contract\RenderableInterface;
18use phpOMS\Localization\Localization;
19use phpOMS\Log\FileLogger;
20use phpOMS\Message\ResponseAbstract;
21use phpOMS\System\MimeType;
22use phpOMS\Utils\ArrayUtils;
23use phpOMS\Utils\StringUtils;
24use 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 */
36final 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}