Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
230 / 230
100.00% covered (success)
100.00%
14 / 14
CRAP
100.00% covered (success)
100.00%
1 / 1
HTTPCollector
100.00% covered (success)
100.00%
230 / 230
100.00% covered (success)
100.00%
14 / 14
47
100.00% covered (success)
100.00%
1 / 1
 setRequest
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setResponse
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getActivities
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
 getContents
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 renderRequest
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
2
 renderRequestUserAgent
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
1 / 1
4
 renderRequestBody
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 renderRequestForm
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
4
 renderRequestFiles
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
3
 renderResponse
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
8
 renderResponseCookies
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
5
 renderResponseBody
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
4
 renderHeadersTable
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
3
 getCodeLanguage
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
1<?php declare(strict_types=1);
2/*
3 * This file is part of Aplus Framework HTTP Library.
4 *
5 * (c) Natan Felles <natanfelles@gmail.com>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10namespace Framework\HTTP\Debug;
11
12use Framework\Debug\Collector;
13use Framework\Debug\Debugger;
14use Framework\Helpers\ArraySimple;
15use Framework\HTTP\Request;
16use Framework\HTTP\Response;
17
18/**
19 * Class HTTPCollector.
20 *
21 * @package http
22 */
23class HTTPCollector extends Collector
24{
25    protected Request $request;
26    protected Response $response;
27
28    public function setRequest(Request $request) : static
29    {
30        $this->request = $request;
31        return $this;
32    }
33
34    public function setResponse(Response $response, bool $replaceRequest = true) : static
35    {
36        $this->response = $response;
37        if ($replaceRequest) {
38            $this->setRequest($response->getRequest());
39        }
40        return $this;
41    }
42
43    public function getActivities() : array
44    {
45        $activities = [];
46        foreach ($this->getData() as $data) {
47            if (isset($data['message'], $data['type']) &&
48                $data['message'] === 'response' &&
49                $data['type'] === 'send'
50            ) {
51                $activities[] = [
52                    'collector' => $this->getName(),
53                    'class' => static::class,
54                    'description' => 'Send response',
55                    'start' => $data['start'],
56                    'end' => $data['end'],
57                ];
58            }
59        }
60        return $activities;
61    }
62
63    public function getContents() : string
64    {
65        \ob_start(); ?>
66        <h1>Request</h1>
67        <?= $this->renderRequest() ?>
68        <h1>Response</h1>
69        <?= $this->renderResponse() ?>
70        <?php
71        return \ob_get_clean(); // @phpstan-ignore-line
72    }
73
74    protected function renderRequest() : string
75    {
76        if ( ! isset($this->request)) {
77            return '<p>A Request instance has not been set on this collector.</p>';
78        }
79        \ob_start(); ?>
80        <p title="REMOTE_ADDR"><strong>IP:</strong> <?= $this->request->getIp() ?></p>
81        <p><strong>Protocol:</strong> <?= $this->request->getProtocol() ?></p>
82        <p><strong>Method:</strong> <?= $this->request->getMethod() ?></p>
83        <p><strong>URL:</strong> <?= $this->request->getUrl() ?></p>
84        <p><strong>Server:</strong> <?= $this->request->getServer('SERVER_SOFTWARE') ?></p>
85        <p><strong>Hostname:</strong> <?= \gethostname() ?></p>
86        <?= $this->renderRequestUserAgent() ?>
87        <?php
88        echo $this->renderHeadersTable($this->request->getHeaderLines());
89        echo $this->renderRequestBody();
90        echo $this->renderRequestForm();
91        echo $this->renderRequestFiles();
92        return \ob_get_clean(); // @phpstan-ignore-line
93    }
94
95    protected function renderRequestUserAgent() : string
96    {
97        $userAgent = $this->request->getUserAgent();
98        if ($userAgent === null) {
99            return '';
100        }
101        \ob_start(); ?>
102        <h2>User-Agent</h2>
103        <table>
104            <thead>
105            <tr>
106                <th>Type</th>
107                <th>Name</th>
108                <th>Version</th>
109                <th>Platform</th>
110                <th>Is Mobile</th>
111            </tr>
112            </thead>
113            <tbody>
114            <tr>
115                <td><?= \htmlentities($userAgent->getType()) ?></td>
116                <td><?= \htmlentities($userAgent->getName()) ?></td>
117                <td><?= $userAgent->isBrowser()
118                        ? \htmlentities((string) $userAgent->getBrowserVersion())
119                        : '' ?></td>
120                <td><?= \htmlentities((string) $userAgent->getPlatform()) ?></td>
121                <td><?= $userAgent->isMobile() ? 'Yes' : 'No' ?></td>
122            </tr>
123            </tbody>
124        </table>
125        <?php
126        return \ob_get_clean(); // @phpstan-ignore-line
127    }
128
129    protected function renderRequestBody() : string
130    {
131        $body = $this->request->hasFiles()
132            ? \http_build_query($this->request->getPost())
133            : $this->request->getBody();
134        if ($body === '') {
135            return '';
136        }
137        \ob_start(); ?>
138        <h2>Body Contents</h2>
139        <pre><code class="<?= $this->getCodeLanguage(
140            $this->request->getHeader('Content-Type')
141        ) ?>"><?= \htmlentities($body) ?></code></pre>
142        <?php
143        return \ob_get_clean(); // @phpstan-ignore-line
144    }
145
146    protected function renderRequestForm() : string
147    {
148        if ( ! $this->request->isPost() && ! $this->request->isForm()) {
149            return '';
150        }
151        \ob_start(); ?>
152        <h2>Form</h2>
153        <table>
154            <thead>
155            <tr>
156                <th>Field</th>
157                <th>Value</th>
158            </tr>
159            </thead>
160            <tbody>
161            <?php foreach (ArraySimple::convert($this->request->getParsedBody()) as $field => $value): ?>
162                <tr>
163                    <td><?= \htmlentities($field) ?></td>
164                    <td>
165                        <pre><?= \htmlentities($value) ?></pre>
166                    </td>
167                </tr>
168            <?php endforeach ?>
169            </tbody>
170        </table>
171        <?php
172        return \ob_get_clean(); // @phpstan-ignore-line
173    }
174
175    protected function renderRequestFiles() : string
176    {
177        if ( ! $this->request->hasFiles()) {
178            return '';
179        }
180        \ob_start(); ?>
181        <h2>Uploaded Files</h2>
182        <table>
183            <thead>
184            <tr>
185                <th>Field</th>
186                <th>Name</th>
187                <th>Full Path</th>
188                <th>Type</th>
189                <th>Client Type</th>
190                <th>Extension</th>
191                <th>Size</th>
192                <th>Destination</th>
193                <th colspan="2">Error</th>
194            </tr>
195            </thead>
196            <tbody>
197            <?php foreach (ArraySimple::convert($this->request->getFiles()) as $field => $file): ?>
198                <tr>
199                    <td><?= \htmlentities($field) ?></td>
200                    <td><?= \htmlentities($file->getName()) ?></td>
201                    <td><?= \htmlentities($file->getFullPath()) ?></td>
202                    <td><?= \htmlentities($file->getType()) ?></td>
203                    <td><?= \htmlentities($file->getClientType()) ?></td>
204                    <td><?= \htmlentities($file->getExtension()) ?></td>
205                    <td><?= Debugger::convertSize($file->getSize()) ?></td>
206                    <td><?= $file->getDestination() ?></td>
207                    <td><?= $file->getError() ?></td>
208                    <td><?= \htmlentities($file->getErrorMessage()) ?></td>
209                </tr>
210            <?php endforeach ?>
211            </tbody>
212        </table>
213        <?php
214        return \ob_get_clean(); // @phpstan-ignore-line
215    }
216
217    protected function renderResponse() : string
218    {
219        if ( ! isset($this->response)) {
220            return '<p>A Response instance has not been set on this collector.</p>';
221        }
222        \ob_start(); ?>
223        <p><strong>Protocol:</strong> <?= \htmlentities($this->response->getProtocol()) ?></p>
224        <p><strong>Status:</strong> <?= \htmlentities($this->response->getStatus()) ?></p>
225        <p><strong>Sent:</strong> <?= $this->response->isSent() ? 'Yes' : 'No' ?></p>
226        <?php
227        if ($this->response->isSent()):
228            $info = [];
229            foreach ($this->getData() as $data) {
230                if (
231                    isset($data['message'], $data['type'])
232                    && $data['message'] === 'response'
233                    && $data['type'] === 'send'
234                ) {
235                    $info = $data;
236                    break;
237                }
238            } ?>
239            <p>
240                <strong>Time Sending:</strong> <?= \round($info['end'] - $info['start'], 6) ?> seconds
241            </p>
242        <?php
243        endif;
244        echo $this->renderHeadersTable($this->response->getHeaderLines());
245        echo $this->renderResponseCookies();
246        echo $this->renderResponseBody();
247        return \ob_get_clean(); // @phpstan-ignore-line
248    }
249
250    protected function renderResponseCookies() : string
251    {
252        if ( ! $this->response->getCookies()) {
253            return '';
254        }
255        \ob_start(); ?>
256        <h2>Cookies</h2>
257        <table>
258            <thead>
259            <tr>
260                <th>Name</th>
261                <th>Value</th>
262                <th>Expires</th>
263                <th>Path</th>
264                <th>Domain</th>
265                <th>Is Secure</th>
266                <th>Is HTTP Only</th>
267                <th>SameSite</th>
268            </tr>
269            </thead>
270            <tbody>
271            <?php foreach ($this->response->getCookies() as $cookie): ?>
272                <tr>
273                    <td><?= \htmlentities($cookie->getName()) ?></td>
274                    <td><?= \htmlentities($cookie->getValue()) ?></td>
275                    <td><?= $cookie->getExpires()?->format('D, d-M-Y H:i:s \G\M\T') ?></td>
276                    <td><?= \htmlentities((string) $cookie->getPath()) ?></td>
277                    <td><?= \htmlentities((string) $cookie->getDomain()) ?></td>
278                    <td><?= $cookie->isSecure() ? 'Yes' : 'No' ?></td>
279                    <td><?= $cookie->isHttpOnly() ? 'Yes' : 'No' ?></td>
280                    <td><?= \htmlentities((string) $cookie->getSameSite()) ?></td>
281                </tr>
282            <?php endforeach ?>
283            </tbody>
284        </table>
285        <?php
286        return \ob_get_clean(); // @phpstan-ignore-line
287    }
288
289    protected function renderResponseBody() : string
290    {
291        \ob_start(); ?>
292        <h2>Body Contents</h2>
293        <?php
294        if ( ! $this->response->isSent()) {
295            echo '<p>Response has not been sent.</p>';
296            return \ob_get_clean(); // @phpstan-ignore-line
297        }
298        if ($this->response->hasDownload()) {
299            echo '<p>Body has downloadable content.</p>';
300            return \ob_get_clean(); // @phpstan-ignore-line
301        }
302        $body = $this->response->getBody();
303        if ($body === '') {
304            echo '<p>Body is empty.</p>';
305            return \ob_get_clean(); // @phpstan-ignore-line
306        } ?>
307        <pre><code class="<?= $this->getCodeLanguage(
308            $this->response->getHeader('Content-Type')
309        ) ?>"><?= \htmlentities($body) ?></code></pre>
310        <?php
311        return \ob_get_clean(); // @phpstan-ignore-line
312    }
313
314    /**
315     * @param array<string> $headerLines
316     *
317     * @return string
318     */
319    protected function renderHeadersTable(array $headerLines) : string
320    {
321        \ob_start(); ?>
322        <h2>Headers</h2>
323        <?php
324        if (empty($headerLines)) {
325            echo '<p>No headers.</p>';
326            return \ob_get_clean(); // @phpstan-ignore-line
327        } ?>
328        <table>
329            <thead>
330            <tr>
331                <th>Name</th>
332                <th>Value</th>
333            </tr>
334            </thead>
335            <tbody>
336            <?php foreach ($headerLines as $line):
337                [$name, $value] = \explode(': ', $line, 2); ?>
338                <tr>
339                    <td><?= \htmlentities($name) ?></td>
340                    <td><?= \htmlentities($value) ?></td>
341                </tr>
342            <?php endforeach ?>
343            </tbody>
344        </table>
345        <?php
346        return \ob_get_clean(); // @phpstan-ignore-line
347    }
348
349    protected function getCodeLanguage(?string $contentType) : string
350    {
351        $language = 'none';
352        if ($contentType) {
353            $contentType = \explode(';', $contentType, 2);
354            $language = \explode('/', $contentType[0], 2)[1] ?? $language;
355        }
356        return 'language-' . $language;
357    }
358}